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