Einführung in SQL: Nützliche Erweiterungen

Aus Wikibooks

Wechseln zu: Navigation, Suche


In diesem Kapitel werden verschiedene Erweiterungen des SELECT-Befehls genauer behandelt.

Die Beispiele beziehen sich auch hier auf den Anfangsbestand der Beispieldatenbank; auf die Ausgabe der selektierten Datensätze verzichte ich wiederum weitgehend. Bitte probieren Sie alle Beispiele aus und nehmen Sie verschiedene Änderungen vor, um die Auswirkungen zu erkennen.

Inhaltsverzeichnis

[Bearbeiten] DISTINCT – keine doppelten Einträge

Wenn Sie den DISTINCT-Parameter bei einem SELECT-Befehl angeben, erhalten Sie nur eindeutige Ergebnisse:

Liste der Fahrzeuge, für die Schadensfälle aufgetreten sind
Crystal Clear app terminal.png SQL-Quelltext:

SELECT DISTINCT Fahrzeug_ID
  FROM Zuordnung_SF_FZ;

Crystal Clear app kscreensaver.png SQL-Ausgabe:  

 Fahrzeug_ID
       3
       4
       5
       6
       7

Bitte beachten Sie: Als "eindeutig" gilt immer die gesamte Zeile, also alle Spalten zusammen. Die folgende Abfrage liefert alle Datensätze; Fahrzeuge mit mehreren Schadensfällen stehen auch mehrfach in der Liste.

Crystal Clear action button cancel.png Nur theoretisch DISTINCT, praktisch nicht
Crystal Clear app terminal.png SQL-Quelltext:

SELECT DISTINCT Fahrzeug_ID, ID
  FROM Zuordnung_SF_FZ;

Als Gegenstück zu DISTINCT gibt es den ALL-Parameter, der ausdrücklich alle Datensätze abfragt. Dies ist der Standardwert, er kann also auch weggelassen werden und wird äußerst selten benutzt:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT ALL Fahrzeug_ID
  FROM Zuordnung_SF_FZ

[Bearbeiten] Beschränkung auf eine Anzahl Zeilen

Häufig will man nicht sofort das gesamte Ergebnis sehen, sondern nur einen Teil der Zeilen.

Vor allem im Netzwerk kostet es seine Zeit, eine größere Menge von Datensätzen zu übertragen. Es ist deshalb oft praktisch, zunächst einen Teil des Ergebnisses zu holen und anzuzeigen. Während der Anwender sich mit diesem Teilergebnis beschäftigt, wird "im Hintergrund" der nächste Abschnitt geholt usw.

Der SQL-Standard hat dafür (noch) kein Verfahren festgelegt. Jedes DBMS bietet eine eigene Lösung an; abweichend vom üblichen Vorgehen in diesem Buch zeige ich mehrere Lösungen.

Anstelle konstanter Werte (ohne Klammern) kann in allen folgenden Fällen auch ein Ausdruck (in Klammern) angegeben werden.


[Bearbeiten] Firebird: FIRST SKIP oder ROWS

Firebird bietet gleich zwei Lösungen an.

 SELECT [DISTINCT]
   [ FIRST <value1> ]
   [  SKIP <value2> ]
   <select list> FROM ... /* usw. */

Mit dem FIRST-Parameter wird angegeben, wie viele Zeilen am Anfang angezeigt werden sollen. Mit dem SKIP-Parameter wird angegeben, wie viele Zeilen davor übersprungen werden sollen. Beide Parameter können einzeln oder zusammen benutzt werden; sie folgen direkt als erste Klausel nach DISTINCT, noch vor der Spaltenliste. Einige Beispiele:

Nur FIRST zeigt die ersten Zeilen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
 FIRST 10
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

Nur SKIP überspringt die ersten Zeilen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
  SKIP 10
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

FIRST zeigt die ersten 10 Zeilen an, aber wegen SKIP werden vorher 5 Zeilen übersprungen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
 FIRST 10 SKIP 5
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

Mit einem Ausdruck kann dafür gesorgt werden, dass etwa das erste Viertel der Datensätze abgerufen wird:

Der Wert für FIRST wird aus der Anzahl der Datensätze berechnet
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
 FIRST ( (SELECT count(*) FROM Mitarbeiter) / 4 )
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

Die zweite Firebird-Variante benutzt direkt Zeilennummern:

 SELECT ... FROM ... WHERE ...
  ORDER BY ...
   ROWS <value1> [ TO <value2> ]

Mit den ROWS-Parametern wird festgelegt, dass (nur) eine bestimmte Anzahl von Zeilen angezeigt werden sollen, die durch die Zeilennummern gekennzeichnet sind.

  • Wenn nur ROWS benutzt wird, bezeichnet <value1> die Gesamtzahl der angezeigten Zeilen.
  • Wenn ROWS zusammen mit TO benutzt wird, ist <value1> die erste Zeilennummer und <value2> die letzte Zeilennummer.

Einige Beispiele:

Ausgabe der Zeilen 10 bis 20 (also insgesamt 11 Zeilen)
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name
  ROWS 10 TO 20;

Der erste Datensatz gemäß Sortierung
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name
  ROWS 1;

Der letzte Datensatz gemäß Sortierung
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name DESC
  ROWS 1;

Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird, ist oft sehr nützlich.

Bei einer Interbase-Datenbank sind auch prozentuale Angaben möglich; das ist bei Firebird entfallen.


[Bearbeiten] Microsoft SQL: TOP

Der MS-SQL Server benutzt diese Syntax:

 SELECT [DISTINCT]
   TOP ( <value> ) [PERCENT] [WITH TIES]
   <select list> FROM ... /* usw. */

Mit den TOP-Parametern wird festgelegt, dass (nur) eine bestimmte Anzahl von Zeilen angezeigt werden sollen, die durch die Zeilennummern gekennzeichnet sind. Diese Parameter folgen direkt als erste Klausel nach DISTINCT, noch vor der Spaltenliste.

  • <value> bezeichnet die Gesamtzahl der angezeigten Zeilen. Es wird empfohlen, die Klammern immer zu setzen.
  • Wenn TOP zusammen mit PERCENT benutzt wird, handelt es sich dabei um die prozentuale Angabe der Zeilenzahl.
  • WITH TIES muss zusammen mit ORDER BY benutzt werden und liefert dann zusätzliche doppelte Zeilen, wenn der letzte Wert nach der Reihenfolge gleich ist den Werten in danach folgenden Zeilen.

Einige Beispiele:

Nur TOP zeigt die ersten Zeilen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
   TOP 10
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

TOP + PERCENT zeigt z.B. das erste Viertel an
Crystal Clear app terminal.png SQL-Quelltext:

SELECT
   TOP 25 PERCENT
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name;

Der letzte Datensatz gemäß Sortierung
Crystal Clear app terminal.png SQL-Quelltext:

SELECT 
   TOP 1
       ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name DESC;

Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird, ist oft sehr nützlich.


[Bearbeiten] MySql: LIMIT

Dieses DBMS benutzt den LIMIT-Parameter. Dieser Parameter folgt nach ORDER BY, wobei die Sortierung nicht angegeben werden muss.

 SELECT ... FROM ... WHERE ...
  ORDER BY ...
  LIMIT <value1> OFFSET <value2>

Dabei wird mit <value1> angegeben, wie viele Zeilen am Anfang angezeigt werden sollen. Mit <value2> kann nach dem Begriff OFFSET außerdem angegeben werden, wie viele Zeilen davor übersprungen werden sollen.

Es werden die ersten 10 Zeilen angezeigt
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name
 LIMIT 10;

Es werden die ersten 10 Zeilen angezeigt, aber vorher werden 5 Zeilen übersprungen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name
 LIMIT 10 OFFSET 5;

Der letzte Datensatz gemäß Sortierung
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Name, Vorname, Abteilung_ID AS Abt
  FROM Mitarbeiter
 ORDER BY Name DESC
 LIMIT 1;

Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird, ist oft sehr nützlich.

MySql bietet noch eine andere Schreibweise für diesen Parameter ohne das Wort OFFSET:

 SELECT ... FROM ... WHERE
  ORDER BY ...
  LIMIT [ <value2>, ] <value1>

Bei dieser Variante wird genauso mit <value1> angegeben, wie viele Zeilen am Anfang angezeigt werden sollen. Mit <value2> kann außerdem angegeben werden, wie viele Zeilen davor übersprungen werden sollen; dieser Wert wird jedoch zuerst angegeben und durch ein Komma von der gewünschten Zeilenzahl getrennt.

Die Bedeutung dieser Variante ist mit der ersten Variante identisch, sodass ich auf Beispiele verzichte. Es ist wohl Geschmackssache, welche Version "eingängiger" ist.


[Bearbeiten] Oracle: ROWNUM

Bei dem DBMS Oracle gibt es bei jedem SELECT-Ergebnis eine implizite Spalte Rownum. Man kann diese Spalte mit ausgeben lassen. Solange man kein ORDER BY angibt, ist die Reihenfolge der ausgegebenen Sätze nicht festgelegt. Es kann durchaus sein, dass dieselbe Abfrage an einem anderen Tag eine andere Nummerierung der Sätze hervorbringt. Das kann z.B. daran liegen, dass jemand die Datensätze in der Zwischenzeit reorganisiert hat.

Rownum als implizite Spalte der Ergebnismenge
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name, rownum 
  FROM Mitarbeiter;

Crystal Clear app kscreensaver.png SQL-Ausgabe: %nbsp;

 NAME   ROWNUM
 Müller      1
 Schneider   2
 Meyer       3
 Schmitz     4   /* usw. */

Wenn man diese Spalte Rownum nicht angibt, dann wird sie auch nicht ausgegeben. Der Vorteil dieser Spalte ist, dass man sie auch bei WHERE verwenden kann:

Es werden nur die ersten 2 Zeilen angezeigt
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name
  FROM Mitarbeiter 
 WHERE Rownum <= 2;

Crystal Clear app kscreensaver.png SQL-Ausgabe: %nbsp;

 NAME   ROWNUM
 Müller      1
 Schneider   2

Folgende Formulierung funktioniert allerdings nicht, wenn man nur den 10. Satz ausgeben will:

Crystal Clear action button cancel.png Nur den 10. Satz anzeigen - so geht es nicht
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name
  FROM Mitarbeiter; 
 WHERE Rownum = 10;

Das liegt daran, dass der Zähler Rownum nur die Zeilen zählt, die auch wirklich ausgegeben werden. Wenn die Tabelle 500 Sätze hat, dann wird bei jedem Satz geprüft, ob Rownum bereits den Wert 10 erreicht hat. Das ist jedoch nie der Fall, da der Zähler immer den Wert 0 behält.

Da muss man schon das DBMS zwingen, die Ergebnismenge mit der Rownum zwischenzuspeichern. Dann geht es:

Crystal Clear action apply.png Nur den 10. Satz anzeigen - so klappt es
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name
  FROM ( SELECT Name, rownum R
         FROM Mitarbeiter )
 WHERE R = 10;

Welcher Satz dabei ausgegeben wird, ist jedoch dem Zufall überlassen.

Wenn man die Ergebnismenge sortiert ausgeben will und dabei nur die ersten 6 Sätze ausgeben haben will, dann funktioniert die folgende Formulierung nicht:

Crystal Clear action button cancel.png Die alphabetisch ersten 6 Namen ausgeben - so geht es nicht
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name
  FROM Mitarbeiter; 
 WHERE Rownum <= 6
 ORDER BY Name;

Man wird feststellen, dass zwar nur 6 Sätze ausgegeben werden, dass das aber keinesfalls immer die alphabetisch ersten 6 Namen sind. Das liegt daran, dass zunächst WHERE ausgewertet wird und danach erst sortiert wird. Es werden also – wie bei den vorangegangenen Beispielen beschrieben – beliebige 6 Sätze gelesen, und nur diese 6 Sätze werden sortiert.

Um diese Aufgabe richtig zu lösen, muss die Datenbank schon etwas mehr tun. Sie muss nämlich alle vorhandenen Sätze sortieren und danach die ersten 6 Sätze ausgeben. Bei dieser Formulierung klappt es:

Crystal Clear action apply.png Die alphabetisch ersten 6 Namen ausgeben - so klappt es
Crystal Clear app terminal.png SQL-Quelltext:

SELECT Name
  FROM ( SELECT Name
           FROM Mitarbeiter 
          ORDER BY Name )
 WHERE Rownum <= 6;

[Bearbeiten] Mehrere Abfragen zusammenfassen

[Bearbeiten] UNION – Vereinigung

Mit der UNION-Klausel werden mehrere Abfragen verknüpft und als einheitliche Ergebnismenge geliefert. Dieses Verfahren ist Standard und steht in jedem DBMS zur Verfügung.

Dies ist vor allem in zwei Situationen sinnvoll:

  • Die Daten stammen aus verschiedenen Tabellen mit ähnlicher Struktur und sollen gemeinsam angezeigt werden.
  • Die Daten stammen aus derselben Tabelle; die Auswahlbedingungen sind so komplex, dass eine einzelne Abfrage nicht möglich, nicht sinnvoll oder zu unübersichtlich wäre.

Die Syntax einer solchen Verknüpfung sieht aus wie folgt, wobei auch mehr als zwei Abfragen verknüpft werden können:

 SELECT <Spaltenliste1> 
   FROM <Tabellenliste1> 
  WHERE <Bedingungen1>
        UNION [ DISTINCT | ALL ]
 SELECT <Spaltenliste2> 
   FROM <Tabellenliste2> 
  WHERE <Bedingungen2>

Bei den einzelnen Abfragen können grundsätzlich alle Klauseln benutzt werden. Bitte beachten Sie folgende Bedingungen:

  • Alle Einzelabfragen müssen in der Anzahl und Reihenfolge der Spalten übereinstimmen. Die Datentypen müssen je nach DBMS und Version genau gleich sein oder zumindest ähnlich, sodass sie automatisch konvertiert werden können.
  • Die Spaltennamen werden aus der ersten Spaltenliste übernommen, ggf. unter Berücksichtigung von Alias-Namen.
  • Grundsätzlich werden doppelte Zeilen aus den Einzelabfragen unterdrückt, d.h. DISTINCT ist Standard und kann weggelassen werden. Wenn doppelte Zeilen angezeigt werden sollen, ist ALL anzugeben.
  • Die Benutzung von Klammern sowie ORDER BY für das Gesamtergebnis wird je nach DBMS unterschiedlich geregelt.
Aufgabe
Das folgende Beispiel ist eine einfache Zusammenfassung aller Fahrzeuge aus den Tabellen Fahrzeuge und Dienstwagen:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Kennzeichen, Farbe
  FROM Dienstwagen
UNION
SELECT ID, Kennzeichen, Farbe
  FROM Fahrzeug;

Im folgenden Beispiel werden als Spalte Var Konstanten eingetragen, die die Herkunft der Daten angeben, und es werden verschiedene Auswahlbedingungen benutzt.

Crystal Clear app terminal.png SQL-Quelltext:

SELECT 'D' AS Var, ID, Kennzeichen, Farbe
  FROM Dienstwagen
 WHERE Fahrzeugtyp_ID <= 3
UNION
SELECT 'F', ID, Kennzeichen, Farbe
  FROM Fahrzeug
 WHERE Kennzeichen LIKE 'B%';

Wenn die Ergebnismenge sortiert werden soll, werden je nach DBMS unterschiedliche Schreibweisen benötigt.

MySql: benutzt Klammern um die Einzelabfragen herum, ORDER BY zusätzlich angeben
Crystal Clear app terminal.png SQL-Quelltext:

( SELECT 'D' AS Var, ID, Kennzeichen, Farbe
    FROM Dienstwagen
   WHERE Fahrzeugtyp_ID <= 3 )
UNION
( SELECT 'F', ID, Kennzeichen, Farbe
    FROM Fahrzeug
   WHERE Fahrzeugtyp_ID <= 3 )
ORDER BY Kennzeichen;

Firebird: benutzt eine Hauptabfrage mit ORDER BY, die UNION-Verknüpfung als Unterabfrage
Crystal Clear app terminal.png SQL-Quelltext:

SELECT * FROM
( SELECT 'D' AS Var, ID, Kennzeichen, Farbe
    FROM Dienstwagen
   WHERE Fahrzeugtyp_ID <= 3
  UNION
  SELECT 'F', ID, Kennzeichen, Farbe
    FROM Fahrzeug
   WHERE Fahrzeugtyp_ID <= 3 )
ORDER BY Kennzeichen;

Mit der folgenden Abfrage werden verschiedene Listen von Mitarbeitern zusammengefasst: zum einen diejenigen, die im September Geburtstag haben, zum anderen die Leiter mit einer Mobil-Rufnummer:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT Personalnummer, Name, Vorname, Geburtsdatum, Ist_Leiter AS Leiter, Mobil
  FROM Mitarbeiter
 WHERE EXTRACT(month FROM Geburtsdatum) = 9
UNION
SELECT Personalnummer, Name, Vorname, Geburtsdatum, Ist_Leiter, Mobil
  FROM Mitarbeiter
 WHERE Ist_Leiter = 'J' AND Mobil <> '';

Offensichtlich kann diese Abfrage auch ohne UNION durch WHERE <bedingung1> OR ( <bedingung2> AND <bedingung3> ) erreicht werden. Sie können sich aber sicherlich noch viel kompliziertere Situationen vorstellen, vor allem wenn die WHERE-Bedingungen auf Informationen in unterschiedlichen weiteren Tabellen zugreifen müssen.


[Bearbeiten] Andere Varianten

Unter den gleichen Voraussetzungen wie UNION gibt es teilweise auch andere Wege, um Einzelabfragen zusammenzufassen. Diese stehen aber nur vereinzelt zur Verfügung; ich beschränke mich deshalb auf Hinweise.

Mit INTERSECT (MS-SQL) wird der Durchschnitt der Teilabfragen zurückgegeben.

 SELECT <Spaltenliste1>   FROM <Tabellenliste1>   WHERE <Bedingungen1>
        INTERSECT
 SELECT <Spaltenliste2>   FROM <Tabellenliste2>   WHERE <Bedingungen2>

Die Ergebnismenge besteht aus allen Zeilen, die sowohl zum ersten SELECT-Befehl als auch zum zweiten SELECT-Befehl gehören.

Mit EXCEPT (MS-SQL) oder MINUS (wer benutzt diese Variante? ich finde es nicht mehr) wird die Differenz der Teilabfragen zurückgegeben.

 SELECT <Spaltenliste1>   FROM <Tabellenliste1>   WHERE <Bedingungen1>
        EXCEPT | MINUS
 SELECT <Spaltenliste2>   FROM <Tabellenliste2>   WHERE <Bedingungen2>

Die Ergebnismenge besteht aus allen Zeilen, die zum ersten SELECT-Befehl gehören, aber beim zweiten SELECT-Befehl nicht enthalten sind.

Wie bei UNION können auch solche Verknüpfungen durch sehr komplizierte JOINs und WHERE-Klauseln nachgebildet werden.


[Bearbeiten] CASE WHEN – Fallunterscheidungen

Immer wieder möchte man innerhalb eines SQL-Befehls je nach Situation unterschiedliche Werte erhalten. Das einfachste Beispiel ist die Tabelle Mitarbeiter: Anstelle des Feldinhalts Ist_Leiter mit "J" oder "N" soll "Leiter" oder eine leere Zeichenkette angezeigt werden.

Dafür ist der CASE-Ausdruck vorgesehen, den es in zwei Varianten gibt.

Als <expression> können wahlweise konstante Werte oder komplexe Ausdrücke verwendet werden. Der CASE-Ausdruck ist nicht nur für SELECT, sondern auch für Speichern-Befehle geeignet und nicht nur (wie in den meisten Beispielen) als Teil der Spaltenliste, sondern auch in der WHERE-Klausel oder an anderen Stellen, an denen entsprechende Werte benötigt werden.

Der Wert, den der CASE-Ausdruck zurückgibt, erhält einen passenden Datentyp, der sich aus den Result-Werten ergibt.


[Bearbeiten] Simple Case – die einfache Fallunterscheidung

Die einfache Variante hat folgende Syntax:

 CASE <expression>
     WHEN <expression1> THEN <result1>
   [ WHEN <expression2> THEN <result2> ]  /* usw. */
   [ ELSE <default> ]
 END

Bei dieser Version wird der Wert, der sich durch den ersten Ausdruck beim CASE ergibt, nacheinander mit den Ausdrücken nach WHEN verglichen. Sobald eine Gleichheit festgestellt wird, wird der Wert, der sich durch den Result-Ausdruck ergibt, als Wert für den CASE-Ausdruck übernommen. Wenn es keine Übereinstimmung gibt, wird der Wert des Default-Ausdrucks übernommen; wenn ELSE nicht vorhanden ist, wird NULL als Wert genommen. Beispiele:

Aufgabe
Ein vorhandener Feldinhalt, nämlich 'J' oder 'N', wird durch andere Texte ersetzt:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb,
       CASE Ist_Leiter
         WHEN 'J' THEN 'Leiter'
         ELSE ''
       END AS Leiter, Mobil
  FROM Mitarbeiter;

Crystal Clear app kscreensaver.png SQL-Ausgabe: (Auszug)

 PERS       NAME          VORNAME    GEB         LEITER  MOBIL
 40001      Langmann      Matthias   28.03.1976  Leiter
 40002      Peters        Michael    15.11.1973
 50001      Pohl          Helmut     27.10.1980  Leiter  (0171) 4123456
 50002      Braun         Christian  05.09.1966          (0170) 8351647

Dieselbe Lösung ohne ELSE-Zweig ändert nur die Anzeige:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb,
       CASE Ist_Leiter
         WHEN 'J' THEN 'Leiter'
       END AS Leiter, Mobil
  FROM Mitarbeiter;

Crystal Clear app kscreensaver.png SQL-Ausgabe: (Auszug)

 PERS       NAME          VORNAME    GEB         LEITER  MOBIL
 40001      Langmann      Matthias   28.03.1976  Leiter
 40002      Peters        Michael    15.11.1973  <null>
 50001      Pohl          Helmut     27.10.1980  Leiter  (0171) 4123456
 50002      Braun         Christian  05.09.1966  <null>  (0170) 8351647
Aufgabe
Bei der Art der Versicherungsverträge möchten wir die Varianten im "Klartext" lesen:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT Vertragsnummer AS Vertrag, Abschlussdatum AS Datum,
       CASE Art
         WHEN 'VK' THEN 'Vollkasko'
         WHEN 'TK' THEN 'Teilkasko'
         WHEN 'HP' THEN 'Haftpflicht'
       END
  FROM Versicherungsvertrag;

Crystal Clear app kscreensaver.png SQL-Ausgabe: (Auszug)

 VERTRAG  DATUM       CASE
 DO-03    09.10.1994  Haftpflicht
 DB-04    25.01.2008  Haftpflicht
 RH-01    11.12.1976  Vollkasko
 RD-02    29.01.1988  Haftpflicht
 RM-03    13.01.1996  Haftpflicht
 RD-04    23.11.2006  Haftpflicht
 RR-05    29.06.1990  Teilkasko
 KB-01    14.02.1978  Teilkasko

[Bearbeiten] Searched Case – die komplexe Fallunterscheidung

Die komplexe Variante hat keinen Ausdruck hinter dem CASE; sie arbeitet nach folgender Syntax:

CASE 
    WHEN <bedingung1> THEN <result1>
  [ WHEN <bedingung2> THEN <result2> ]  /* usw. */
  [ ELSE <default> ]
END

Bei dieser Variante werden nacheinander Bedingungen geprüft. Sobald eine Bedingung als WAHR festgestellt wird, wird der Wert, der sich durch den Result-Ausdruck ergibt, als Wert für den CASE-Ausdruck übernommen. Wenn es keine Übereinstimmung gibt, wird der Wert des Default-Ausdrucks übernommen; wenn ELSE nicht vorhanden ist, wird NULL als Wert genommen. Beispiele:

Aufgabe
In einer Zeile wird gezählt, wieviele Versicherungsverträge innerhalb eines Jahrzehnts abgeschlossen wurden.

Crystal Clear app terminal.png SQL-Quelltext:

SELECT
    SUM( CASE
           WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1970 AND 1979 
           THEN 1 
           ELSE 0 END ) AS SUM_197,
    SUM( CASE
           WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1980 AND 1989 
           THEN 1 
           ELSE 0 END ) AS SUM_198,
    SUM( CASE
           WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1990 AND 1999 
           THEN 1 
           ELSE 0 END ) AS SUM_199,
    SUM( CASE
           WHEN EXTRACT(YEAR FROM Abschlussdatum) >= 2000               
           THEN 1 
           ELSE 0 END ) AS SUM_200
  FROM Versicherungsvertrag;

Crystal Clear app kscreensaver.png SQL-Ausgabe:  

 SUM_197   SUM_198   SUM_199  SUM_200
     6         6         6        5

Dazu wird für jedes Jahrzehnt eine Spalte vorgesehen. Jede Spalte enthält einen CASE-Ausdruck mit einer WHEN-Bedingung. Wenn für eine Zeile diese Bedingung TRUE ergibt, wird 1 zur Summe addiert, andernfalls 0.

Aufgabe
In den folgenden Versuchen wird der CASE-Ausdruck innerhalb von ORDER BY für eine unterschiedliche Sortierung, abhängig von der Art der Versicherung, verwendet:

Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Art,
       Abschlussdatum AS Datum, Vertragsnummer AS Vertr,
       Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG
  FROM Versicherungsvertrag
  ORDER BY Art,
           CASE
              WHEN Art = 'TK' THEN ID
              WHEN Art = 'VK' THEN Mitarbeiter_ID
              WHEN Art = 'HP' THEN Fahrzeug_ID
           END;

Crystal Clear app kscreensaver.png SQL-Ausgabe: (Auszug)

 ID  ART  DATUM       VERTR  MIT  FZG
 14  HP   15.03.1998  KG-03    9   16
 18  HP   17.05.2000  HG-03    9   17
 19  HP   21.09.2004  HB-04    9   19
 10  HP   23.11.2006  RD-04    9   20
  6  HP   25.01.2008  DB-04    9   21
 15  HP   27.03.1988  KV-04   10   22
 11  TK   29.06.1990  RR-05    9   23
 12  TK   14.02.1978  KB-01   10    6
 21  VK   20.06.1982  XH-02    9    8
 23  VK   19.07.2002  XO-04    9   18
  7  VK   11.12.1976  RH-01   10    5
 22  VK   05.06.1992  XW-03   10   13

In diesem Beispiel wurden alle Abschnitte numerisch sortiert. Auch die Sortierung eines Teils der Datensätze nach einem Datum funktioniert, muss aber unter Umständen überarbeitet werden:

Crystal Clear action button cancel.png unter Firebird so nicht möglich
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Art,
       Abschlussdatum AS Datum, Vertragsnummer AS Vertr,
       Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG
  FROM Versicherungsvertrag
  ORDER BY Art,
           CASE
              WHEN Art = 'TK' THEN ID
              WHEN Art = 'VK' THEN Mitarbeiter_ID
              WHEN Art = 'HP' THEN Abschlussdatum
           END;

Crystal Clear app kscreensaver.png SQL-Ausgabe:  

 Invalid token.
 SQL error code = -104.
 Datatypes are not comparable in expression CASE.

Auch wenn Firebird (wie im ersten Beispiel) Namen von Spalten akzeptiert, werden die Datentypen der Spalten vergleichen. Diese sind offensichtlich nicht kompatibel; also bricht Firebird diese Abfrage ab. Aber in der ORDER BY-Klausel können auch die Nummern der Spalten angegeben werden; dann klappt es:

Crystal Clear action apply.png mit Nummern der Spalten anstelle der Namen
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Art,
       Abschlussdatum AS Datum, Vertragsnummer AS Vertr,
       Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG
  FROM Versicherungsvertrag
  ORDER BY Art,
           CASE
              WHEN Art = 'TK' THEN ID
              WHEN Art = 'VK' THEN Mitarbeiter_ID
              WHEN Art = 'HP' THEN 2
           END;

Bitte wundern Sie sich nicht über das identische Ergebnis wie oben bei der Sortierung nach Fahrzeug_ID: Die Fahrzeuge wurden in der gleichen zeitlichen Reihenfolge erfasst wie die Verträge; also stimmen in der ersten Zeit beide Sortierungen überein.

Die Sortierung kann nur einheitlich festgelegt werden, aber nicht mal so, mal so:

Crystal Clear action button cancel.png Versuch, nur nach dem Abschlussdatum absteigend zu sortieren
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Art,
       Abschlussdatum AS Datum, Vertragsnummer AS Vertr,
       Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG
  FROM Versicherungsvertrag
  ORDER BY Art,
           CASE
              WHEN Art = 'TK' THEN ID             ASC
              WHEN Art = 'VK' THEN Mitarbeiter_ID ASC
              WHEN Art = 'HP' THEN 2              DESC
           END;

Crystal Clear app kscreensaver.png SQL-Ausgabe:  

 Invalid token.
 Dynamic SQL Error.
 SQL error code = -104.
 Token unknown - line 7, column 39. ASC.

Crystal Clear action apply.png Einheitliche Sortierung DESC sollte möglich sein
Crystal Clear app terminal.png SQL-Quelltext:

SELECT ID, Art,
       Abschlussdatum AS Datum, Vertragsnummer AS Vertr,
       Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG
  FROM Versicherungsvertrag
  ORDER BY Art,
           CASE
              WHEN Art = 'TK' THEN ID             
              WHEN Art = 'VK' THEN Mitarbeiter_ID 
              WHEN Art = 'HP' THEN 2              
           END DESC;

Zumindest unter Firebird gelingt mir dennoch keine korrekte Sortierung: nur der Abschnitt 'TK' wird absteigend sortiert, die beiden anderen nicht. Sie müssen aber sowieso selbst ausprobieren, was in welchem DBMS möglich ist und was nicht.


[Bearbeiten] CASE-Ausdruck beim Speichern

Wie beim SELECT-Befehl kann ein CASE-Ausdruck auch beim Speichern an verschiedenen Stellen benutzt werden, vor allem bei den Zuweisungen der Werte für INSERT und UPDATE sowie bei der WHERE-Klausel für UPDATE und DELETE. Da sich diese Kapitel vor allem mit Abfragen beschäftigen, beschränke ich mich jetzt auf ein Beispiel.

Aufgabe
Die Zuständigkeit der Mitarbeiter für die Versicherungsverträge wird neu geregelt:
  • Vollkasko-Verträge gehören zum Aufgabenbereich des Abteilungsleiters (Mitarbeiter-ID 9).
  • Teilkasko-Verträge gehören zum Aufgabenbereich des Mitarbeiters mit ID 12.
  • Die Haftpflicht-Verträge werden in Abhängigkeit von der ID aufgeteilt auf die Mitarbeiter 10 und 11.

Crystal Clear app terminal.png SQL-Quelltext:

UPDATE Versicherungsvertrag
   SET Mitarbeiter_id =
       CASE
          WHEN Art = 'VK' THEN  9
          WHEN Art = 'TK' THEN 12
          WHEN Art = 'HP' THEN 10 + MOD(ID, 2)
       END;

Es wird also eine normale SET-Anweisung geschrieben. Die CASE-Anweisung liefert die benötigten Werte, wobei für den Fall "Haftpflicht" für gerade IDs der Wert (10+0) und für ungerade IDs der Wert (10+1) gesetzt wird. Eine WHERE-Klausel ist nicht erforderlich, weil alle Verträge neu zugeordnet werden sollen.


[Bearbeiten] Zusammenfassung

In diesem Kapitel lernten Sie einige nützliche Erweiterungen vor allem für Abfragen kennen:

  • Auf den DISTINCT-Parameter zur Auswahl wird genauer eingegangen.
  • Je nach DBMS wird unterschiedlich geregelt, wenn nur eine gewisse Anzahl von Zeilen gewünscht wird.
  • Mit UNION wird das Ergebnis von zwei oder mehr Abfragen in einer Ergebnistabelle zusammengefasst.
  • Mit CASE WHEN sind Fallunterschiedungen für gewisse Werte möglich; dies ist auch beim Speichern von Datensätzen sowohl für die Werte als auch für die WHERE-Klausel hilfreich.

[Bearbeiten] Übungen


Persönliche Werkzeuge