Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Flusso condizionale di SQL Server

Riscriverei il test come

IF CASE
     WHEN EXISTS (SELECT ...) THEN CASE
                                   WHEN EXISTS (SELECT ...) THEN 1
                                 END
   END = 1  

Ciò garantisce il cortocircuito come descritto qui, ma significa che devi selezionare quello più economico da valutare in anticipo piuttosto che lasciarlo all'ottimizzatore.

Nei miei test estremamente limitati di seguito, sembrava che fosse vero durante il test

1. EXISTS AND EXISTS

Il EXISTS AND EXISTS la versione sembra più problematica. Questo incatena alcuni semigiunti esterni. In nessuno dei casi ha riorganizzato l'ordine dei test per provare a fare prima quello più economico (un problema discusso nella seconda metà di questo post sul blog). Nel IF ... versione non avrebbe fatto alcuna differenza se l'avesse fatto in quanto non andava in cortocircuito. Tuttavia, quando questo predicato combinato viene inserito in un WHERE clausola il piano cambia e fa cortocircuito in modo che il riarrangiamento avrebbe potuto essere vantaggioso.

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

*/

I piani per tutti questi sembrano molto simili. Il motivo della differenza di comportamento tra SELECT 1 WHERE ... versione e il IF ... la versione è che per la prima se la condizione è falsa, il comportamento corretto è di non restituire alcun risultato, quindi si limita a concatenare i OUTER SEMI JOINS e se uno è falso, zero righe portano avanti a quella successiva.

Tuttavia il IF versione sempre deve restituire un risultato pari a 1 o zero. Questo piano utilizza una colonna probe nei suoi join esterni e lo imposta su false se EXISTS il test non viene superato (piuttosto che scartare semplicemente la riga). Ciò significa che c'è sempre 1 riga che alimenta il join successivo e viene sempre eseguito.

Il CASE la versione ha un piano molto simile ma usa un PASSTHRU predicato che usa per saltare l'esecuzione del JOIN se il precedente THEN condizione non era soddisfatta. Non so perché combinare AND s non userebbe lo stesso approccio.

2. EXISTS OR EXISTS

Il EXISTS OR EXISTS versione utilizzava una concatenazione (UNION ALL ) come input interno per un semi join esterno. Questa disposizione significa che può interrompere la richiesta di righe dal lato interno non appena viene restituita la prima (cioè può effettivamente cortocircuitare) Tutte e 4 le query sono finite con lo stesso piano in cui è stato valutato per primo il predicato più economico.

/*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. Aggiunta di un ELSE

Mi è venuto in mente di provare la legge di De Morgan per convertire AND a OR e vedi se questo ha fatto la differenza. La conversione della prima query dà

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

Quindi questo ancora non fa alcuna differenza per il comportamento in cortocircuito. Tuttavia, se rimuovi il NOT e invertire l'ordine di IF ... ELSE condizioni che ora fa cortocircuito!

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