2009-02-03 4 views
11

От this question, a neat answer about using COALESCE для упрощения сложных деревьев логики. Я рассмотрел проблему короткого замыкания.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 

Если оценивали второй случай, когда Знаменатель = 0, я хотел бы ожидать чтобы увидеть ошибку, как:

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

Я нашел некоторые mentionsrelated в Oracle. И некоторые тесты с SQL Server. Похоже, короткое замыкание может сломаться при включении пользовательских функций.

Итак, должно ли это поведение гарантируется стандартом ANSI?

+1

[Высоко Связанные] (http://stackoverflow.com/q/7473045/73226) –

+0

Резюмируя DBA ответ, 'ВЫБРАТЬ COALESCE (1, (SELECT 1/0))' выполняется без ошибок и показывает, что это короткое замыкание. Интерпретатор рассматривает это как сокращенную инструкцию 'CASE'. –

ответ

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 

СОАЬЕЗСЕ осуществляется в соответствии с 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 

Вот некоторые доказательства того, что основная реализация с футляром будет выполнять подзапросы.

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. 
+0

Да, похоже, что COALESCE полностью эквивалентен CASE и, таким образом, имеет короткое замыкание, как показано на рисунке, поведение CASE не всегда короткое замыкание, которое действительно довольно неприятно. –

+0

COALESCE делает короткое замыкание правильно (даже с подзапросами) в 11g –

+0

Он делает ** не ** делает 2 сканирования таблицы, даже если в плане показано 2 сканирования. Это легко проверить с помощью «SET STATISTICS IO ON» или просто взглянуть на «количество исполнений» в свойствах плана выполнения. Там ** есть ** [проблема] (http://connect.microsoft.com/SQLServer/feedback/details/336002/unnecessarily-bad-performance-for-coalesce-subquery) с 'COALESCE', которая не возникает с 'ISNULL', хотя. –

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 после последнего случая!)?

+0

Это должно попасть в первый случай. За десятилетие SQL Server, и я никогда не считал COALESCE коротким, потому что он СМОТРЕТСЯ, как вызов функции. Очевидно, что CASE делает, и он швы, как COALESCE, определяется как тождественно для CASE. –

+0

мой плохой ... конечно, он попадает в первый случай. Теперь миссия моей жизни - найти случай, когда это не сработает :) – Learning

+0

@ Ученик, убедитесь, что вы смотрите на мой расширенный ответ, он исправляет некоторые вещи. –

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 
+0

См. Мой ответ, есть основная проблема с CASE, которая стекает до ISNULL и т. Д. –

+0

Да, CASE может выполнять подзапросы, но I0m не уверен в релевантности для вопроса OP. Я видел, что он используется как короткое замыкание, но мне это не нравится лично из-за сканирования таблиц или увеличения ввода-вывода (как вы показали) – gbn

Смежные вопросы