it-swarm.com.de

Liest SQL Server die gesamte COALESCE-Funktion, auch wenn das erste Argument nicht NULL ist?

Ich verwende eine T-SQL COALESCE -Funktion, bei der das erste Argument in etwa 95% der Fälle nicht null ist. Wenn das erste Argument NULL ist, ist das zweite Argument ein ziemlich langwieriger Prozess:

SELECT COALESCE(c.FirstName
                ,(SELECT TOP 1 b.FirstName
                  FROM TableA a 
                  JOIN TableB b ON .....)
                )

Wenn zum Beispiel c.FirstName = 'John', Würde SQL Server die Unterabfrage trotzdem ausführen?

Ich weiß mit der VB.NET IIF() Funktion, wenn das zweite Argument True ist, liest der Code immer noch das dritte Argument (obwohl es nicht verwendet wird).

102
Curt

Nein . Hier ist ein einfacher Test:

SELECT COALESCE(1, (SELECT 1/0)) -- runs fine
SELECT COALESCE(NULL, (SELECT 1/0)) -- throws error

Wenn die zweite Bedingung ausgewertet wird, wird eine Ausnahme für die Division durch Null ausgelöst.

Gemäß MSDN-Dokumentation hängt dies damit zusammen, wie COALESCE vom Interpreter angezeigt wird - es ist nur eine einfache Möglichkeit, eine CASE -Anweisung zu schreiben.

CASE ist als eine der wenigen Funktionen in SQL Server bekannt, die (meistens) zuverlässig kurzschließen.

Es gibt einige Ausnahmen beim Vergleich mit skalaren Variablen und Aggregationen, wie von Aaron Bertrand in einer anderen Antwort hier gezeigt (und dies würde sowohl für CASE als auch für COALESCE gelten):

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

erzeugt eine Division durch Null Fehler.

Dies sollte als Fehler betrachtet werden und in der Regel wird COALESCE von links nach rechts analysiert.

96
JNK

Wie wäre es mit diesem - wie mir von Itzik Ben-Gan berichtet wurde, der erzählt von Jaime Lafargue ?

DECLARE @i INT = 1;
SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END;

Ergebnis:

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

Es gibt natürlich triviale Problemumgehungen, aber der Punkt ist immer noch, dass CASE nicht immer Auswertung von links nach rechts/Kurzschluss garantiert. Ich habe den Fehler hier gemeldet und er wurde als "beabsichtigt" geschlossen. Paul White reichte anschließend dieses Connect-Element ein und es wurde als behoben geschlossen. Nicht weil es per se behoben wurde, sondern weil sie Books Online mit einer genaueren Beschreibung des Szenarios aktualisiert haben, in dem Aggregate die Auswertungsreihenfolge eines CASE Ausdrucks ändern können. Ich habe vor kurzem mehr darüber hier gebloggt .

[~ # ~] edit [~ # ~] nur ein Nachtrag, während ich zustimme, dass dies Edge-Fälle sind, die die meisten der Zeit Sie können sich auf die Auswertung von links nach rechts und das Kurzschließen verlassen, und dass dies Fehler sind, die der Dokumentation widersprechen und wahrscheinlich irgendwann behoben werden (dies ist nicht definitiv - siehe das nachfolgende Gespräch unter Bart Duncans Blogpost um zu sehen warum), muss ich nicht zustimmen, wenn Leute sagen, dass etwas immer wahr ist, auch wenn es einen einzigen Edge-Fall gibt, der es widerlegt. Wenn Itzik und andere solche einsamen Fehler finden können, besteht zumindest im Bereich der Möglichkeit die Möglichkeit, dass es auch andere Fehler gibt. Und da wir den Rest der Anfrage des OP nicht kennen, können wir nicht mit Sicherheit sagen, dass er sich auf diesen Kurzschluss verlassen wird, aber am Ende von ihm gebissen wird. Für mich ist die sicherere Antwort:

Während Sie normalerweise sich auf CASE verlassen können, um von links nach rechts und den Kurzschluss zu bewerten, wie in der Dokumentation beschrieben, ist es nicht richtig zu sagen, dass Sie dies immer tun können damit. Auf dieser Seite werden zwei Fälle demonstriert, in denen dies nicht der Fall ist und in keiner öffentlich verfügbaren Version von SQL Server ein Fehler behoben wurde.

[~ # ~] edit [~ # ~] hier ist ein anderer Fall (ich muss damit aufhören) wo a Der Ausdruck CASE wird nicht in der erwarteten Reihenfolge ausgewertet, obwohl keine Aggregate beteiligt sind.

75
Aaron Bertrand

Die Dokumentation macht ziemlich deutlich, dass die Absicht darin besteht, dass CASE kurzgeschlossen wird. Als Aaron erwähnt wurden mehrere Fälle gemeldet, in denen gezeigt wurde, dass dies nicht immer der Fall ist. Bisher wurden die meisten davon als Fehler erkannt und behoben.

Es gibt andere Probleme mit CASE (und daher COALESCE), bei denen Nebenwirkungen oder Unterabfragen verwendet werden. Erwägen:

SELECT COALESCE((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);
SELECT ISNULL((SELECT CASE WHEN Rand() <= 0.5 THEN 999 END), 999);

Das Formular COALESCE gibt häufig null zurück, wie in ein Fehlerbericht von Hugo Kornelis beschrieben.

Aufgrund der nachgewiesenen Probleme mit Optimierertransformationen und der Verfolgung allgemeiner Ausdrücke kann nicht garantiert werden, dass CASE unter allen Umständen kurzgeschlossen wird.

Ich denke, Sie können ziemlich sicher sein, dass CASE im Allgemeinen kurzgeschlossen wird (insbesondere wenn eine einigermaßen qualifizierte Person den Ausführungsplan inspiziert und dieser Ausführungsplan mit einem Planleitfaden oder Hinweisen "durchgesetzt" wird), aber wenn Wenn Sie eine absolute Garantie benötigen, müssen Sie SQL schreiben, das den Ausdruck überhaupt nicht enthält.

38
Paul White 9

Ich bin auf einen anderen Fall gestoßen, in dem CASE/COALESCE nicht kurzschließen. Die folgende TVF löst eine PK-Verletzung aus, wenn 1 als Parameter.

CREATE FUNCTION F (@P INT)
RETURNS @T TABLE (
  C INT PRIMARY KEY)
AS
  BEGIN
      INSERT INTO @T
      VALUES      (1),
                  (@P)

      RETURN
  END

Wenn wie folgt aufgerufen

DECLARE @Number INT = 1

SELECT COALESCE(@Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = @Number), 
                         (SELECT TOP (1)  C
                          FROM   F(@Number))) 

Oder als

DECLARE @Number INT = 1

SELECT CASE
         WHEN @Number = 1 THEN @Number
         ELSE (SELECT TOP (1) C
               FROM   F(@Number))
       END 

Beide geben das Ergebnis

Verletzung der PRIMARY KEY-Einschränkung 'PK__F__3BD019A800551192'. Es kann kein doppelter Schlüssel in das Objekt 'dbo. @ T' eingefügt werden. Der doppelte Schlüsselwert ist (1).

dies zeigt, dass SELECT (oder zumindest die Tabellenvariablenpopulation) weiterhin ausgeführt wird und einen Fehler auslöst, obwohl dieser Zweig der Anweisung niemals erreicht werden sollte. Der Plan für die Version COALESCE ist unten aufgeführt.

(Plan

Dieses Umschreiben der Abfrage scheint das Problem zu vermeiden

SELECT COALESCE(Number, (SELECT number
                          FROM   master..spt_values
                          WHERE  type = 'P'
                                 AND number = Number), 
                         (SELECT TOP (1)  C
                          FROM   F(Number))) 
FROM (VALUES(1)) V(Number)   

Welches gibt Plan

(Plan2

20
Martin Smith

Ein anderes Beispiel

CREATE TABLE T1 (C INT PRIMARY KEY)

CREATE TABLE T2 (C INT PRIMARY KEY)

INSERT INTO T1 
OUTPUT inserted.* INTO T2
VALUES (1),(2),(3);

Die Abfrage

SET STATISTICS IO ON;

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (LOOP JOIN)

Zeigt keine Lesevorgänge gegen T2 überhaupt.

Die Suche nach T2 befindet sich unter einem Pass-Through-Prädikat und der Operator wird niemals ausgeführt. Aber

SELECT T1.C,
       COALESCE(T1.C , CASE WHEN EXISTS (SELECT * FROM T2 WHERE T2.C = T1.C)  THEN -1 END)
FROM T1
OPTION (MERGE JOIN)

Tut zeigt, dass T2 ist gelesen. Obwohl kein Wert von T2 wird jemals tatsächlich benötigt.

Das ist natürlich nicht wirklich überraschend, aber ich dachte, es lohnt sich, es dem Gegenbeispiel-Repository hinzuzufügen, schon allein deshalb, weil es die Frage aufwirft, was Kurzschluss in einer satzbasierten deklarativen Sprache überhaupt bedeutet.

9
Martin Smith

Ich wollte nur eine Strategie erwähnen, die Sie vielleicht nicht in Betracht gezogen haben. Es mag hier kein Match sein, aber es ist manchmal nützlich. Überprüfen Sie, ob diese Änderung zu einer besseren Leistung führt:

SELECT COALESCE(c.FirstName
            ,(SELECT TOP 1 b.FirstName
              FROM TableA a 
              JOIN TableB b ON .....
              WHERE C.FirstName IS NULL) -- this is the changed part
            )

Eine andere Möglichkeit besteht darin (im Grunde genommen gleichwertig, ermöglicht Ihnen jedoch den Zugriff auf weitere Spalten aus der anderen Abfrage, falls erforderlich):

SELECT COALESCE(c.FirstName, x.FirstName)
FROM
   TableC c
   OUTER APPLY (
      SELECT TOP 1 b.FirstName
      FROM
         TableA a 
         JOIN TableB b ON ...
      WHERE
         c.FirstName IS NULL -- the important part
   ) x

Grundsätzlich handelt es sich hierbei um eine Technik zum "harten" Verbinden von Tabellen, die jedoch die Bedingung enthält, wann Zeilen überhaupt verbunden werden sollen. Nach meiner Erfahrung hat dies manchmal wirklich zu Ausführungsplänen beigetragen.

7
ErikE

Der tatsächliche Standard besagt, dass alle WHEN-Klauseln (sowie die ELSE-Klausel) analysiert werden müssen, um den Datentyp des Ausdrucks als Ganzes zu bestimmen. Ich müsste wirklich einige meiner alten Notizen herausholen, um festzustellen, wie ein Fehler behandelt wird. Aber auf Anhieb verwendet 1/0 ganze Zahlen, daher würde ich davon ausgehen, dass dies ein Fehler ist. Es ist ein Fehler mit dem Datentyp Integer. Wenn die Koaleszenzliste nur Nullen enthält, ist es etwas schwieriger, den Datentyp zu bestimmen, und das ist ein weiteres Problem.

3
Joe Celko

Nein, würde es nicht. Es würde nur ausgeführt, wenn c.FirstNameNULL ist.

Sie sollten es jedoch selbst versuchen. Experiment. Sie sagten, Ihre Unterabfrage sei langwierig. Benchmark. Ziehen Sie hierzu Ihre eigenen Schlussfolgerungen.

Die Antwort von @Aaron auf die ausgeführte Unterabfrage ist vollständiger.

Ich denke jedoch immer noch, dass Sie Ihre Abfrage überarbeiten und LEFT JOIN Verwenden sollten. In den meisten Fällen können Unterabfragen entfernt werden, indem Sie Ihre Abfrage so überarbeiten, dass LEFT JOIN Verwendet wird.

Das Problem bei der Verwendung von Unterabfragen besteht darin, dass Ihre Gesamtanweisung langsamer ausgeführt wird, da die Unterabfrage für jede Zeile in der Ergebnismenge der Hauptabfrage ausgeführt wird.

2