Bu periyodik olarak karşılaştığım ve henüz iyi bir çözüm bulamadığım bir konudur.
Aşağıdaki tablo yapısını varsayalım
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
ve gereklilik, ya null değerine sahip sütunlardan birinin ya B
da C
gerçekten herhangi bir NULL
değer içerip içermediğinin belirlenmesidir (ve eğer öyleyse hangisi (ler)).
Ayrıca, tablonun milyonlarca satır içerdiğini varsayalım (ve bu sorgu sınıfı için daha genel bir çözümle ilgilendiğim için bakılabilecek sütun istatistiklerinin bulunmadığını varsayalım).
Buna yaklaşmanın birkaç yolunu düşünebilirim ama hepsinin zayıf yönleri var.
İki ayrı EXISTS
ifade. Bu, sorguların en kısa sürede NULL
bulunursa taramayı durdurmalarına izin verme avantajına sahip olacaktır . Ancak aslında her iki sütun da NULL
s içermiyorsa, iki tam tarama sonuçlanır.
Tekli Toplam Sorgu
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
Bu, her iki sütunu aynı anda işleyebilir, bu nedenle bir tam taramanın en kötü durumudur. Dezavantajı, karşılaşsa bile birNULL
sorguda çok erken iki sütunda , tablonun geri kalanının tamamını taramaya devam etmesidir.
Kullanıcı değişkenleri
Ben yapabilirsiniz bunu yapmanın üçüncü yol düşünemiyorum
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)
ancak toplu bir birleştirme sorgusu için doğru davranış tanımsız olduğundan bu, üretim kodu için uygun değildir . ve bir hata atarak taramayı sonlandırmak yine de çok korkunç bir çözüm.
Yukarıdaki yaklaşımların güçlü yönlerini birleştiren başka bir seçenek var mı?
Düzenle
Sadece bunu şimdiye kadar gönderilen cevaplarla ilgili okuduğum sonuçlarla güncellemek için (@ ypercube test verilerini kullanarak)
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 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) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
Thomas'ın cevap @ ben değiştirdi için TOP 3
için TOP 2
potansiyel izin daha önce çıkmak için. Bu cevap için varsayılan olarak paralel bir plan aldım, bu nedenle MAXDOP 1
okuma sayısını diğer planlarla daha karşılaştırılabilir hale getirmek için bir ipucu ile de denedim . Daha önceki testimde bu sorguyu kısa devreyi tüm tabloyu okumadan görmüş olduğum için elde ettiğim sonuçlara biraz şaşırdım.
Test verilerim için kısa devre planı
Ypercube'un verileri için plan:
Böylece plana engelleyici bir sıralama işleci ekler. Ben de HASH GROUP
ipucu ile denedim ama yine de tüm satırları okuma biter
Bu yüzden, anahtar, bir hash match (flow distinct)
operatörün bu planın kısa devre yapmasına izin vermesi gibi görünüyor , çünkü diğer alternatifler yine de tüm satırları engeller ve tüketir. Bunu özellikle zorlamak için bir ipucu olduğunu sanmıyorum ama görünüşe göre "genel olarak, iyimser, girdi kümesinde farklı değerlerden daha az çıktı satırının gerekli olduğunu belirlediği bir Akış Ayrıntısı seçer". .
@ ypercube'ın verileri her sütunda yalnızca NULL
değerleri olan 1 satır vardır (tablo kardinalitesi = 30300) ve operatörün içine giren ve çıkan tahmini satırlar her ikisi dedir 1
. Öngörüyü, optimizer için biraz daha opak hale getirerek, Flow Distinct operatörüyle bir plan oluşturdu.
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
Düzenle 2
Aklıma gelen son bir çentik, yukarıdaki sorgunun hala NULL
her iki sütunda B
ve NULL değerine sahip olduğu ilk satırın karşılaştığı durumlarda gerekenden daha fazla satır işlemesiyle sonuçlanabileceğidir C
. Hemen çıkmak yerine taramaya devam edecektir. Bundan kaçınmanın bir yolu, tarandıkları sırada satırları açmaktır. Öyleyse Thomas Kejer'in cevabındaki son değişikim aşağıda
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
Tahminin olması muhtemelen daha iyi olurdu, WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
ancak önceki test verisine karşı, birinin bana bir Akış Ayrıntısı olan bir plan vermemesi, bir tanesinin yapması daha iyi olurdu NullExists IS NOT NULL
(aşağıdaki plan).
TOP 3
sadece olabilirTOP 2
o aşağıdakilerden her biri birini bulana kadar tarar anda olarak(NOT_NULL,NULL)
,(NULL,NOT_NULL)
,(NULL,NULL)
. Bu 3 kişiden 2'si yeterli olacaktır - ve(NULL,NULL)
ilk bulursa ikinciye de ihtiyaç duyulmaz. Ayrıca, kısa devre yapmak için, planın farklılığıhash match (flow distinct)
operatörün yerinehash match (aggregate)
veya yerine bir operatör vasıtasıyla uygulaması gerekecektirdistinct sort