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

我正在创build一个存储过程来通过一个表进行search。 我有许多不同的search字段,所有这些都是可选的。 有没有办法来创build一个存储过程,将处理这个? 假设我有一个包含四个字段的表: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为空的logging。 如果没有在search参数中指定标题,我想包括Title为NULL的logging – 对于FirstName和LastName是相同的。 我知道我可以用dynamicSQL做这个,但我想避免这种情况。

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

阅读并考虑所有的方法。 你最好的方法将取决于你的参数,你的数据,你的模式,和你的实际使用情况:

T-SQL中的dynamicsearch条件by Erland Sommarskog

Erland SommarskogdynamicSQL的诅咒和祝福

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

在你的查询中joinOPTION (RECOMPILE) , 参见Erland的文章 ,SQL Server将根据本地variables的运行时间值在内部parsingOR (@LastName IS NULL OR LastName= @LastName)可以使用索引。

这将适用于任何SQL Server版本(返回正确的结果),但只有在SQL 2008 SP1 CU5(10.0.2746)及更高版本中才包括OPTION(RECOMPILE)。 OPTION(RECOMPILE)会重新编译你的查询,只有列出的verison会根据当地的variables的当前运行时间值重新编译它,这会给你提供最好的性能。 如果不在该版本的SQL Server 2008上,只需将该行closures即可。

 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 

@KM的回答虽然很好,但还没有完全跟上他早期的一些build议。

…,忽略紧凑的代码,忽略重复代码的担心,…

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

 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 

这种方法的优点是,在定制查询处理的常见情况下,查询效率可以达到 – 不受标准影响。 而且,索引和其他性能增强可以针对特定的定制查询,而不是试图满足所有可能的情况。

你可以在下面的情况下做,

 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 

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

扩展你的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, '') = '') 

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

晚会五年了。

在提供的接受答案的链接中提到了这个问题,但我认为它应该在SO上进行明确的回答 – 根据提供的参数dynamic构build查询。 例如:

build立

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

优点:

  • 容易写和理解
  • 灵活性 – 轻松生成棘手的过滤查询(如dynamicTOP)

缺点:

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

不是直接的答案,而是涉及到大问题

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

一个例子是使用LINQ2SQL根据提供的filter来生成查询:

  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; } 

优点:

  • 基于提供的filterdynamic生成查询。 没有参数嗅探或重新编译提示需要
  • 在面向对象的世界中写起来容易一些
  • 通常performance友好,因为“简单”查询将被发布(尽pipe适当的索引仍然需要)

缺点:

  • 可能会达到LINQ2QL的限制,并强制降级到LINQ2Objects或根据情况返回到纯SQL解决scheme
  • LINQ的粗心写作可能会产生糟糕的查询(或许多查询,如果导航属性被加载)

这也适用:

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