当传递值为null的变量时,Sql Server UDF的行为与传递常量null时的行为不同

时间:2016-09-08 13:22:45

标签: sql-server user-defined-functions udf short-circuiting

我正在编写一个包含大量昂贵工作的存储过程,可能会也可能不会使用过滤器参数。进行过滤本身非常昂贵,并且被过滤的表很大。我只是尝试更改内部过滤功能,因此如果使用无效参数调用则抛出错误,以警告开发人员不要以这种方式使用它。

但是 - 如果我用NULL调用我的外部测试函数,它就像我期望的那样工作,而不是调用内部函数而不是抛出错误。如果我使用VALUE为NULL的变量调用我的外部测试函数,那么它使用null参数调用filter函数,并抛出错误,即使代码只是在值不为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!

2 个答案:

答案 0 :(得分:0)

传递值null时的差异比传递常量null时的差异是使用(是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)

注意:抱歉在这里输入这个答案,它应该是评论而不是答案,规则是“你必须有50个评论的声誉”,我只有22 :(

答案 1 :(得分:0)

我认为正在发生的是

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子句短路,因此不知道预先计算子查询。

或者类似的东西。