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.
quelle
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
Wie ich die Frage verstehe, möchten Sie wissen, ob in einem der Spaltenwerte eine Null vorhanden ist, anstatt tatsächlich die Zeilen zurückzugeben, in denen entweder B oder C Null ist. Wenn dies der Fall ist, warum nicht:
Auf meinem Teststand mit SQL 2008 R2 und einer Million Zeilen wurden auf der Registerkarte Client Statistics die folgenden Ergebnisse in ms angezeigt:
Wenn Sie den Nolock-Hinweis hinzufügen, sind die Ergebnisse noch schneller:
Als Referenz habe ich den SQL-Generator von Red-gate verwendet, um die Daten zu generieren. Von meinen einer Million Zeilen hatten 9.886 Zeilen einen Null-B-Wert und 10.019 einen Null-C-Wert.
In dieser Testreihe hat jede Zeile in Spalte B einen Wert:
Vor jedem Test (beide Sätze) lief ich
CHECKPOINT
undDBCC DROPCLEANBUFFERS
.Hier sind die Ergebnisse, wenn die Tabelle keine Nullen enthält. Beachten Sie, dass die von ypercube bereitgestellte 2-Exists-Lösung in Bezug auf Lesevorgänge und Ausführungszeit nahezu identisch mit der von mir ist. Ich (wir) glaube, dies liegt an den Vorteilen der Enterprise / Developer Edition, die Advanced Scanning verwendet . Wenn Sie nur die Standard Edition oder eine niedrigere Version verwenden, ist die Lösung von Kejser möglicherweise die schnellste.
quelle
Sind
IF
Aussagen erlaubt?Dies sollte es Ihnen ermöglichen, das Vorhandensein von B oder C bei einem Durchgang durch die Tabelle zu bestätigen:
quelle
Getestet in SQL-Fiddle in den Versionen: 2008 r2 und 2012 mit 30K Zeilen.
EXISTS
Abfrage zeigt einen enormen Effizienzvorteil, wenn Nullen frühzeitig gefunden werden - was zu erwarten ist.EXISTS
Abfrage - in allen Fällen im Jahr 2012, was ich nicht erklären kann.CASE
.Abfragen und Timings. Timings wo getan:
B
mit einerNULL
kleinenid
.NULL
jeweils eine kleine ID.Los geht's (es gibt ein Problem mit den Plänen, ich versuche es später noch einmal. Folgen Sie den Links fürs Erste):
Abfrage mit 2 EXISTS-Unterabfragen
Martin Smiths Single Aggregate Query
Abfrage von Thomas Kejser
Mein Vorschlag (1)
Die Ausgabe muss etwas poliert werden, aber die Effizienz ähnelt der
EXISTS
Abfrage. Ich dachte, es wäre besser, wenn es keine Nullen gäbe, aber Tests zeigen, dass dies nicht der Fall ist.Vorschlag (2)
Der Versuch, die Logik zu vereinfachen:
Es scheint 2008R2 eine bessere Leistung zu bringen als der vorherige Vorschlag, 2012 jedoch eine schlechtere (möglicherweise kann die zweite
INSERT
mitIF
@ 8kb umgeschrieben werden ):quelle
Wenn Sie EXISTS verwenden, weiß SQL Server, dass Sie eine Existenzprüfung durchführen. Wenn es den ersten passenden Wert findet, gibt es TRUE zurück und hört auf zu suchen.
Wenn Sie 2 Spalten verketten und wenn eine null ist, ist das Ergebnis null
z.B
Überprüfen Sie diesen Code
quelle
Wie wäre es mit:
Wenn dies funktioniert (ich habe es nicht getestet), würde es eine einzeilige Tabelle mit 2 Spalten ergeben, von denen jede entweder WAHR oder FALSCH ist. Ich habe die Effizienz nicht getestet.
quelle
T.B is null
wird dann als boolesches Ergebnis behandeltEXISTS(SELECT true)
undEXISTS(SELECT false)
würde beide true zurückgeben. Dieses MySQL-Beispiel zeigt an, dass beide Spalten NULL enthalten,