从 这个问题, 关于使用COALESCE的一个简洁的答案 简化复杂的逻辑树。我考虑过短路问题。
例如,在大多数语言的函数中,参数被完全评估,然后传递给函数。在C:
int f(float x, float y) {
return x;
}
f(a, a / b) ; // This will result in an error if b == 0
这似乎不是一个限制 COALESCE
SQL Server中的“函数”:
CREATE TABLE Fractions (
Numerator float
,Denominator float
)
INSERT INTO Fractions VALUES (1, 1)
INSERT INTO Fractions VALUES (1, 2)
INSERT INTO Fractions VALUES (1, 3)
INSERT INTO Fractions VALUES (1, 0)
INSERT INTO Fractions VALUES (2, 0)
INSERT INTO Fractions VALUES (3, 0)
SELECT Numerator
,Denominator
,COALESCE(
CASE WHEN Denominator = 0 THEN 0 ELSE NULL END,
CASE WHEN Numerator <> 0 THEN Numerator / Denominator ELSE NULL END,
0
) AS TestCalc
FROM Fractions
DROP TABLE Fractions
如果在Denominator = 0时正在评估第二种情况,我希望看到如下错误:
Msg 8134, Level 16, State 1, Line 1
Divide by zero error encountered.
我发现了一些 提到 有关 到Oracle。和一些测试 SQL Server。当您包含用户定义的函数时,看起来短路可能会中断。
那么,这种行为是否应该由ANSI标准保证?
我刚看了一下链接的文章,可以确认COALESCE和ISNULL的短路都会失败。
如果您涉及任何子查询,它似乎失败,但它适用于标量函数和硬编码值。
例如,
DECLARE @test INT
SET @test = 1
PRINT 'test2'
SET @test = COALESCE(@test, (SELECT COUNT(*) FROM sysobjects))
SELECT 'test2', @test
-- OUCH, a scan through sysobjects
COALESCE是根据实施的 ANSI标准。它只是CASE声明的简写。 ISNULL不是ANSI标准的一部分。第6.9节似乎没有明确要求短路,但它确实暗示了第一个真正的条款 when
声明应该退回。
这是一些适用于基于标量的函数的证明(我运行它 SQL Server 2005):
CREATE FUNCTION dbo.evil
(
)
RETURNS int
AS
BEGIN
-- Create an huge delay
declare @c int
select @c = count(*) from sysobjects a
join sysobjects b on 1=1
join sysobjects c on 1=1
join sysobjects d on 1=1
join sysobjects e on 1=1
join sysobjects f on 1=1
return @c / 0
END
go
select dbo.evil()
-- takes forever
select ISNULL(1, dbo.evil())
-- very fast
select COALESCE(1, dbo.evil())
-- very fast
这里有一些证明,CASE的底层实现将执行子查询。
DECLARE @test INT
SET @test = 1
select
case
when @test is not null then @test
when @test = 2 then (SELECT COUNT(*) FROM sysobjects)
when 1=0 then (SELECT COUNT(*) FROM sysobjects)
else (SELECT COUNT(*) FROM sysobjects)
end
-- OUCH, two table scans. If 1=0, it does not result in a table scan.
该 高效 保证MS SQL Server短路的方法是使用CASE。
对于成功的WHEN条款,不评估其他条款。
COALESCE可能有问题
在这种情况下,为什么在COALESCE / CASE结构中有这么多分支?
SELECT Numerator
,Denominator
,CASE
WHEN Denominator = 0 THEN 0 END,
ELSE Numerator / Denominator
END AS TestCalc
FROM Fractions
我也惊讶地发现答案有效!我不确定这种行为是否得到保证。 (但我找不到一个不起作用的例子!)
五年了 SQL,我仍然感到惊讶。
我也继续前进并做了一个改变:
INSERT INTO #Fractions VALUES (0, 0)
SELECT Numerator
,Denominator
,coalesce (
CASE WHEN Denominator = 0 THEN 0 ELSE NULL END,
CASE WHEN Numerator <> 0 THEN Numerator / Denominator ELSE NULL END)
AS TestCalc
FROM #Fractions
我得到的结果是:
Numerator Denominator TestCalc
1 1 1
1 2 0.5
1 3 0.3333333333333335
1 0 0
2 0 0
3 0 0
0 0 0
现在我更加困惑了!对于num = 0和den = 0的情况,我如何将testcalc作为0(特别是因为我在最后一个案例后删除了0)?