COALESCE - 保证短路?

时间:2009-02-03 04:00:00

标签: sql sql-server tsql coalesce short-circuiting

this questiona neat answer about using COALESCE简化复杂的逻辑树。我考虑了短路问题。

例如,在大多数语言的函数中,参数被完全评估,然后传递给函数。在C:

int f(float x, float y) {
    return x;
}

f(a, a / b) ; // This will result in an error if b == 0

这似乎不是SQL Server中COALESCE“函数”的限制:

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发现了一些mentions related。还有SQL Server的一些测试。当您包含用户定义的函数时,看起来短路可能会崩溃。

那么,这种行为是否应该由ANSI标准保证?

3 个答案:

答案 0 :(得分:8)

我刚看了一下链接的文章,可以确认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 standard实施。它只是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.

答案 1 :(得分:3)

保证MS SQL Server短路的高效方法是使用CASE。 对于成功的WHEN条款,不评估其他条款。

COALESCE can have issues

在这种情况下,为什么在COALESCE / CASE结构中有这么多分支?

SELECT Numerator
    ,Denominator
    ,CASE
        WHEN Denominator = 0 THEN 0 END,
        ELSE Numerator / Denominator
     END AS TestCalc
FROM Fractions

答案 2 :(得分:1)

我也惊讶地发现答案有效!我不确定这种行为是否得到保证。 (但我找不到一个不起作用的例子!)

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)?