Domanda

questa domanda , una risposta accurata sull'utilizzo COALESCE semplificare alberi logici complessi. Ho considerato il problema del corto circuito.

Per esempio, nelle funzioni nella maggior parte delle lingue, gli argomenti vengono valutati attentamente e sono poi passati alla funzione. In C:

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

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

Questo non sembra essere una limitazione della "funzione" COALESCE in 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

Se fosse valutando il secondo caso in cui Denominatore = 0, mi aspetto di vedere un errore del tipo:

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

Ho trovato menziona = "http://forums.oracle.com/forums/thread.jspa?threadID=685976" rel = "nofollow noreferrer"> relative a Oracle. E alcuni test con SQL Server. Sembra che il corto circuito potrebbe abbattere quando si include funzioni definite dall'utente.

Quindi, è questo comportamento dovrebbe essere garantita dallo standard ANSI?

È stato utile?

Soluzione

Ho appena dato un'occhiata al articolo collegato e posso confermare corto circuito può fare a meno sia per COALESCE e ISNULL.

E sembra venire a mancare se si dispone di qualsiasi sub-query coinvolto, ma funziona bene per le funzioni scalari e valori hard coded.

Ad esempio,

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 è implementato in base alla ANSI . E 'semplicemente una scorciatoia per una dichiarazione CASE. ISNULL non è parte dello standard ANSI. Sezione 6.9 non sembra richiedere corto circuito in modo esplicito, ma non implica che il primo vero clausola when dichiarazione deve essere restituito.

Un po 'la prova che è lavori per funzioni scalari base (mi sono imbattuto su 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

Ecco una prova che l'implementazione sottostante con CASO eseguirà query sub.

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.

Altri suggerimenti

efficace modo per garantire corto circuito in MS SQL Server è quello di utilizzare CASE. Per il successo clausola WHEN, non vengono valutati altri.

COALESCE può avere problemi di

In questo esempio, perché hanno tanti rami della COALESCE / CASE costruisce?

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

Sono stato anche sorpreso di vedere che le opere di risposta! Non sono sicuro che questo comportamento è garantito. (Ma non sono stato in grado di trovare un esempio che non funziona!)

Cinque anni di SQL , e sono ancora sorpreso.

Sono anche andato avanti e ha fatto un altro cambiamento:

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

Il risultato ho ottenuto è stato:

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

Ora sono ancora più confuso! Per il caso in cui num = 0 e den = 0, come sono arrivato testcalc come 0 (soprattutto perché ho tolto il 0 dopo l'ultimo caso!)?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top