2009-02-03 10 views
11

Von this question, a neat answer about using COALESCE, um komplexe logische Bäume zu vereinfachen. Ich habe über das Problem des Kurzschlusses nachgedacht.COALESCE - garantiert kurzgeschlossen?

Zum Beispiel werden in Funktionen in den meisten Sprachen Argumente vollständig ausgewertet und dann in die Funktion übernommen. In C:

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

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

Das ist nicht eine Beschränkung der COALESCE "Funktion" in SQL Server zu sein scheint:

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 

Wenn es den zweiten Fall der Bewertung wurde, wenn Denominator = 0, würde ich erwarten, einen Fehler wie zu sehen:

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

fand ich einige mentionsrelated zu Oracle. Und einige Tests mit SQL Server. Es sieht so aus, als könnte der Kurzschluss zusammenbrechen, wenn Sie benutzerdefinierte Funktionen hinzufügen.

Soll dieses Verhalten also vom ANSI-Standard garantiert werden?

+1

[Highly Verwandte] (http://stackoverflow.com/q/7473045/73226) –

+0

die DBA Antwort Zusammengefasst SELECT COALESCE (1, (SELECT 1/0)) 'läuft ohne Fehler und zeigt, dass es kurzschließt. Der Dolmetscher sieht es als eine verkürzte "CASE" -Anweisung. –

Antwort

8

Ich habe mir gerade den verlinkten Artikel angesehen und kann bestätigen, dass ein Kurzschluss sowohl für COALESCE als auch für ISNULL fehlschlagen kann.

Es scheint zu scheitern, wenn Sie eine Unterabfrage beteiligt sind, aber es funktioniert gut für Skalarfunktionen und fest codierte Werte.

Zum Beispiel

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 wird entsprechend der ANSI standard umgesetzt. Es ist einfach eine Abkürzung für eine CASE-Anweisung. ISNULL ist nicht Teil des ANSI-Standards. Abschnitt 6.9 scheint keinen expliziten Kurzschluss zu erfordern, aber es bedeutet, dass der erste wahre Abschnitt in der when-Anweisung zurückgegeben werden sollte.

Hier sind einige Beweise, die Werke für skalare basierten Funktionen (Ich lief es auf SQL Server 2005) sind:

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 

hier etwas Beweis dafür, dass die zugrunde liegende Implementierung mit CASE Unterabfragen ausführen wird.

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

Ja, es sieht aus wie COALESCE ist völlig gleichbedeutend mit CASE und Kurzschlüsse auf die gleiche Weise jedoch, wie Sie zeigen, das Verhalten von CASE nicht immer Kurzschluss, der ist wirklich ziemlich böse. –

+0

COALESCE schließt in 11g –

+0

einen Kurzschluss korrekt (sogar mit Unterabfragen) Es macht ** nicht ** 2 Tabellen Scans, obwohl der Plan 2 Scans zeigt. Dies ist mit 'SET STATISTICS IO ON' einfach zu überprüfen oder Sie sehen sich einfach die" Anzahl der Ausführungen "in den Eigenschaften des Ausführungsplans an. There ** ist ** [ein Problem] (http://connect.microsoft.com/SQLServer/feedback/details/336002/uncessessary-bad-performance-for-coalesce-subquery) mit 'COALESCE', die nicht mit auftritt 'ISNULL' allerdings. –

1

Ich war auch überrascht zu sehen, dass die Antwort funktioniert! Ich bin mir nicht sicher, ob dieses Verhalten garantiert ist. (Aber ich konnte kein Beispiel finden, das nicht funktioniert!)

Fünf Jahre SQL, und ich bin immer noch überrascht.

Ich ging auch voran und tat eine weitere Änderung:

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 

Das Ergebnis, das ich bekam, war:

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 

Jetzt bin ich noch mehr verwirrt! Für den Fall, wenn num = 0 und den = 0, wie habe ich testcalc als 0 bekommen (zumal ich die 0 nach dem letzten Fall entfernt habe)?

+0

Das sollte in den ersten Fall fallen. Über ein Jahrzehnt SQL Server, und ich dachte nie, COALESCE würde kurzschließen, weil es wie ein Funktionsaufruf aussieht. Offensichtlich CASE, und es scheint wie COALESCE definiert ist, um CASE identisch zu funktionieren. –

+0

meine schlechte ... natürlich fällt es in den ersten Fall. Es ist jetzt meine Lebensaufgabe, einen Fall zu finden, in dem das nicht funktioniert :) – Learning

+0

@Learning, stelle sicher, dass du dir meine erweiterte Antwort ansiehst, sie korrigiert einige Dinge. –

3

Die effiziente Art und Weise Kurzschluss in MS SQL Server zu garantieren CASE zu verwenden. Für die Erfolg WHEN-Klausel werden keine anderen ausgewertet.

COALESCE can have issues

In diesem Fall, warum haben so viele Filialen in den COALESCE/CASE-Konstrukte?

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

Siehe meine Antwort, es gibt ein zugrunde liegendes Problem mit CASE, das zu ISNULL etc geht. –

+0

Ja, CASE kann Unterabfragen durchführen, aber ich bin mir nicht sicher, ob die Frage des OPs relevant ist. Ich habe gesehen, dass es als Kurzschluss verwendet wurde, aber ich mag es nicht persönlich wegen der Tabellenscans oder Erhöhung der IO (wie du gezeigt hast) – gbn