2011-09-04 1 views
4

J'ai une requête comme ceci sur Sql Server 2008:Est-ce que SQL Server optimise le calcul DATEADD dans une requête de sélection?

DECLARE @START_DATE DATETIME 
SET @START_DATE = GETDATE() 

SELECT * FROM MY_TABLE 
WHERE TRANSACTION_DATE_TIME > DATEADD(MINUTE, -1440, @START_DATE) 

Dans la requête de sélection que vous voyez ci-dessus, ne SqlServer optimisation de la requête afin de ne pas calculer encore et encore le résultat DATEADD. Ou est-ce ma responsabilité de stocker le résultat DATEADD sur une variable temporaire?

Répondre

8

Étonnamment, j'ai trouvé que l'utilisation de GETDATE() inline semble être plus efficace que d'effectuer ce type de calcul préalable.

DECLARE @sd1 DATETIME, @sd2 DATETIME; 
SET @sd1 = GETDATE(); 

SELECT * FROM dbo.table 
WHERE datetime_column > DATEADD(MINUTE, -1440, @sd1) 

SELECT * FROM dbo.table 
WHERE datetime_column > DATEADD(MINUTE, -1440, GETDATE()) 

SET @sd2 = DATEADD(MINUTE, -1440, @sd1); 

SELECT * FROM dbo.table 
WHERE datetime_column > @sd2; 

Si vous vérifiez les plans sur ceux-ci, la requête moyenne viendra toujours avec le plus bas coût (mais pas toujours le temps écoulé le plus bas). Bien sûr, cela peut dépendre de vos index et données, et vous ne devriez pas faire d'hypothèses basées sur une requête que la même optimisation préemptive fonctionnera sur une autre requête. Mon instinct serait de ne pas effectuer de calculs en ligne, et d'utiliser plutôt la variation @sd2 ci-dessus ... mais j'ai appris que je ne peux pas faire confiance à mon instinct tout le temps et je ne peux pas faire d'hypothèses générales basées sur le comportement que j'éprouve dans des scénarios particuliers.

+1

+1 c'est surprenant. Je l'ai juste regardé et je pense que c'est parce que SQL Server ne fait pas de reniflage de variable donc supposera toujours que '> @ variable' correspondra à 30% des rangées (et les coûts montrés dans le plan sont juste basés sur cette estimation) . Il semble être plus précis pour la version 'DATEADD (MINUTE, -1440, getdate())' au moins au moment où le plan est compilé. –

+1

Un problème potentiel/cas de bord avec requêtes GetDate() 'est qu'il est évalué au moment de la compilation et les sélectivités peuvent changer énormément juste à travers le temps [sans déclencher une recompilation] (http://stackoverflow.com/a/9905880/73226) –

3

Il sera exécuté une seule fois. Vous pouvez le vérifier en vérifiant le plan d'exécution ("Compute Scalar" -> Estimated Number of execution == 1)

10

Les fonctions SQL Server considérées comme runtime constants sont évaluées une seule fois. GETDATE() est une telle fonction et DATEADD(..., constant, GETDATE()) est également une constante d'exécution. En laissant l'appel de la fonction réelle dans la requête, vous laissez l'optimiseur voir quelle valeur sera réellement utilisée (par opposition à un sniff de valeur variable) et ensuite il peut ajuster ses estimations de cardinalité en conséquence, éventuellement avec un meilleur plan.

À lire aussi: Troubleshooting Poor Query Performance: Constant Folding and Expression Evaluation During Cardinality Estimation.

Smith @ Martin

Vous pouvez exécuter cette requête:

set nocount on; 
declare @known int; 
select @known = count(*) from sysobjects; 
declare @cnt int = @known; 
while @cnt = @known 
    select @cnt = count(*) from sysobjects where getdate()=getdate() 
select @cnt, @known; 

Dans mon cas, au bout de 22 secondes, il a frappé le cas limite et la boucle est sortie. La chose importante est que la boucle est sortie avec @cntzéro. On s'attendrait à ce que si le getdate() est évalué par ligne alors nous aurions un @cnt différent du compte @known correct, mais pas 0. Le fait que @cnt soit zéro quand la boucle existe montre que chaque getdate() a été évalué une fois puis le la même valeur constante a été utilisée pour chaque ligne WHERE filtrage (correspondant à aucun). Je suis conscient qu'un exemple positif ne prouve pas un théorème, mais je pense que le cas est assez concluant.

+0

Le premier lien semble impliquer que 'getdate()' n'est évalué qu'une fois par ** requête ** mais cela ne boucle pas indéfiniment 'SET NOCOUNT ON; WHILE EXISTS (SELECT 1 FROM sysobjects OERE GETDATE() = GETDATE()) IMPRIMER 'Yes' –

+0

@Martin: Je pense que je sais ce qui se passe.L'inspection du plan XML montre que votre requête a * deux * const expressions, chacune évaluée une fois (dans mon XML showplan, elles s'appelaient 'ConstExpr1080' et' ConstExpr1081'). Les valeurs sont toujours différentes, puisqu'elles sont évaluées à des moments distincts, mais en raison de la faible précision du type datetime (3ms), elles seront souvent la même valeur plusieurs fois. De temps en temps, l'évaluation du premier getdate et du second getdate tombe sur différents côtés d'une limite d'arrondi, puis la boucle s'arrête. –

+0

Il semble donc que la mise en cache se passe par référence de fonction plutôt que d'avoir un appel de fonction avec le résultat réutilisé dans tous les endroits de la requête, comme le suggère l'article de Conor. Ou êtes-vous d'avis que j'ai mal compris ce qu'il dit et que cela ne devrait pas s'appliquer dans ce cas? –

Questions connexes