英文:
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 => fundKey > 0 ? x.FundKey === fundKey : x.FundKey < 1000000)
I tried to write a query like this
DECLARE @fundKey INT = -1;
SELECT FundKey, FundName FROM dbo.Funds
WHERE CASE WHEN @fundKey > 0 THEN FundKey = @fundKey ELSE FundKey < 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
不是唯一的,并且存在显著的偏差),你可以动态构建命令,这样只有在@fundKey
为0
时才会扫描所有匹配的行。
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 > 0 THEN @fundKey END
OR FundKey < CASE WHEN @fundKey <= 0 THEN 1000000 END
Since the implicit ELSE NULL
can't match to =
or <
, 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 > 0 THEN @fundKey ELSE 0 END -- or 1?
AND CASE WHEN @fundKey > 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'SELECT FundKey, FundName FROM dbo.Funds'
+ CASE WHEN @fundKey > 0 THEN N' WHERE FundKey = @fundKey'
ELSE N' WHERE FundKey < 1000000';
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。应该使用明确的 AND
和 OR
逻辑。
对于你的查询,这意味着你实际上应该这样做:
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 > 0 AND FundKey = @fundKey)
OR (@fundKey <= 0 AND FundKey < 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 > 0 THEN @fundKey
WHEN @fundKey <= 0 AND FundKey < 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论