COALESCE - garantito per corto circuito?
-
21-08-2019 - |
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?
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!)?