在 Where 子句中使用 case 构建动态查询

Posted

技术标签:

【中文标题】在 Where 子句中使用 case 构建动态查询【英文标题】:Building Dynamic Query Using Case in Where Clause 【发布时间】:2017-09-24 02:46:06 【问题描述】:

我有一个存储过程,我想使用 Case 语句实现以下查询,但我不知道该怎么做。 这里提供了我想要的伪代码:

declare @PI_X decimal(18);
declare @PI_y decimal (18);

SELECT F1, F2,F3
FROM TABLE T 
WHERE 
 CASE 
 WHEN @PI_X IS NULL THEN @PI_Y = T.Y
 WHEN @PI_Y IS NULL THEN @PI_X = T.X

似乎对 条件 使用 case 语句是不正确的,它适用于 values

注意:

我想在 DB2 和 SQL 服务器中运行这个查询,但是数据库供应商对我来说并不重要,使用 sql 动态查询(OR ) where 子句对性能有影响。我不想要它。我真的很想知道如何在 where 子句中使用 case 来实现这样的逻辑。

你能帮我解决这个问题吗? 任何帮助和建议将不胜感激。

【问题讨论】:

【参考方案1】:

CASE 表达式的结果是一个,而不是一个表达式。您不能使用CASE 表达式来决定将运行哪些代码。您只能使用它来选择将与您的代码一起使用的值。

在这种情况下,您可以像这样实现您的目标:

declare @PI_X decimal(18);
declare @PI_y decimal (18);

SELECT F1, F2,F3
FROM TABLE T 
WHERE 1 = 
 CASE 
 WHEN @PI_X IS NULL AND @PI_Y = T.Y THEN 1
 WHEN @PI_X IS NOT NULL AND @PI_Y IS NULL AND @PI_X = T.X THEN 1 
 ELSE 0 END 

你也可以这样试试:

declare @PI_X decimal(18);
declare @PI_y decimal (18);

SELECT F1, F2,F3
FROM TABLE T 
WHERE coalesce(@PI_X, T.X) = T.X AND coalesce(@PI_y, T.Y) = T.Y

尽管如果@PI_y 可能不是NULL@PI_x 具有值,则第二个选项可能会产生意外结果。如果您可以保证两个变量中的一个或另一个将有一个值,但不能同时有一个值,那么您还可以简化第一个选项以删除表达式中多余的 @PI_X IS NOT NULL AND 部分。

【讨论】:

谢谢 :),我对你的回答很感兴趣,但我需要测试一下 您对此有何看法:WHERE T.X = CASE WHEN PI_X IS NOT NULL THEN PI_X ELSE T.X END ADN T.Y = CASE WHEN PI_Y IS NOT NULL THEN PI_Y ELSE T.Y END ------- -------------------------------------- 我认为这个解决方案只有一个缺点,因为最后我们将有一个无用的条件 T.y= T.y 或 T.x =T.x,我怀疑这是否会影响性能。您对这种方法有什么想法?【参考方案2】:

我不确定为什么您的问题同时被标记为 SQL Server 和 DB2...但我假设 SQL Server...

declare @PI_X decimal(18);
declare @PI_y decimal (18);

SELECT 
    T.F1, 
    T.F2,
    T.F3
FROM 
    TABLE T 
WHERE 
    (@PI_X = T.X OR @PI_X IS NULL)
    AND ((@PI_y = T.Y OR @PI_y IS NULL)
OPTION (RECOMPILE); -- Prevent's the forced scan operation cause by the use of "optional" parameters.

【讨论】:

我想在 db2 和 sql server 中运行这个查询,但是数据库供应商真的不重要,我知道你的解决方案但是使用 OR 会导致完整的扫描表,我不知道如何在 DB2 中防止它 其实供应商很重要。不同的 RDBMS 有不同的 SQL 语法实现。在这一点上,跨平台代码可移植性的想法非常可笑,考虑到您很幸运能够在同一产品的不同发行版本之间移植代码。上面的代码将允许使用 OPTION(RECOMPILE) 在 SQL Server 中进行查找操作。唯一的其他选择是使用动态 sql,IMO 不会保证像这样简单的事情。至于 DB2... 我自己不使用它,所以我不知道它如何处理可选参数。 您对此有何看法:WHERE T.X = CASE WHEN PI_X IS NOT NULL THEN PI_X ELSE T.X END ADN T.Y = CASE WHEN PI_Y IS NOT NULL THEN PI_Y ELSE T.Y END ------- -------------------------------------- 我认为这个解决方案只有一个缺点,因为最后我们将有一个无用的条件 T.y= T.y 或 T.x =T.x 我怀疑这是否会影响性能? 我认为你最终会得到一个非 SARGEable 查询。【参考方案3】:

您可以尝试放置可读的 if 条件,但会增加代码行数。

if(@PI_X IS NULL)
begin
select 
....
where 
T.Y=@PI_Y
end

else
begin
select 
....
where 
T.X = @PI_X
end

【讨论】:

这正是直接的解决方案,但如果需要在功能中进行任何更改,您应该记住要更改的选择查询代码的两部分。【参考方案4】:

除了在WHERE 子句中使用OR,您还可以构建2 个单独的查询并使用IF...ELSE... 来决定使用哪一个;类似于 Coder1991 建议的内容。或者您可以使用UNION ALL 构造来避免IF 并消除任何分支。

SELECT F1, F2,F3
 FROM TABLE T 
WHERE @PI_X IS NULL 
  AND @PI_Y = T.Y

UNION ALL

SELECT F1, F2,F3
  FROM TABLE T 
 WHERE @PI_Y IS NULL 
   AND @PI_X = T.X

【讨论】:

感谢您的新方法,它很聪明,但是在这种情况下,我认为 Union ALL 方法与 IF-Else 方法相比没有优势,您需要重复 Select F1,F2,F3 部分他们两个的查询,如果我必须这样做,我个人更喜欢使用 IF-Else 。 您对此有何看法: WHERE T.X = CASE WHEN PI_X IS NOT NULL THEN PI_X ELSE T.X END ADN T.Y = CASE WHEN PI_Y IS NOT NULL THEN PI_Y ELSE T.Y END ------- --------------------------------------------------------我认为这个解决方案只有一个缺点,因为最后我们会有一个无用的条件 T.y= T.y 或 T.x =T.x 我怀疑这是否会影响性能。你对这种方法有什么想法 唯一确定的方法是测试它。查询优化器有时会将“复杂的代码”转化为出色的性能,但有时由于某种原因它“没有得到它”,然后你就被卡住了。只需一次运行这两个查询并比较它们的查询计划和它们到达那里所用的时间。请记住,有时成本高的查询仍然是最快的查询(阅读:成本!=时间)。

以上是关于在 Where 子句中使用 case 构建动态查询的主要内容,如果未能解决你的问题,请参考以下文章

从搜索表单动态构建 WHERE 子句时如何防止 SQL 注入?

有没有比在开头使用 1=1 更好的方法来动态构建 SQL WHERE 子句?

WHERE 子句中的 CASE

Linq to Entities 中的动态 where 子句 (OR)

在 where 子句的 Case 语句中定义一个变量

如何在 SQL Server 的 Where 子句中使用 case 语句