[Atualização: esta pergunta descreve um bug que foi corrigido na atualização cumulativa 5 para SQL Server 2019. ]
Considere o seguinte exemplo de reprodução ( fiddle ):
CREATE FUNCTION dbo.Repro (@myYear int)
RETURNS datetime
AS
BEGIN
IF @myYear <> 1990
BEGIN
RETURN NULL
END
DECLARE @firstOfYear datetime;
SET @firstOfYear = DATEFROMPARTS(@myYear, 1, 1);
IF DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0
BEGIN
RETURN NULL
END
RETURN @firstOfYear
END
SELECT dbo.Repro(0);
Obviamente, essa função deve retornar o primeiro de janeiro de 1990 se a entrada for 1990
, e NULL
caso contrário. Sim, eu sei que DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0
é uma operação sem sentido. Este é um mcve para demonstrar um possível bug, não o código de produção.
Agora vamos executar SELECT dbo.Repro(0)
no SQL Server 2017 e SQL Server 2019.
Resultado esperado : NULL
.
Resultado real no SQL Server 2017 :NULL
Resultado real no SQL Server 2019 :
Msg 289 Nível 16 Estado 1 Linha 1
Não é possível construir o tipo de dados data, alguns dos argumentos possuem valores que não são válidos.
Aparentemente, o SQL Server 2019 executa parte do código abaixo da cláusula de guarda inicial ( IF @myYear <> 1990
), mesmo que não devesse.
Minhas perguntas:
- Esse comportamento é esperado ou encontrei um bug no SQL Server 2019?
- Se esse for o comportamento esperado, como escrever corretamente uma cláusula de guarda validando os parâmetros de entrada?
Este é um bug com o inlining de UDFs escalares (ou talvez um bug com o otimizador de consulta que está sendo mais exposto pelo inlining de UDF escalar). Você pode usar
WITH INLINE = OFF
para desativar o inlining para essa função.Usar uma variável em vez de uma constante mostra um pouco mais de detalhes
Expr1000 = CASE WHEN [@myYear]<>(1990) THEN (1) ELSE (0) END
[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(datetime,datefromparts([@myYear],(1),(1)),0))
Essas expressões são simplificadas ao usar o literal
0
para1
eCONVERT_IMPLICIT(datetime,datefromparts((0),(1),(1)),0)
respectivamente.O
datefromparts(0
lançará o erro quando avaliado.Expr1002 = CASE WHEN [Expr1000] = (1) THEN (1) ELSE (0) END
E
Expr1002
é usado como um predicado intermediário em uma junção de loops aninhados (nó 3). No interior desses loops aninhados, a varredura constante (nó 7) não retorna nenhuma coluna.Portanto, isso parece o mesmo problema básico da resposta aqui, onde uma expressão no interior de um loop aninhado protegido por um predicado de passagem é movida para uma região desprotegida.