Le casting à ce jour est sargable mais est-ce une bonne idée?

47

Dans SQL Server 2008, le type de date a été ajouté.

La conversion d' une datetimecolonne en dateest sargable et peut utiliser un index sur la datetimecolonne.

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

L'autre option que vous avez est d'utiliser une plage à la place.

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

Ces requêtes sont-elles également bonnes ou faut-il privilégier l'une ou l'autre?

Mikael Eriksson
la source
4
Que dit le plan d'exécution?
a_horse_with_no_name
3
Je ne peux pas m'empêcher de remarquer que LINQ2SQL génère du SQL where cast(date_column as date) = 'value'lorsque C # est similaire à where obj.date_column.Date == date_variable.
GSerg
6
C'est un excellent article Connect. :)
Rob Farley
1
Le site Connect a été supprimé ainsi que Sargable dans Wikipedia
Ivanzinho le

Réponses:

59

Le mécanisme derrière la sargabilité du casting à ce jour est appelé recherche dynamique .

SQL Server appelle une fonction interne GetRangeThroughConvertpour obtenir le début et la fin de la plage.

De manière quelque peu surprenante, ce n'est pas la même plage que vos valeurs littérales.

Création d'un tableau avec une ligne par page et 1440 lignes par jour

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 

Puis courir

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 première requête a 1443lu et la seconde 2883, il lit un jour supplémentaire puis le rejette contre un prédicat résiduel.

Le plan montre que le prédicat de recherche est

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

Ainsi, au lieu de >= '20130101' ... < '20130102'le lire, > '20121231' ... < '20130102'puis élimine toutes les 2012-12-31lignes.

Le fait que les estimations de cardinalité ne soient pas aussi précises que dans le cas d’une interrogation de distance classique présente un autre inconvénient. Cela se voit dans une version modifiée de votre SQL Fiddle .

Les 100 lignes de la table correspondent maintenant au prédicat (avec des dates-heures distantes d'une minute le même jour).

La deuxième requête (plage) estime correctement que 100 correspondra et utilisera une analyse d'index en cluster. La CAST( AS DATE)requête estime à tort qu'une seule ligne correspondra et produit un plan avec des recherches de clé.

Les statistiques ne sont pas complètement ignorées. Si toutes les lignes de la table ont la même chose datetimeet si elles correspondent au prédicat (par exemple 20130101 00:00:00ou 20130101 01:00:00), le plan affiche une analyse d'index en cluster avec environ 31,6228 lignes.

100 ^ 0.75 = 31.6228

Donc, dans ce cas, il apparaît que l'estimation est dérivée de la formule ici .

Si toutes les lignes de la table ont la même chose datetimeet si elles ne correspondent pas au prédicat (par exemple 20130102 01:00:00), alors le nombre de lignes estimé est égal à 1 et le plan contient des recherches.

Dans les cas où la table a plusieurs DISTINCTvaleurs, les lignes estimées semblent être les mêmes que si la requête cherchait exactement 20130101 00:00:00.

Si l'histogramme des statistiques comporte un pas, 2013-01-01 00:00:00.000l'estimation est basée sur le EQ_ROWS(c'est-à-dire qu'elle ne tient pas compte des autres heures à cette date). Sinon, s'il n'y a pas d'étape, il semblerait que vous utilisiez les AVG_RANGE_ROWSétapes précédentes.

Comme datetimeavec une précision d'environ 3 ms dans de nombreux systèmes, il y aura très peu de valeurs de doublons réelles et ce nombre sera égal à 1.

Martin Smith
la source
1
Bonjour Martin, pourriez-vous ajouter une TL;DRpartie avec quelques points avec des cas différents et préciser si, dans ce cas, la distribution à ce jour est-elle une bonne idée ou non?
TT.
6
@TT. Je pense que le fait est que ce n'est pas une bonne idée. Pourquoi voulez-vous utiliser une méthode qui nécessite un aide-mémoire?
Aaron Bertrand
10

Je sais que cela a une longue réponse Great Answer® de Martin, mais je voulais ajouter quelques changements au comportement dans les nouvelles versions de SQL Server. Cela semble n'avoir été testé que jusqu'en 2008R2.

Avec les nouveaux USE HINT qui permettent d'effectuer une estimation du temps de cardinalité, nous pouvons voir quand les choses ont changé.

Utilisation de la même configuration que dans 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;

Nous pouvons tester les différents niveaux comme suit:

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 

Les plans pour tout cela sont disponibles ici . Les niveaux de compatibilité 100 et 110 donnent tous les deux le plan de recherche clé, mais à partir du niveau de compatibilité 120, nous commençons à obtenir le même plan de balayage avec des estimations sur 100 lignes. Cela est vrai jusqu'au niveau 140.

DES NOISETTES

DES NOISETTES

DES NOISETTES

L'estimation de la cardinalité pour les >= '20130101', < '20130102'plans reste à 100, ce qui était prévu.

Erik Darling
la source