我正在创建一个存储过程来通过表进行搜索。我有许多不同的搜索字段,所有这些都是可选的。有没有一种方法可以创建存储过程来处理呢?假设我有一个包含四个字段的表: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的记录。如果在搜索参数中未指定标题,则我要包括标题为NULL的记录-与名字和姓氏相同。我知道我可以使用动态SQL来做到这一点,但我想避免这种情况。

评论

在这里看看:stackoverflow.com/questions/11396919/…

尝试执行以下where语句:代码ISNULL(FirstName,')= ISNULL(@FirstName,'')–这将使每个NULL变成一个空字符串,并且可以通过eq比较它们。操作员。如果要在输入参数为null的情况下获取所有标题,请尝试执行以下操作:codeFirstName = @FirstName或@FirstName IS NULL。

#1 楼

根据给定的参数动态更改搜索是一个复杂的主题,并且即使只有很小的不同,以一种方式进行另一种方式也可能会带来巨大的性能影响。关键是要使用索引,忽略紧凑的代码,而不必担心重复代码,必须制定良好的查询执行计划(使用索引)。

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

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

如果您拥有正确的SQL Server 2008版本(SQL 2008 SP1 CU5(10.0.2746)和更高版本),则可以使用此小技巧实际使用索引:Erland Sommarskog提供的动态SQL的祝福。

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


评论


请注意AND / OR优先级。 AND的优先级高于OR,因此,如果没有正确的括号,此示例将无法产生预期的结果...因此,它应该读为:(@FirstName IS NULL OR(FirstName = @FirstName))AND(@LastNameIS NULL OR(LastName = @LastName))和(@TitleIS NULL或(Title = @Title))

–布莱克
2011-10-10 11:51



...(@FirstName是NULL或(FirstName = @FirstName)应该是...(FirstName = Coalesce(@ firstname,FirstName))

–fcm
18-2-27在16:25



不要忘记括号,否则它将无法正常工作。

– Pablo CarrascoHernández
19年4月22日在20:33

#2 楼

@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


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

评论


当然,为每种情况编写一个单独的存储过程会更好。那就不用担心欺骗和重新编译。

–乔德雷尔
18年5月10日在8:07

不用说,这种方法很快成为维护的噩梦。

– Atario
18年5月24日在22:58

@Atario易于维护与性能是一个常见的权衡,这个答案是针对性能的。

– Rhys Jones
18年5月31日在18:41

#3 楼

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

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


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

#4 楼

聚会晚了五年。

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

设置

-- 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可能会产生糟糕的结果查询(或许多查询,如果已加载导航属性)


评论


确保所有中间字符串都是N''而不是''-如果SQL超过8000个字符,则会遇到截断问题。

–艾伦·辛菲尔德
3月11日8:07



另外,如果您拒绝了对用户的直接SELECT权限,则可能需要在存储过程中添加“ WITH EXECUTE AS OWNER”子句。但是,如果使用此子句,请务必避免避免SQL注入。

–艾伦·辛菲尔德
3月11日8:11

#5 楼

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


i。 e。将不同的情况与布尔条件结合起来。

#6 楼

这也适用:

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