不能在SQL的WHERE子句内部使用布尔表达式作为CASE表达式的一部分。

huangapple go评论54阅读模式
英文:

Why can't I use a boolean expression in a SQL CASE expression inside a WHERE clause?

问题

  • If @fundKey is greater than 0, look for funds with a key matching @fundKey.
  • Otherwise, look for funds with a key less than 1000000.
DECLARE @fundKey INT = -1;
SELECT FundKey, FundName FROM dbo.Funds
WHERE CASE WHEN @fundKey > 0 THEN FundKey = @fundKey ELSE FundKey < 1000000 END

但是似乎语法无效。为什么我不能在 WHERE 子句中使用布尔表达式的结果?有没有一种不那么冗长的方法来编写这个逻辑?

英文:

I want to write a query with the following logic:

  • If @fundKey is greater than 0, look for funds with a key matching @fundKey.
  • Otherwise, look for funds with a key less than 1000000.

If I were to write this logic in JavaScript it would be something like this:

funds.filter(x =&gt; fundKey &gt; 0 ? x.FundKey === fundKey : x.FundKey &lt; 1000000)

I tried to write a query like this

DECLARE @fundKey INT = -1;
SELECT FundKey, FundName FROM dbo.Funds
WHERE CASE WHEN @fundKey &gt; 0 THEN FundKey = @fundKey ELSE FundKey &lt; 1000000 END

but it seems that the syntax is invalid. Why can't I use the result of a boolean expression in a WHERE clause? Is there a different way to write this logic without being too verbose?

答案1

得分: 3

在T-SQL中,CASE是一个返回值的表达式。它不用于流程控制。因此,你必须这样写:

WHERE FundKey = CASE WHEN @fundKey > 0 THEN @fundKey END
   OR FundKey < CASE WHEN @fundKey <= 0 THEN 1000000 END

由于隐式的ELSE NULL无法与=<匹配,因此对于任何给定行,只有一个条件可以为真。

虽然不太直观,稍微冗长,但从性能方面来说可能更好的写法是:

WHERE FundKey BETWEEN 
         CASE WHEN @fundKey > 0 THEN @fundKey ELSE 0 END -- 或 1?
     AND CASE WHEN @fundKey > 0 THEN @fundKey ELSE 999999 END

如果性能仍然不理想(通常在大规模情况下可能会出现这种情况,特别是如果FundKey不是唯一的,并且存在显著的偏差),你可以动态构建命令,这样只有在@fundKey0时才会扫描所有匹配的行。

DECLARE @sql nvarchar(max) = N'SELECT FundKey, FundName FROM dbo.Funds'
  + CASE WHEN @fundKey > 0 THEN N' WHERE FundKey = @fundKey'
                           ELSE N' WHERE FundKey < 1000000';

EXEC sys.sp_executesql @sql;

你也可以使用OPTION (RECOMPILE)来实现类似的效果,而无需使用动态SQL,但如果直接点查找很常见,或者可能会添加其他谓词,或者查询会频繁运行,动态SQL将提供长期稳定的少量计划,而不需要不断重新编译。

有关CASE的更多信息,请参阅:

有关处理不断变化的谓词的动态SQL的更多信息(我称之为“厨房水槽”):

英文:

In T-SQL, CASE is an expression that returns a value. It is not for control of flow. Therefore you must say:

WHERE FundKey = CASE WHEN @fundKey &gt;  0 THEN @fundKey END
   OR FundKey &lt; CASE WHEN @fundKey &lt;= 0 THEN 1000000  END

Since the implicit ELSE NULL can't match to = or &lt;, only one condition can be true for any given row.

Less intuitive, and slightly wordier, but potentially better performance-wise:

WHERE FundKey BETWEEN 
         CASE WHEN @fundKey &gt; 0 THEN @fundKey ELSE 0      END -- or 1?
     AND CASE WHEN @fundKey &gt; 0 THEN @fundKey ELSE 999999 END

If you find performance still suffers (which is potentially the case at scale in general or specifically if FundKey isn't unique and you have significant skew), you can build the command dynamically, so that you're only performing a scan of all those matching rows when @fundKey is 0.

DECLARE @sql nvarchar(max) = N&#39;SELECT FundKey, FundName FROM dbo.Funds&#39;
  + CASE WHEN @fundKey &gt; 0 THEN N&#39; WHERE FundKey = @fundKey&#39;
                           ELSE N&#39; WHERE FundKey &lt; 1000000&#39;;

EXEC sys.sp_executesql;

You can achieve similar with OPTION (RECOMPILE) and no dynamic SQL, but if the direct point lookup is common, or there are potentially other predicates that will be added in, or the query will be run a lot, dynamic SQL will provide long-term stability of small number of plans without constant recompiles.

For a lot more on CASE, see:

For more on dynamic SQL for handling shifting predicates (I call this "the kitchen sink"):

答案2

得分: 1

因为 CASE 表达式返回一个标量值而不是布尔结果。不过,在 WHERE 子句中,你不应该在列上使用 CASE 表达式;它不会是 SARGable。应该使用明确的 ANDOR 逻辑。

对于你的查询,这意味着你实际上应该这样做:

DECLARE @fundKey INT = -1;

SELECT FundKey,
       FundName
FROM dbo.Funds
WHERE (@fundKey > 0 AND FundKey = @fundKey)
   OR (@fundKey <= 0 AND FundKey < 1000000);

很可能,你还希望添加一个 OPTION (RECOMPILE),因为这两个布尔子句的执行计划可能会有很大的不同。

英文:

Because a CASE expression returns a scalar value not a boolean result. Though, you shouldn't be use a CASE expression on a column in the WHERE anyway; it won't be SARGable. Use explicit AND and OR logic.

For your query, this means you should actually be doing:

DECLARE @fundKey INT = -1;

SELECT FundKey,
       FundName
FROM dbo.Funds
WHERE (@fundKey &gt; 0 AND FundKey = @fundKey)
   OR (@fundKey &lt;= 0 AND FundKey &lt; 1000000);

Likely, as well, you'll want to add an OPTION (RECOMPILE) as the plans could be quite different for the 2 boolean clauses.

答案3

得分: 1

这是在WHERE子句中应用逻辑的另一种方式:

DECLARE @fundKey INT = -1;
SELECT FundKey, FundName FROM dbo.Funds
WHERE FundKey = (CASE 
                  WHEN @fundKey > 0 THEN @fundKey
                  WHEN @fundKey <= 0 AND FundKey < 1000000 THEN FundKey
                  ELSE 0 
                END)

由于CASE表达式在WHERE子句中不具备SARGable性,因此此查询的性能可能不如使用显式的AND和OR逻辑好。这只是演示如何使用CASE表达式的方式。

英文:

This is another way to apply your logic in WHERE clause

DECLARE @fundKey INT = -1;
SELECT FundKey, FundName FROM dbo.Funds
WHERE FundKey = (CASE 
                  WHEN @fundKey &gt; 0 THEN @fundKey
                  WHEN @fundKey &lt;= 0 AND FundKey &lt; 1000000 THEN FundKey
                  ELSE 0 
                END)

Since CASE expressions are not sargable in WHERE clause, so the performance of this query could be not good as using explicit AND and OR logic. It's just a demonstration of how we could use CASE expression.

huangapple
  • 本文由 发表于 2023年3月3日 23:26:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75628964.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定