Estimaciones con datos sesgados: cuando un valor concentra el 80% de la tabla
En cualquier proyecto de Business Intelligence, tarde o temprano nos topamos con la realidad del dato sucio o, lo que es técnicamente más complejo, el dato sesgado. No me refiero a errores de calidad, sino a distribuciones donde un único valor (como un cliente genérico, un código de almacén central o un estado de pedido ‘Completado’) acapara el 80% o el 90% de los registros de una tabla de millones de filas. Para el motor de SQL Server, esta asimetría es una pesadilla de rendimiento si no sabemos cómo gestionarla.
El problema fundamental reside en cómo el Optimizador de Consultas estima el esfuerzo necesario para recuperar los datos. Si el optimizador cree que va a recuperar 10 filas pero acaba procesando 10 millones, el plan de ejecución elegido será desastroso. Esto ocurre principalmente porque las estadísticas de SQL Server tienen limitaciones físicas, específicamente en sus histogramas, y porque el mecanismo de reutilización de planes no entiende de excepciones estadísticas por defecto.
Como consultores, a menudo vemos cómo un informe de Power BI empieza a ir lento no por el DAX, sino porque la consulta SQL que lo alimenta tarda minutos en responder debido a un plan de ejecución ineficiente. Este fenómeno afecta directamente al plegado de consultas en Power Query, ya que si la base de datos de origen no devuelve los datos de forma óptima, toda la cadena de suministro del dato se rompe.
La tiranía de los 200 pasos del histograma
SQL Server utiliza estadísticas para crear planes de ejecución. El componente más importante de estas estadísticas es el histograma. Independientemente de si tu tabla tiene 1.000 filas o 1.000 millones, SQL Server solo utiliza un máximo de 200 pasos (buckets) para representar la distribución de los datos en una columna. Cada paso almacena el valor de límite, cuántas filas son iguales a ese valor y cuántas filas caen en el rango entre ese valor y el anterior.
Cuando tenemos un valor que representa el 80% de la tabla, este valor «secuestra» el histograma. Al ocupar tanto espacio estadístico, el resto de los 199 pasos deben repartirse el 20% de los datos restantes. Esto provoca que la granularidad para los valores menos frecuentes sea ínfima. El Optimizador pierde precisión y empieza a promediar, lo que lleva a estimaciones de cardinalidad incorrectas.
El punto de inflexión del optimizador
SQL Server decide entre un Index Seek (búsqueda en índice) combinado con un Key Lookup, o un Index Scan (escaneo completo) basándose en este histograma. Si buscas el valor que representa el 80% de los datos, un Index Scan es lo más eficiente. Si buscas un valor que representa el 0.01%, un Seek es preferible. El problema surge cuando el motor confunde ambos escenarios debido a la falta de resolución en esos 200 pasos.
Parameter Sniffing: el enemigo silencioso
El sesgo de datos es el combustible perfecto para el Parameter Sniffing. Este proceso ocurre cuando SQL Server compila un plan de ejecución para un procedimiento almacenado o una consulta parametrizada utilizando los valores del parámetro que se pasaron en la primera ejecución. Si esa primera ejecución fue para el valor que representa el 80% de los datos, el plan generado será un Scan. Todas las ejecuciones posteriores, incluso las que busquen valores minoritarios, usarán ese mismo plan de Scan, penalizando el rendimiento.
En entornos donde el linaje de datos es crítico, como explicamos en la gobernanza de datos en el flujo de trabajo, tener procesos de carga que fluctúan drásticamente en tiempo debido a este problema hace imposible cumplir con los SLAs (Service Level Agreements).
Técnicas avanzadas de mitigación
Para solucionar el problema del sesgo, no basta con actualizar las estadísticas con FULLSCAN. Necesitamos indicarle al motor que trate a los valores atípicos de forma especial. Aquí es donde entran las estadísticas filtradas y los hints de consulta.
1. Estadísticas Filtradas
Las estadísticas filtradas permiten crear un histograma exclusivo para un subconjunto de datos. Si sabemos que el 80% de nuestras ventas pertenecen al IdCliente = 0 (Venta Anónima), podemos crear una estadística que ignore ese valor para que los 200 pasos se dediquen exclusivamente al 20% restante, y otra estadística específica para el valor sesgado.
-- Crear una estadística filtrada para los clientes reales (el 20% del volumen)
CREATE STATISTICS ST_Ventas_ClienteReal
ON dbo.Ventas (IdCliente)
WHERE IdCliente <> 0;
-- Esto permite que el optimizador tenga 200 pasos de resolución
-- solo para los clientes que no son el 'Vendedor Anónimo'.2. Consultas separadas y lógica condicional
A veces, la mejor forma de ayudar al optimizador es no darle una sola consulta para todo. Si detectamos que el parámetro de entrada es el valor sesgado, podemos redirigir el flujo a una consulta optimizada para escaneos masivos. Si es cualquier otro valor, usamos una consulta optimizada para búsquedas puntuales.
CREATE PROCEDURE dbo.ObtenerVentasPorCliente
@IdCliente INT
AS
BEGIN
IF @IdCliente = 0 -- El valor que concentra el 80%
BEGIN
-- Forzamos un plan de escaneo o usamos configuraciones específicas
SELECT IdVenta, Fecha, Importe
FROM dbo.Ventas
WHERE IdCliente = @IdCliente
OPTION (MAXDOP 4);
END
ELSE
BEGIN
-- Para el resto, dejamos que el optimizador use el índice
SELECT IdVenta, Fecha, Importe
FROM dbo.Ventas
WHERE IdCliente = @IdCliente
OPTION (RECOMPILE); -- Evita el parameter sniffing
END
END;Comparativa de estrategias ante el sesgo
No todas las soluciones son válidas para todos los escenarios. Dependiendo de si el sesgo es estático (siempre el mismo valor) o dinámico, deberemos elegir una técnica u otra.
| Técnica | Escenario ideal | Ventajas | Inconvenientes |
|---|---|---|---|
| Statistics Fullscan | Sesgo moderado | Fácil de implementar | No resuelve el límite de 200 pasos |
| Estadísticas Filtradas | Valores sesgados conocidos (ej. IdCliente=0) | Alta precisión en el 20% restante | Mantenimiento manual si los valores cambian |
| OPTION (RECOMPILE) | Consultas con alta variabilidad | Plan óptimo para cada ejecución | Consumo de CPU por recompilación constante |
| Lógica IF/ELSE | Sesgo extremo en SPs críticos | Control total del plan de ejecución | Código más complejo y difícil de mantener |
Impacto en la arquitectura de datos y Power BI
Muchos de los problemas que diagnosticamos en la gestión proactiva de errores en Power Query tienen su origen en una base de datos que no responde uniformemente. Cuando Power BI lanza múltiples consultas en paralelo para refrescar un informe, si una de ellas cae en una trampa de parameter sniffing debido al sesgo de datos, puede bloquear el pool de conexiones o agotar los recursos de la instancia.
En modelos de tipo Kimball, las tablas de hechos suelen ser las candidatas al sesgo. Por ejemplo, en el sector retail, las devoluciones pueden concentrarse en un puñado de fechas o tiendas. Si no gestionamos las estadísticas de la columna TipoMovimiento correctamente, una consulta que analice devoluciones (poco frecuentes) podría acabar haciendo un escaneo de toda la tabla de ventas simplemente porque el optimizador se preparó para una consulta de ventas normales (muy frecuentes).
Errores comunes al tratar con datos sesgados
- Confiar en el Auto-Update de estadísticas: SQL Server actualiza las estadísticas cuando cambia un porcentaje de filas (umbral de variación). En tablas gigantes con 80% de sesgo, es posible que nunca se alcance el umbral para el 20% de datos reales, dejando las estadísticas obsoletas meses.
- Usar variables locales en lugar de parámetros: Al usar variables locales en un
WHERE, el optimizador no puede «oler» el valor y usa una estimación basada en la densidad media, lo cual es desastroso con sesgo. - No monitorizar los ‘Spills’ a tempdb: Si el optimizador subestima las filas debido al sesgo, asignará poca memoria (Memory Grant). Al llegar los datos reales, la consulta desbordará a disco (tempdb), multiplicando el tiempo de ejecución por diez.
Conclusiones para el consultor de BI
El sesgo de datos es una realidad física de los negocios. Nuestra labor no es intentar aplanar la distribución, sino preparar la infraestructura para que sea consciente de ella. Medir los Server Timings y analizar los planes de ejecución reales frente a los estimados es el primer paso para detectar si los 200 pasos del histograma nos están jugando una mala pasada.
Preguntas frecuentes
¿Cómo puedo saber si una columna tiene sesgo antes de que falle la consulta?
Puedes ejecutar DBCC SHOW_STATISTICS ('NombreTabla', 'NombreIndice') y observar el histograma. Si ves que un solo RANGE_HI_KEY tiene un EQ_ROWS que representa la mayoría del Rows Sampled, tienes un problema de sesgo que requiere atención.
¿Las estadísticas filtradas afectan al rendimiento de las inserciones?
El impacto es mínimo. SQL Server debe evaluar si la fila insertada cumple el predicado del filtro para actualizar la estadística, pero es un proceso muy optimizado que no suele ser el cuello de botella en cargas de DWH.
¿Es mejor OPTION (RECOMPILE) o OPTIMIZE FOR UNKNOWN?
Depende de la frecuencia. Si la consulta se lanza miles de veces por minuto, RECOMPILE quemará tu CPU. En ese caso, OPTIMIZE FOR UNKNOWN o OPTIMIZE FOR (@Param = ValorComun) es más seguro. Para procesos ETL de carga diaria, RECOMPILE es generalmente la mejor opción.
Checklist de optimización ante sesgo
- Identificar columnas con valores que superan el 50% del volumen total.
- Verificar si el plan de ejecución cambia drásticamente según el parámetro de entrada.
- Implementar estadísticas filtradas para los valores «excepción».
- Evaluar el uso de
OPTION (RECOMPILE)en las consultas que alimentan Power BI. - Configurar alertas de Tempdb Spill en el monitor de rendimiento.






