Reescribiría la prueba como
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Esto garantiza un cortocircuito como se describe aquí, pero significa que debe seleccionar el más barato para evaluarlo por adelantado en lugar de dejarlo en manos del optimizador.
En mis pruebas extremadamente limitadas a continuación, lo siguiente pareció ser cierto al probar
1. EXISTS AND EXISTS
El EXISTS AND EXISTS
versión parece más problemática. Esto encadena algunas semiuniones externas. En ninguno de los casos se reorganizó el orden de las pruebas para tratar de hacer primero la más económica (un tema discutido en la segunda mitad de esta publicación de blog). En el IF ...
versión, no habría hecho ninguna diferencia si lo hubiera hecho, ya que no tuvo un cortocircuito. Sin embargo, cuando este predicado combinado se coloca en un WHERE
cláusula el plan cambia y hace cortocircuito, por lo que la reorganización podría haber sido beneficiosa.
/*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
*/
Los planes para todos estos parecen muy similares. El motivo de la diferencia de comportamiento entre SELECT 1 WHERE ...
versión y el IF ...
la versión es que para la primera, si la condición es falsa, entonces el comportamiento correcto es no devolver ningún resultado, por lo que solo encadena las OUTER SEMI JOINS
y si uno es falso, cero filas se transfieren a la siguiente.
Sin embargo, el IF
versión siempre necesita devolver un resultado de 1 o cero. Este plan usa una columna de sondeo en sus uniones externas y establece esto en falso si EXISTS
no se pasa la prueba (en lugar de simplemente descartar la fila). Esto significa que siempre hay 1 fila alimentando al siguiente Join y siempre se ejecuta.
El CASE
La versión tiene un plan muy similar pero usa un PASSTHRU
predicado que utiliza para omitir la ejecución de JOIN si el anterior THEN
no se cumplió la condición. No estoy seguro de por qué combinó AND
s no usaría el mismo enfoque.
2. EXISTS OR EXISTS
El EXISTS OR EXISTS
versión utilizó una concatenación (UNION ALL
) como la entrada interna de una semiunión externa. Este arreglo significa que puede dejar de solicitar filas desde el lado interno tan pronto como se devuelve la primera (es decir, puede provocar un cortocircuito). Las 4 consultas terminaron con el mismo plan en el que se evaluó primero el predicado más económico.
/*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. Agregando un ELSE
Se me ocurrió probar la ley de De Morgan para convertir AND
a OR
y ver si eso hizo alguna diferencia. Convertir la primera consulta da
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
*/
Entonces, esto todavía no hace ninguna diferencia en el comportamiento de cortocircuito. Sin embargo, si elimina el NOT
e invertir el orden de IF ... ELSE
condiciona lo que ahora sí ¡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
*/