【问题标题】:How to conditionally filter on a column in a WHERE clause?如何有条件地过滤 WHERE 子句中的列?
【发布时间】:2010-12-30 23:52:01
【问题描述】:

好的,第无数个条件列​​问题:

我正在编写一个存储过程,它接受一个映射到几个标志列之一的输入参数。过滤请求的列的最佳方法是什么?我目前正在使用 SQL2000,但即将迁移到 SQL2008,因此如果有可用的解决方案,我将采用现代解决方案。

sproc中查询到的表是这样的

ID ...  fooFlag  barFlag  bazFlag  quuxFlag
--      -------  -------  -------  --------
01         1        0       0          1
02         0        1       0          0
03         0        0       1          1
04         1        0       0          0

我想做类似的事情

select ID, name, description, ...
from myTable
where (colname like @flag + 'Flag') = 1

所以如果我像 exec uspMyProc @flag = 'foo' 这样调用存储过程,我会返回第 1 行和第 4 行。

我知道我不能直接在 SQL 中完成括号中的部分。为了执行动态 SQL,我必须将整个查询填充到一个字符串中,连接 WHERE 子句中的 @flag 参数,然后执行该字符串。除了我在执行动态 SQL 时得到的肮脏感觉之外,我的查询相当大(我选择了几十个字段,连接 5 个表,调用了几个函数),所以它是一个很大的字符串,都是因为一行在 3 行 WHERE 过滤器中。

或者,我可以有 4 个查询副本,并在 CASE 语句中选择它们。这使得 SQL 代码可以直接执行(并且受到语法高亮等的影响),但代价是重复大量代码,因为我不能只在 WHERE 子句上使用 CASE。

还有其他选择吗?可以应用任何棘手的连接或逻辑操作吗?还是我应该克服它并执行动态 SQL?

【问题讨论】:

  • "exec uspMyProc @flag = 'foo' 我会回到第 1 行。"为什么只有第 1 行而不是第 4 行?

标签: tsql dynamic conditional where-clause


【解决方案1】:

int 应该被接受为 varchar 值

declare @CompanyID as varchar(10) = ''    -- or anyother value

select *  from EmployeeChatTbl chat

  where chat.ConversationDetails like '%'+@searchKey+'%' 

                and
                (
                    (0 = CASE WHEN (@CompanyID = '' ) THEN 0 ELSE 1 END) 
                                or 
                    (chat.CompanyID = @CompanyID)
                )

工作

当 companyID 存在时,基于它的过滤完成,否则,过滤被跳过。

【讨论】:

    【解决方案2】:
    where
       case when @value<>0 then Field else 1 end
       =
       case when @value<>0 then @value else 1 end
    

    【讨论】:

    • 在您的代码周围添加一些文字说明将对提问者有所帮助。
    【解决方案3】:

    有几种方法可以做到这一点:

    您可以使用 case 语句来做到这一点。

    select ID, name, description, ...
    from myTable
    where CASE
        WHEN @flag = 'foo' then fooFlag
        WHEN @flag = 'bar' then barFlag
    END = 1
    

    你可以使用 IF。

    IF (@flag = 'foo') BEGIN
        select ID, name, description, ...
        from myTable
        where fooFlag = 1
    END ELSE IF (@flag = 'bar') BEGIN
        select ID, name, description, ...
        from myTable
        where barFlag = 1
    END
    
    ....
    

    你可以有一个带有很多括号的复杂 where 子句。

    select ID, name, description, ...
    from myTable
    where (@flag = 'foo' and fooFlag = 1)
    OR (@flag = 'bar' and barFlag = 1) OR ...
    

    您可以使用动态 sql 来做到这一点:

    DECLARE @SQL nvarchar(4000)
    
    SELECT @SQL = N'select ID, name, description, ...
    from myTable
    where (colname like ''' + @flag + 'Flag'') = 1'
    
    EXECUTE sp_ExecuteSQL @SQL, N''
    

    还有更多,但我认为其中之一会让你继续前进。

    【讨论】:

    • +1 - 我个人会使用CASE 方法,因为我认为它是最简洁的,尽管我更可能使用简单案例而不是搜索案例,如您所示要匹配的东西是恒定的(即CASE @flag WHEN 'foo' THEN fooFlag WHEN 'bar' then barFlag END = 1
    • @Greg:当然,这可能是所有选项中性能最差的,因为您现在不仅将一列包装在函数中,而且还包装了所有列。如果每列都有不同的索引,那么这将成为表扫描。
    • +1 - 取决于查询的复杂性。尽管根据我的经验,即使查询开始时很简单,但最终(在维护等期间)总是很复杂,我希望我从动态 sql 开始。
    • 另外,如果您发现您的查询超过 4000 个字符,请使用 NVARCHAR(MAX),这将为您提供 2GB 大小 (sql 2008)
    • 关于使用动态 sql 的另一个注意事项:如果您尝试连接,两个变量的大小必须相同
    【解决方案4】:

    我要做的是CASE 开头的一些变量。示例:

    DECLARE
        @fooFlag int,
        @barFlag int,
        @bazFlag int,
        @quuxFlag int
    
    SET @fooFlag = CASE WHEN @flag = 'foo' THEN 1 ELSE NULL END
    SET @barFlag = CASE WHEN @flag = 'bar' THEN 1 ELSE NULL END
    SET @bazFlag = CASE WHEN @flag = 'baz' THEN 1 ELSE NULL END
    SET @quuxFlag = CASE WHEN @flag = 'quux' THEN 1 ELSE NULL END
    
    SELECT ID, name, description, ...
    FROM myTable
    WHERE (fooFlag >= ISNULL(@fooFlag, 0) AND fooFlag <= ISNULL(@fooFlag, 1))
    AND (barFlag >= ISNULL(@barFlag, 0) AND barFlag <= ISNULL(@barFlag, 1))
    AND (bazFlag >= ISNULL(@bazFlag, 0) AND bazFlag <= ISNULL(@bazFlag, 1))
    AND (quuxFlag >= ISNULL(@quuxFlag, 0) AND quuxFlag <= ISNULL(@quuxFlag, 1))
    

    这个查询的好处是,因为“标志”的可能值是有界的,所以您可以计算所有条件作为先决条件,而不是在其中包装列。这保证了对任何被索引的列的高性能索引查找,并且不需要编写任何动态 SQL。而且它比编写 4 个单独的查询要好,原因很明显。

    【讨论】:

    • 这并不能保证高性能的索引搜索,因为它每次都会重新评估 barFlag >= ISNULL(@barFlag, 0)。
    • @barFlag 只是一个标量变量,而不是表列。它是 sargable - 您可以将整个表达式扩展为 8 个单独的常量标志。试试看。
    • 抱歉,Aaron,我使用的是带有 IS NULL OR .. 表达式的查询 :) 评估数字表达式似乎没有这样做。对不起
    • 不,它没有。没有重新评估,ISNULL 在查询开始时评估一次。
    • @Russel,别担心,这东西也让我困惑了很长时间!
    【解决方案5】:

    您可以为每个可能的标志列设置一个参数,然后检查该参数是否为空或该列中的值是否等于该参数。然后为要检查的标志传入 1 并将其他标志保留为空。

    select id, name, description, ...
    from myTable
    where (@fooFlag is null or fooFlag = @fooFlag) AND
          (@barFlag is null or barFlag = @barFlag) AND
          ...
    

    不过,老实说,这似乎是构建动态 LINQ 查询并在进入 SQL2008 后跳过 SPROC 的理想选择。

    【讨论】:

    • 如果性能不是主要问题(即标志列未编入索引),我会这样做。
    • 不,我真的很想避免使用多个参数——如果我扩展到更多标志,则需要更多维护。使用单个参数,维护仅限于 SP;如果我添加参数,我也必须更改调用代码的结构,而不仅仅是在现有参数中传递新值。谢谢!
    • 哦,是的——忘了提,虽然我将数据库移动到 SQL2008,但我的应用程序是 ASP Classic,我不会有机会移动应用程序的这一部分很快到 .NET。所以 LINQ 出来了……
    【解决方案6】:

    “或者,我可以有 4 个查询副本,并在 CASE 语句中选择它们。”

    您不需要将整个查询复制 4 次,只需将所有可能性添加到您的单个查询副本的 where 子句中即可:

    select ID, name, description, ...
    from myTable
    where (@flag = 'foo' and fooFlag = 1) OR (@flag = 'bar' and barFlag = 1) OR ...
    

    【讨论】:

    • +1:这将是我的方法,尽管我仍会使用动态,因此每次查询运行时都不会拖拽所有 OR。
    • 是的,这是我的首选解决方案。我选择了加布里埃尔的答案,因为他给了我很多选择,包括。这个。
    猜你喜欢
    • 1970-01-01
    • 2019-03-11
    • 2012-12-06
    • 2023-03-27
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多