A menudo he leído cuando hay que verificar la existencia de una fila que siempre debe hacerse con EXISTS en lugar de con un COUNT.
Es muy raro que algo sea siempre cierto, especialmente cuando se trata de bases de datos. Hay varias formas de expresar la misma semántica en SQL. Si hay una regla general útil, podría ser escribir consultas utilizando la sintaxis más natural disponible (y, sí, eso es subjetivo) y solo considerar reescrituras si el plan de consulta o el rendimiento que obtiene es inaceptable.
Por lo que vale, mi propia opinión sobre el tema es que las consultas de existencia se expresan de manera más natural usando EXISTS. También ha sido mi experiencia que EXISTS tiende a optimizar mejor que la alternativa de OUTER JOINrechazo NULL. Usar COUNT(*)y filtrar =0es otra alternativa, que tiene cierto soporte en el optimizador de consultas de SQL Server, pero personalmente he encontrado que esto no es confiable en consultas más complejas. En cualquier caso, me EXISTSparece mucho más natural (para mí) que cualquiera de esas alternativas.
Me preguntaba si había una falla no anunciada con EXISTS que diera sentido a las mediciones que hice
Su ejemplo particular es interesante, porque resalta la forma en que el optimizador trata las subconsultas en CASEexpresiones (y EXISTSpruebas en particular).
Subconsultas en expresiones CASE
Considere la siguiente consulta (perfectamente legal):
DECLARE @Base AS TABLE (a integer NULL);
DECLARE @When AS TABLE (b integer NULL);
DECLARE @Then AS TABLE (c integer NULL);
DECLARE @Else AS TABLE (d integer NULL);
SELECT
CASE
WHEN (SELECT W.b FROM @When AS W) = 1
THEN (SELECT T.c FROM @Then AS T)
ELSE (SELECT E.d FROM @Else AS E)
END
FROM @Base AS B;
La semántica deCASE es que las WHEN/ELSEcláusulas generalmente se evalúan en orden textual. En la consulta anterior, sería incorrecto que SQL Server devolviera un error si la ELSEsubconsulta devolvía más de una fila, si se WHENcumplía la cláusula. Para respetar esta semántica, el optimizador produce un plan que utiliza predicados de transferencia:

El lado interno de las uniones de bucle anidado solo se evalúa cuando el predicado de paso devuelve falso. El efecto general es que las CASEexpresiones se prueban en orden, y las subconsultas solo se evalúan si no se satisfizo ninguna expresión previa.
Expresiones CASE con una subconsulta EXISTS
Cuando se CASEutiliza una subconsulta EXISTS, la prueba de existencia lógica se implementa como una semiunión, pero las filas que normalmente serían rechazadas por la semiunión deben conservarse en caso de que una cláusula posterior las necesite. Las filas que fluyen a través de este tipo especial de semi-unión adquieren una bandera para indicar si la semi-unión encontró una coincidencia o no. Esta bandera se conoce como la columna de la sonda .
Los detalles de la implementación es que la subconsulta lógica se reemplaza por una unión correlacionada ('aplicar') con una columna de sonda. El trabajo se realiza mediante una regla de simplificación en el optimizador de consultas llamado RemoveSubqInPrj(eliminar subconsulta en proyección). Podemos ver los detalles usando la marca de seguimiento 8606:
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606);
Parte del árbol de entrada que muestra la EXISTSprueba se muestra a continuación:
ScaOp_Exists
LogOp_Project
LogOp_Select
LogOp_Get TBL: #T2
ScaOp_Comp x_cmpEq
ScaOp_Identifier [T2].ID
ScaOp_Identifier [T1].ID
Esto se transforma RemoveSubqInPrjen una estructura encabezada por:
LogOp_Apply (x_jtLeftSemi probe PROBE:COL: Expr1008)
Esta es la aplicación de semiunión izquierda con la sonda descrita anteriormente. Esta transformación inicial es la única disponible en los optimizadores de consultas de SQL Server hasta la fecha, y la compilación simplemente fallará si esta transformación está deshabilitada.
Una de las formas posibles del plan de ejecución para esta consulta es una implementación directa de esa estructura lógica:

El Escalar Compute final evalúa el resultado de la CASEexpresión utilizando el valor de la columna de la sonda:

La forma básica del árbol del plan se conserva cuando la optimización considera otros tipos de unión física para la semiunión. Solo merge join admite una columna de sonda, por lo que no se considera una semiunión hash, aunque lógicamente posible:

Observe que la fusión genera una expresión etiquetada Expr1008(que el nombre es el mismo que antes es una coincidencia), aunque no aparece ninguna definición en ningún operador del plan. Esta es solo la columna de la sonda nuevamente. Como antes, el Escalar Compute final utiliza este valor de sonda para evaluar el CASE.
El problema es que el optimizador no explora completamente las alternativas que solo valen la pena con la combinación (o hash) de semiunión. En el plan de bucles anidados, no hay ninguna ventaja en verificar si las filas T2coinciden con el rango en cada iteración. Con un plan de fusión o hash, esto podría ser una optimización útil.
Si agregamos un BETWEENpredicado coincidente T2en la consulta, todo lo que sucede es que esta verificación se realiza para cada fila como un residuo en la semiunión de fusión (difícil de detectar en el plan de ejecución, pero está allí):
SELECT
T1.ID,
CASE
WHEN EXISTS
(
SELECT 1
FROM #T2 AS T2
WHERE T2.ID = T1.ID
AND T2.ID BETWEEN 5000 AND 7000 -- New
) THEN 1
ELSE 0
END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

Esperamos que el BETWEENpredicado sea empujado hacia abajo para dar como T2resultado una búsqueda. Normalmente, el optimizador consideraría hacer esto (incluso sin el predicado adicional en la consulta). Reconoce predicados implícitos ( BETWEENon T1y el predicado de unión entre T1y T2juntos implican el BETWEENon T2) sin que estén presentes en el texto de consulta original. Desafortunadamente, el patrón de aplicación de la sonda significa que esto no se explora.
Hay formas de escribir la consulta para generar búsquedas en ambas entradas para una semiunión de fusión. Una forma implica escribir la consulta de una manera bastante poco natural (derrotando la razón que generalmente prefiero EXISTS):
WITH T2 AS
(
SELECT TOP (9223372036854775807) *
FROM #T2 AS T2
WHERE ID BETWEEN 5000 AND 7000
)
SELECT
T1.ID,
DoesExist =
CASE
WHEN EXISTS
(
SELECT * FROM T2
WHERE T2.ID = T1.ID
) THEN 1 ELSE 0 END
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

No estaría contento de escribir esa consulta en un entorno de producción, es solo para demostrar que la forma de plan deseada es posible. Si la consulta real que necesita escribir se usa CASEde esta manera en particular, y el rendimiento sufre al no haber una búsqueda en el lado de la sonda de una semiunión de fusión, puede considerar escribir la consulta usando una sintaxis diferente que produzca los resultados correctos y un plan de ejecución más eficiente.