[更新:此问题描述了SQL Server 2019累积更新5中修复的错误。]

考虑以下再现示例(小提琴):
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);

如果输入是1990,则该函数应返回1990年1月的第一天,否则返回NULL。是的,我知道DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0是无意义的操作。这是一个演示潜在错误而不是生产代码的方法。
现在让我们在SQL Server 2017和SQL Server 2019上执行SELECT dbo.Repro(0)。 :NULL
在SQL Server 2019上的实际结果:

消息289级16状态1行1

显然,SQL Server 2019执行初始防护子句(NULL)下的某些代码,即使它不应该执行。
我的问题:

这是预期的行为,还是在SQL Server 2019中发现了错误?
如果这是预期的行为,如何正确编写保护输入参数的保护子句?


#1 楼

这是带有标量UDF内联的错误(或者可能是带有由标量UDF内联暴露的查询优化程序的错误)。您可以使用WITH INLINE = OFF来关闭该函数的内联。

使用变量而不是常量会显示更多细节

declare @myYear int = 0

SELECT dbo.Repro(@myYear);




节点5定义Expr1000 = CASE WHEN [@myYear]<>(1990) THEN (1) ELSE (0) END

节点2定义[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(datetime,datefromparts([@myYear],(1),(1)),0))


使用文字0简化这些表达式分别为1CONVERT_IMPLICIT(datetime,datefromparts((0),(1),(1)),0)

在评估时,datefromparts(0会引发错误。


节点6定义了Expr1002 = CASE WHEN [Expr1000] = (1) THEN (1) ELSE (0) END
Expr1002用作嵌套循环连接(节点3)上的passthru谓词。在该嵌套循环的内部,常量扫描(节点7)不返回任何列。 passthru谓词移出到不受保护的区域。

评论


仅供参考Microsoft知道,并且下一个累积更新中应该有修复程序。感谢您举报,很抱歉您遇到了这个问题。

– Conor Cunningham MSFT
20/04/27在14:31

@ConorCunninghamMSFT:太好了,谢谢您的反馈!

–亨氏
20年4月28日在8:53