Sql 服务器 UDF 在传递值为 null 的变量时的行为与传递常量 null 时的行为不同

Sql Server UDF behaves differently when a variable with value null is passed than when constant null is passed

我正在编写一个存储过程,其中包含大量昂贵的工作,可能需要也可能不需要过滤器参数。进行过滤本身就非常昂贵,而且要过滤的 table 很大。我只是尝试更改内部过滤功能,以便在使用无效参数调用时抛出错误,以警告开发人员不要那样使用它。

但是 - 如果我用 NULL 调用我的外部测试函数,它会按我预期的那样工作,而不是调用内部函数并且不会抛出错误。如果我用一个值为 NULL 的变量调用我的外部测试函数,那么它会用一个空参数调用过滤器函数,并抛出错误,即使代码只说在值不为空时调用函数。

这是怎么回事?

更简单的例子:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U')) DROP TABLE MyTable 
GO

CREATE TABLE MyTable (Pk int, Field int)
GO

INSERT INTO MyTable VALUES (1, 1)
INSERT INTO MyTable VALUES (2, 4)
INSERT INTO MyTable VALUES (3, 9)
INSERT INTO MyTable VALUES (4, 16)
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FilterRows]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION FilterRows
GO
CREATE FUNCTION FilterRows(@searchParameter int)
RETURNS @Pks TABLE 
    (           
        Pk int
    )
AS 
BEGIN
    IF (@searchParameter IS null)
    BEGIN
        -- This is bad news. We don't want to be here with a null search, as the only thing we can do is return every row in the whole table
        -- RAISERROR ('Avoid calling FilterRows with no search parameter', 16, 1)       
        -- we can't raise errors in functions!
        -- Make it divide by zero instead then
        INSERT INTO @Pks SELECT Pk FROM MyTable WHERE 1/0 = 1
    END
    ELSE
    BEGIN
        INSERT INTO @Pks SELECT Pk FROM MyTable WHERE Field > @searchParameter
    END
    RETURN
END
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OuterFunction]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) DROP FUNCTION OuterFunction
GO
CREATE FUNCTION OuterFunction(@searchParameter int)
RETURNS TABLE AS
RETURN 
SELECT * 
FROM 
MyTable
WHERE
(@SearchParameter IS NULL) OR (@searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(@searchParameter)))
GO

SELECT * FROM dbo.OuterFunction(2) -- Returns filtered values
SELECT * FROM dbo.OuterFunction(null) -- returns everything, doesn't call FilterRows
DECLARE @x int = null
SELECT * FROM dbo.OuterFunction(@x) -- WTF! Throws error!

传递 null 值与传递 null 常量的区别与使用 (is Null) 和 (= null) 之间的区别相同

@var = null -- considered as false

@var is null -- considered as unknown

更多详情:SQL is null and = null

所以如果你想让两者的行为(调用常量 null 和传递 Null 值)相同,请使用以下技巧,尽管我不喜欢这个。

将 FilterRows 函数更改为

IF (@searchParameter = null)
--IF (@searchParameter is null)

注意:抱歉在这里输入这个答案,它应该是评论而不是答案,规则是 "You must have 50 reputation to comment" 而我只有 22 :(

我认为发生的事情是

SELECT * FROM MyTable WHERE (@SearchParameter IS NULL) OR 
(@searchParameter IS NOT NULL AND Pk IN (SELECT Pk FROM dbo.FilterRows(@searchParameter)))

查询分析器可以看到子查询

(SELECT Pk FROM dbo.FilterRows(@searchParameter))

不依赖于 MyTable 中的任何值。因为它对所有行都是不变的,所以它首先运行该子查询,以便将 MyTable 连接到结果。所以它在评估 WHERE 子句之前执行它,它测试 @searchParameter 是否为 NULL。

当@searchParameter 只是"NULL",而不是值为NULL 的变量时,分析器可以将执行计划中的整个where 子句短路,从而知道不预先计算子查询。

或者,类似的东西。