Przepisałbym test jako
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Gwarantuje to zwarcie, jak opisano tutaj, ale oznacza to, że musisz wybrać najtańszy do oceny z góry, zamiast pozostawiać to optymalizatorowi.
W moich niezwykle ograniczonych testach poniżej, podczas testowania sprawdziły się następujące elementy
1. EXISTS AND EXISTS
EXISTS AND EXISTS
wersja wydaje się najbardziej problematyczna. To łączy ze sobą niektóre zewnętrzne półłącza. W żadnym z przypadków nie zmieniła kolejności testów, aby najpierw wykonać tańszy (kwestia omówiona w drugiej połowie tego wpisu na blogu). W IF ...
wersja nie zrobiłaby żadnej różnicy, gdyby miała, ponieważ nie ma zwarcia. Jednak gdy ten połączony predykat zostanie umieszczony w WHERE
klauzula o zmianach w planie i tak zwarcie, aby zmiana układu mogła być korzystna.
/*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
*/
Plany na to wszystko wydają się bardzo podobne. Powód różnicy w zachowaniu między SELECT 1 WHERE ...
wersja i IF ...
wersja jest taka, że w przypadku pierwszego, jeśli warunek jest fałszywy, prawidłowym zachowaniem jest nie zwracanie żadnego wyniku, więc po prostu łączy OUTER SEMI JOINS
a jeśli jeden jest fałszywy, to zero wierszy jest przenoszone do następnego.
Jednak IF
wersja zawsze musi zwrócić wynik 1 lub zero. Ten plan używa kolumny sondującej w swoich zewnętrznych sprzężeniach i ustawia ją na wartość false, jeśli EXISTS
test nie został zaliczony (zamiast po prostu odrzucić wiersz). Oznacza to, że do następnego złączenia zawsze podawany jest jeden wiersz i zawsze jest on wykonywany.
CASE
wersja ma bardzo podobny plan, ale używa PASSTHRU
predykat, którego używa do pominięcia wykonania JOIN, jeśli poprzedni THEN
warunek nie został spełniony. Nie jestem pewien, dlaczego łączymy AND
s nie zastosowaliby tego samego podejścia.
2. EXISTS OR EXISTS
EXISTS OR EXISTS
wersja użyła konkatenacji (UNION ALL
) jako wewnętrzne dane wejściowe do zewnętrznego sprzężenia częściowego. Ten układ oznacza, że może przestać żądać wierszy od wewnętrznej strony, gdy tylko pierwszy z nich zostanie zwrócony (tj. może skutecznie spowodować zwarcie). Wszystkie 4 zapytania zakończyły się tym samym planem, w którym najpierw oceniano tańszy predykat.
/*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. Dodawanie ELSE
Przyszło mi do głowy, aby wypróbować prawo De Morgana, aby przekonwertować AND
do OR
i zobacz, czy to coś zmieniło. Konwersja pierwszego zapytania daje
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
*/
Więc to nadal nie ma żadnego wpływu na zachowanie zwarciowe. Jeśli jednak usuniesz NOT
i odwróć kolejność IF ... ELSE
warunkuje to teraz tak zwarcie!
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
*/