2009-02-03 13 views
11

Da this question, a neat answer about using COALESCE per semplificare alberi logici complessi. Ho considerato il problema del cortocircuito.COALESCE: garantito al cortocircuito?

Ad esempio, nelle funzioni nella maggior parte delle lingue, gli argomenti vengono valutati completamente e vengono quindi 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 

Ciò 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 secondo caso in cui denominatore = 0, I si aspetterebbe per vedere un errore come:

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

ho trovato alcuni mentionsrelated a Oracle. E alcuni test con SQL Server. Sembra che il cortocircuito potrebbe interrompersi quando si includono funzioni definite dall'utente.

Quindi, questo comportamento dovrebbe essere garantito dallo standard ANSI?

+1

[altamente correlati] (http://stackoverflow.com/q/7473045/73226) –

+0

Per riassumere la risposta DBA, 'SELEZIONARE COALESCE (1, (SELECT 1/0))' viene eseguita senza errori e mostra che cortocircuiti. L'interprete lo vede come un'istruzione 'CASE' abbreviata. –

risposta

8

Ho appena dato un'occhiata all'articolo collegato e posso confermare che il cortocircuito può fallire sia per COALESCE che per ISNULL.

Sembra non funzionare se si dispone di un'interrogazione secondaria, ma funziona correttamente per le funzioni scalari e i valori codificati.

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 è attuata secondo la ANSI standard. È semplicemente una scorciatoia per una dichiarazione CASE. ISNULL non fa parte dello standard ANSI. La sezione 6.9 non sembra richiedere esplicitamente un cortocircuito, ma implica che debba essere restituita la prima clausola vera nella dichiarazione when.

Ecco una 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'attuazione di fondo 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. 
+0

Sì, sembra che COALESCE sia totalmente equivalente a CASE, e cortocircuiti allo stesso modo, tuttavia, come si mostra, il comportamento di CASE non è sempre un cortocircuito, il che è davvero piuttosto sgradevole. –

+0

COALESCE esegue correttamente il cortocircuito (anche con sottoquery) in 11g –

+0

Fa ** non ** esegue 2 scansioni di tabelle anche se il piano mostra 2 scansioni. Questo è facile da verificare con 'SET STATISTICS IO ON 'o semplicemente guardare il" numero di esecuzioni "nelle proprietà del piano di esecuzione. Ci ** è ** [un problema] (http://connect.microsoft.com/SQLServer/feedback/details/336002/unnecessarily-bad-performance-for-coalesce-subquery) con 'COALESCE' che non si verifica con 'ISNULL' comunque. –

1

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

Cinque anni di SQL, e sono ancora sorpreso.

ho 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 ho ottenuto testcalc come 0 (soprattutto perché ho rimosso lo 0 dopo l'ultimo caso!)?

+0

Questo dovrebbe rientrare nel primo caso. Oltre un decennio di SQL Server, e non ho mai considerato COALESCE un cortocircuito, perché sembra una chiamata di funzione. Ovviamente CASE lo fa e sembra che COALESCE sia definito per funzionare in modo identico a CASE. –

+0

mio cattivo ... ovviamente cade nel primo caso. È ora la missione della mia vita trovare un caso in cui questo non funziona :) – Learning

+0

@Learning, assicurati di dare un'occhiata alla mia risposta espansa, corregge alcune cose. –

3

Il efficiente modo per garantire cortocircuito in MS SQL Server è quello di utilizzare CASE. Per la clausola WHEN di successo, nessun altro viene valutato.

COALESCE can have issues

In questo caso, perché hanno tanti rami nei costrutti COALESCE/caso?

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

Vedere la mia risposta, c'è un problema di fondo con CASE che scorre fino a ISNULL ecc ... –

+0

Sì, CASE può fare sottoquery ma I0m non è sicuro della pertinenza alla domanda dell'OP. L'ho visto usato come cortocircuito ma non mi piace personalmente a causa delle scansioni della tabella o dell'aumento di IO (come hai dimostrato) – gbn