Apakah SQL Server mengevaluasi fungsi satu kali untuk setiap baris?

9

Saya punya pertanyaan seperti ini:

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

Ini memberikan tooltip pada rencana eksekusi yang mirip dengan ini:

Tooltip Eksekusi

Apakah dateaddbagian dari predikat pencarian dieksekusi untuk setiap baris dalam kueri? Atau apakah SQL Server menghitung nilai sekali untuk seluruh permintaan?

Stuart Blackler
sumber

Jawaban:

13

Fungsi-fungsi tertentu yang dikenal sebagai konstanta runtime melalui proses yang disebut pelipatan konstan . Dengan 'melipat' konstanta ekspresi dievaluasi lebih awal dalam eksekusi query, hasilnya di-cache dan hasilnya di-cache jika diperlukan. Ekspresi dalam kueri Anda DATEADD(dd, 0, DATEDIFF(dd, 0, getdate()))adalah, afaik, konstanta runtime dan karenanya akan dilipat dan dievaluasi hanya sekali per kueri.

Sebagai hal yang sepele: RAND()fungsi yang diharapkan tidak bisa dilipat sebenarnya bisa dilipat, yang mengarah pada beberapa perilaku yang tidak terduga. Tetapi yang lain, misalnya NEWID(), tidak dapat dilipat dan akan memaksa evaluasi per baris.

Remus Rusanu
sumber
2
@StuartBlackler - Ini adalah demonstrasi bagaimana SQL Server berfungsi seperti lipatan GETDATE().
Nick Chammas
2

Rencana eksekusi itu bagus, tetapi kadang-kadang mereka tidak mengatakan yang sebenarnya kepada Anda. Jadi, inilah bukti berdasarkan tes kinerja.

(dan intinya - ekspresi tidak dievaluasi untuk setiap baris)


;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 baris terpengaruh)

Ini adalah kueri OP dan butuh sekitar 12 detik untuk berjalan

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

Kueri ini yang menyimpan tanggal dalam parameter sebelum eksekusi, membutuhkan waktu yang hampir bersamaan, 12 detik.

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

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

Dan hanya untuk memverifikasi hasilnya -
Permintaan ini yang melakukan perhitungan pada col1 dan karena itu harus menghitung ulang ekspresi untuk setiap baris membutuhkan waktu sekitar 30 detik untuk dijalankan.

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

Semua kueri dieksekusi berulang kali menunjukkan tentang metrik yang sama

David Markודו Markovitz
sumber