Eu reescreveria o teste como
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Isso garante um curto-circuito conforme descrito aqui, mas significa que você precisa selecionar o mais barato para avaliar antecipadamente, em vez de deixá-lo para o otimizador.
Em meus testes extremamente limitados abaixo, o seguinte parecia ser verdade ao testar
1. EXISTS AND EXISTS
O
EXISTS AND EXISTS
versão parece mais problemática. Isso encadeia algumas semijunções externas. Em nenhum dos casos ele reorganizou a ordem dos testes para tentar fazer o mais barato primeiro (uma questão discutida na segunda metade deste post). No IF ...
versão, não teria feito nenhuma diferença se tivesse, pois não causava curto-circuito. No entanto, quando este predicado combinado é colocado em um WHERE
cláusula que o plano muda e faz curto-circuito para que o rearranjo pudesse ter sido benéfico. /*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
*/
Os planos para todos estes parecem muito semelhantes. A razão para a diferença de comportamento entre o
SELECT 1 WHERE ...
versão e o IF ...
versão é que para o primeiro, se a condição for falsa, o comportamento correto é não retornar nenhum resultado, então ele apenas encadeia o OUTER SEMI JOINS
e se uma for falsa, zero linhas são transferidas para a próxima. No entanto, o
IF
versão sempre precisa retornar um resultado de 1 ou zero. Este plano usa uma coluna de teste em suas junções externas e define isso como false se o EXISTS
teste não for aprovado (em vez de simplesmente descartar a linha). Isso significa que sempre há 1 linha alimentando a próxima junção e ela sempre é executada. O
CASE
versão tem um plano muito semelhante, mas usa um PASSTHRU
predicado que ele usa para pular a execução do JOIN se o THEN
anterior condição não foi atendida. Não sei por que combinou AND
s não usaria a mesma abordagem. 2. EXISTS OR EXISTS
O
EXISTS OR EXISTS
versão usou uma concatenação (UNION ALL
) como a entrada interna para uma semijunção externa. Esse arranjo significa que ele pode parar de solicitar linhas do lado interno assim que o primeiro for retornado (ou seja, pode efetivamente causar um curto-circuito) Todas as 4 consultas terminaram com o mesmo plano em que o predicado mais barato foi avaliado primeiro. /*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. Adicionando um ELSE
Ocorreu-me tentar a lei de De Morgan para converter
AND
para OR
e veja se isso fez alguma diferença. A conversão da primeira consulta fornece 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
*/
Portanto, isso ainda não faz diferença no comportamento de curto-circuito. No entanto, se você remover o
NOT
e inverta a ordem do IF ... ELSE
condições que agora faz curto circuito! 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
*/