Einführung in SQL: Nützliche Erweiterungen
Aus Wikibooks
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
SQL-Quelltext:
SELECT DISTINCT Fahrzeug_ID FROM Zuordnung_SF_FZ;
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.
Nur theoretisch DISTINCT, praktisch nicht
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:
[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
SQL-Quelltext:
SELECT FIRST 10 ID, Name, Vorname, Abteilung_ID AS Abt FROM Mitarbeiter ORDER BY Name;
Nur SKIP überspringt die ersten Zeilen
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
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
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)
SQL-Quelltext:
SELECT ID, Name, Vorname, Abteilung_ID AS Abt FROM Mitarbeiter ORDER BY Name ROWS 10 TO 20;
Der erste Datensatz gemäß Sortierung
SQL-Quelltext:
SELECT ID, Name, Vorname, Abteilung_ID AS Abt FROM Mitarbeiter ORDER BY Name ROWS 1;
Der letzte Datensatz gemäß Sortierung
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
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
SQL-Quelltext:
SELECT TOP 25 PERCENT ID, Name, Vorname, Abteilung_ID AS Abt FROM Mitarbeiter ORDER BY Name;
Der letzte Datensatz gemäß Sortierung
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
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
SQL-Quelltext:
SELECT ID, Name, Vorname, Abteilung_ID AS Abt FROM Mitarbeiter ORDER BY Name LIMIT 10 OFFSET 5;
Der letzte Datensatz gemäß Sortierung
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
SQL-Quelltext:
SELECT Name, rownum FROM Mitarbeiter;
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
SQL-Quelltext:
SELECT Name FROM Mitarbeiter WHERE Rownum <= 2;
NAME ROWNUM Müller 1 Schneider 2
Folgende Formulierung funktioniert allerdings nicht, wenn man nur den 10. Satz ausgeben will:
Nur den 10. Satz anzeigen - so geht es nicht
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:
Nur den 10. Satz anzeigen - so klappt es
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:
Die alphabetisch ersten 6 Namen ausgeben - so geht es nicht
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:
Die alphabetisch ersten 6 Namen ausgeben - so klappt es
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.
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.
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
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
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:
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:
Ein vorhandener Feldinhalt, nämlich 'J' oder 'N', wird durch andere Texte ersetzt:SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb, CASE Ist_Leiter WHEN 'J' THEN 'Leiter' ELSE '' END AS Leiter, Mobil FROM Mitarbeiter;
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:
SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb, CASE Ist_Leiter WHEN 'J' THEN 'Leiter' END AS Leiter, Mobil FROM Mitarbeiter;
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
SELECT Vertragsnummer AS Vertrag, Abschlussdatum AS Datum, CASE Art WHEN 'VK' THEN 'Vollkasko' WHEN 'TK' THEN 'Teilkasko' WHEN 'HP' THEN 'Haftpflicht' END FROM Versicherungsvertrag;
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:
In einer Zeile wird gezählt, wieviele Versicherungsverträge innerhalb eines Jahrzehnts abgeschlossen wurden.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;
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.
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: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;
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:
unter Firebird so nicht möglich
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;
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:
mit Nummern der Spalten anstelle der Namen
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:
Versuch, nur nach dem Abschlussdatum absteigend zu sortieren
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;
Invalid token. Dynamic SQL Error. SQL error code = -104. Token unknown - line 7, column 39. ASC.
Einheitliche Sortierung DESC sollte möglich sein
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.
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.
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.