Cast fino ad oggi è sargable ma è una buona idea?


47

In SQL Server 2008 è stato aggiunto il tipo di dati della data .

Il cast di una datetimecolonna dateè sargable e può usare un indice sulla datetimecolonna.

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

L'altra opzione che hai è usare invece un intervallo.

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

Queste domande sono ugualmente valide o dovrebbero essere preferite l'una rispetto all'altra?


4
Cosa dice il piano di esecuzione?
a_horse_with_no_name

3
Non posso fare a meno di notare che LINQ2SQL genera SQL where cast(date_column as date) = 'value'quando viene presentato con C # simile a where obj.date_column.Date == date_variable.
GSerg,

6
Questo è un eccellente oggetto Connect. :)
Rob Farley,

1
Il sito Connect è stato rimosso e Sargable in Wikipedia
Ivanzinho,

Risposte:


59

Il meccanismo alla base della sargibilità del cast fino ad oggi si chiama ricerca dinamica .

SQL Server chiama una funzione interna GetRangeThroughConvertper ottenere l'inizio e la fine dell'intervallo.

Un po 'sorprendentemente, questo non è lo stesso intervallo dei tuoi valori letterali.

Creazione di una tabella con una riga per pagina e 1440 righe al giorno

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 

Quindi correndo

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 prima query ha 1443letto e la seconda 2883quindi sta leggendo un intero giorno aggiuntivo, quindi scartandola da un predicato residuo.

Il piano mostra che è il predicato di ricerca

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

Quindi invece di >= '20130101' ... < '20130102'leggere, > '20121231' ... < '20130102'quindi scarta tutte le 2012-12-31righe.

Un altro svantaggio di fare affidamento su di esso è che le stime della cardinalità potrebbero non essere accurate come con l'interrogazione di intervallo tradizionale. Questo può essere visto in una versione modificata del tuo SQL Fiddle .

Tutte le 100 righe nella tabella ora corrispondono al predicato (con intervalli di tempo di 1 minuto l'uno dall'altro tutti nello stesso giorno).

La seconda query (intervallo) stima correttamente che 100 corrisponderà e utilizza una scansione dell'indice cluster. La CAST( AS DATE)query stima erroneamente che una sola riga corrisponderà e produce un piano con ricerche chiave.

Le statistiche non vengono completamente ignorate. Se tutte le righe nella tabella hanno le stesse datetimee corrispondono al predicato (ad es. 20130101 00:00:00O 20130101 01:00:00), il piano mostra una scansione dell'indice cluster con una stima di 31.6228 righe.

100 ^ 0.75 = 31.6228

Quindi in quel caso sembra che la stima sia derivata dalla formula qui .

Se tutte le righe nella tabella hanno lo stesso datetimee non corrisponde al predicato (ad es. 20130102 01:00:00), Allora ricade sul conteggio delle righe stimato di 1 e il piano con ricerche.

Per i casi in cui la tabella ha più di un DISTINCTvalore, le righe stimate sembrano essere le stesse della query esattamente cercata 20130101 00:00:00.

Se l'istogramma statistico presenta un passaggio, 2013-01-01 00:00:00.000la stima si baserà sul EQ_ROWS(ovvero non prendendo in considerazione altre volte in quella data). Altrimenti se non ci sono passaggi sembra che usi i AVG_RANGE_ROWSpassaggi circostanti.

Come datetimeha una precisione di circa 3 ms in molti sistemi, ci saranno pochissimi valori effettivi duplicati e questo numero sarà 1.


1
Ciao Martin, potresti aggiungere una TL;DRparte con alcuni punti elenco con casi diversi aggiungendo se in quel caso il cast fino ad oggi è una buona idea o no?
TT.

6
@TT. Penso che il punto sia che non è una buona idea. Perché vuoi usare un metodo che richiede un cheat sheet?
Aaron Bertrand

10

So che questo ha una Great Answer® di lunga data di Martin, ma volevo aggiungere alcune modifiche al comportamento qui nelle versioni più recenti di SQL Server. Questo sembra essere stato testato solo fino al 2008R2.

Con i nuovi SUGGERIMENTI PER L'USO che rendono possibile fare un po 'di tempo per la stima della cardinalità, possiamo vedere quando le cose sono cambiate.

Utilizzando la stessa configurazione di SQL Fiddle.

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;

Possiamo testare i diversi livelli in questo modo:

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 

I piani per tutti questi sono disponibili qui . I livelli di compatibilità 100 e 110 forniscono entrambi il piano di ricerca chiave, ma a partire dal livello di compatibilità 120, iniziamo a ottenere lo stesso piano di scansione con stime a 100 righe. Questo è vero fino al livello di compatibilità 140.

NOCCIOLINE

NOCCIOLINE

NOCCIOLINE

La stima della cardinalità per i >= '20130101', < '20130102'piani rimane a 100, come previsto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.