Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Fluxo condicional do SQL Server


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