Ini adalah masalah yang saya hadapi secara berkala dan belum menemukan solusi yang baik untuk itu.
Misalkan struktur tabel berikut
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
dan persyaratannya adalah untuk menentukan apakah salah satu kolom yang dapat dibatalkan B
atau C
benar - benar berisi NULL
nilai apa pun (dan jika demikian yang mana).
Juga asumsikan tabel berisi jutaan baris (dan bahwa tidak ada statistik kolom yang tersedia yang dapat diintip karena saya tertarik pada solusi yang lebih umum untuk kelas kueri ini).
Saya bisa memikirkan beberapa cara untuk mendekati ini tetapi semua memiliki kelemahan.
Dua EXISTS
pernyataan terpisah . Ini akan memiliki keuntungan membiarkan kueri berhenti memindai lebih awal segera setelah NULL
ditemukan. Tetapi jika kedua kolom sebenarnya mengandung no NULL
s maka dua pemindaian penuh akan menghasilkan.
Permintaan Agregat Tunggal
SELECT
MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T
Ini dapat memproses kedua kolom secara bersamaan sehingga memiliki kasus terburuk dari satu pemindaian penuh. Kerugiannya adalah bahwa bahkan jika itu bertemu NULL
di kedua kolom sangat awal pada permintaan masih akan berakhir memindai seluruh sisa tabel.
Variabel pengguna
Saya bisa memikirkan cara ketiga untuk melakukan ini
BEGIN TRY
DECLARE @B INT, @C INT, @D INT
SELECT
@B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
@C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
/*Divide by zero error if both @B and @C are 1.
Might happen next row as no guarantee of order of
assignments*/
@D = 1 / (2 - (@B + @C))
FROM T
OPTION (MAXDOP 1)
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
BEGIN
SELECT 'B,C both contain NULLs'
RETURN;
END
ELSE
RETURN;
END CATCH
SELECT ISNULL(@B,0),
ISNULL(@C,0)
tetapi ini tidak cocok untuk kode produksi karena perilaku yang benar untuk kueri gabungan agregat tidak ditentukan. dan mengakhiri pemindaian dengan membuat kesalahan adalah solusi yang cukup mengerikan.
Apakah ada opsi lain yang menggabungkan kekuatan pendekatan di atas?
Edit
Hanya untuk memperbarui ini dengan hasil yang saya dapatkan dari bacaan untuk jawaban yang diajukan sejauh ini (menggunakan data uji @ ypercube)
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 2 * EXISTS | CASE | Kejser | Kejser | Kejser | ypercube | 8kb |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | | | | MAXDOP 1 | HASH GROUP, MAXDOP 1 | | |
| No Nulls | 15208 | 7604 | 8343 | 7604 | 7604 | 15208 | 8346 (8343+3) |
| One Null | 7613 | 7604 | 8343 | 7604 | 7604 | 7620 | 7630 (25+7602+3) |
| Two Null | 23 | 7604 | 8343 | 7604 | 7604 | 30 | 30 (18+12) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
Untuk @ jawaban Thomas aku berubah TOP 3
untuk TOP 2
untuk berpotensi memungkinkan untuk keluar sebelumnya. Saya mendapat paket paralel secara default untuk jawaban itu, jadi saya juga mencobanya dengan MAXDOP 1
petunjuk agar jumlah bacaan lebih sebanding dengan paket lainnya. Saya agak terkejut dengan hasilnya seperti pada tes saya sebelumnya saya telah melihat bahwa hubung singkat tanpa membaca seluruh tabel.
Rencana untuk data pengujian saya bahwa hubung singkat ada di bawah
Paket data ypercube adalah
Jadi itu menambahkan operator semacam pemblokiran ke rencana. Saya juga mencoba dengan HASH GROUP
petunjuk tetapi itu masih berakhir dengan membaca semua baris
Jadi kuncinya adalah untuk mendapatkan hash match (flow distinct)
operator untuk membiarkan rencana ini mengalami hubungan pendek karena alternatif lain akan memblokir dan mengkonsumsi semua baris pula. Saya tidak berpikir ada petunjuk untuk memaksakan ini secara khusus tetapi tampaknya "secara umum, pengoptimal memilih Perbedaan Arus di mana ia menentukan bahwa lebih sedikit baris output yang diperlukan daripada ada nilai yang berbeda dalam set input." .
@ data ypercube hanya memiliki 1 baris di setiap kolom dengan NULL
nilai (tabel kardinalitas = 30300) dan baris yang diperkirakan masuk dan keluar dari operator keduanya 1
. Dengan membuat predikat sedikit lebih buram bagi pengoptimal itu menghasilkan rencana dengan operator Flow Distinct.
SELECT TOP 2 *
FROM (SELECT DISTINCT
CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
, CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
FROM test T
WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT
Edit 2
Tweak terakhir yang muncul pada saya adalah bahwa kueri di atas masih dapat memproses lebih banyak baris daripada yang diperlukan jika baris pertama yang ditemuinya NULL
memiliki NULL di kolom B
dan C
. Ini akan melanjutkan pemindaian alih-alih segera keluar. Salah satu cara untuk menghindarinya adalah dengan tidak memproteksi baris saat mereka dipindai. Jadi jawaban terakhir saya untuk jawaban Thomas Kejser ada di bawah ini
SELECT DISTINCT TOP 2 NullExists
FROM test T
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
(CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL
Mungkin akan lebih baik untuk predikat itu WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
tetapi terhadap data uji sebelumnya bahwa seseorang tidak memberi saya rencana dengan Flow Distinct, sedangkan yang NullExists IS NOT NULL
melakukannya (rencana di bawah).
TOP 3
hanya bisa menjadiTOP 2
seperti saat ini akan memindai sampai menemukan satu dari masing-masing berikut ini(NOT_NULL,NULL)
,(NULL,NOT_NULL)
,(NULL,NULL)
. Setiap 2 dari 3 itu akan cukup - dan jika ditemukan(NULL,NULL)
pertama maka yang kedua tidak akan diperlukan juga. Juga untuk hubungan pendek rencana perlu menerapkan perbedaan melaluihash match (flow distinct)
operator daripadahash match (aggregate)
ataudistinct sort