Este es un error en la normalización del proyecto , expuesto mediante el uso de una subconsulta dentro de una expresión de caso con una función no determinista.
Para explicarlo, debemos tener en cuenta dos cosas por adelantado:
- SQL Server no puede ejecutar subconsultas directamente, por lo que siempre se desenrollan o se convierten en una aplicación .
- La semántica de
CASE
es tal que una THEN
expresión solo debe evaluarse si la WHEN
cláusula devuelve verdadero.
La subconsulta (trivial) introducida en el caso problemático da como resultado un operador de aplicación (unión de bucles anidados). Para cumplir con el segundo requisito, SQL Server inicialmente coloca la expresión dbo.test6(1) + dbo.test6(2)
en el lado interno de la aplicación:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
... con la CASE
semántica honrada por un predicado de transferencia en la unión:
[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)
El lado interno del bucle solo se evalúa si la condición de transferencia se evalúa como falsa (significado @i = 3
). Todo esto es correcto hasta ahora. El cálculo escalar que sigue a los bucles anidados también honra la CASE
semántica correctamente:
[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
El problema es que la etapa de normalización del proyecto de la compilación de consultas ve que Expr1000
no está correlacionada y determina que sería seguro ( narrador: no lo es ) moverlo fuera del ciclo:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
Esto rompe la semántica * implementadas por el paso a través de predicados, por lo que la función se evalúa cuando no debería ser, y una infinidad de resultados de bucle.
Deberías reportar este error. Una solución alternativa es evitar que la expresión se mueva fuera de la aplicación haciendo que esté correlacionada (es decir, incluida @i
en la expresión), pero esto es un truco, por supuesto. Hay una manera de deshabilitar la normalización del proyecto, pero antes me han pedido que no la comparta públicamente, por lo que no lo haré.
Este problema no surge en SQL Server 2019 cuando la función escalar está en línea , porque la lógica de línea opera directamente en el árbol analizado (mucho antes de la normalización del proyecto). La lógica simple en la pregunta puede simplificarse mediante la lógica de alineación a la no recursiva:
[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))
... que devuelve 3.
Otra forma de ilustrar el problema central es:
-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error()
RETURNS integer
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
RETURN 1/0;
END;
GO
DECLARE @i integer = 1;
SELECT
CASE
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
ELSE NULL
END;
Se reproduce en las últimas compilaciones de todas las versiones desde 2008 R2 hasta 2019 CTP 3.0.
Otro ejemplo (sin una función escalar) proporcionado por Martin Smith :
SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))
Esto tiene todos los elementos clave necesarios:
CASE
(implementado internamente como ScaOp_IIF
)
- Una función no determinista (
CRYPT_GEN_RANDOM
)
- Una subconsulta en la rama que no debe ejecutarse (
(SELECT ...)
)
* Estrictamente, la transformación anterior aún podría ser correcta si la evaluación de Expr1000
se difiere correctamente, ya que solo se hace referencia por la construcción segura:
[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)
... pero esto requiere un indicador interno de ForceOrder (no una sugerencia de consulta), que tampoco está configurado. En cualquier caso, la implementación de la lógica aplicada por la normalización del proyecto es incorrecta o incompleta.
Informe de error en el sitio de comentarios de Azure para SQL Server.