Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Bedingter SQL Server-Fluss

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 zuzutreffen

1. 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
*/