Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Flux conditionnel SQL Server

Je réécrirais le test comme

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

Cela garantit le court-circuit comme décrit ici, mais cela signifie que vous devez sélectionner le moins cher à évaluer à l'avance plutôt que de le laisser à l'optimiseur.

Dans mes tests extrêmement limités ci-dessous, les éléments suivants semblaient être vrais lors des tests

1. EXISTS AND EXISTS

Le EXISTS AND EXISTS version semble la plus problématique. Cela enchaîne des semi-jointures externes. Dans aucun des cas, il n'a réorganisé l'ordre des tests pour essayer de faire le moins cher en premier (un problème abordé dans la seconde moitié de ce billet de blog). Dans le IF ... version, cela n'aurait fait aucune différence s'il l'avait fait car il n'a pas court-circuité. Cependant, lorsque ce prédicat combiné est placé dans un WHERE clause le plan change et il fait court-circuit de sorte que le réarrangement aurait pu être bénéfique.

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

*/

Les plans pour tout cela semblent très similaires. La raison de la différence de comportement entre le SELECT 1 WHERE ... version et le IF ... version est que pour la première si la condition est fausse alors le comportement correct est de ne renvoyer aucun résultat donc il enchaîne simplement les OUTER SEMI JOINS et si l'un est faux, alors zéro ligne est reportée à la suivante.

Cependant le IF version toujours doit retourner un résultat de 1 ou zéro. Ce plan utilise une colonne de sonde dans ses jointures externes et la définit sur false si EXISTS test n'est pas réussi (plutôt que de simplement supprimer la ligne). Cela signifie qu'il y a toujours 1 ligne qui alimente la jointure suivante et qu'elle est toujours exécutée.

Le CASE version a un plan très similaire mais il utilise un PASSTHRU prédicat qu'il utilise pour ignorer l'exécution du JOIN si le précédent THEN condition n'était pas remplie. Je ne sais pas pourquoi combiner AND s n'utiliseraient pas la même approche.

2. EXISTS OR EXISTS

Le EXISTS OR EXISTS version a utilisé une concaténation (UNION ALL ) comme entrée interne d'une semi-jointure externe. Cet arrangement signifie qu'il peut arrêter de demander des lignes du côté interne dès que le premier est renvoyé (c'est-à-dire qu'il peut effectivement court-circuiter) Les 4 requêtes se sont terminées avec le même plan où le prédicat le moins cher a été évalué en premier.

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

Il m'est venu à l'esprit d'essayer la loi de De Morgan pour convertir AND à OR et voir si cela a fait une différence. La conversion de la première requête donne

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

Cela ne fait donc toujours aucune différence pour le comportement de court-circuit. Cependant, si vous supprimez le NOT et inverser l'ordre des IF ... ELSE conditions qu'il fait maintenant court-circuit !

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