【问题标题】:Using of CASE in WHERE clause to evaluate empty parameters在 WHERE 子句中使用 CASE 评估空参数
【发布时间】:2023-04-09 22:22:01
【问题描述】:

有没有更好的方法来获取以下内容?

DECLARE @Desc VARCHAR(200) = ''

SELECT [id],
       [Desc],
       [Col1],
       [Col2]
FROM [dbo].[tbl]
WHERE [Desc] LIKE CASE
                WHEN @Desc LIKE ''
                     THEN [Desc]
                     ELSE '%'+ @Desc +'%'
                END

这允许在参数未定义 (@Desc='') 或返回值的子集 (@Desc='test') 时返回所有值。

【问题讨论】:

  • 如果你只传递一个空字符串或一个非空字符串(而不是 NULL),那么这不是做同样的事情:WHERE [Desc] LIKE '%' + @Desc + '%';?即使您确实传递了 NULL,WHERE [Desc] LIKE '%' + COALESCE(@Desc, '') + '%';
  • 相关,可能是duplicate。如果这是一个存储过程,并且Better 表示“更高性能”,那么最好使用动态 SQL(或自定义 SQL 的 ORM)并完全删除可选谓词,而是为分支运行不同的查询。原因?查询计划。
  • 旁白:在 where 子句中添加通配符 (%) 会阻止使用 @Desc 搜索以模式开头或结尾的字符串,例如'Mac%'。它也排除了使用索引查找,尽管索引 scan 仍然是可能的。
  • @HABO 一般来说,字符串末尾的通配符仍然允许使用索引。 'Mac%' 会起作用,但 '%Mac' 不会。此外,您可以使用reverse 值构建索引,以允许您查询'%Mac'。请参阅this question 进行一些讨论。

标签: sql sql-server tsql case sql-like


【解决方案1】:

使用 OR 运算符代替大小写

DECLARE @Desc VARCHAR(200) = ''

SELECT [id],
       [Desc],
       [Col1],
       [Col2]
FROM [dbo].[tbl]
WHERE 
    (
        ISNULL(@Desc,'')=''
    )
    OR
    (
        ISNULL(@Desc,'')<>''
        AND
        [Desc] LIKE '%'+ @Desc +'%'
    )

使用两种逻辑的执行计划差异

用例

使用或

【讨论】:

  • 您能否澄清这是一种更好的方式,还是“只是”一种替代的方式?
  • 这是更好的方法,因为在 Where 子句中使用 Case 语句不是一个好的编程策略
  • 为什么使用 case 表达式是一种糟糕的编程策略?为什么使用 nonSARGable 谓词更好?
  • @RToyo 不,没有新引入的 nonSARGable 谓词。在函数中包装变量不是问题。
  • @SeanLange 我已将执行计划的差异添加到答案中
【解决方案2】:

执行引擎最好在查询之前进行尽可能多的参数处理。

DECLARE @Desc VARCHAR(200) = '';
DECLARE @SelectAll bit;

SET @SelectAll = CASE WHEN @Desc = '' THEN 1 ELSE 0 END;
SET @Desc = CASE WHEN @Desc = '' THEN @Desc ELSE ('%' + @Desc + '%') END;


SELECT [id],
       [Desc],
       [Col1],
       [Col2]
FROM [dbo].[tbl]
WHERE 
    (@SelectAll = 1)
        OR
    (@SelectAll = 0 AND [Desc] LIKE @Desc);

如果您不介意代码重复,您可以更进一步,通过 IF / ELSE 执行两个单独的查询。

【讨论】:

    【解决方案3】:

    如果你使用 null 你会节省一些步骤

    declare @userId  int = null; 
    
    SELECT TOP 1000 [AuctionId]
          ,[UserId]
          ,[BiddingPrice]
          ,[DateTime]
      FROM [Test].[dbo].[Bid] 
      WHERE isnull(@userId, [UserId]) = [UserId];
    

    【讨论】:

    • OP 使用带有通配符的like。这样的问题可能更适合这个问题:WHERE [UserId] LIKE '%' + isnull(@userId, '') + '%'
    【解决方案4】:

    嗯,正如 Aaron Bertrand 评论的那样,您当前的查询可以简单地写成这样:

    DECLARE @Desc VARCHAR(200) = ''
    
    SELECT [id],
           [Desc],
           [Col1],
           [Col2]
    FROM [dbo].[tbl]
    WHERE [Desc] LIKE '%'+ @Desc +'%' 
    

    因为如果@Desc 包含一个空字符串,它会产生[Desc] LIKE '%%' - 所以所有[Desc] 不为空的记录无论如何都会被返回。

    如果@Desc 可以作为null 传递,则使用Coalescenull 转换为空字符串:

    ...WHERE [Desc] LIKE '%'+ COALESCE(@Desc, '') +'%' 
    

    请注意,在这两个问题中,Desc 列包含null 的记录将不会被返回。如果这是一个可为空的列,并且您还想返回它为 null@Desc 参数也为空或为空的记录,那么您应该使用 OR

    SELECT [id],
           [Desc],
           [Col1],
           [Col2]
    FROM [dbo].[tbl]
    WHERE [Desc] LIKE '%'+ @Desc +'%' 
    OR (COALESCE(@Desc, '') = '' AND [Desc] IS NULL)
    

    另外,请注意这仅仅是因为您使用了LIKE - 如果您尝试使用不同的运算符(例如=&lt;&gt;etc')来评估条件,您应该使用OR 语法与其他答案一样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-24
      • 2022-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-14
      相关资源
      最近更新 更多