KASUS SQL? Sepotong kue!
Benarkah?
Tidak sampai Anda menemukan 3 masalah merepotkan yang dapat menyebabkan kesalahan runtime dan kinerja yang lambat.
Jika Anda mencoba memindai subjudul untuk melihat apa masalahnya, saya tidak dapat menyalahkan Anda. Pembaca, termasuk saya, sudah tidak sabar.
Saya yakin Anda sudah mengetahui dasar-dasar SQL CASE, jadi, saya tidak akan membuat Anda bosan dengan perkenalan yang panjang. Mari gali pemahaman yang lebih dalam tentang apa yang terjadi di balik layar.
1. SQL CASE Tidak Selalu Mengevaluasi Secara Berurutan
Ekspresi dalam pernyataan Microsoft SQL CASE sebagian besar dievaluasi secara berurutan atau dari kiri ke kanan. Ini adalah cerita yang berbeda, ketika menggunakannya dengan fungsi agregat. Mari kita ambil contoh:
-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;
Kode di atas terlihat normal. Jika saya menanyakan apa hasil dari pernyataan tersebut, Anda mungkin akan menjawab 1. Inspeksi visual memberi tahu kami bahwa karena @nilai disetel ke 0. Ketika @nilai adalah 0, hasilnya adalah 1.
Tapi itu tidak terjadi di sini. Lihat hasil nyata dari SQL Server Management Studio:
Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.
Tapi kenapa?
Ketika ekspresi bersyarat menggunakan fungsi agregat seperti MAX() dalam SQL CASE, itu dievaluasi terlebih dahulu. Jadi, MAX(1/@nilai) akan menyebabkan kesalahan pembagian dengan nol karena @nilai adalah nol.
Situasi ini lebih merepotkan jika disembunyikan. Saya akan menjelaskannya nanti.
2. Ekspresi SQL CASE Sederhana Mengevaluasi Beberapa Kali
Jadi apa?
Pertanyaan bagus. Sebenarnya, tidak ada masalah sama sekali jika Anda menggunakan literal atau ekspresi sederhana. Tetapi jika Anda menggunakan subkueri sebagai ekspresi bersyarat, Anda akan mendapatkan kejutan besar.
Sebelum Anda mencoba contoh di bawah ini, Anda mungkin ingin memulihkan salinan database dari sini. Kami akan menggunakannya untuk contoh lainnya.
Sekarang, pertimbangkan pertanyaan yang sangat sederhana ini:
SELECT TOP 1 manufacturerID FROM SportsCars
Ini sangat sederhana, bukan? Ini mengembalikan 1 baris dengan 1 kolom data. STATISTICS IO mengungkapkan pembacaan logis minimal.
Catatan singkat :Untuk yang belum tahu, memiliki pembacaan logis yang lebih tinggi membuat kueri menjadi lambat. Baca ini untuk detail selengkapnya.
Rencana Eksekusi juga mengungkapkan proses sederhana:
Sekarang, mari kita masukkan kueri itu ke dalam ekspresi CASE sebagai subkueri:
-- Using a subquery in a SQL CASE
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Analisis
Silangkan jari Anda karena ini akan mengacaukan pembacaan logis sebanyak 4 kali.
Kejutan! Dibandingkan dengan Gambar 1 dengan hanya 2 pembacaan logis, ini 4 kali lebih tinggi. Dengan demikian, kueri 4 kali lebih lambat. Bagaimana itu bisa terjadi? Kami hanya melihat subquery sekali.
Tapi itu bukan akhir dari cerita. Lihat Rencana Eksekusi:
Kami melihat 4 contoh operator Pemindaian Atas dan Indeks pada Gambar 4. Jika setiap Pemindaian Atas dan Indeks menggunakan 2 pembacaan logis, itu menjelaskan mengapa pembacaan logis menjadi 8 pada Gambar 3. Dan karena setiap Pemindaian Atas dan Indeks memiliki biaya 25% , itu juga memberi tahu kita bahwa mereka sama.
Tapi itu tidak berakhir di sana. Properti operator Hitung Skalar mengungkapkan bagaimana seluruh pernyataan diperlakukan.
Kita melihat 4 ekspresi CASE WHEN yang berasal dari Operator Hitung Skalar Nilai yang Ditentukan. Sepertinya ekspresi CASE sederhana kita menjadi ekspresi CASE yang dicari seperti ini:
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Mari kita rekap. Ada 2 pembacaan logis untuk setiap operator Pemindaian Atas dan Indeks. Ini dikalikan dengan 4 membuat 8 pembacaan logis. Kami juga melihat 4 ekspresi CASE WHEN di operator Compute Scalar.
Pada akhirnya, subquery dalam ekspresi CASE sederhana dievaluasi 4 kali. Ini akan memperlambat kueri Anda.
Cara Menghindari Beberapa Evaluasi Subquery dalam Ekspresi CASE Sederhana
Untuk menghindari masalah kinerja seperti beberapa pernyataan CASE dalam SQL, kita perlu menulis ulang kueri.
Pertama, masukkan hasil subquery ke dalam variabel. Kemudian, gunakan variabel tersebut dalam kondisi ekspresi SQL Server CASE sederhana, seperti ini:
DECLARE @manufacturer NVARCHAR(50)
DECLARE @ManufacturerID INT -- create a new variable
-- store the result of the subquery in a variable
SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars)
-- use the new variable in the simple CASE expression
SET @manufacturer = (CASE @ManufacturerID
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Apakah ini perbaikan yang bagus? Mari kita lihat pembacaan logis di STATISTICS IO:
Kami melihat pembacaan logis yang lebih rendah dari kueri yang dimodifikasi. Mengambil subquery dan menetapkan hasilnya ke variabel jauh lebih baik. Bagaimana dengan Rencana Eksekusi? Lihat di bawah.
Operator Pemindaian Atas dan Indeks hanya muncul sekali, bukan 4 kali. Luar biasa!
Bawa pulang :Jangan gunakan subquery sebagai kondisi dalam ekspresi CASE. Jika Anda perlu mengambil nilai, masukkan hasil subquery ke dalam variabel terlebih dahulu. Kemudian, gunakan variabel itu dalam ekspresi CASE.
3. 3 Fungsi Bawaan Ini Secara Diam-diam Berubah ke SQL CASE
Ada rahasia, dan pernyataan SQL Server CASE ada hubungannya dengan itu. Jika Anda tidak tahu bagaimana perilaku 3 fungsi ini, Anda tidak akan tahu bahwa Anda melakukan kesalahan yang kami coba hindari di poin #1 dan #2 sebelumnya. Ini dia:
- IIF
- COALESCE
- PILIH
Mari kita periksa satu per satu.
IIF
Saya menggunakan JIKA Segera, atau IIF, dalam Visual Basic dan Visual Basic for Applications. Ini juga setara dengan operator ternary C#:
Fungsi ini diberikan kondisi akan mengembalikan 1 dari 2 argumen berdasarkan hasil kondisi. Dan fungsi ini juga tersedia di T-SQL. Pernyataan CASE dalam klausa WHERE dapat digunakan dalam pernyataan SELECT
Tapi itu hanya lapisan gula dari ekspresi CASE yang lebih panjang. Bagaimana kami bisa tahu? Mari kita periksa sebuah contoh.
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');
Hasil dari kueri ini adalah 'Tidak.' Namun, periksa Rencana Eksekusi bersama dengan properti Compute Scalar.
Karena IIF adalah CASE WHEN menurut Anda apa yang akan terjadi jika Anda mengeksekusi sesuatu seperti ini?
DECLARE @averageCost MONEY = 1000000.00;
DECLARE @noOfPayments TINYINT = 0; -- intentional to force the error
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));
Ini akan menghasilkan kesalahan Divide by Zero jika @noOfPayments adalah 0. Hal yang sama terjadi pada poin #1 sebelumnya.
Anda mungkin bertanya-tanya apa yang menyebabkan kesalahan ini karena kueri di atas akan menghasilkan TRUE dan harus mengembalikan 83333.33. Periksa kembali poin #1.
Jadi, jika Anda terjebak dengan kesalahan seperti ini saat menggunakan IIF, SQL CASE adalah penyebabnya.
COALESCE
COALESCE juga merupakan jalan pintas dari ekspresi SQL CASE. Ini mengevaluasi daftar nilai dan mengembalikan nilai non-null pertama. Dalam artikel sebelumnya tentang COALESCE, saya menyajikan contoh yang mengevaluasi subquery dua kali. Tapi saya menggunakan metode lain untuk mengungkapkan KASUS SQL dalam Rencana Eksekusi. Berikut contoh lain yang akan menggunakan teknik yang sama.
SELECT
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID
Mari kita lihat Execution Plan dan Compute Scalar Defined Values.
KASUS SQL baik-baik saja. Kata kunci COALESCE tidak ada di jendela Nilai yang Ditentukan. Ini membuktikan rahasia di balik fungsi ini.
Tapi itu tidak semua. Berapa kali Anda melihat [Kendaraan].[dbo].[Gaya].[Gaya] di jendela Nilai yang Ditentukan? DUA KALI! Ini konsisten dengan dokumentasi resmi Microsoft. Bayangkan jika salah satu argumen di COALESCE adalah subquery. Kemudian, gandakan pembacaan logis dan dapatkan eksekusi yang lebih lambat juga.
PILIH
Terakhir, PILIH. Ini mirip dengan fungsi MS Access CHOOSE. Ini mengembalikan 1 nilai dari daftar nilai berdasarkan posisi indeks. Ini juga bertindak sebagai indeks ke dalam array.
Mari kita lihat apakah kita dapat menggali transformasi menjadi SQL CASE dengan sebuah contoh. Lihat kode di bawah ini:
;WITH McLarenCars AS
(
SELECT
CASE
WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
ELSE '2'
END AS [type]
,sc.Model
,s.Style
FROM SportsCars sc
INNER JOIN Styles s ON sc.StyleID = s.StyleID
WHERE sc.ManufacturerID = 108
)
SELECT
Model
,Style
,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
FROM McLarenCars
Ada contoh PILIH kami. Sekarang, mari kita periksa Execution Plan dan Compute Scalar Defined Values:
Apakah Anda melihat kata kunci CHOOSE di jendela Defined Values pada Gambar 10? Bagaimana dengan KASUS KAPAN?
Seperti contoh sebelumnya, fungsi CHOOSE ini hanyalah lapisan gula untuk ekspresi CASE yang lebih panjang. Dan karena kueri memiliki 2 item untuk MEMILIH, kata kunci KASUS KETIKA muncul dua kali. Lihat jendela Nilai yang Ditentukan yang diapit kotak merah.
Namun, kami memiliki beberapa CASE WHEN dalam SQL di sini. Itu karena ekspresi CASE dalam kueri dalam CTE. Jika Anda perhatikan baik-baik, bagian dari kueri dalam itu juga muncul dua kali.
Bawa Pulang
Sekarang setelah rahasianya terungkap, apa yang telah kita pelajari?
- SQL CASE berperilaku berbeda ketika fungsi agregat digunakan. Berhati-hatilah saat meneruskan argumen ke fungsi agregat seperti MIN, MAX, atau COUNT.
- Ekspresi CASE sederhana akan dievaluasi beberapa kali. Perhatikan dan hindari melewatkan subquery. Meskipun secara sintaksis benar, performanya akan buruk.
- IIF, CHOOSE, dan COALESCE punya rahasia kotor. Ingatlah sebelum memberikan nilai ke fungsi tersebut. Ini akan berubah menjadi KASUS SQL. Bergantung pada nilainya, Anda menyebabkan kesalahan atau penalti kinerja.
Saya harap pandangan berbeda tentang SQL CASE ini bermanfaat bagi Anda. Jika demikian, teman-teman pengembang Anda mungkin menyukainya juga. Silakan bagikan di platform media sosial favorit Anda. Dan beri tahu kami pendapat Anda tentang hal itu di bagian Komentar.