Ini adalah bug dalam normalisasi proyek , diekspos dengan menggunakan subquery di dalam ekspresi kasus dengan fungsi non-deterministik.
Untuk menjelaskannya, kita perlu mencatat dua hal di muka:
- SQL Server tidak dapat mengeksekusi subquery secara langsung, sehingga mereka selalu terbuka atau dikonversi menjadi berlaku .
- Semantiknya
CASE
sedemikian sehingga THEN
ekspresi hanya harus dievaluasi jika WHEN
klausa mengembalikan true.
Subquery (sepele) yang diperkenalkan dalam kasus bermasalah karenanya menghasilkan operator yang berlaku (nested loop bergabung). Untuk memenuhi persyaratan kedua, SQL Server pada awalnya menempatkan ekspresi dbo.test6(1) + dbo.test6(2)
di sisi dalam dari penerapan:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
... dengan CASE
semantik yang dihormati oleh predikat lulus pada bergabung:
[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)
Sisi dalam loop hanya dievaluasi jika kondisi pass-through bernilai false (artinya @i = 3
). Sejauh ini semuanya benar. The Hitung skalar mengikuti loop bersarang bergabung juga menghormati CASE
semantik benar:
[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
Masalahnya adalah bahwa tahap normalisasi proyek kompilasi permintaan melihat yang Expr1000
tidak berkorelasi dan menentukan bahwa itu akan aman ( narator: tidak ) untuk memindahkannya ke luar loop:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
Ini memecah * semantik yang diimplementasikan oleh predikat pass-through , sehingga fungsi dievaluasi ketika seharusnya tidak, dan hasil loop tak terbatas.
Anda harus melaporkan bug ini. Solusinya adalah untuk mencegah ekspresi dipindahkan di luar penerapan dengan membuatnya berkorelasi (yaitu termasuk @i
dalam ekspresi) tetapi ini adalah retasan tentu saja. Ada cara untuk menonaktifkan normalisasi proyek, tetapi saya telah diminta sebelumnya untuk tidak membagikannya kepada publik, jadi saya tidak akan melakukannya.
Masalah ini tidak muncul di SQL Server 2019 ketika fungsi skalar diuraikan, karena logika inlining beroperasi langsung pada pohon yang diurai (jauh sebelum normalisasi proyek). Logika sederhana dalam pertanyaan dapat disederhanakan dengan logika inlining ke non-rekursif:
[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))
... yang mengembalikan 3.
Cara lain untuk menggambarkan masalah inti adalah:
-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error()
RETURNS integer
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
RETURN 1/0;
END;
GO
DECLARE @i integer = 1;
SELECT
CASE
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
ELSE NULL
END;
Diproduksi ulang pada versi terbaru dari semua versi dari 2008 R2 hingga 2019 CTP 3.0.
Contoh lebih lanjut (tanpa fungsi skalar) disediakan oleh Martin Smith :
SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))
Ini memiliki semua elemen kunci yang dibutuhkan:
CASE
(diimplementasikan secara internal sebagai ScaOp_IIF
)
- Fungsi non-deterministik (
CRYPT_GEN_RANDOM
)
- Subquery pada cabang yang seharusnya tidak dieksekusi (
(SELECT ...)
)
* Secara ketat, transformasi di atas masih bisa benar jika evaluasi Expr1000
ditangguhkan dengan benar, karena hanya dirujuk oleh konstruksi yang aman:
[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
... tapi ini memerlukan flag ForceOrder internal (bukan petunjuk permintaan), yang tidak disetel juga. Dalam kasus apa pun, implementasi logika yang diterapkan oleh normalisasi proyek tidak benar atau tidak lengkap.
Laporan bug di situs Umpan Balik Azure untuk SQL Server.