[更新:此问题描述了已在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
是一个荒谬的操作。这是一个演示潜在错误的mcve ,而不是生产代码。
现在让我们SELECT dbo.Repro(0)
在 SQL Server 2017 和 SQL Server 2019 上执行。
预期结果:NULL
。
SQL Server 2017 上的实际结果:NULL
SQL Server 2019 上的实际结果:
消息 289 级别 16 状态 1 行 1
无法构造数据类型日期,某些参数的值无效。
显然,SQL Server 2019 会执行初始保护子句 ( IF @myYear <> 1990
) 下面的一些代码,即使它不应该执行。
我的问题:
- 这是预期的行为,还是我在 SQL Server 2019 中发现了错误?
- 如果这是预期的行为,我该如何正确编写验证输入参数的保护子句?
这是标量 UDF 内联的错误(或者可能是标量 UDF 内联更多地暴露的查询优化器的错误)。您可以使用
WITH INLINE = OFF
该函数关闭内联。使用变量而不是常量可以显示更多细节
Expr1000 = CASE WHEN [@myYear]<>(1990) THEN (1) ELSE (0) END
[Expr1003] = Scalar Operator(CONVERT_IMPLICIT(datetime,datefromparts([@myYear],(1),(1)),0))
分别使用字面量
0
to1
和时,这些表达式被简化了CONVERT_IMPLICIT(datetime,datefromparts((0),(1),(1)),0)
。datefromparts(0
评估时将抛出错误。Expr1002 = CASE WHEN [Expr1000] = (1) THEN (1) ELSE (0) END
并且
Expr1002
用作嵌套循环连接(节点 3)上的通路谓词。在该嵌套循环的内部,常量扫描(节点 7)不返回任何列。因此,这看起来与此处的答案相同的基本问题,其中受通路谓词保护的嵌套循环内部的表达式被移出不受保护的区域。