Il s'agit d'un bogue dans la normalisation de projet , exposé en utilisant une sous-requête à l'intérieur d'une expression de cas avec une fonction non déterministe.
Pour expliquer, nous devons noter deux choses à l'avance:
- SQL Server ne peut pas exécuter les sous-requêtes directement, elles sont donc toujours déroulées ou converties en application .
- La sémantique de
CASE
est telle qu'une THEN
expression ne doit être évaluée que si la WHEN
clause renvoie true.
La sous-requête (triviale) introduite dans le cas problématique se traduit donc par un opérateur d'application (jointure de boucles imbriquées). Pour répondre à la deuxième exigence, SQL Server place initialement l'expression dbo.test6(1) + dbo.test6(2)
sur le côté intérieur de l'application:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
... avec la CASE
sémantique honorées par une répercussion prédicat sur la jointure:
[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)
Le côté intérieur de la boucle n'est évalué que si la condition de transmission est évaluée à false (signification @i = 3
). Tout cela est correct jusqu'à présent. Le calcul scalaire suivant la jointure des boucles imbriquées honore également CASE
correctement la sémantique:
[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)
Le problème est que l' étape de normalisation du projet de la compilation des requêtes voit que ce Expr1000
n'est pas corrélé et détermine qu'il serait sûr ( narrateur: ce n'est pas ) de le déplacer en dehors de la boucle:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
Ce pauses * la sémantique mis en œuvre par le pass-through prédicat, de sorte que la fonction est évaluée quand il ne doit pas être, et un résultat de boucle infinie.
Vous devez signaler ce bogue. Une solution de contournement consiste à empêcher l'expression d'être déplacée en dehors de l'application en la rendant corrélée (c'est-à-dire en l'incluant @i
dans l'expression), mais il s'agit bien sûr d'un hack. Il existe un moyen de désactiver la normalisation du projet, mais on m'a déjà demandé de ne pas le partager publiquement, donc je ne le ferai pas.
Ce problème ne se pose pas dans SQL Server 2019 lorsque la fonction scalaire est insérée , car la logique d'inline fonctionne directement sur l'arborescence analysée (bien avant la normalisation du projet). La logique simple dans la question peut être simplifiée par la logique en ligne au non récursif:
[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))
... qui renvoie 3.
Une autre façon d'illustrer le problème central est:
-- 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;
Reproduit sur les dernières versions de toutes les versions de 2008 R2 à 2019 CTP 3.0.
Un autre exemple (sans fonction scalaire) fourni par Martin Smith :
SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))
Cela a tous les éléments clés nécessaires:
CASE
(implémenté en interne comme ScaOp_IIF
)
- Une fonction non déterministe (
CRYPT_GEN_RANDOM
)
- Une sous-requête sur la branche qui ne doit pas être exécutée (
(SELECT ...)
)
* Strictement, la transformation ci-dessus pourrait toujours être correcte si l'évaluation de Expr1000
était correctement différée, car elle n'est référencée que par la construction sûre:
[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)
... mais cela nécessite un indicateur ForceOrder interne (pas un indice de requête), qui n'est pas non plus défini. Dans tous les cas, l'implémentation de la logique appliquée par la normalisation du projet est incorrecte ou incomplète.
Rapport de bogue sur le site Azure Feedback pour SQL Server.