sql >> Base de Datos >  >> RDS >> Sqlserver

Flujo condicional de SQL Server

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