El reparto hasta la fecha es sargable pero ¿es una buena idea?

47

En SQL Server 2008, se agregó el tipo de datos de fecha .

La conversión de una datetimecolumna a datees sargable y puede usar un índice en la datetimecolumna.

select *
from T
where cast(DateTimeCol as date) = '20130101';

La otra opción que tiene es usar un rango en su lugar.

select *
from T
where DateTimeCol >= '20130101' and
      DateTimeCol < '20130102'

¿Son estas consultas igualmente buenas o debería preferirse una sobre la otra?

Mikael Eriksson
fuente
44
¿Qué dice el plan de ejecución?
a_horse_with_no_name
3
No puedo evitar notar que LINQ2SQL genera SQL where cast(date_column as date) = 'value'cuando se presenta con C # similar a where obj.date_column.Date == date_variable.
GSerg
66
Ese es un excelente artículo de Connect. :)
Rob Farley
1
El sitio Connect se ha eliminado, así como Sargable en Wikipedia
Ivanzinho

Respuestas:

59

El mecanismo detrás de la sargabilidad del casting hasta la fecha se llama búsqueda dinámica .

SQL Server llama a una función interna GetRangeThroughConvertpara obtener el inicio y el final del rango.

Sorprendentemente, este no es el mismo rango que sus valores literales.

Crear una tabla con una fila por página y 1440 filas por día

CREATE TABLE T
  (
     DateTimeCol DATETIME PRIMARY KEY,
     Filler      CHAR(8000) DEFAULT 'X'
  );

WITH Nums(Num)
     AS (SELECT number
         FROM   spt_values
         WHERE  type = 'P'
                AND number BETWEEN 1 AND 1440),
     Dates(Date)
     AS (SELECT {d '2012-12-30'} UNION ALL
         SELECT {d '2012-12-31'} UNION ALL
         SELECT {d '2013-01-01'} UNION ALL
         SELECT {d '2013-01-02'} UNION ALL
         SELECT {d '2013-01-03'})
INSERT INTO T
            (DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM   Nums,
       Dates 

Entonces corriendo

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT *
FROM   T
WHERE  DateTimeCol >= '20130101'
       AND DateTimeCol < '20130102'

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'; 

La primera consulta tiene 1443lecturas y la segunda, 2883por lo que lee un día adicional completo y luego la descarta contra un predicado residual.

El plan muestra que el predicado de búsqueda es

Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]), 
               End: DateTimeCol < Scalar Operator([Expr1007])

Entonces, en lugar de >= '20130101' ... < '20130102'leer, > '20121231' ... < '20130102'luego descarta todas las 2012-12-31filas.

Otra desventaja de confiar en él es que las estimaciones de cardinalidad pueden no ser tan precisas como con la consulta de rango tradicional. Esto se puede ver en una versión modificada de su SQL Fiddle .

Las 100 filas de la tabla ahora coinciden con el predicado (con fecha y hora con 1 minuto de diferencia, todo en el mismo día).

La segunda consulta (rango) estima correctamente que 100 coincidirán y utiliza un escaneo de índice agrupado. La CAST( AS DATE)consulta estima incorrectamente que solo una fila coincidirá y produce un plan con búsquedas clave.

Las estadísticas no se ignoran por completo. Si todas las filas de la tabla tienen el mismo datetimey coincide con el predicado (por ejemplo, 20130101 00:00:00o 20130101 01:00:00), el plan muestra un análisis de índice agrupado con un estimado de 31.6228 filas.

100 ^ 0.75 = 31.6228

Entonces, en ese caso, parece que la estimación se deriva de la fórmula aquí .

Si todas las filas de la tabla tienen el mismo datetimey no coincide con el predicado (p 20130102 01:00:00. Ej. ), Vuelve al recuento de filas estimado de 1 y al plan con búsquedas.

Para los casos en que la tabla tiene más de un DISTINCTvalor, las filas estimadas parecen ser las mismas que si la consulta estuviera buscando exactamente 20130101 00:00:00.

Si el histograma de estadísticas tiene un paso, 2013-01-01 00:00:00.000entonces la estimación se basará en EQ_ROWS(es decir, sin tener en cuenta otras veces en esa fecha). De lo contrario, si no hay un paso, parece que usa el AVG_RANGE_ROWSde los pasos circundantes.

Como datetimetiene una precisión de aproximadamente 3 ms en muchos sistemas, habrá muy pocos valores duplicados reales y este número será 1.

Martin Smith
fuente
1
Hola Martin, ¿podrías agregar una TL;DRparte con algunos puntos con diferentes casos agregando si en ese caso, el reparto hasta la fecha es una buena idea o no?
TT.
66
@TT. Creo que el punto es que no es una buena idea. ¿Por qué quieres usar un método que requiere una hoja de trucos?
Aaron Bertrand
10

Sé que tiene una gran respuesta de Martin desde hace mucho tiempo, pero quería agregar algunos cambios al comportamiento aquí en las versiones más recientes de SQL Server. Esto parece haber sido probado hasta 2008R2.

Con las nuevas SUGERENCIAS DE USO que hacen posible realizar un viaje en el tiempo de estimación de cardinalidad, podemos ver cuándo cambiaron las cosas.

Usando la misma configuración que en el Fiddle de SQL.

CREATE TABLE T ( ID INT IDENTITY PRIMARY KEY, DateTimeCol DATETIME, Filler CHAR(8000) NULL );

CREATE INDEX IX_T_DateTimeCol ON T ( DateTimeCol );


WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
     E02(N) AS (SELECT 1 FROM E00 a, E00 b),
     E04(N) AS (SELECT 1 FROM E02 a, E02 b),
     E08(N) AS (SELECT 1 FROM E04 a, E04 b),
     Num(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY E08.N) FROM E08)
INSERT INTO T(DateTimeCol)
SELECT TOP 100 DATEADD(MINUTE, Num.N, '20130101')
FROM Num;

Podemos probar los diferentes niveles así:

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_100' ));
GO

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_110' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_130' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140' ));
GO 

Los planes para todo esto están disponibles aquí . Los niveles de compatibilidad 100 y 110 proporcionan el plan de búsqueda clave, pero a partir del nivel de compatibilidad 120, comenzamos a obtener el mismo plan de exploración con estimaciones de 100 filas. Esto es cierto hasta el nivel de compatibilidad 140.

NUECES

NUECES

NUECES

La estimación de la cardinalidad para los >= '20130101', < '20130102'planes se mantiene en 100, lo que se esperaba.

Erik Darling
fuente