【问题标题】:COALESCE - guaranteed to short-circuit?COALESCE - 保证短路?
【发布时间】:2010-10-05 02:14:20
【问题描述】:

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

如果它在分母 = 0 时评估第二种情况,我预计会看到如下错误:

Msg 8134, Level 16, State 1, Line 1
Divide by zero error encountered.

我在 Oracle 上找到了一些 mentions related。还有一些使用SQL Server 的测试。当您包含用户定义的函数时,看起来短路可能会发生故障。

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

【问题讨论】:

  • 总结 DBA 的答案,SELECT COALESCE(1, (SELECT 1/0)) 运行没有错误,并显示它短路。解释器将其视为缩短的 CASE 语句。

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


【解决方案1】:

我刚刚查看了链接的文章,可以确认 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 语句中的第一个 true 子句。

这里有一些适用于基于标量的函数的证明(我在 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.

【讨论】:

  • 是的,看起来 COALESCE 完全等同于 CASE,并且短路方式相同,但是,正如您所展示的,CASE 的行为并不总是短路,这真的很讨厌。
  • COALESCE 在 11g 中确实短路(即使是子查询)
  • 它确实执行 2 次表扫描,即使计划显示 2 次扫描。这很容易通过SET STATISTICS IO ON 进行验证,或者只需查看执行计划属性中的“执行次数”。 an issueCOALESCE 但不会出现在 ISNULL 中。
【解决方案2】:

在 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

【讨论】:

  • 看我的回答,CASE 存在一个潜在问题,它会流向 ISNULL 等......
  • 是的,CASE 可以进行子查询,但我不确定与 OP 问题的相关性。我已经看到它用作短路,但我个人不喜欢它,因为表扫描或 IO 增加(如您所演示的)
【解决方案3】:

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

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

【讨论】:

  • 这应该属于第一种情况。十多年的 SQL Server,我从来没有考虑过 COALESCE 会短路,因为它看起来像一个函数调用。显然 CASE 确实如此,并且它看起来像 COALESCE 被定义为与 CASE 功能相同。
  • 我的错……当然属于第一种情况。现在我的人生使命是找到一个不起作用的案例:)
  • @Learning,确保你看看我的扩展答案,它纠正了一些问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-03
  • 1970-01-01
  • 2012-03-05
  • 2013-10-18
  • 2011-02-11
  • 1970-01-01
相关资源
最近更新 更多