Ich würde den Test umschreiben als
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Dies garantiert Kurzschlüsse wie hier beschrieben, bedeutet aber, dass Sie den billigsten auswählen müssen, um ihn im Voraus zu bewerten, anstatt dies dem Optimierer zu überlassen.
In meinen extrem eingeschränkten Tests unten schien beim Testen von
Folgendes zuzutreffen1. EXISTS AND EXISTS
Der EXISTS AND EXISTS
Version scheint am problematischsten. Dadurch werden einige äußere Halbverbindungen miteinander verkettet. In keinem der Fälle wurde die Reihenfolge der Tests geändert, um zu versuchen, zuerst den billigeren Test durchzuführen (ein Problem, das in der zweiten Hälfte dieses Blogbeitrags diskutiert wird). Im IF ...
Version hätte es keinen Unterschied gemacht, wenn es gewesen wäre, da es keinen Kurzschluss gegeben hätte. Wenn dieses kombinierte Prädikat jedoch in ein WHERE
eingefügt wird Klausel ändert sich der Plan und es macht Kurzschluss, so dass eine Neuanordnung von Vorteil gewesen sein könnte.
/*All tests are testing "If False And False"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
PRINT 'Y'
/*
Table 'spt_values'. Scan count 1, logical reads 9
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
PRINT 'Y'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_values'. Scan count 1, logical reads 9
*/
Die Pläne für all diese scheinen sehr ähnlich zu sein. Der Grund für das unterschiedliche Verhalten zwischen SELECT 1 WHERE ...
Version und das IF ...
Version ist, dass für die erstere, wenn die Bedingung falsch ist, das korrekte Verhalten darin besteht, kein Ergebnis zurückzugeben, sodass nur die OUTER SEMI JOINS
verkettet werden und wenn einer falsch ist, werden null Zeilen zum nächsten übertragen.
Jedoch das IF
Version immer muss ein Ergebnis von 1 oder 0 zurückgeben. Dieser Plan verwendet eine Sondenspalte in seinen Outer Joins und setzt diese auf „false“, wenn EXISTS
Test nicht bestanden (anstatt die Zeile einfach zu verwerfen). Das bedeutet, dass immer 1 Zeile in den nächsten Join einfließt und dieser immer ausgeführt wird.
Der CASE
Version hat einen sehr ähnlichen Plan, aber sie verwendet einen PASSTHRU
Prädikat, das verwendet wird, um die Ausführung des JOINs zu überspringen, wenn der vorherige THEN
Bedingung nicht erfüllt. Ich bin mir nicht sicher, warum AND
kombiniert wird s würden nicht denselben Ansatz verwenden.
2. EXISTS OR EXISTS
Der EXISTS OR EXISTS
Version verwendete eine Verkettung (UNION ALL
)-Operator als innere Eingabe für einen äußeren Semi-Join. Diese Anordnung bedeutet, dass es aufhören kann, Zeilen von der inneren Seite anzufordern, sobald die erste zurückgegeben wird (d. h. es kann effektiv kurzgeschlossen werden). Alle 4 Abfragen endeten mit demselben Plan, bei dem das billigere Prädikat zuerst ausgewertet wurde.
/*All tests are testing "If True Or True"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
3. Hinzufügen eines ELSE
Es kam mir in den Sinn, das Gesetz von De Morgan auszuprobieren, um AND
umzuwandeln zu OR
und sehen, ob das einen Unterschied gemacht hat. Das Umwandeln der ersten Abfrage ergibt
IF NOT ((NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)))
PRINT 'Y'
ELSE
PRINT 'N'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
Am Kurzschlussverhalten ändert dies also noch nichts. Wenn Sie jedoch NOT
entfernen und die Reihenfolge des IF ... ELSE
umkehren Bedingungen, die es jetzt macht Kurzschluss!
IF (NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1))
PRINT 'N'
ELSE
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/