Este é um erro na normalização do projeto , exposto usando uma subconsulta dentro de uma expressão de caso com uma função não determinística.
Para explicar, precisamos observar duas coisas com antecedência:
- O SQL Server não pode executar subconsultas diretamente, portanto elas sempre são desenroladas ou convertidas em um aplicativo .
- A semântica de
CASE
é tal que uma THEN
expressão só deve ser avaliada se a WHEN
cláusula retornar verdadeira.
A subconsulta (trivial) introduzida no caso problemático resulta em um operador de aplicação (junção de loops aninhados). Para atender ao segundo requisito, o SQL Server inicialmente coloca a expressão dbo.test6(1) + dbo.test6(2)
no lado interno da aplicação:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
... com a CASE
semântica respeitada por um predicado de passagem na junção:
[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)
O lado interno do loop é avaliado apenas se a condição de passagem for avaliada como falsa (significado @i = 3
). Tudo está correto até agora. O Escalar de computação após a junção de loops aninhados também honra a CASE
semântica corretamente:
[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)
O problema é que o estágio de normalização do projeto da compilação de consultas vê isso sem Expr1000
correlação e determina que seria seguro ( narrador: não é ) movê-lo para fora do loop:
[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))
Isso quebra * a semântica implementada pelo predicado de passagem , de modo que a função é avaliada quando não deveria ser e resulta em um loop infinito.
Você deve relatar esse bug. Uma solução alternativa é impedir que a expressão seja movida para fora da aplicação, tornando-a correlacionada (isto é, incluindo @i
a expressão), mas isso é óbvio. Existe uma maneira de desativar a normalização do projeto, mas me pediram antes para não compartilhá-lo publicamente, por isso não vou.
Esse problema não surge no SQL Server 2019 quando a função escalar é incorporada , porque a lógica embutida opera diretamente na árvore analisada (muito antes da normalização do projeto). A lógica simples na pergunta pode ser simplificada pela lógica embutida para o não recursivo:
[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))
... que retorna 3.
Outra maneira de ilustrar a questão principal é:
-- 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;
Reproduz nas versões mais recentes de todas as versões de 2008 R2 a 2019 CTP 3.0.
Um outro exemplo (sem uma função escalar) fornecido por Martin Smith :
SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))
Este possui todos os elementos-chave necessários:
CASE
(implementado internamente como ScaOp_IIF
)
- Uma função não determinística (
CRYPT_GEN_RANDOM
)
- Uma subconsulta na ramificação que não deve ser executada (
(SELECT ...)
)
* Estritamente, a transformação acima ainda pode estar correta se a avaliação de Expr1000
for adiada corretamente, pois é referenciada apenas pela construção 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)
... mas isso requer um sinalizador ForceOrder interno (não dica de consulta), que também não está definido. De qualquer forma, a implementação da lógica aplicada pela normalização do projeto está incorreta ou incompleta.
Relatório de bug no site de Feedback do Azure para SQL Server.