La forma más eficiente de recuperar rangos de fechas


16

¿Cuál es la forma más eficiente de recuperar rangos de fechas con una estructura de tabla como esta?

create table SomeDateTable
(
    id int identity(1, 1) not null,
    StartDate datetime not null,
    EndDate datetime not null
)
go

Digamos que quieres un rango para ambos StartDatey EndDate. En otras palabras, si StartDatecae entre @StartDateBeginy @StartDateEnd, y EndDatecae entre @EndDateBeginy @EndDateEnd, entonces haz algo.

Sé que hay algunas maneras de hacerlo, pero ¿cuál es la más recomendada?

Respuestas:


29

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 DATETIMEcolumnas, de modo que el optimizador pueda al menos elegir si desea buscar StartDateo no EndDate.

CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)

Naturalmente, las desigualdades en tanto StartDatey EndDatemedia 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 INCLUDEsea ​​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 SELECTconsulta 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.


5

No conozco una solución que sea rápida para todas las distribuciones de datos, pero si todos sus rangos son cortos, generalmente podemos acelerarlo. Si, por ejemplo, los rangos son más cortos que un día, en lugar de esta consulta:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt

podemos agregar una condición más:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt
    AND StartedAt >= '20101202'
    AND FinishedAt <= '20101204' ;

Como resultado, en lugar de escanear toda la tabla, la consulta escaneará solo el rango de dos días, que es más rápido. Si los rangos pueden ser más largos, podemos almacenarlos como secuencias de los más cortos. Detalles aquí: Ajuste de consultas SQL con la ayuda de restricciones

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.