Dies ist ein Problem, auf das ich regelmäßig stoße und für das ich noch keine gute Lösung gefunden habe.
Angenommen, die folgende Tabellenstruktur
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
und die Anforderung ist , ob eine der beiden Spalten auf NULL festlegbare , um zu bestimmen B
oder C
tatsächlich jegliche enthalten NULL
Werte (und wenn ja , welche (s)).
Nehmen Sie außerdem an, die Tabelle enthält Millionen von Zeilen (und es sind keine Spaltenstatistiken verfügbar, die überprüft werden könnten, da ich an einer allgemeineren Lösung für diese Klasse von Abfragen interessiert bin).
Ich kann mir ein paar Möglichkeiten vorstellen, wie ich das angehen könnte, aber alle haben Schwächen.
Zwei getrennte EXISTS
Aussagen. Dies hätte den Vorteil, dass die Abfragen den Scanvorgang vorzeitig beenden können, sobald ein NULL
gefunden wird. Aber wenn beide Spalten tatsächlich keine NULL
s enthalten, ergeben sich zwei vollständige Scans.
Einzelne aggregierte Abfrage
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
Dadurch können beide Spalten gleichzeitig verarbeitet werden, sodass der schlimmste Fall ein vollständiger Scan ist. Der Nachteil besteht darin, dass NULL
die Abfrage auch dann den gesamten Rest der Tabelle durchsucht , wenn sie sehr früh auf ein in beiden Spalten stößt .
Benutzervariablen
Ich kann mir einen dritten Weg vorstellen, dies zu tun
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)
Dies ist jedoch nicht für Seriencode geeignet, da das richtige Verhalten für eine Abfrage der aggregierten Verkettung undefiniert ist. und das Beenden des Scans durch einen Fehler ist sowieso eine schreckliche Lösung.
Gibt es eine andere Option, die die Stärken der oben genannten Ansätze kombiniert?
Bearbeiten
Nur um dies mit den Ergebnissen zu aktualisieren, die ich in Form von Lesevorgängen für die bisher eingereichten Antworten erhalte (unter Verwendung der Testdaten von @ 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) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
Bei der Antwort von @ Thomas habe ich mich zu geändert TOP 3
, TOP 2
um möglicherweise zuzulassen, dass es früher beendet wird. Ich habe standardmäßig einen parallelen Plan für diese Antwort erhalten, also habe ich es auch mit einem MAXDOP 1
Hinweis versucht , um die Anzahl der Lesevorgänge mit den anderen Plänen vergleichbarer zu machen. Die Ergebnisse überraschten mich ein wenig, da ich in meinem früheren Test diesen Abfragekurzschluss gesehen hatte, ohne die gesamte Tabelle zu lesen.
Der Plan für meine Testdaten, die kurzschließen, ist unten
Der Plan für die Daten von ypercube lautet
Daher wird dem Plan ein blockierender Sortieroperator hinzugefügt. Ich habe auch versucht, mit dem HASH GROUP
Hinweis, aber das endet immer noch alle Zeilen zu lesen
Es scheint also der Schlüssel zu sein, einen hash match (flow distinct)
Operator dazu zu bringen, diesen Plan kurzschließen zu lassen, da die anderen Alternativen sowieso alle Zeilen blockieren und verbrauchen. Ich glaube nicht, dass es einen Hinweis gibt, dies speziell zu erzwingen, aber anscheinend "wählt das Optimierungsprogramm im Allgemeinen einen Flow Distinct, bei dem bestimmt wird, dass weniger Ausgabezeilen erforderlich sind, als unterschiedliche Werte in der Eingabemenge vorhanden sind.".
Die Daten von @ypercube enthalten in jeder Spalte nur eine Zeile mit NULL
Werten (Tabellenkardinalität = 30300), und die geschätzten Zeilen, die in den Operator hinein- und aus ihm herausgehen, sind beide 1
. Indem das Prädikat für das Optimierungsprogramm etwas undurchsichtiger gemacht wurde, wurde ein Plan mit dem Flow Distinct-Operator generiert.
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
Bearbeiten 2
Eine letzte Änderung, die mir einfiel, war, dass die obige Abfrage NULL
möglicherweise immer noch mehr Zeilen als erforderlich verarbeitet, falls die erste Zeile, auf die sie trifft, in beiden Spalten B
und NULL enthält C
. Der Scanvorgang wird fortgesetzt und nicht sofort beendet. Eine Möglichkeit, dies zu vermeiden, besteht darin, die Zeilen beim Scannen zu deaktivieren. Mein letzter Änderungsantrag zu Thomas Kejsers Antwort ist also unten
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
Es wäre wahrscheinlich besser für das Prädikat, WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
aber gegen die vorherigen Testdaten, wenn man mir keinen Plan mit Flow Distinct gibt, wohingegen NullExists IS NOT NULL
derjenige (Plan unten) es tut.
TOP 3
könnte nur sein ,TOP 2
wie zur Zeit wird es scannen , bis es einen von jedem der folgenden findet(NOT_NULL,NULL)
,(NULL,NOT_NULL)
,(NULL,NULL)
.(NULL,NULL)
Zwei von diesen drei wären ausreichend - und wenn der erste gefunden wird, wird auch der zweite nicht benötigt. Auch um den Plan kurzzuschließen, müsste der Unterschiedhash match (flow distinct)
eher über einen Operator als überhash match (aggregate)
oder implementiert werdendistinct sort