Test bych přepsal jako
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Tím je zaručeno zkratování, jak je popsáno zde, ale znamená to, že musíte vybrat ten nejlevnější, který chcete vyhodnotit předem, a ne to nechat na optimalizátorovi.
V mých extrémně omezených testech níže se zdálo, že následující platí při testování
1. EXISTS AND EXISTS
EXISTS AND EXISTS
verze se zdá nejproblematičtější. Toto spojuje dohromady některé vnější polospojky. V žádném z případů nezměnil pořadí testů tak, aby se nejprve pokusil provést ten levnější (problém diskutovaný v druhé polovině tohoto blogového příspěvku). V IF ...
verze by to nedělalo žádný rozdíl, kdyby to mělo, protože nezkratovalo. Když je však tento kombinovaný predikát vložen do WHERE
klauzule, že se plán změní, a to dělá zkrat, takže přeskupení mohlo být prospěšné.
/*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
*/
Plány pro všechny se zdají být velmi podobné. Důvod rozdílu v chování mezi SELECT 1 WHERE ...
verzi a IF ...
verze je taková, že pokud je podmínka nepravdivá, u první verze je správným chováním nevracet žádný výsledek, takže pouze zřetězí OUTER SEMI JOINS
a pokud je jedna nepravda, pak se nula řádků přenese na další.
Nicméně IF
verze vždy potřebuje vrátit výsledek 1 nebo nula. Tento plán používá ve svých vnějších spojích sloupec sondy a nastaví toto na hodnotu false, pokud EXISTS
test neprojde (spíše než jednoduše zahodit řádek). To znamená, že do dalšího spojení je vždy vložen 1 řádek a vždy se provede.
CASE
verze má velmi podobný plán, ale používá PASSTHRU
predikát, který používá k přeskočení provedení JOIN, pokud je předchozí THEN
podmínka splněna nebyla. Nejsem si jistý, proč kombinovat AND
s by nepoužil stejný přístup.
2. EXISTS OR EXISTS
EXISTS OR EXISTS
verze používala zřetězení (UNION ALL
) operátor jako vnitřní vstup do vnějšího polovičního spojení. Toto uspořádání znamená, že může přestat vyžadovat řádky z vnitřní strany, jakmile se vrátí první (tj. může účinně zkratovat). Všechny 4 dotazy skončily se stejným plánem, kde byl jako první vyhodnocen levnější predikát.
/*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. Přidání ELSE
Napadlo mě zkusit De Morganův zákon převést AND
na OR
a uvidíme, jestli se to nějak změnilo. Převod prvního dotazu dává
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
*/
Takže to stále nemění žádný vliv na chování při zkratu. Pokud však odstraníte NOT
a obrátit pořadí IF ... ELSE
podmínky, které nyní dělá zkrat!
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
*/