Este es un problema difícil de resolver en general, pero hay un par de cosas que podemos hacer para ayudar al optimizador a elegir un plan. Este script crea una tabla con 10,000 filas con una distribución pseudoaleatoria de filas conocida para ilustrar:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
La primera pregunta es cómo indexar esta tabla. Una opción es proporcionar dos índices en las DATETIME
columnas, de modo que el optimizador pueda al menos elegir si desea buscar StartDate
o no EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Naturalmente, las desigualdades en tanto StartDate
y EndDate
media que sólo una columna en cada índice puede soportar una buscan en la consulta de ejemplo, pero esto es de lo mejor que podemos hacer. Podríamos considerar hacer que la segunda columna en cada índice INCLUDE
sea una clave en lugar de una clave, pero podríamos tener otras consultas que puedan realizar una búsqueda de igualdad en la columna inicial y una búsqueda de desigualdad en la segunda columna. Además, podemos obtener mejores estadísticas de esta manera. De todas formas...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
Esta consulta utiliza variables, por lo que, en general, el optimizador adivinará la selectividad y la distribución, lo que dará como resultado una estimación de cardinalidad adivinada de 81 filas . De hecho, la consulta produce 2076 filas, una discrepancia que podría ser importante en un ejemplo más complejo.
En SQL Server 2008 SP1 CU5 o posterior (o R2 RTM CU1) podemos aprovechar la optimización de incrustación de parámetros para obtener mejores estimaciones, simplemente agregando OPTION (RECOMPILE)
a la SELECT
consulta anterior. Esto provoca una compilación justo antes de que se ejecute el lote, lo que permite a SQL Server 'ver' los valores de los parámetros reales y optimizarlos. Con este cambio, la estimación mejora a 468 filas (aunque debe verificar el plan de tiempo de ejecución para ver esto). Esta estimación es mejor que 81 filas, pero aún no está tan cerca. Las extensiones de modelado habilitadas por el indicador de traza 2301 pueden ayudar en algunos casos, pero no con esta consulta.
El problema es dónde se superponen las filas calificadas por las dos búsquedas de rango. Una de las suposiciones simplificadoras hechas en el componente de estimación de costos y cardinalidad del optimizador es que los predicados son independientes (por lo tanto, si ambos tienen una selectividad del 50%, se supone que el resultado de aplicar ambos califica el 50% del 50% = 25% de las filas ) Cuando este tipo de correlación es un problema, a menudo podemos solucionarlo con estadísticas de varias columnas y / o filtradas. Con dos rangos con puntos de inicio y final desconocidos, esto se vuelve poco práctico. Aquí es donde a veces tenemos que recurrir a reescribir la consulta a un formulario que produce una mejor estimación:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Este formulario produce una estimación de tiempo de ejecución de 2110 filas (frente a 2076 real). A menos que tenga TF 2301 encendido, en cuyo caso las técnicas de modelado más avanzadas verán el truco y producirán exactamente la misma estimación que antes: 468 filas.
Un día, SQL Server podría obtener soporte nativo para intervalos. Si eso viene con un buen soporte estadístico, los desarrolladores podrían temer un poco menos los planes de consulta de ajuste como este.