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

Tsql 如何在T-SQL存储过程中使用可选参数?,tsql,optional-parameters,Tsql,Optional Parameters,我正在创建一个存储过程来搜索表。我有许多不同的搜索字段,所有这些字段都是可选的。有没有办法创建一个存储过程来处理这个问题?假设我有一个包含四个字段的表:ID、FirstName、LastName和Title。我可以这样做: CREATE PROCEDURE spDoSearch @FirstName varchar(25) = null, @LastName varchar(25) = null, @Title varchar(25) = null AS BEGI

我正在创建一个存储过程来搜索表。我有许多不同的搜索字段,所有这些字段都是可选的。有没有办法创建一个存储过程来处理这个问题?假设我有一个包含四个字段的表: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为空的记录。如果在搜索参数中没有指定Title,我希望包括Title为NULL的记录-FirstName和LastName相同。我知道我可能可以用动态SQL来实现这一点,但我希望避免这种情况。

基于给定参数动态更改搜索是一个复杂的问题,并且以一种方式进行搜索,即使只是很小的差异,也会对性能产生巨大影响。关键是要使用索引,忽略压缩代码,忽略对重复代码的担忧,必须制定好查询执行计划(使用索引)

<>阅读并考虑所有的方法。最佳方法取决于参数、数据、模式和实际使用情况:

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

在查询中添加
选项(重新编译)
,SQL Server将从
中解析
(@LastName为NULL或LastName=@LastName)
,然后根据本地变量的运行时值创建查询计划,并且可以使用索引

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

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
CREATE PROCEDURE-spDoSearch
@FirstName varchar(25)=空,
@LastName varchar(25)=空,
@Title varchar(25)=空
作为
开始
选择ID、FirstName、LastName、Title
来自特布卢斯
哪里
(@FirstName为NULL或(FirstName=@FirstName))
和(@LastName为NULL或(LastName=@LastName))
和(@Title为空或(Title=@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 IS NULL OR FirstName = @FirstName) AND
        (@LastNameName IS NULL OR LastName = @LastName) AND
        (@Title IS NULL OR Title = @Title)
END

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

扩展您的
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, '') = '')
一,。E将不同的案例与布尔条件结合起来。

这也适用于:

    ...
    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))

@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

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

延迟了五年

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

设置

-- 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'
用法

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)
缺点:

  • 可能的性能问题取决于提供的参数、索引和数据量
不是直接的答案,而是与问题或大局相关的

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'
通常,这些过滤存储过程不是浮动的,而是从某个服务层调用的。这就留下了将业务逻辑(过滤)从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;
    }
public IList GetServiceModels(自定义过滤器)
{
var query=DataAccess.SomeRepository.AllNoTracking;
//部分和不敏感搜索
如果(!string.IsNullOrWhiteSpace(filters.SomeName))
query=query.Where(item=>item.SomeName.IndexOf(filters.SomeName,StringComparison.OrdinalIgnoreCase)!=-1);
//多选过滤
如果((filters.CreatedByList?.Count±0)>0)
query=query.Where(item=>filters.CreatedByList.Contains(item.CreatedById));
如果(过滤器启用)
query=query.Where(item=>item.IsEnabled);
var modelList=query.ToList();
var serviceModelList=MappingService.MapEx(模型列表);
返回serviceModelList;
}
优点:

  • 根据提供的筛选器动态生成查询。不需要任何提示或提示
  • 对于OOP领域的人来说,写起来更容易一些
  • 通常性能友好,因为将发出“简单”查询(但仍然需要适当的索引)
缺点:

  • 可能会达到LINQ2QL限制,强制降级到LINQ2对象或返回纯SQL解决方案取决于