it-swarm.asia

Apakah SQL Server membaca semua fungsi COALESCE bahkan jika argumen pertama bukan NULL?

Saya menggunakan fungsi T-SQL COALESCE di mana argumen pertama tidak akan menjadi nol pada sekitar 95% dari waktu dijalankan. Jika argumen pertama adalah NULL, argumen kedua adalah proses yang cukup panjang:

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

Jika, misalnya, c.FirstName = 'John', Apakah SQL Server masih menjalankan sub-kueri?

Saya tahu dengan fungsi VB.NET IIF(), jika argumen kedua Benar, kode masih membaca argumen ketiga (meskipun tidak akan digunakan).

102
Curt

Tidak . Inilah tes sederhana:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

Jika kondisi kedua dievaluasi, pengecualian dilemparkan untuk divide-by-zero.

Per Dokumentasi MSDN ini terkait dengan bagaimana COALESCE dilihat oleh penerjemah - itu hanya cara mudah untuk menulis pernyataan CASE.

CASE dikenal sebagai satu-satunya fungsi di SQL Server yang (kebanyakan) andal pendek.

Ada beberapa pengecualian ketika membandingkan dengan variabel skalar dan agregasi seperti yang ditunjukkan oleh Aaron Bertrand dalam jawaban lain di sini (dan ini akan berlaku untuk CASE dan COALESCE):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

akan menghasilkan pembagian dengan kesalahan nol.

Ini harus dianggap sebagai bug, dan sebagai aturan COALESCE akan diurai dari kiri ke kanan.

96
JNK

Bagaimana dengan yang ini - seperti yang dilaporkan kepada saya oleh Itzik Ben-Gan, yang diberitahu oleh Jaime Lafargue ?

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

Hasil:

Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.

Tentu saja ada beberapa penyelesaian sepele, tetapi intinya tetap bahwa CASE tidak selalu menjamin evaluasi kiri-ke-kanan/korsleting. saya melaporkan bug di sini dan ditutup sebagai "oleh desain." Paul White kemudian mengajukan item Hubungkan ini , dan ditutup sebagai Fixed. Bukan karena itu diperbaiki sendiri, tetapi karena mereka memperbarui Buku Online dengan deskripsi yang lebih akurat dari skenario di mana agregat dapat mengubah urutan evaluasi ekspresi CASE. Saya baru-baru ini lebih banyak blog tentang ini di sini .

[~ # ~] sunting [~ # ~] hanya sebuah tambahan, sementara saya setuju bahwa ini adalah kasus Tepi, yang sebagian besar waktu Anda dapat mengandalkan evaluasi kiri-ke-kanan dan hubungan arus pendek, dan ini adalah bug yang bertentangan dengan dokumentasi dan mungkin pada akhirnya akan diperbaiki (ini tidak pasti - lihat percakapan tindak lanjut di posting blog Bart Duncan untuk melihat mengapa), saya harus tidak setuju ketika orang mengatakan bahwa sesuatu selalu benar bahkan jika ada kasus Edge tunggal yang membantahnya. Jika Itzik dan yang lainnya dapat menemukan bug soliter seperti ini, itu membuatnya setidaknya di bidang kemungkinan ada bug lain juga. Dan karena kita tidak tahu sisa dari kueri OP, kita tidak bisa mengatakan dengan pasti bahwa dia akan bergantung pada hubungan arus pendek ini tetapi akhirnya digigit olehnya. Jadi bagi saya, jawaban yang lebih aman adalah:

Meskipun Anda dapat biasanya mengandalkan CASE untuk mengevaluasi dari kiri ke kanan dan korsleting, seperti dijelaskan dalam dokumentasi, tidak akurat untuk mengatakan bahwa Anda selalu dapat melakukan begitu. Ada dua kasus yang ditunjukkan pada halaman ini di mana itu tidak benar, dan tidak ada bug yang diperbaiki dalam versi SQL Server yang tersedia untuk umum.

[~ # ~] edit [~ # ~] di sini ada kasus lain (Saya harus berhenti melakukan itu) di mana CASE ekspresi tidak mengevaluasi dalam urutan yang Anda harapkan, meskipun tidak ada agregat yang terlibat.

75
Aaron Bertrand

Dokumentasi membuatnya cukup jelas bahwa niat adalah untuk CASE untuk hubungan pendek. Seperti Aaron menyebutkan , ada beberapa contoh yang dilaporkan dimana hal ini terbukti tidak selalu benar. Sejauh ini, sebagian besar telah diakui sebagai bug dan diperbaiki.

Ada masalah lain dengan CASE (dan karenanya COALESCE) di mana fungsi atau sub-kueri efek samping digunakan. Mempertimbangkan:

SELECT COALESCE((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);

Bentuk COALESCE sering mengembalikan nol, seperti yang dijelaskan dalam laporan bug oleh Hugo Kornelis.

Masalah yang diperlihatkan dengan transformasi pengoptimal dan pelacakan persamaan-umum berarti bahwa tidak mungkin untuk menjamin bahwa CASE akan mengalami hubungan pendek dalam semua keadaan.

Saya pikir Anda dapat cukup yakin bahwa CASE akan mengalami hubungan singkat secara umum (terutama jika orang yang cukup terampil memeriksa rencana eksekusi, dan bahwa rencana eksekusi 'ditegakkan' dengan panduan atau petunjuk rencana) tetapi jika Anda memerlukan jaminan mutlak, Anda harus menulis SQL yang tidak menyertakan ekspresi sama sekali.

38
Paul White 9

Saya telah menemukan kasus lain di mana CASE/COALESCE tidak mengalami hubungan pendek. TVF berikut akan meningkatkan pelanggaran PK jika disahkan 1 sebagai parameter.

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

Jika dipanggil sebagai berikut

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

Atau sebagai

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

Keduanya memberikan hasil

Pelanggaran batasan UTAMA 'PK__F__3BD019A800551192'. Tidak dapat memasukkan kunci duplikat ke objek 'dbo. @ T'. Nilai kunci duplikat adalah (1).

menunjukkan bahwa SELECT (atau setidaknya populasi variabel tabel) masih dilakukan dan menimbulkan kesalahan meskipun cabang pernyataan itu tidak boleh dihubungi. Paket versi COALESCE di bawah.

Plan

Penulisan ulang kueri ini muncul untuk menghindari masalah

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

Yang memberi rencana

Plan2

20
Martin Smith

Contoh lain

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

Kueri

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

Tidak menunjukkan pembacaan T2 sama sekali.

Pencarian T2 berada di bawah predikat lulus dan operator tidak pernah dieksekusi. Tapi

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

Apakah menunjukkan bahwa T2 adalah membaca. Meskipun tidak ada nilai dari T2 benar-benar dibutuhkan.

Tentu saja ini tidak benar-benar mengejutkan tetapi saya pikir layak menambahkan ke repositori contoh counter jika hanya karena menimbulkan masalah apa hubungan pendek bahkan berarti dalam bahasa deklaratif set yang ditetapkan.

9
Martin Smith

Saya hanya ingin menyebutkan strategi yang mungkin tidak Anda pertimbangkan. Ini mungkin bukan pertandingan di sini, tapi kadang-kadang berguna. Lihat apakah modifikasi ini memberi Anda kinerja yang lebih baik:

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

Cara lain untuk melakukannya adalah ini (pada dasarnya setara, tetapi memungkinkan Anda untuk mengakses lebih banyak kolom dari permintaan lain jika perlu):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

Pada dasarnya ini adalah teknik "keras" bergabung dengan tabel tetapi termasuk kondisi kapan setiap baris harus BERGABUNG. Dalam pengalaman saya, ini benar-benar membantu rencana eksekusi.

7
ErikE

Standar aktual mengatakan bahwa semua klausa KETIKA (serta klausa ELSE) harus diuraikan untuk menentukan tipe data dari ekspresi secara keseluruhan. Saya benar-benar harus mengeluarkan beberapa catatan lama saya untuk menentukan bagaimana kesalahan ditangani. Tapi begitu saja, 1/0 menggunakan bilangan bulat, jadi saya akan berasumsi bahwa itu kesalahan. Ini kesalahan dengan tipe data integer. Ketika Anda hanya memiliki nol di daftar gabungan, itu sedikit lebih sulit untuk menentukan tipe data, dan itu masalah lain.

3
Joe Celko

Tidak, itu tidak akan terjadi. Itu hanya akan berjalan ketika c.FirstName adalah NULL.

Namun, Anda harus mencobanya sendiri. Percobaan. Anda bilang subquery Anda panjang. Tolok ukur. Buat kesimpulan sendiri tentang ini.

@ Harun jawaban pada sub-kueri yang dijalankan lebih lengkap.

Namun, saya masih berpikir Anda harus mengolah kembali pertanyaan Anda dan menggunakan LEFT JOIN. Sebagian besar waktu, sub kueri dapat dihapus dengan mengerjakan ulang kueri Anda untuk menggunakan LEFT JOINs.

Masalah dengan menggunakan sub kueri adalah bahwa keseluruhan pernyataan Anda akan berjalan lebih lambat karena sub kueri dijalankan untuk setiap baris di set hasil kueri utama.

2
Adriano Carneiro