如何在T-SQL存储过程中使用可选参数?

Cor*_*ett 179 t-sql optional-parameters

我正在创建一个存储过程来搜索表.我有许多不同的搜索字段,所有这些字段都是可选的.有没有办法创建一个存储过程来处理这个?假设我有一个包含四个字段的表: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
Run Code Online (Sandbox Code Playgroud)

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

KM.*_*KM. 253

基于给定参数动态改变搜索是一个复杂的主题,即使只有非常小的差异,它也可以单向执行,可能会产生巨大的性能影响.关键是使用索引,忽略紧凑代码,忽略担心重复代码,你必须做出一个好的查询执行计划(使用索引).

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

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)创建查询计划之前根据局部变量的运行时值解析from from ,并且可以使用索引.

这适用于任何SQL Server版本(返回正确的结果),但如果您使用的是SQL 2008 SP1 CU5(10.0.2746)及更高版本,则仅包含OPTION(RECOMPILE).OPTION(RECOMPILE)将重新编译您的查询,只有列出的verison将根据局部变量的当前运行时值重新编译它,这将为您提供最佳性能.如果不是在该版本的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
Run Code Online (Sandbox Code Playgroud)

  • 注意AND/OR优先级.AND优先于OR,所以没有适当的括号,这个例子不会产生预期的结果......所以它应该读取:(@ FirstName IS NULL OR(FirstName = @FirstName))AND(@ LastNameIS NULL OR(LastName = @LastName))AND(@TitleIS NULL OR(Title = @Title)) (14认同)

Rhy*_*nes 27

@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
Run Code Online (Sandbox Code Playgroud)

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

  • 毫无疑问,这种方法很快就会成为维护的噩梦. (4认同)
  • @Atario易于维护与性能相关是一种常见的权衡,这个答案是针对性能的. (3认同)

Mic*_*sov 25

您可以在以下情况下执行,

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
Run Code Online (Sandbox Code Playgroud)

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


dev*_*vio 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, '') = '')
Run Code Online (Sandbox Code Playgroud)

即将不同情况与布尔条件相结合.


Ale*_*xei 8

晚会五年.

在接受的答案的提供链接中提到,但我认为它应该在SO上得到明确答案 - 基于提供的参数动态构建查询.例如:

建立

-- 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
Run Code Online (Sandbox Code Playgroud)

程序

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
Run Code Online (Sandbox Code Playgroud)

用法

exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'
Run Code Online (Sandbox Code Playgroud)

优点:

  • 易于编写和理解
  • 灵活性 - 轻松生成棘手的过滤查询(例如动态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;
    }
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

  • 可能会达到LINQ2QL限制并强制降级到LINQ2Objects或返回到纯SQL解决方案,具体取决于具体情况
  • 不小心写LINQ可能会生成糟糕的查询(或许多查询,如果加载了导航属性)