SQL Server évalue-t-il les fonctions une fois pour chaque ligne?

9

J'ai une requête comme celle-ci:

SELECT col1
FROM   MyTable
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 
       BETWEEN col2 
       AND     col3
;

Cela donne une info-bulle sur le plan d'exécution similaire à ceci:

Info-bulle d'exécution

La dateaddpartie des prédicats de recherche est-elle exécutée pour chaque ligne de la requête? Ou SQL Server calcule-t-il la valeur une fois pour la requête entière?

Stuart Blackler
la source

Réponses:

13

Certaines fonctions connues pour être des constantes d'exécution passent par le processus appelé repliement constant . En «repliant» une constante, une expression est évaluée au début de l'exécution de la requête, le résultat est mis en cache et le résultat mis en cache à la place lorsque cela est nécessaire. L'expression dans votre requête DATEADD(dd, 0, DATEDIFF(dd, 0, getdate()))est, afaik, une constante d'exécution et ne sera donc pliée et évaluée qu'une seule fois par requête.

Comme anecdote: la RAND()fonction que l'on s'attendrait à être dépliable est en fait pliable, ce qui conduit à un comportement inattendu. Mais d'autres, par exemple NEWID(), ne sont pas pliables et forceront une évaluation par ligne.

Remus Rusanu
la source
2
@StuartBlackler - Voici une démonstration de la façon dont SQL Server replie des fonctions comme GETDATE().
Nick Chammas
2

Les plans d'exécution sont excellents mais parfois ils ne vous disent tout simplement pas la vérité. Voici donc une preuve basée sur un test de performance.

(et la ligne du bas - l'expression n'est pas évaluée pour chaque ligne)


;with t(i) as (select 0 union all select i+1 from t where i < 9)
select getdate()-1 as col1,getdate() as col2,getdate() as col3 
into #t 
from t t0,t t1,t t2,t t3,t t4,t t5,t t6,t t7

(100000000 ligne (s) affectée (s))

Il s'agit de la requête OP et son exécution prend environ 12 secondes

SELECT col1
FROM   #t
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 
       BETWEEN col2 
       AND     col3
;

Cette requête qui stocke la date dans un paramètre avant l'exécution, prend environ le même temps, 12 secondes.

declare @dt datetime = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 

SELECT col1
FROM   #t
WHERE  
      @dt
       BETWEEN col2 
       AND     col3
;

Et juste pour vérifier les résultats -
Cette requête qui effectue le calcul sur col1 et doit donc recalculer l'expression pour chaque ligne prend environ 30 secondes pour s'exécuter.

SELECT col1
FROM   #t
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, col1)) 
       BETWEEN col2 
       AND     col3
;

Toutes les requêtes ont été exécutées à plusieurs reprises montrant les mêmes mesures

David דודו Markovitz
la source