192

我正在创建一个存储过程来通过表进行搜索。我有许多不同的搜索字段,所有这些都是可选的。有没有办法创建一个存储过程来处理这个问题?假设我有一个包含四个字段的表:ID、FirstName、LastName 和 Title。我可以做这样的事情:

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = ISNULL(@FirstName, FirstName) AND
            LastName = ISNULL(@LastName, LastName) AND
            Title = ISNULL(@Title, Title)
    END

这类作品。但是,它会忽略 FirstName、LastName 或 Title 为 NULL 的记录。如果在搜索参数中未指定 Title,我想包含 Title 为 NULL 的记录 - FirstName 和 LastName 相同。我知道我可能可以使用动态 SQL 来做到这一点,但我想避免这种情况。

4

6 回答 6

271

基于给定参数动态更改搜索是一个复杂的主题,并且以一种方式而不是另一种方式进行搜索,即使只有非常微小的差异,也会对性能产生巨大影响。关键是使用索引,忽略紧凑代码,忽略重复代码的担心,必须制定好的查询执行计划(使用索引)。

阅读本文并考虑所有方法。您的最佳方法将取决于您的参数、数据、架构和实际使用情况:

Erland Sommarskog 在 T-SQL 中的动态搜索条件

Erland Sommarskog 的动态 SQL 的诅咒和祝福

如果您有正确的 SQL Server 2008 版本(SQL 2008 SP1 CU5 (10.0.2746) 及更高版本),您可以使用这个小技巧来实际使用索引:

添加OPTION (RECOMPILE)到您的查询中, 请参阅 Erland 的文章,SQL Server 将在根据本地变量的运行时值创建查询计划之前OR从内部解析(@LastName IS NULL OR LastName= @LastName),并且可以使用索引。

这适用于任何 SQL Server 版本(返回正确的结果),但如果您使用的是 SQL 2008 SP1 CU5 (10.0.2746) 及更高版本,则仅包括 OPTION(RECOMPILE)。OPTION(RECOMPILE) 将重新编译您的查询,只有列出的版本会根据局部变量的当前运行时值重新编译它,这将为您提供最佳性能。如果不在该版本的 SQL Server 2008 上,请取消该行。

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))
        OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
    END
于 2010-08-05T14:21:36.850 回答
28

就目前而言,@KM 的回答很好,但未能完全跟进他早期的建议之一;

...,忽略紧凑的代码,忽略对重复代码的担忧,...

如果您希望获得最佳性能,那么您应该为每个可能的可选条件组合编写一个定制查询。这听起来可能很极端,如果您有很多可选标准,那么它可能是,但性能通常是努力和结果之间的权衡。在实践中,可能有一组通用的参数组合可以针对定制查询,然后是针对所有其他组合的通用查询(根据其他答案)。

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
BEGIN

    IF (@FirstName IS NOT NULL AND @LastName IS NULL AND @Title IS NULL)
        -- Search by first name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName

    ELSE IF (@FirstName IS NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by last name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            LastName = @LastName

    ELSE IF (@FirstName IS NULL AND @LastName IS NULL AND @Title IS NOT NULL)
        -- Search by title only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            Title = @Title

    ELSE IF (@FirstName IS NOT NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by first and last name
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName
            AND LastName = @LastName

    ELSE
        -- Search by any other combination
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))

END

这种方法的优点是,在定制查询处理的常见情况下,查询尽可能高效 - 未提供标准没有影响。此外,索引和其他性能增强可以针对特定的定制查询,而不是试图满足所有可能的情况。

于 2015-03-25T19:19:26.190 回答
27

您可以在以下情况下执行此操作,

CREATE PROCEDURE spDoSearch
   @FirstName varchar(25) = null,
   @LastName varchar(25) = null,
   @Title varchar(25) = null
AS
  BEGIN
      SELECT ID, FirstName, LastName, Title
      FROM tblUsers
      WHERE
        (@FirstName IS NULL OR FirstName = @FirstName) AND
        (@LastNameName IS NULL OR LastName = @LastName) AND
        (@Title IS NULL OR Title = @Title)
END

但是有时依赖数据更好地创建动态查询并执行它们。

于 2010-08-05T14:35:20.953 回答
12

晚了五年。

在提供的已接受答案的链接中提到了它,但我认为它应该得到一个明确的答案——根据提供的参数动态构建查询。例如:

设置

-- drop table Person
create table Person
(
    PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
    FirstName NVARCHAR(64) NOT NULL,
    LastName NVARCHAR(64) NOT NULL,
    Title NVARCHAR(64) NULL
)
GO

INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'), 
    ('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'), 
    ('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
    ('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
    ('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO

程序

ALTER PROCEDURE spDoSearch
    @FirstName varchar(64) = null,
    @LastName varchar(64) = null,
    @Title varchar(64) = null,
    @TopCount INT = 100
AS
BEGIN
    DECLARE @SQL NVARCHAR(4000) = '
        SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
        FROM Person
        WHERE 1 = 1'

    PRINT @SQL

    IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
    IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
    IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'

    EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)', 
         @TopCount, @FirstName, @LastName, @Title
END
GO

用法

exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'

优点:

  • 易于书写和理解
  • 灵活性 - 轻松生成更复杂过滤的查询(例如动态 TOP)

缺点:

  • 可能的性能问题取决于提供的参数、索引和数据量

不是直接回答,而是与问题相关,即大局

通常,这些过滤存储过程不会四处浮动,而是从某个服务层调用。这留下了将业务逻辑(过滤)从 SQL 转移到服务层的选项。

一个示例是使用 LINQ2SQL 根据提供的过滤器生成查询:

    public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
    {
        var query = DataAccess.SomeRepository.AllNoTracking;

        // partial and insensitive search 
        if (!string.IsNullOrWhiteSpace(filters.SomeName))
            query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
        // filter by multiple selection
        if ((filters.CreatedByList?.Count ?? 0) > 0)
            query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
        if (filters.EnabledOnly)
            query = query.Where(item => item.IsEnabled);

        var modelList = query.ToList();
        var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
        return serviceModelList;
    }

优点:

  • 基于提供的过滤器动态生成的查询。无需参数嗅探重新编译提示
  • 对 OOP 世界中的人来说更容易编写
  • 通常性能友好,因为将发出“简单”查询(尽管仍然需要适当的索引)

缺点:

  • 可能会达到 LINQ2QL 限制并根据情况强制降级到 LINQ2Objects 或返回纯 SQL 解决方案
  • 粗心的 LINQ 编写可能会产生糟糕的查询(或许多查询,如果加载了导航属性)
于 2016-12-19T15:40:36.247 回答
8

扩展您的WHERE条件:

WHERE
    (FirstName = ISNULL(@FirstName, FirstName)
    OR COALESCE(@FirstName, FirstName, '') = '')
AND (LastName = ISNULL(@LastName, LastName)
    OR COALESCE(@LastName, LastName, '') = '')
AND (Title = ISNULL(@Title, Title)
    OR COALESCE(@Title, Title, '') = '')

即结合不同的情况与布尔条件。

于 2010-08-05T14:36:24.960 回答
-3

这也有效:

    ...
    WHERE
        (FirstName IS NULL OR FirstName = ISNULL(@FirstName, FirstName)) AND
        (LastName IS NULL OR LastName = ISNULL(@LastName, LastName)) AND
        (Title IS NULL OR Title = ISNULL(@Title, Title))
于 2014-07-17T16:10:32.163 回答