Answers:
都能跟得上。这是一个简单的测试:
SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error
如果对第二个条件求值,则会抛出一个除以零的异常。
根据MSDN文档,这与COALESCE
解释器的查看方式有关-这只是编写CASE
语句的一种简便方法。
CASE
众所周知,它是SQL Server中(主要)可靠地短路的仅有函数之一。
与标量变量和集合进行比较时,有一些例外,如Aaron Bertrand在此处的另一个答案所示(这适用于CASE
和COALESCE
):
DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;
将产生除以零的误差。
应该将其视为错误,并且通常COALESCE
会从左到右进行解析。
SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 1 END), 1);
-重复多次。您NULL
有时会得到。与再试一次ISNULL
-你永远不会NULL
...
由Jaime Lafargue告诉我的Itzik Ben-Gan向我报告的那个怎么样?
DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;
结果:
Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.
当然,有很简单的解决方法,但重点仍然是CASE
不能始终保证从左到右的评估/短路。我在这里报告了该错误,并将其关闭为“设计使然”。保罗·怀特(Paul White)随后提交了此Connect项目,并且已按固定关闭。不是因为它本身是固定的,而是因为他们用更准确的描述更新了联机丛书,在这种情况下,聚合可以更改CASE
表达式的评估顺序。我最近在这里写了更多有关此的博客。
编辑只是一个附录,尽管我同意这些是极端情况,但大多数时候您可以依靠从左到右的评估和短路,并且这些是与文档相矛盾的错误,并且最终可能会得到修复(这是不确定的-请参阅Bart Duncan的博客文章中的后续对话,以了解原因),当人们说某事总是对的,即使有一个极端的情况不能证明这一点,我也不得不不同意。如果Itzik和其他人可以找到这样的单独错误,那么至少在可能的范围内,还存在其他错误。而且由于我们不了解OP的其余查询,因此不能确定地说他会依靠这种短路,但最终会被其咬伤。所以对我来说,更安全的答案是:
如文档中所述,虽然通常可以依靠它CASE
来评估从左到右和短路,但是说总是可以这样做并不太准确。在此页面上有两种已证明的情况,这是不正确的,并且在任何公共可用的SQL Server版本中均未修复任何错误。
编辑 这里是另一种情况(我需要停止这样做),其中CASE
表达不你所期望的顺序计算,即使没有聚集的参与。
我对此的看法是,该文档清楚地表明,目的是使CASE发生短路。正如亚伦(Aaron)所提到的,在许多情况下(ha!),这并不总是正确的。
到目前为止,所有这些都已被确认为错误并已修复-尽管不一定在您今天可以购买和修补的SQL Server版本中存在(不断折叠的错误尚未纳入累积更新AFAIK)。最新的潜在错误(最初由Itzik Ben-Gan报告)尚待调查(或者Aaron或我不久将其添加到Connect中)。
与原始问题有关,CASE(还有COALESCE)还有其他问题,其中使用了副作用函数或子查询。考虑:
SELECT COALESCE((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN RAND() <= 0.5 THEN 999 END), 999);
COALESCE表单通常返回NULL,有关更多详细信息,请访问https://connect.microsoft.com/SQLServer/feedback/details/546437/coalesce-subquery-1-may-return-null
优化器转换和公共表达跟踪所表现出的问题意味着不可能保证CASE在所有情况下都会短路。我可以设想一些情况,甚至可能无法通过检查公共表演计划的输出来预测行为,尽管今天我对此没有任何评论。
总而言之,我认为您可以肯定CASE总体上会发生短路(尤其是如果一个熟练的技术人员检查了执行计划,并且执行计划被计划指南或提示“强制执行了”),但是如果您需要绝对保证,您必须编写完全不包含表达式的SQL。
我想这不是非常令人满意的状况。
我遇到了另一种情况,其中CASE
/ COALESCE
不要短路。如果1
作为参数传递,则以下TVF将引发PK违规。
CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
C INT PRIMARY KEY)
AS
BEGIN
INSERT INTO @T
VALUES (1),
(@P)
RETURN
END
如果如下调用
DECLARE @Number INT = 1
SELECT COALESCE(@Number, (SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number = @Number),
(SELECT TOP (1) C
FROM F(@Number)))
或作为
DECLARE @Number INT = 1
SELECT CASE
WHEN @Number = 1 THEN @Number
ELSE (SELECT TOP (1) C
FROM F(@Number))
END
两者都给出结果
违反主键约束'PK__F__3BD019A800551192'。无法在对象“ dbo。@ T”中插入重复密钥。重复键值为(1)。
表示SELECT
仍将执行(或至少是表变量填充)并且即使从不到达该语句的该分支也会引发错误。COALESCE
版本计划如下。
查询的这种重写似乎可以避免该问题
SELECT COALESCE(Number, (SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number = Number),
(SELECT TOP (1) C
FROM F(Number)))
FROM (VALUES(1)) V(Number)
给出计划
另一个例子
CREATE TABLE T1 (C INT PRIMARY KEY)
CREATE TABLE T2 (C INT PRIMARY KEY)
INSERT INTO T1
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);
查询
SET STATISTICS IO ON;
SELECT T1.C,
COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C) THEN -1 END)
FROM T1
OPTION (LOOP JOIN)
完全不显示任何读取T2
。
寻找T2
是在通过谓词之下,并且运算符从不执行。但
SELECT T1.C,
COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C) THEN -1 END)
FROM T1
OPTION (MERGE JOIN)
是否表明T2
被读取。即使T2
实际上并不需要任何价值。
当然,这并不令人感到意外,但是我认为值得将其添加到反例存储库中,因为仅仅是因为它提出了短路甚至在基于集合的声明性语言中意味着什么的问题。
我只是想提一个您可能没有考虑过的策略。这里可能不匹配,但有时确实派上用场。查看此修改是否为您带来更好的性能:
SELECT COALESCE(c.FirstName
,(SELECT TOP 1 b.FirstName
FROM TableA a
JOIN TableB b ON .....
WHERE C.FirstName IS NULL) -- this is the changed part
)
这样做的另一种方法是(基本上是等效的,但是如果需要的话,允许您从其他查询访问更多列):
SELECT COALESCE(c.FirstName, x.FirstName)
FROM
TableC c
OUTER APPLY (
SELECT TOP 1 b.FirstName
FROM
TableA a
JOIN TableB b ON ...
WHERE
c.FirstName IS NULL -- the important part
) x
基本上,这是一种“硬”联接表的技术,但包括何时应联接任何行的条件。以我的经验,这有时确实有助于执行计划。
不,不会。它只会在c.FirstName
is 时运行NULL
。
但是,您应该自己尝试。实验。您说您的子查询很长。基准测试。对此得出自己的结论。
正在运行的子查询上的@Aaron答案更加完整。
但是,我仍然认为您应该重新编写查询并使用LEFT JOIN
。在大多数情况下,可以通过将查询修改为使用来删除子查询LEFT JOIN
。
使用子查询的问题在于,由于为主查询的结果集中的每一行都运行了子查询,因此整体语句的运行速度会变慢。
CASE
始终评估从左到右并总是短路)。