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

Sql server 传递值为null的变量时,Sql Server UDF的行为与传递常数null时不同,sql-server,user-defined-functions,udf,short-circuiting,Sql Server,User Defined Functions,Udf,Short Circuiting,我正在编写一个存储过程,需要做大量昂贵的工作,可能需要也可能不需要使用过滤器参数。进行过滤本身非常昂贵,而且被过滤的表很大。我只是尝试更改内部过滤函数,因此如果使用无效参数调用,则会抛出一个错误,以警告开发人员不要以这种方式使用它 但是-如果我用NULL调用外部测试函数,它会像我预期的那样工作,不会调用内部函数,也不会抛出错误。如果我用一个值为NULL的变量调用外部测试函数,那么它用NULL参数调用filter函数,并抛出错误,即使代码只说在值不为NULL时调用函数 这是怎么回事 非常简单的示例

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

但是-如果我用NULL调用外部测试函数,它会像我预期的那样工作,不会调用内部函数,也不会抛出错误。如果我用一个值为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!

传递值null时与传递常数null时的差异与使用(is null)和(=null)时的差异相同

有关详细信息:

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

将FilterRows函数更改为

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


注意:很抱歉在这里键入此答案,它应该是注释而不是答案,规则是“您必须有50个声誉才能进行注释”,我只有22:(

传递值null时与传递常数null时的差异与使用(is null)和(=null)时的差异相同)

有关详细信息:

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

将FilterRows函数更改为

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


注意:很抱歉在这里输入这个答案,它应该是评论而不是答案,规则是“你必须有50个声誉才能发表评论”,而我只有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子句,因此知道不预计算子查询


或者,类似的事情。

我想这是怎么回事

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


或者类似的内容。

“可选过滤器”这是一个设计错误,因为优化器将缓存为第一次调用生成的执行计划。而不是短路,最终会导致性能下降。对这类问题的需求随着仅使用必要过滤器生成查询的ORMs和LINQ的出现而消失。无论您是否执行临时查询,性能都完全相同ry或等效的存储过程过滤与昂贵相反-它会减少结果。如果不是,则查询有问题。如果有适当的索引,条件越多,执行速度就越快。“短路”尝试会导致执行速度变慢,因为他们必须处理所有事情以找到要做的事情…我故意简化了情况,以便提出一个简洁的问题。我有很好的理由去做我正在做的事情,并且我知道查询分析器是如何工作的。我问的问题是关于UDF的意外行为。我不是要求被傲慢地告知我试图实现的目标是错误的。然后你应该在问题文本中发布错误,包括完整错误,而不是在代码第二页的最后一行。尝试使用“可选”过滤参数是一个常见的错误,我在意识到后果之前也犯过这个错误。错误发生在哪里?好的。我真正拥有的是一个表示文件夹的表,因此主表有一个文件夹id和一个父文件夹id。我正在为报表编写一个查询,报表可以有一个目标文件夹,在这种情况下,它应该为此运行文件夹,并包括其所有子项和内容。“可选筛选器”这是一个设计错误,因为优化器将缓存为第一次调用生成的执行计划。而不是短路,最终会导致性能下降。对这类问题的需求随着仅使用必要过滤器生成查询的ORMs和LINQ的出现而消失。无论您是否执行临时查询,性能都完全相同ry或等效的存储过程过滤与昂贵相反-它会减少结果。如果不是,则查询有问题。如果有适当的索引,条件越多,执行速度就越快。“短路”尝试会导致执行速度变慢,因为他们必须处理所有事情以找到要做的事情…我故意简化了情况,以便提出一个简洁的问题。我有很好的理由去做我正在做的事情,并且我知道查询分析器是如何工作的。我问的问题是关于UDF的意外行为。我不是要求别人高高在上地告诉我,我试图实现的是一件错误的事情。然后你应该发布问题文本本身有错误,包括完整的错误