case — bedingte Ausdrücke


SQL ist eine deklarative Programmiersprache: sie erlaubt keine Steuerung des Programmablaufes wie es if in imperativen Programmen macht. Dennoch hat SQL etwas sehr ähnliches: den case-Ausdruck. Das case ein Ausdruck ist – anstatt einer Kontrollstruktur - bedeutet, dass case das Ergebnis von Formeln (Ausdrücken) abhängig von Bedingungen macht. Die Verwendung entspricht der des ternären Bedingungsoperator ?: in anderen Programmiersprachen.

Syntax

Die Syntax des case-Ausdruckes ist sehr flexible und kennt einige Abkürzungen. Das folgende Beispiel zeigt die Syntax der allgemeinsten Form – das sogenannte searched case. Alle anderen Formen können auf einen Gleichwertiges searched case zurückgeführt werden. Darüber hinaus funktioniert searched case in praktisch allen SQL-Datenbanken. Man könnte also sagen, searched case ist die einzige Syntax, die man sich unbedingt merken muss.

CASE WHEN <Bedingung> THEN <Ergebnis>
    [WHEN <Bedingung> THEN <Ergebnis>
     ...]
    [ELSE <Ergebnis>]
END

Dabei ist <Bedingung> eine Bedingung, wie man sie in der where-Klausel verwendet – zum Beispiel spaltenname IS NULL. Daher auch der Name searched case. <Ergebnis> steht für einen beliebigen SQL-Ausdruck: Das kann ein einfacher Ausdruck wie eine Konstante oder ein Spaltenname sein, oder aber ein komplexer Ausdruck mit Unterabfragen und – natürlich auch – verschachtelten case-Ausdrücken.

Ein case-Ausdruck kann mehrere when-then Paare enthalten. Dadurch kann man sogar ohne Verschachtelung mehrere Bedingungen nutzen – im Gegensatz zum ?:-Operator in anderen Programmiersprachen. Andererseits entsteht beim Verschachteln eine inhärente Rangordnung unter den Bedingungen. Die Bedingungen eines flachen case-Ausdruckes haben dagegen keine inhärente Rangordnung. Um diese Lücke zu schließen verwendet SQL die Reihenfolge der Nennung der Bedingungen0 im case-Ausdruck als Rangordnung. Einfach gesagt: case liefert das <Ergebnis> der der ersten wahren <Bedingung>.

Bevor der case-Ausdruck durch end beendet wird, ist noch ein optionaler else-Zweig erlaubt. Das <Ergebnis> des else-Zweiges kommt zur Anwendung, wenn keine <Bedingung> wahr ist1. Fehlt der else-Zweiges wird er als else null angenommen2.

Der SQL-Standard schreibt nicht vor, wie ein case-Ausdruck abgearbeitet wird – der Standard spezifiziert nur das Ergebnis3. Tatsächlich gibt es Produkte, die ein then-Zweig manchmal sogar vor dem entsprechenden when-Zweig ausgewertet wird (nochmal: then vor when). Letzten Endes liefern diese Produkte natürlich auch das richtige Ergebnis – unnötig ermittelte Werte werden einfach verworfen. Letztendlich schlägt hier die deklarative Natur von SQL zu: Die Datenbank – nicht der Entwickler – entscheidet, wie das Ergebnis ermittelt wird. Mehr dazu in Proprietäre Erweiterungen: Dokumentierter Evaluierungsablauf.

Tipp

Verwende keine nicht-deterministischen Funktionen oder Funktionen, die Daten ändern, in case-Ausdrücken. Der undefinierte Evaluierungsablauf könnte unvorhersehbare Nebenwirkungen haben.

Die simple Form

Damit ein Operand, der in allen when-Klausel vorkommt, nicht ständig wiederholt werden muss, bietet der SQL-Standard das sogenannte simple case an:

CASE <gemeinsamer Operand>
     WHEN <Ausdruck> THEN <Ergebnis>
    [WHEN <Ausdruck> THEN <Ergebnis>
     ...]
    [ELSE <Ergebnis>]
END

Dabei wird die <Bedingung> zweigeteilt: der gemeinsame Operand (z. B. ein Spaltenname) wird unmittelbar nach dem Schlüsselwort case angeführt. Die andere Seite des Vergleiches bleibt in den when-Klauseln. Der Vergleichsoperator Ist-gleich (=) ist implizit4.

Der SQL-Standard definiert simple case als eine Transformation in ein searched case. Daher bleiben die oben beschriebenen Regeln aufrecht.

Achtung

Mit simple case kann man nicht auf null prüfen, weil immer der Gleichheitsoperator (=) verwendet wird. Da null = null nicht wahr ist5, kommt beim Operandenwert null immer der else-Zweig zur Anwendung.

SQL bietet zwei Kurzformen von case für den Umgang mit null: coalesce und nullif. Beide lassen sich wie Funktionen benutzen – kommen also ohne die Schlüsselworte case, when, then, else und end aus.

Coalesce liefert den ersten nicht-null-Parameter oder null, wenn alle Parameter null sind. Die Anzahl der Parameter ist nicht begrenzt. Der Standard definiert coalesce durch Transformation in einen case-Ausdruck. Die folgenden beiden Ausdrücke sind daher völlig gleichwertig:

COALESCE(a, b)
CASE WHEN a IS NOT NULL THEN a
     ELSE b
END

Nullif braucht zwei Parameter und liefert grundsätzlich den Wert des ersten zurück. Nur wenn die Werte beider Parameter gleich sind (=), wird null geliefert. nullif ist ebenfalls durch Übersetzung auf case definiert und wird oft verwendet um „Division durch Null (0)“-Fehler zu vermeiden:

x / NULLIF(y, 0)
x / CASE WHEN y = 0 THEN null 
         ELSE y
    END

Kaum unterstützte Formen

Die bisher gezeigten Formen von case wurden alle mit fortgeschrittenem SQL-92 eingeführt und sind in de-facto allen SQL-Datenbanken verfügbar. SQL:2003 hat zwei weitere Kurzformen eingeführt, die beide das simple case erweitern. Beide sind optionale Funktionen und werden nur vereinzelt unterstützt.

Das sogenannte extended case akzeptiert einen Operator direkt nach when und hebt damit die Beschränkung von simple case auf, nur mit Ist-gleich (=) vergleichen zu können. Das folgende Beispiel verwendet den Weniger-als (<) Operator, um Werte in Intervalle einzuteilen. Das Beispiel verlässt sich dabei auf die Rangordnung der when-Klauseln: die erste wahre Bedingung gewinnt.

CASE x WHEN < 0   THEN '< 0'
       WHEN < 10  THEN '[0, 10['
       WHEN < 100 THEN '[10, 100['
       ELSE '>100'
END

Für in-Listen bietet SQL:2003 eine weitere Verkürzung: kommaseparierte Listen in der when-Klausel:

CASE <gemeinsamer Operand>
     WHEN <Ausdruck>[, <Ausdruck> ...] THEN <Ergebnis>
    [WHEN <Ausdruck>[, <Ausdruck> ...] THEN <Ergebnis>
     ...]
    [ELSE <Ergebnis>]
END

Ermittlung des Ergebnistyps

Der Datentyp des Ergebnisses eines case-Ausdruckes wird durch die Gesamtheit der <Ergebnis>-Ausdrücke6 bestimmt. Der SQL-Standard definiert explizite Regeln7, wie sich der Datentyp des Ergebnisses aus der Kombination artverwandter Datentypen ergibt – also zum Beispiel wenn eine then-Klausel vom Typ char(3) ist, eine andere aber vom Typ varchar(255). Diese Regeln entsprechen im Wesentlichen dem Hausverstand: Das Ergebnis des case-Ausdruckes ist der kleinste Typ, der alle möglichen Ergebniswerte darstellen kann8. Bei diesem Beispiel ist das Ergebnis vom Typ varchar(255).

Wie so oft nimmt null hier auch eine Sonderstellung ein: Da ein null-Literal keinen bekannten Datentyp hat, werden then null und else null-Klauseln bei der Ermittlung des Ergebnistyps nicht berücksichtigt9. Im Endeffekt passt sich null damit automatisch an den Ergebnistypen des cast-Ausdruckes an.

Gleitkommazahlen sind ebenfalls besonders: sobald ein <Ergebnis> vom Typ float, real oder double precision ist, legt der Standard lediglich fest, dass der case-Ausdruck einen Gleitkommatypen hat – welcher Typ genau ist der Implementierung überlassen10. Ebenso überlässt es der SQL-Standard den Herstellern zu entscheiden, ob und welche Typenkonvertierungen implizit stattfinden11. Dadurch erlauben manche Produkte die Mischung ungleichartiger Datentypen ohne explizitem cast.

Tipp

Vermeide es, Datentypen in den then-Zweigen zu Mischen. Wenn nötig, verwende cast und konvertiere explizit auf den benötigten Typ.

Anwendungsfälle

Die folgenden Artikel beschreiben häufige Anwendungen von case:

Kompatibilität

Der case-Ausdruck wurde mit SQL-92 im fortgeschrittenen Level eingeführt. SQL:1999 hat case als Funktion F261 in Core-SQL aufgenommen und damit verpflichtend gemacht. De-facto alle SQL-Produkten unterstützt diese Funktion heutzutage.

SQL:2003 hat die kaum unterstützten, optionalen Erweiterungen „Extended CASE expression“ (F262) und „Comma-separated predicates in simple CASE expression“ (F263) eingeführt.

Proprietäre Erweiterungen

Dokumentierter Evaluierungsablauf

Der SQL-Standard legt legt nicht fest, wie case-Ausdrücke auszuwerten sind. Insbesondere schreibt der Standard nicht vor, dass die Bedingungen in der Reihenfolge der Nennung zu prüfen sind. Solange sichergestellt ist, dass letztendlich das richtige Ergebnis geliefert wird, ist alles erlaubt.

Obwohl verschiedene Abläufe dasselbe Ergebnis liefern müssen, können sich die Nebenwirkungen, die verschiedene Abläufe haben, unterschieden. Wenn man zum Beispiel Funktionen aufruft, die Daten ändern, könnten diese auch aufgerufen werden, wenn es nicht unbedingt notwendig ist. Die Nebenwirkungen, die der Funktionsaufruf hat – Daten werden geändert –, wäre dennoch sichtbar. Ebenso kann das Auftreten oder Ausbleiben von Laufzeitfehlern vom Evaluierungsablauf abhängen: Wenn der Fehler in einem Teil des case-Ausdruckes auftritt, der nicht unbedingt evaluiert werden muss, könnte der Fehler auftreten, oder auch nicht. Zu guter Letzt ist natürlich auch die Ausführungsdauer eine Nebenwirkung, die vom Evaluierungsablauf abhängig ist.

Da es schon alleine aus Performancesicht sinnvoll ist, die when-Klauseln der Reihe nach auszuwerten und bei der ersten wahren Bedingung abzubrechen, ist genau dieser Evaluierungsablauf in der Dokumentation einiger Produkte festgeschrieben. Dennoch gibt es einen Fall (siehe Unten), in dem der Evaluierungsablauf oft durcheinander gerät. Mache Produkte weisen in der Dokumentation darauf hin (PostgreSQL), andere nicht (SQL Server). Letztendlich kann man sich also nicht einmal auf einen dokumentierten Ablauf verlassen.

Ein häufiger Grund für einen abweichenden Evaluierungsablauf ist folgender: Einige Produkte ziehen die Evaluierung konstanter Ausdrücke vor – z. B. in die prepare-Phase vor der eigentlichen Ausführung. Das ist vergleichbar mit einem Compiler, der einen Ausdruck (z. B. 1/3) bereits während der Übersetzung auflöst, anstatt Code zur Berechnung zur Laufzeit zu erzeugen. Wenn diese Optimierung einen Teil eines case-Ausdruckes betrifft, kommt der Evaluierungsablauf durcheinander.

Das folgende Beispiel kann daher einen „Division durch Null (0)“-Fehler auslösen, selbst wenn die Bedingung id = 0 nie wahr ist. Selbst wenn die demo-Tabelle komplett leer ist, kann der Fehler auftreten, wenn die Evaluierung des konstanten Ausdruckes 1/0 vorgezogen wird.

SELECT CASE WHEN id = 0 THEN 1/0
            ELSE 1
        END AS hit
  FROM demo

nvl, nvl2, ifnull, … (Oracle, MySQL, DB2 LUW)

Die Funktionen nvl (Oracle, DB2) und ifnull (MySQL) entsprechen im wesentlichen coalesce, nur dass sie auf zwei Argumente beschränkt sind.

decode (Oracle, MySQL)

Bei der Funktion decode wird deutlich, dass proprietäre Funktionen in verschiedenen Produkten verschiedenes bewirken können. Während decode in MySQL eine (überholte) kryptographische Funktion ist, ist decode in der Oracle-Datenbank eine proprietäre Kurzform für case.

if (MySQL)

Die MySQL-Funktion if ist eine proprietäre Kurzfrom eines searched case-Ausdruckes mit nur einem when-Zweig – ähnlich dem ternären Operator ?:.

Über den Autor

Foto von Markus Winand

Markus Winand lehrt effizientes SQL – inhouse und online. Er minimiert die Entwicklungszeit durch modernes SQL und optimiert die Laufzeit durch schlaue Indizierung – dazu hat er auch das Buch SQL Performance Explained veröffentlicht.

Fußnoten

  1. SQL:2016-2:§6.12, General Rule 2a.

  2. SQL:2016-2: §6.12, General Rule 2b.

  3. SQL:2016-2: §6.12, Syntax Rule 4.

  4. SQL:2011-1: §6.3.3.3.

  5. SQL:2016-2: §6.12, Syntax Rule 2fi. Siehe auch extended Form und comma-separated Form.

  6. In der dreiwertigen Logik von SQL ergibt null = null weder wahr noch falsch, sondern unbekannt. Damit ein than-Zweig zur Anwendung kommt, muss die entsprechenden when-Bedingung jedoch wahr sein – nicht falsch genügt nicht.

  7. Genauer: der bekannten Typen der <Ergebnis>-Ausdürcke (SQL:2016-2, §9.5 Syntax Rule 2). Damit bleibt das null-Literal unberücksichtigt. Siehe .

  8. SQL:2016-2: §9.5

  9. SQL:2016-2: §9.5, Syntax Rule 3aiii (für Zeichenketten); Syntax Rule 3c (für exakte Zahlen); SSyntax Rule 3e und 3f (für Temporale Typen)

  10. SQL:2016-2: §6.12, Syntax Rule 5 berücksichtigt nur <result expression>s für die Ermittlung des Ergebnistyps (nicht: <result>, welches null inkludiert).

    Daraus erklärt sich auch die Notwendigkeit, dass zumindest ein <result> eine <result expression> enthält (SQL:2016-2: § 6.12 Syntax Rule 3) – andernfalls könnte der Datentyp des cast-Ausdruckes nicht ermittelt werden (SQL:2016-2: §9.5 Syntax Rule 2). Das ist jedoch keine relevante Einschränkung, da eine case-Ausdruck der in jedem <result>null hat, ohnehin einfach durch null ersetzt werden könnte.

  11. SQL:2016-2: §9.5, Syntax Rule 3cii2.

  12. SQL:2016-2: §4.11

„modern SQL“ von Markus Winand ist unter einer Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License lizenziert.
Impressum | Kontakt | KEINE GEWÄHR | Handelsmarken | Datenschutz | CC-BY-NC-ND 3.0 Lizenz