web-dev-qa-db-ja.com

SQL Serverは行ごとに関数を1回評価しますか?

次のようなクエリがあります。

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

これにより、次のような実行プランのツールチップが表示されます。

Execution Tooltip

シーク述語のdateadd部分は、クエリのすべての行に対して実行されますか?または、SQL Serverはクエリ全体の値を1回計算しますか?

9
Stuart Blackler

ランタイム定数 と呼ばれる特定の関数は、 定数の折りたたみ と呼ばれるプロセスを実行します。定数を「折り畳む」ことにより、式はクエリ実行の初期に評価され、結果はキャッシュされ、必要に応じてキャッシュされた結果が代わりにキャッシュされます。クエリ内の式DATEADD(dd, 0, DATEDIFF(dd, 0, getdate()))はランタイム定数なので、折りたたまれ、クエリごとに1回だけ評価されます。

雑学として:展開可能であると期待される Rand() 関数は実際には折りたたみ可能であり、予期しない動作が発生します。ただし、その他 NEWID() などは折りたたみ可能ではなく、行ごとに評価を強制します。

13
Remus Rusanu

実行計画は素晴らしいですが、時には真実を伝えないこともあります。したがって、ここにパフォーマンステストに基づく証明があります。

(そして一番下の行-式はすべての行に対して評価されていません)


;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行が影響を受けました)

これはOPクエリであり、実行に約12秒かかります

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

実行前にパラメータに日付を保存するこのクエリは、ほぼ同じ時間、12秒かかります。

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

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

そして結果を確認するだけです-
col1で計算を行うこのクエリは、すべての行の式を再計算する必要があるため、実行に約30秒かかります。

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

すべてのクエリが繰り返し実行され、ほぼ同じメトリックが表示された