WHERE子句不按逻辑顺序进行过滤

时间:2012-03-27 06:58:00

标签: sql-server sql-server-2008 tsql isnull

我正在编写一般搜索存储过程,以便根据用户可以在UI中选择的许多过滤器(使用MS-SQL 2008)在表格中进行搜索。

这是简化版本:

CREATE PROCEDURE SearchAll
    @FirstName NVARCHAR(MAX) = NULL,
    @LastName NVARCHAR(MAX) = NULL,
    @Age INT = NULL
AS
    SELECT * 
    FROM persons 
    WHERE 
        (@FirstName IS NULL OR FirstName = @firstname)
        AND (@LastName IS NULL OR LastName = @LastName)
        AND (@Age IS NULL OR Age = @Age)

似乎如果我将NULL传递给@Age,那么就没有性能成本。但是,当我测试大量数据时,我的性能很差!

这些查询在逻辑上是相同但实际上非常不同:

DECLARE @FirstName NVARCHAR(MAX) = NULL
DECLARE @Age INT = 23
------------First slow------------
SELECT * 
FROM persons 
WHERE 
    (@FirstName IS NULL OR FirstName = @firstname)
    AND (@Age IS NULL OR Age = @Age)
------------Very fast------------
SELECT * 
FROM persons 
WHERE 
    Age = @Age

错过了一点吗?

我知道SQL引擎找到索引的最佳匹配,并且......(在运行查询之前),但很明显:@FirstName IS NULL并且不需要分析任何内容。

我还在查询中测试了ISNULL函数(结果相同)。

2 个答案:

答案 0 :(得分:5)

包含此@variable is null or @variable = column构造的查询是性能灾难。这是因为SQL计划已创建,因此它们适用于变量的任何值。有关该主题的冗长讨论,问题和可能的解决方案请参见Dynamic Search Conditions in T-SQL

答案 1 :(得分:0)

正如已经提到的那样,问题是将构建查询计划以使用任何变量值。您可以通过仅使用所需的参数来构建查询来避免这种情况,如下所示:

CREATE PROCEDURE SearchAll
    @FirstName NVARCHAR(MAX) = NULL,
    @LastName NVARCHAR(MAX) = NULL,
    @Age INT = NULL
AS
BEGIN
    DECLARE @sql NVARCHAR(MAX), @has_where BIT
    SELECT @has_where = 0, @sql = 'SELECT * FROM persons '

    IF @FirstName IS NOT NULL
        SELECT @sql = @sql + 'WHERE FirstName = ''' + @FirstName + '''', @has_where = 1
    IF @LastName IS NOT NULL
        SELECT @sql = @sql + CASE WHEN @has_where = 0 THEN 'WHERE ' ELSE 'AND ' END + 'LastName = ''' + @LastName + '''', @has_where = 1
    IF @Age IS NOT NULL
        SELECT @sql = @sql + CASE WHEN @has_where = 0 THEN 'WHERE ' ELSE 'AND ' END + 'Age = ' + CAST(@Age AS VARCHAR), @has_where = 1

    EXEC sp_executesql @sql
END