Cast to Date ist Sargable, aber ist es eine gute Idee?

47

In SQL Server 2008 das Datum wurde Datentyp hinzugefügt.

Das Casting einer datetimeSpalte in dateist sargable und kann einen Index für die datetimeSpalte verwenden.

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

Die andere Möglichkeit besteht darin, stattdessen einen Bereich zu verwenden.

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

Sind diese Abfragen gleich gut oder sollte eine der anderen vorgezogen werden?

Mikael Eriksson
quelle
4
Was steht im Ausführungsplan?
a_horse_with_no_name
3
Ich kann nicht umhin zu bemerken, dass LINQ2SQL SQL generiert, where cast(date_column as date) = 'value'wenn es mit C # ähnlich präsentiert wird where obj.date_column.Date == date_variable.
GSerg
6
Das ist ein ausgezeichneter Connect-Artikel. :)
Rob Farley
1
Die Connect-Site wurde entfernt und Sargable in Wikipedia
Ivanzinho

Antworten:

59

Der Mechanismus hinter der Sargabilität des bisherigen Castings heißt dynamisches Suchen .

SQL Server ruft eine interne Funktion GetRangeThroughConvertauf, um den Anfang und das Ende des Bereichs abzurufen.

Es ist etwas überraschend, dass dies nicht der gleiche Bereich ist wie Ihre Literalwerte.

Erstellen einer Tabelle mit einer Zeile pro Seite und 1440 Zeilen pro Tag

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 

Dann rennen

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'; 

Die erste Abfrage 1443lautet "read" und die zweite " 2883read", sodass ein ganzer zusätzlicher Tag gelesen und dann gegen ein verbleibendes Prädikat verworfen wird.

Der Plan zeigt, dass das Suchprädikat ist

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

Also anstatt >= '20130101' ... < '20130102'es zu lesen, werden > '20121231' ... < '20130102'dann alle 2012-12-31Zeilen verworfen .

Ein weiterer Nachteil der Verwendung ist, dass die Kardinalitätsschätzungen möglicherweise nicht so genau sind wie bei der herkömmlichen Bereichsabfrage. Dies kann in einer geänderten Version Ihrer SQL-Geige gesehen werden .

Alle 100 Zeilen in der Tabelle stimmen jetzt mit dem Prädikat überein (mit einem Abstand von 1 Minute am selben Tag).

Die zweite Abfrage (Bereich) schätzt korrekt, dass 100 übereinstimmt, und verwendet einen Clustered-Index-Scan. Die CAST( AS DATE)Abfrage schätzt fälschlicherweise, dass nur eine Zeile übereinstimmt, und erstellt einen Plan mit Schlüsselsuchen.

Die Statistiken werden nicht vollständig ignoriert. Wenn alle Zeilen in der Tabelle identisch sind datetimeund mit dem Prädikat (z. B. 20130101 00:00:00oder 20130101 01:00:00) übereinstimmen, zeigt der Plan einen Clustered-Index-Scan mit geschätzten 31.6228 Zeilen an.

100 ^ 0.75 = 31.6228

In diesem Fall wird die Schätzung also aus der hier angegebenen Formel abgeleitet .

Wenn alle Zeilen in der Tabelle gleich sind datetimeund sie nicht mit dem Prädikat übereinstimmen (z. B. 20130102 01:00:00), wird auf die geschätzte Zeilenanzahl von 1 und den Plan mit Nachschlägen zurückgegriffen.

In den Fällen, in denen die Tabelle mehr als einen DISTINCTWert enthält, scheinen die geschätzten Zeilen so zu sein, als ob die Abfrage genau danach gesucht hätte 20130101 00:00:00.

Wenn das Statistik-Histogramm zu einem bestimmten 2013-01-01 00:00:00.000Zeitpunkt einen Sprung aufweist , basiert die Schätzung auf dem EQ_ROWS(dh, andere Zeitpunkte an diesem Datum werden nicht berücksichtigt). Andernfalls sieht es so aus, als würde die AVG_RANGE_ROWSvon den umliegenden Stufen verwendet, wenn es keine Stufe gibt .

Da datetimees in vielen Systemen eine Genauigkeit von ca. 3 ms gibt, gibt es nur sehr wenige doppelte Werte, und diese Zahl ist 1.

Martin Smith
quelle
1
Hallo Martin, könntest du einen TL;DRTeil mit ein paar Aufzählungspunkten mit verschiedenen Fällen hinzufügen, ob in diesem Fall die bisherige Besetzung eine gute Idee ist oder nicht?
TT.
6
@TT. Ich denke, der Punkt ist, dass es keine gute Idee ist. Warum möchten Sie eine Methode verwenden, für die ein Spickzettel erforderlich ist?
Aaron Bertrand
10

Ich weiß, dass dies eine langjährige Great Answer® von Martin ist, aber ich wollte hier in neueren Versionen von SQL Server einige Änderungen am Verhalten vornehmen. Dies scheint nur bis 2008R2 getestet worden zu sein.

Mit den neuen USE HINTs , die das Durchführen einiger Zeitreisen zur Schätzung der Kardinalität ermöglichen, können wir sehen, wann sich die Dinge ändern.

Verwenden Sie das gleiche Setup wie in der SQL-Geige.

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;

Wir können die verschiedenen Level so testen:

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 

Die Pläne für all dies finden Sie hier . Die Kompatibilitätsstufen 100 und 110 geben beide den Schlüssel-Lookup-Plan an. Ab Kompatibilitätsstufe 120 erhalten wir jedoch den gleichen Scan-Plan mit 100 Zeilenschätzungen. Dies gilt bis zur Kompatibilitätsstufe 140.

NÜSSE

NÜSSE

NÜSSE

Die >= '20130101', < '20130102'erwartete Schätzung der Kardinalität für die Pläne liegt weiterhin bei 100.

Erik Darling
quelle