Einführung in SQL - wikimedia.org
Einführung in SQL - wikimedia.org
Einführung in SQL - wikimedia.org
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
<strong>E<strong>in</strong>führung</strong> <strong>in</strong> <strong>SQL</strong><br />
Datenbanken bearbeiten<br />
Jürgen Thomas<br />
E<strong>in</strong> Wiki-Buch
Bibliografische Information<br />
Diese Publikation ist bei der Deutschen Nationalbibliothek registriert. Detaillierte<br />
Angaben s<strong>in</strong>d im Internet zu erhalten:<br />
https://portal.d-nb.de/opac.htm?method=showOptions#top<br />
Titel „<strong>E<strong>in</strong>führung</strong> <strong>in</strong> <strong>SQL</strong>“, E<strong>in</strong>schränkung auf Jahr „2011–“.<br />
Namen von Programmen und Produkten sowie weitere technische Angaben s<strong>in</strong>d<br />
häufig geschützt. Da es auch freie Bezeichnungen gibt, wird das Symbol ® nicht<br />
verwendet.<br />
Die Onl<strong>in</strong>e-Version, die PDF-Version und die gedruckte Fassung unterscheiden<br />
sich vor allem wegen unterschiedlicher Seitenformate und technischer Möglichkeiten.<br />
ISBN 978-3-9815260-0-4 (Juli 2012)<br />
Verlag: Jürgen Thomas, DE-13189 Berl<strong>in</strong>, http://www.vs-polis.de/verlag<br />
Diese Publikation ist entstanden bei Wikibooks, e<strong>in</strong>em Projekt der Wikimedia<br />
Foundation für Lehr-, Sach- und Fachbücher unter den Lizenzen Creative Commons<br />
Attribution/Share-Alike (CC-BY-SA) und GFDL.<br />
PDF- und Druckversion s<strong>in</strong>d entstanden mit dem Programm wb2pdf unter GPL.<br />
Dabei wurde das Textsatzprogramm LATEX verwendet, das unter der LPPL steht.<br />
Die Grafik auf dem Buchumschlag wurde unter der Lizenz CC-BY-SA-3.0 selbst<br />
erstellt und ist zu f<strong>in</strong>den unter:<br />
http://de.wikibooks.<strong>org</strong>/wiki/Datei:<strong>SQL</strong>-Titelbild.png<br />
E<strong>in</strong>zelheiten zu den Lizenzen und Quellen stehen im Anhang auf Seite 451.<br />
Druck und Verarbeitung: Conrad Citydruck & Copy GmbH, Uhlandstraße 147,<br />
DE-10719 Berl<strong>in</strong>
Übersicht<br />
1. Vorwort 1<br />
I. <strong>E<strong>in</strong>führung</strong> 3<br />
2. E<strong>in</strong> E<strong>in</strong>stieg 5<br />
3. E<strong>in</strong>leitung 11<br />
4. Relationale Datenbanken 17<br />
5. Normalisierung 25<br />
6. Beispieldatenbank 39<br />
II. Grundlagen 45<br />
7. <strong>SQL</strong>-Befehle 47<br />
8. DML (1) – Daten abfragen 57<br />
9. DML (2) – Daten speichern 69<br />
10. DDL – Struktur der Datenbank 79<br />
11. TCL – Ablaufsteuerung 89<br />
12. DCL – Zugriffsrechte 95<br />
13. Datentypen 97<br />
14. Funktionen 109<br />
III. Mehr zu Abfragen 127<br />
15. Ausführliche SELECT-Struktur 129<br />
16. Funktionen (2) 141<br />
17. WHERE-Klausel im Detail 155<br />
18. Mehrere Tabellen 167<br />
19. E<strong>in</strong>fache Tabellenverknüpfung 173<br />
20. Arbeiten mit JOIN 181<br />
21. OUTER JOIN 191<br />
22. Mehr zu JOIN 203<br />
23. Nützliche Erweiterungen 213<br />
24. Berechnete Spalten 235<br />
25. Gruppierungen 241<br />
26. Unterabfragen 251<br />
27. Erstellen von Views 271<br />
III
Übersicht<br />
IV. Erweiterungen 283<br />
28. DDL – E<strong>in</strong>zelheiten 285<br />
29. Fremdschlüssel-Beziehungen 305<br />
30. <strong>SQL</strong>-Programmierung 323<br />
31. Eigene Funktionen 347<br />
32. Prozeduren 357<br />
33. Trigger 377<br />
34. Tipps und Tricks 389<br />
35. Änderung der Datenbankstruktur 397<br />
36. Testdaten erzeugen 407<br />
V. Anhang 417<br />
A. Tabellenstruktur der Beispieldatenbank 419<br />
B. Downloads 423<br />
C. Befehlsreferenz 431<br />
D. Weitere Informationen 445<br />
E. Zu diesem Buch 451<br />
IV
Inhaltsverzeichnis<br />
1. Vorwort 1<br />
I. <strong>E<strong>in</strong>führung</strong> 3<br />
2. E<strong>in</strong> E<strong>in</strong>stieg 5<br />
2.1. Datenbanken enthalten Informationen . . . . . . . . . . . . . . . 5<br />
2.2. Abfrage nach den Mitarbeitern . . . . . . . . . . . . . . . . . . . . . 6<br />
2.3. Neuaufnahme bei den Mitarbeitern . . . . . . . . . . . . . . . . . . 7<br />
2.4. <strong>SQL</strong> und natürliche Sprache . . . . . . . . . . . . . . . . . . . . . . 8<br />
2.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
2.6. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />
3. E<strong>in</strong>leitung 11<br />
3.1. Geschichte von <strong>SQL</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />
3.2. Übersicht über Datenbankmanagementsysteme . . . . . . . . . 12<br />
3.3. Schreibweisen im Buch . . . . . . . . . . . . . . . . . . . . . . . . . 14<br />
3.4. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />
4. Relationale Datenbanken 17<br />
4.1. Grundstruktur relationaler Datenbanken . . . . . . . . . . . . . . 17<br />
4.2. Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />
4.3. Spalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
4.4. Verknüpfungen und Schlüssel . . . . . . . . . . . . . . . . . . . . . 22<br />
4.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />
5. Normalisierung 25<br />
5.1. Grundgedanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
5.2. Die 1. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
5.3. Die 2. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />
5.4. Die 3. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
5.5. Zusätzliche Maßnahmen . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
5.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
5.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
6. Beispieldatenbank 39<br />
6.1. Sachverhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />
V
Inhaltsverzeichnis<br />
6.2. Schematische Darstellung . . . . . . . . . . . . . . . . . . . . . . . . 41<br />
6.3. Tabellenstruktur und Datenbank . . . . . . . . . . . . . . . . . . . 42<br />
6.4. Anmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43<br />
II. Grundlagen 45<br />
7. <strong>SQL</strong>-Befehle 47<br />
7.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . . . 48<br />
7.2. DML − Data Manipulation Language . . . . . . . . . . . . . . . . . 49<br />
7.3. DDL − Data Def<strong>in</strong>ition Language . . . . . . . . . . . . . . . . . . . 51<br />
7.4. TCL − Transaction Control Language . . . . . . . . . . . . . . . . . 52<br />
7.5. DCL − Data Control Language . . . . . . . . . . . . . . . . . . . . . 52<br />
7.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />
7.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />
7.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />
8. DML (1) – Daten abfragen 57<br />
8.1. SELECT – Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . 57<br />
8.2. Die e<strong>in</strong>fachsten Abfragen . . . . . . . . . . . . . . . . . . . . . . . . 58<br />
8.3. DISTINCT – Ke<strong>in</strong>e doppelten Zeilen . . . . . . . . . . . . . . . . . 59<br />
8.4. WHERE – E<strong>in</strong>grenzen der Ergebnismenge . . . . . . . . . . . . . . 60<br />
8.5. ORDER BY – Sortieren . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
8.6. FROM – Mehrere Tabellen verknüpfen . . . . . . . . . . . . . . . . 62<br />
8.7. Ausblick auf komplexe Abfragen . . . . . . . . . . . . . . . . . . . . 64<br />
8.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />
8.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />
9. DML (2) – Daten speichern 69<br />
9.1. INSERT – Daten e<strong>in</strong>fügen . . . . . . . . . . . . . . . . . . . . . . . . 69<br />
9.2. UPDATE – Daten ändern . . . . . . . . . . . . . . . . . . . . . . . . 72<br />
9.3. DELETE – Daten löschen . . . . . . . . . . . . . . . . . . . . . . . . 74<br />
9.4. TRUNCATE – Tabelle leeren . . . . . . . . . . . . . . . . . . . . . . . 75<br />
9.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75<br />
9.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75<br />
10. DDL – Struktur der Datenbank 79<br />
10.1. Allgeme<strong>in</strong>e Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79<br />
10.2. Hauptteile der Datenbank . . . . . . . . . . . . . . . . . . . . . . . . 80<br />
10.3. Ergänzungen zu Tabellen . . . . . . . . . . . . . . . . . . . . . . . . 83<br />
10.4. Programmieren mit <strong>SQL</strong> . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />
10.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />
10.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />
10.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87<br />
VI
Inhaltsverzeichnis<br />
11. TCL – Ablaufsteuerung 89<br />
11.1. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89<br />
11.2. Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90<br />
11.3. Misserfolg regeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91<br />
11.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />
11.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />
12. DCL – Zugriffsrechte 95<br />
13. Datentypen 97<br />
13.1. Vordef<strong>in</strong>ierte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . 97<br />
13.2. Konstruierte und benutzerdef<strong>in</strong>ierte Datentypen . . . . . . . . . 101<br />
13.3. Spezialisierte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . 102<br />
13.4. Nationale und <strong>in</strong>ternationale Zeichensätze . . . . . . . . . . . . . 103<br />
13.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
13.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
13.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />
14. Funktionen 109<br />
14.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . . . 110<br />
14.2. Funktionen für Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . 110<br />
14.3. Funktionen für Zeichenketten . . . . . . . . . . . . . . . . . . . . . 113<br />
14.4. Funktionen für Datums- und Zeitwerte . . . . . . . . . . . . . . . 115<br />
14.5. Funktionen für logische und NULL-Werte . . . . . . . . . . . . . . 116<br />
14.6. Konvertierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117<br />
14.7. Spaltenfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120<br />
14.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123<br />
14.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123<br />
14.10. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126<br />
III. Mehr zu Abfragen 127<br />
15. Ausführliche SELECT-Struktur 129<br />
15.1. Allgeme<strong>in</strong>e Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129<br />
15.2. Set Quantifier – Mengenquantifizierer . . . . . . . . . . . . . . . . 130<br />
15.3. Select List – Auswahlliste . . . . . . . . . . . . . . . . . . . . . . . . 130<br />
15.4. Table Reference List − Tabellen-Verweise . . . . . . . . . . . . . . 132<br />
15.5. WHERE-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133<br />
15.6. GROUP BY- und HAVING-Klausel . . . . . . . . . . . . . . . . . . . 134<br />
15.7. UNION-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135<br />
15.8. ORDER BY-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
15.9. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
15.10. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
VII
Inhaltsverzeichnis<br />
16. Funktionen (2) 141<br />
16.1. Funktionen für Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . 141<br />
16.2. Funktionen für Zeichenketten . . . . . . . . . . . . . . . . . . . . . 144<br />
16.3. Funktionen für Datums- und Zeitwerte . . . . . . . . . . . . . . . 146<br />
16.4. Funktionen für logische und NULL-Werte . . . . . . . . . . . . . . 148<br />
16.5. Verschiedene Funktionen . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154<br />
17. WHERE-Klausel im Detail 155<br />
17.1. E<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung . . . . . . . . . . . . . . . . . . . . . . . . 155<br />
17.2. Mehrere Bed<strong>in</strong>gungen verknüpfen . . . . . . . . . . . . . . . . . . 160<br />
17.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162<br />
17.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163<br />
17.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165<br />
18. Mehrere Tabellen 167<br />
18.1. Schreibweisen bei mehreren Tabellen . . . . . . . . . . . . . . . . 167<br />
18.2. Mit Hilfe von WHERE – der traditionelle Weg . . . . . . . . . . . . 168<br />
18.3. JOINs – der moderne Weg . . . . . . . . . . . . . . . . . . . . . . . . 168<br />
18.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169<br />
18.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169<br />
19. E<strong>in</strong>fache Tabellenverknüpfung 173<br />
19.1. Alle Komb<strong>in</strong>ationen aller Datensätze . . . . . . . . . . . . . . . . . 173<br />
19.2. Tabellen e<strong>in</strong>fach verb<strong>in</strong>den . . . . . . . . . . . . . . . . . . . . . . . 174<br />
19.3. Verknüpfungs- und Auswahlbed<strong>in</strong>gungen . . . . . . . . . . . . . . 176<br />
19.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176<br />
19.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176<br />
19.6. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179<br />
20. Arbeiten mit JOIN 181<br />
20.1. Die Syntax von JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . 181<br />
20.2. INNER JOIN von zwei Tabellen . . . . . . . . . . . . . . . . . . . . . 182<br />
20.3. WHERE-Klausel bei JOINs . . . . . . . . . . . . . . . . . . . . . . . . 183<br />
20.4. INNER JOIN mehrerer Tabellen . . . . . . . . . . . . . . . . . . . . 185<br />
20.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185<br />
20.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186<br />
20.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189<br />
21. OUTER JOIN 191<br />
21.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . . . 191<br />
21.2. LEFT OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192<br />
21.3. RIGHT OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . 193<br />
VIII
Inhaltsverzeichnis<br />
21.4. FULL OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194<br />
21.5. Verknüpfung mehrerer Tabellen . . . . . . . . . . . . . . . . . . . . 195<br />
21.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198<br />
21.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199<br />
22. Mehr zu JOIN 203<br />
22.1. Welcher JOIN-Typ passt wann? . . . . . . . . . . . . . . . . . . . . . 203<br />
22.2. SELF JOIN – Verknüpfung mit sich selbst . . . . . . . . . . . . . . 204<br />
22.3. CROSS JOIN – das kartesische Produkt . . . . . . . . . . . . . . . . 209<br />
22.4. WITH – Inl<strong>in</strong>e-View . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210<br />
22.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211<br />
22.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211<br />
22.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212<br />
23. Nützliche Erweiterungen 213<br />
23.1. Beschränkung auf e<strong>in</strong>e Anzahl Zeilen . . . . . . . . . . . . . . . . . 213<br />
23.2. Mehrere Abfragen zusammenfassen . . . . . . . . . . . . . . . . . 220<br />
23.3. CASE WHEN – Fallunterscheidungen . . . . . . . . . . . . . . . . . 223<br />
23.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228<br />
23.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229<br />
24. Berechnete Spalten 235<br />
24.1. Ergebnis von Berechnungen . . . . . . . . . . . . . . . . . . . . . . 236<br />
24.2. Zeichenketten verb<strong>in</strong>den und bearbeiten . . . . . . . . . . . . . . 236<br />
24.3. Ergebnis von Funktionen . . . . . . . . . . . . . . . . . . . . . . . . 237<br />
24.4. Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
24.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
24.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
25. Gruppierungen 241<br />
25.1. Syntax von GROUP BY . . . . . . . . . . . . . . . . . . . . . . . . . . 241<br />
25.2. Gruppierung bei e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . . . 242<br />
25.3. Gruppierung über mehrere Tabellen . . . . . . . . . . . . . . . . . 243<br />
25.4. Voraussetzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244<br />
25.5. Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245<br />
25.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247<br />
25.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247<br />
25.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250<br />
26. Unterabfragen 251<br />
26.1. Ergebnis als e<strong>in</strong>zelner Wert . . . . . . . . . . . . . . . . . . . . . . . 251<br />
26.2. Ergebnis als Liste mehrerer Werte . . . . . . . . . . . . . . . . . . . 254<br />
26.3. Ergebnis <strong>in</strong> Form e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . . . 256<br />
26.4. Verwendung bei Befehlen zum Speichern . . . . . . . . . . . . . . 258<br />
26.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264<br />
IX
Inhaltsverzeichnis<br />
26.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264<br />
27. Erstellen von Views 271<br />
27.1. E<strong>in</strong>e View anlegen und benutzen . . . . . . . . . . . . . . . . . . . 272<br />
27.2. E<strong>in</strong>e View ändern oder löschen . . . . . . . . . . . . . . . . . . . . 276<br />
27.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277<br />
27.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277<br />
27.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282<br />
IV. Erweiterungen 283<br />
28. DDL – E<strong>in</strong>zelheiten 285<br />
28.1. Def<strong>in</strong>ition e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . 285<br />
28.2. Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte . . . . . . . . . . . . . . . . . . . 287<br />
28.3. Tabelle ändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291<br />
28.4. CONSTRAINTs – E<strong>in</strong>schränkungen . . . . . . . . . . . . . . . . . . 293<br />
28.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301<br />
28.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301<br />
28.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304<br />
29. Fremdschlüssel-Beziehungen 305<br />
29.1. Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305<br />
29.2. Grundsätze der Lösung . . . . . . . . . . . . . . . . . . . . . . . . . 306<br />
29.3. Syntax und Optionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 307<br />
29.4. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311<br />
29.5. Komb<strong>in</strong>ation von Fremdschlüsseln . . . . . . . . . . . . . . . . . . 312<br />
29.6. Rekursive Fremdschlüssel . . . . . . . . . . . . . . . . . . . . . . . . 314<br />
29.7. Reihenfolge der Maßnahmen beachten . . . . . . . . . . . . . . . 316<br />
29.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317<br />
29.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318<br />
29.10. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321<br />
30. <strong>SQL</strong>-Programmierung 323<br />
30.1. Rout<strong>in</strong>en ohne feste Speicherung . . . . . . . . . . . . . . . . . . . 324<br />
30.2. Programmieren <strong>in</strong>nerhalb von Rout<strong>in</strong>en . . . . . . . . . . . . . . . 325<br />
30.3. <strong>SQL</strong>-Programmierung mit Firebird . . . . . . . . . . . . . . . . . . 326<br />
30.4. <strong>SQL</strong>-Programmierung mit MS-<strong>SQL</strong> . . . . . . . . . . . . . . . . . . 331<br />
30.5. <strong>SQL</strong>-Programmierung mit My<strong>SQL</strong> . . . . . . . . . . . . . . . . . . . 334<br />
30.6. <strong>SQL</strong>-Programmierung mit Oracle . . . . . . . . . . . . . . . . . . . 338<br />
30.7. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342<br />
30.8. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343<br />
30.9. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345<br />
X
Inhaltsverzeichnis<br />
31. Eigene Funktionen 347<br />
31.1. Funktion def<strong>in</strong>ieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347<br />
31.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349<br />
31.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351<br />
31.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351<br />
31.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356<br />
32. Prozeduren 357<br />
32.1. Die Def<strong>in</strong>ition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358<br />
32.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360<br />
32.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370<br />
32.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370<br />
32.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375<br />
33. Trigger 377<br />
33.1. Die Def<strong>in</strong>ition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378<br />
33.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379<br />
33.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381<br />
33.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382<br />
33.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386<br />
34. Tipps und Tricks 389<br />
34.1. Die letzte ID abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . 389<br />
34.2. Tabellenstruktur auslesen . . . . . . . . . . . . . . . . . . . . . . . . 391<br />
34.3. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395<br />
35. Änderung der Datenbankstruktur 397<br />
35.1. Spalten h<strong>in</strong>zufügen und ändern . . . . . . . . . . . . . . . . . . . . 397<br />
35.2. E<strong>in</strong>schränkungen auf Spalten . . . . . . . . . . . . . . . . . . . . . 399<br />
35.3. Indizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401<br />
35.4. Fremdschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402<br />
35.5. Weitere Anpassungen . . . . . . . . . . . . . . . . . . . . . . . . . . 402<br />
35.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405<br />
35.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405<br />
36. Testdaten erzeugen 407<br />
36.1. Neue Fahrzeuge registrieren . . . . . . . . . . . . . . . . . . . . . . 408<br />
36.2. Neue Versicherungsverträge registrieren . . . . . . . . . . . . . . . 408<br />
36.3. Probleme mit Testdaten . . . . . . . . . . . . . . . . . . . . . . . . . 414<br />
36.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415<br />
36.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416<br />
XI
Inhaltsverzeichnis<br />
V. Anhang 417<br />
A. Tabellenstruktur der Beispieldatenbank 419<br />
B. Downloads 423<br />
B.1. Die Download-Seite . . . . . . . . . . . . . . . . . . . . . . . . . . . 423<br />
B.2. Verb<strong>in</strong>dung zu den Datenbanksystemen . . . . . . . . . . . . . . . 424<br />
B.3. Die vollständige Beispieldatenbank . . . . . . . . . . . . . . . . . . 427<br />
B.4. Erstellen der Beispieldatenbank . . . . . . . . . . . . . . . . . . . . 427<br />
B.5. Skripte für nachträgliche Änderungen . . . . . . . . . . . . . . . . 428<br />
C. Befehlsreferenz 431<br />
C.1. DDL (Data Def<strong>in</strong>ition Language) . . . . . . . . . . . . . . . . . . . 431<br />
C.2. DML – Data Manipulation Language . . . . . . . . . . . . . . . . . 435<br />
C.3. TCL – Transaction Control Language . . . . . . . . . . . . . . . . . 438<br />
C.4. DCL – Data Control Language . . . . . . . . . . . . . . . . . . . . . 439<br />
C.5. Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440<br />
D. Weitere Informationen 445<br />
D.1. Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . 445<br />
D.2. Webl<strong>in</strong>ks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446<br />
E. Zu diesem Buch 451<br />
E.1. H<strong>in</strong>weise zu den Lizenzen . . . . . . . . . . . . . . . . . . . . . . . . 451<br />
E.2. Autoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452<br />
E.3. Bildnachweis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454<br />
XII
1. Vorwort<br />
Dieses Buch ist im Laufe mehrerer Jahre entstanden mit e<strong>in</strong>er Diskussion darüber,<br />
<strong>in</strong>wieweit die Sprache e<strong>in</strong>er bestimmten <strong>SQL</strong>-Datenbank behandelt werden<br />
sollte oder eher e<strong>in</strong> allgeme<strong>in</strong>er Überblick erforderlich ist. Dies sieht man<br />
dem Buch auch heute noch an; aber die Regel ist klar: Es handelt sich um e<strong>in</strong>e<br />
<strong>E<strong>in</strong>führung</strong> <strong>in</strong> den <strong>SQL</strong>-Standard; die Unterschiede zwischen den <strong>SQL</strong>-Dialekten<br />
(wie man die Unterschiede zwischen den Datenbanksystemen nennt) werden<br />
nur soweit behandelt, wie es unbed<strong>in</strong>gt nötig ist.<br />
Dieses Buch richtet sich an:<br />
• Schüler, Studenten und Andere, die sich mit relationalen Datenbanken beschäftigen<br />
wollen bzw. müssen.<br />
Was dieses Buch erreichen will:<br />
• <strong>E<strong>in</strong>führung</strong> <strong>in</strong> <strong>SQL</strong> anhand e<strong>in</strong>er Beispieldatenbank.<br />
• Der Leser soll die Beispiele anhand von Übungen auf se<strong>in</strong>em eigenen Datenbankmanagementsystem<br />
nachvollziehen können.<br />
Die Schwerpunkte liegen hierbei auf folgenden Themen:<br />
• Abfragen von Daten<br />
• Manipulieren von Daten<br />
• E<strong>in</strong>faches Ändern der Datenbankstruktur<br />
Was dieses Buch nicht se<strong>in</strong> soll:<br />
• Ke<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Grundkonzepte relationaler Datenbankmodelle.<br />
• Ke<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Gestaltung relationaler Datenbanken.<br />
• Ke<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Verwaltung von Datenbankmanagementsystemen.<br />
• Ke<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Verbesserung der Geschw<strong>in</strong>digkeit relationaler Datenbanken.<br />
• Ke<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> prozedurale (z. B. PL/<strong>SQL</strong>) oder objektorientierte Sprachen,<br />
die <strong>in</strong>nerhalb der Datenbank gespeichert und genutzt werden können.<br />
1
Vorwort<br />
Hoffentlich gew<strong>in</strong>nen die Leser mit dieser Darstellung – mit vielen Erläuterungen,<br />
Beispielen und Übungen – e<strong>in</strong>en guten E<strong>in</strong>blick <strong>in</strong> die Möglichkeiten von<br />
<strong>SQL</strong>.<br />
Auch wenn ich mich als Hauptautor bezeichne, wäre dieses Buch nicht so umfangreich<br />
und möglichst hilfreich geworden ohne die Vorarbeit vieler anderer<br />
Autoren bei Wikibooks 1 und die Kontrolle durch Leser und Lernende. Bei all diesen<br />
freundlichen Mitmenschen möchte ich mich herzlich für ihre Beteiligung<br />
bedanken.<br />
Jürgen Thomas Berl<strong>in</strong>, im Juli 2012<br />
1 Siehe die Lizenzh<strong>in</strong>weise auf Seite 451<br />
2
Teil I.<br />
<strong>E<strong>in</strong>führung</strong><br />
3
2. E<strong>in</strong> E<strong>in</strong>stieg<br />
2.1. Datenbanken enthalten Informationen . . . . . . . . . . . . . . 5<br />
2.2. Abfrage nach den Mitarbeitern . . . . . . . . . . . . . . . . . . . 6<br />
2.3. Neuaufnahme bei den Mitarbeitern . . . . . . . . . . . . . . . . 7<br />
2.4. <strong>SQL</strong> und natürliche Sprache . . . . . . . . . . . . . . . . . . . . . 8<br />
2.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
2.6. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />
Nehmen wir e<strong>in</strong>mal an, dass e<strong>in</strong> Versicherungsunternehmen e<strong>in</strong>en neuen Geschäftsführer<br />
bekommt. Dieser sammelt zunächst Informationen, um sich mit<br />
dem Unternehmen vertraut zu machen.<br />
2.1. Datenbanken enthalten Informationen<br />
Der Geschäftsführer f<strong>in</strong>det <strong>in</strong> se<strong>in</strong>em Büro ke<strong>in</strong>e Akten, sondern nur e<strong>in</strong>en PC.<br />
Nach dem E<strong>in</strong>schalten und Anmelden bekommt er folgende Begrüßung:<br />
Wikibooks-Beispieldatenbank<br />
(C) Wiki-<strong>SQL</strong><br />
Bitte geben Sie e<strong>in</strong>en <strong>SQL</strong>-Befehl e<strong>in</strong>:<br />
sql ><br />
Damit wird der Geschäftsführer auf mehrere Punkte der Organisation <strong>in</strong> se<strong>in</strong>em<br />
Unternehmen h<strong>in</strong>gewiesen.<br />
• Die Daten s<strong>in</strong>d <strong>in</strong> e<strong>in</strong>er Datenbank zusammengefasst mit dem Namen Beispieldatenbank.<br />
• Diese Datenbank (DB) stammt von der Firma Wikibooks.<br />
• Um etwas mit dieser DB zu machen, soll er h<strong>in</strong>ter "sql >" e<strong>in</strong>en <strong>SQL</strong>-Befehl<br />
e<strong>in</strong>geben.<br />
Die Inhalte dieser Datenbank s<strong>in</strong>d im Kapitel Beispieldatenbank beschrieben,<br />
E<strong>in</strong>zelheiten unter Tabellenstruktur der Beispieldatenbank.<br />
5
E<strong>in</strong> E<strong>in</strong>stieg<br />
Das Datenbanksystem (DBMS) der Firma Wikibooks wird als Wiki-<strong>SQL</strong> bezeichnet.<br />
Dies bedeutet, dass die Beispieldatenbank zu Wiki-<strong>SQL</strong> passen muss − <strong>in</strong><br />
Bezug auf das Dateiformat, die <strong>in</strong>terne Struktur und den Aufbau der Befehle.<br />
<strong>SQL</strong> ist die Abkürzung für Structured Query Language, also strukturierte Abfragesprache.<br />
Der Geschäftsführer hat schon e<strong>in</strong>mal davon gehört, dass dies e<strong>in</strong>e<br />
standardisierte Form ist, um mit Datenbanken zu arbeiten, und probiert es aus.<br />
2.2. Abfrage nach den Mitarbeitern<br />
Als erstes möchte er e<strong>in</strong>e Liste se<strong>in</strong>er Mitarbeiter haben. Er überlegt, wie die Abfrage<br />
lauten könnte:<br />
Hole Name und Vorname von den Mitarbeitern<br />
Auf Englisch probiert er (mit e<strong>in</strong> paar Synonymen) also so etwas aus:<br />
Get Name, Vorname FROM Mitarbeiter<br />
Fetch Name, Vorname FROM Mitarbeiter<br />
F<strong>in</strong>d Name, Vorname FROM Mitarbeiter<br />
Search Name, Vorname FROM Mitarbeiter<br />
Und schließlich bekommt er ke<strong>in</strong>e Fehlermeldung, sondern e<strong>in</strong> Ergebnis:<br />
SELECT Name, Vorname FROM Mitarbeiter<br />
Die Liste ist ihm zu lang und unübersichtlich. Er will sie zunächst e<strong>in</strong>mal sortieren<br />
und probiert Sortierung, Reihenfolge aus, bis es passt:<br />
SELECT Name, Vorname FROM Mitarbeiter ORDER BY Name<br />
Dann möchte er die Auswahl e<strong>in</strong>schränken, nämlich auf die Mitarbeiter mit Anfangsbuchstaben<br />
'A'. Wieder nach e<strong>in</strong> paar Versuchen weiß er, dass nicht WITH,<br />
sondern WHERE die Lösung liefert.<br />
SELECT Name, Vorname FROM Mitarbeiter WHERE Name < ’B’<br />
Jetzt möchte er beide Abfragen verb<strong>in</strong>den:<br />
6<br />
Quelltext Falsch<br />
SELECT Name, Vorname FROM Mitarbeiter ORDER BY Name WHERE Name < ’B’
<strong>SQL</strong> error code = -104. Token unknown - l<strong>in</strong>e 1, column 53. WHERE.<br />
Neuaufnahme bei den Mitarbeitern<br />
Das kann doch nicht se<strong>in</strong>?! WHERE ist doch das richtige Verfahren für e<strong>in</strong>e solche<br />
E<strong>in</strong>schränkung?! Kommt es etwa auf die Reihenfolge der Zusätze an?<br />
Quelltext Richtig<br />
SELECT Name, Vorname FROM Mitarbeiter WHERE Name < ’B’ ORDER BY Name<br />
NAME VORNAME<br />
Aagenau Karol<strong>in</strong><br />
Aliman Zafer<br />
Welche Informationen s<strong>in</strong>d denn sonst gespeichert? Er weiß auch (z. B. vom DIR-<br />
Befehl des Betriebssystems), dass e<strong>in</strong> Sternchen anstelle von alles gesetzt werden<br />
kann. Und siehe da, es klappt:<br />
SELECT * FROM Mitarbeiter WHERE Name < ’B’ ORDER BY Name<br />
ID PERSONALNUMMER NAME VORNAME GEBURTSDATUM TELEFON (und noch mehr)<br />
13 60001 Aagenau Karol<strong>in</strong> 02.01.1950 0234/66006001 usw.<br />
18 80002 Aliman Zafer 12.11.1965 0201/4012161 usw.<br />
Prima, damit ist klar, wie Informationen aus der Datenbank geholt werden:<br />
SELECT <br />
FROM <br />
WHERE <br />
ORDER BY <br />
2.3. Neuaufnahme bei den Mitarbeitern<br />
Als nächstes möchte der Geschäftsführer sich selbst als Mitarbeiter speichern.<br />
Schnell kommt er auf das „Grundgerüst“ des Befehls:<br />
INSERT INTO Mitarbeiter VALUES<br />
Wenn er danach se<strong>in</strong>en Namen schreibt, bekommt er wieder e<strong>in</strong>e Fehlermeldung<br />
mit "token unknown". Er hat aber schon von der Benutzung von Klammern<br />
<strong>in</strong> der EDV gehört.<br />
7
E<strong>in</strong> E<strong>in</strong>stieg<br />
Quelltext Falsch<br />
INSERT INTO Mitarbeiter VALUES (’Webern’, ’Anton’)<br />
<strong>SQL</strong> error code = -804.<br />
Count of read-write columns does not equal count of values.<br />
Na gut, dann wird eben ausdrücklich angegeben, dass erstmal nur Name und<br />
Vorname e<strong>in</strong>zutragen s<strong>in</strong>d.<br />
Quelltext Falsch<br />
INSERT INTO Mitarbeiter (Name, Vorname) VALUES (’Webern’, ’Anton’)<br />
validation error for column PERSONALNUMMER, value "*** null ***".<br />
Ach so, die Personalnummer muss angegeben werden, und vermutlich alles andere<br />
auch. Aber die ID ist doch gar nicht bekannt? Nun, immerh<strong>in</strong> s<strong>in</strong>d wir auf<br />
diese Grundstruktur des Befehls gekommen:<br />
INSERT INTO <br />
[ ( ) ]<br />
VALUES ( )<br />
2.4. <strong>SQL</strong> und natürliche Sprache<br />
Offensichtlich s<strong>in</strong>d die Befehle von <strong>SQL</strong> der natürlichen englischen Sprache<br />
nachempfunden. (Englisch hat wegen der klaren Satzstruktur und Grammatik<br />
<strong>in</strong>soweit natürlich Vorteile gegenüber der komplizierten deutschen Syntax.)<br />
SELECT für Abfragen<br />
Um Daten abzufragen, gibt es den SELECT-Befehl mit folgenden Details:<br />
8<br />
SELECT wähle aus<br />
[ DISTINCT | ALL ] verschiedene | alle<br />
<br />
FROM aus<br />
[ WHERE ] wobei<br />
[ GROUP BY ] gruppiert durch<br />
[ HAVING ] wobei<br />
[ ORDER BY ] sortiert durch
INSERT für Neuaufnahmen<br />
Zusammenfassung<br />
Um Daten neu zu speichern, gibt es den INSERT-Befehl mit folgenden Details:<br />
INSERT INTO e<strong>in</strong>fügen <strong>in</strong><br />
[ ]<br />
VALUES ( ) Werte<br />
/* oder */<br />
INSERT INTO e<strong>in</strong>fügen <strong>in</strong><br />
[ ]<br />
SELECT durch e<strong>in</strong>e Auswahl<br />
UPDATE für Änderungen<br />
Um Daten zu ändern, gibt es den UPDATE-Befehl mit folgenden Details:<br />
UPDATE aktualisiere<br />
SET = [ , setze fest<br />
= , usw.<br />
= ]<br />
[ WHERE ]; wobei<br />
DELETE für Löschungen<br />
Um Daten zu löschen, gibt es den DELETE-Befehl mit folgenden Details:<br />
DELETE FROM lösche aus<br />
[ WHERE ]; wobei<br />
CREATE TABLE bei der Struktur e<strong>in</strong>er Tabelle<br />
Um e<strong>in</strong>e neue Tabelle zu erstellen, gibt es den CREATE TABLE-Befehl mit folgenden<br />
E<strong>in</strong>zelheiten:<br />
CREATE TABLE erzeuge Tabelle<br />
( ,<br />
)<br />
So e<strong>in</strong>fach kann es gehen? Dann kann man doch auch eigene Daten erzeugen,<br />
speichern, abfragen und auswerten.<br />
2.5. Zusammenfassung<br />
Die e<strong>in</strong>zelnen Teile der <strong>SQL</strong>-Befehle s<strong>in</strong>d leicht verständlich; und es sche<strong>in</strong>t nur<br />
wenige Befehle zu geben, die man als „Anfänger“ wirklich lernen muss. Natürlich<br />
9
E<strong>in</strong> E<strong>in</strong>stieg<br />
kann man nicht sofort alle Möglichkeiten erfassen. Aber angesichts des begrenzten<br />
Umfangs und der klaren Struktur lohnt es sich, sich näher damit zu befassen.<br />
Dies will dieses Buch erleichtern.<br />
2.6. Siehe auch<br />
Wikipedia hat e<strong>in</strong>en Artikel zum Thema <strong>SQL</strong> 1 .<br />
Weitere Informationen gibt es <strong>in</strong> folgenden Kapiteln:<br />
• Beispieldatenbank 2<br />
• Tabellenstruktur der Beispieldatenbank 3<br />
1 http://de.wikipedia.<strong>org</strong>/wiki/<strong>SQL</strong><br />
2 Kapitel 6 auf Seite 39<br />
3 Anhang A auf Seite 419<br />
10
3. E<strong>in</strong>leitung<br />
3.1. Geschichte von <strong>SQL</strong> . . . . . . . . . . . . . . . . . . . . . . . . . . 12<br />
3.2. Übersicht über Datenbankmanagementsysteme . . . . . . . . 12<br />
3.3. Schreibweisen im Buch . . . . . . . . . . . . . . . . . . . . . . . . 14<br />
3.4. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15<br />
Dieses Kapitel gibt Informationen über Inhalt und Aufbau des Buches.<br />
Die Abfragesprache <strong>SQL</strong> ist die etablierte Sprache für die Arbeit mit relationalen<br />
Datenbankmanagementsystemen (DBMS). Es existieren verschiedene Standards,<br />
und jeder Hersteller von DBMS hat se<strong>in</strong>e eigenen Erweiterungen und Besonderheiten<br />
zu den Standards.<br />
Das Buch soll e<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Sprache <strong>SQL</strong> bieten. Ziel ist es, dass der<br />
Leser nach dem Durcharbeiten folgende Aufgaben selbständig lösen kann:<br />
• Eigene Abfragen für relationale Datenbanken erstellen<br />
• Manipulieren von Daten (E<strong>in</strong>fügen, Ändern und Löschen)<br />
• Eigene e<strong>in</strong>fache relationale Datenbanken aufbauen<br />
• Bestehende Datenbankstrukturen erweitern<br />
Um die Ziele zu erreichen, wird <strong>SQL</strong> anhand praxisnaher Beispiele erläutert.<br />
Die Beispiele im Buch wurden unter m<strong>in</strong>destens e<strong>in</strong>em der folgenden DBMS getestet:<br />
• Firebird<br />
• MS-<strong>SQL</strong> Server 2005 oder 2008<br />
• My<strong>SQL</strong> 4.1 oder 5<br />
• Oracle 9, 10 oder 11<br />
Vorzugsweise werden allgeme<strong>in</strong>gültige Schreibweisen nach dem <strong>SQL</strong>-Standard<br />
(siehe unten) benutzt. Deshalb sollten die Befehle <strong>in</strong> aller Regel auf allen<br />
gängigen DBMS funktionieren und höchstens kle<strong>in</strong>ere Änderungen benötigen.<br />
Dort, wo e<strong>in</strong>e Schreibweise wesentlich abweicht, wird das ausdrücklich erwähnt.<br />
11
E<strong>in</strong>leitung<br />
i Achtung<br />
Wegen der unterschiedlichen Schreibweisen der <strong>SQL</strong>-Befehle ist e<strong>in</strong>e vollständige<br />
Prüfung leider nicht möglich. Sie müssen <strong>in</strong> allen Zweifelsfällen <strong>in</strong><br />
der Dokumentation Ihres DBMS die passende Schreibweise nachlesen.<br />
3.1. Geschichte von <strong>SQL</strong><br />
<strong>SQL</strong> ist aus IBM’s SEQUEL <strong>in</strong> den siebziger Jahren entstanden. Der Erfolg der<br />
Sprache <strong>SQL</strong> liegt sicherlich auch dar<strong>in</strong>, dass sie e<strong>in</strong>fach aufgebaut ist und sich<br />
an der englischen Umgangssprache orientiert. Es gibt verschiedene Standards:<br />
• Erster <strong>SQL</strong>-Standard (1986 ANSI)<br />
• <strong>SQL</strong>2 bzw. <strong>SQL</strong>-92 (1992)<br />
• <strong>SQL</strong>3 bzw. <strong>SQL</strong>:1999 (1999 ISO)<br />
• <strong>SQL</strong>:2003 (2003)<br />
• <strong>SQL</strong>:2008 (2008)<br />
Hierbei ist zu beachten, dass die meisten Datenbankmanagementsysteme <strong>SQL</strong>2<br />
unterstützen. Die neueren Versionen s<strong>in</strong>d <strong>in</strong> der Regel nur teilweise oder gar<br />
nicht <strong>in</strong> den e<strong>in</strong>zelnen Datenbankmanagementsystemen umgesetzt.<br />
Alles, was <strong>in</strong> diesem Buch als <strong>SQL</strong>-Standard, also als „offizielle <strong>SQL</strong>-<br />
Feststellung“ angegeben wird, bezieht sich auf die <strong>SQL</strong>-Dokumente von 2003.<br />
Diese Version ist zum Lernen nicht veraltet. Viele Elemente s<strong>in</strong>d nach wie vor<br />
nicht überall verwirklicht. Aber die Grundlagen, die <strong>in</strong> diesem Buch behandelt<br />
werden, s<strong>in</strong>d unverändert die Grundlagen beim Verständnis von <strong>SQL</strong>.<br />
3.2. Übersicht über Datenbankmanagementsysteme<br />
3.2.1. Allgeme<strong>in</strong><br />
Datenbanken s<strong>in</strong>d Systeme (Daten und Programme) zur Verwaltung von Daten.<br />
Es gibt verschiedene Konzepte:<br />
• Relationale DBMS<br />
• Objektrelationale DBMS<br />
• Objektorientierte DBMS<br />
12
Übersicht über Datenbankmanagementsysteme<br />
Bei Wikipedia gibt es e<strong>in</strong>e Liste der Datenbankmanagementsysteme.<br />
Da <strong>SQL</strong> die Abfragesprache für relationale Datenbanken ist, bezieht sich das<br />
Buch nur auf diese Art von Datenbanken. Das Konzept h<strong>in</strong>ter den relationalen<br />
Datenbanken wird im nächsten Kapitel erläutert.<br />
3.2.2. Kommerzielle Datenbankmanagementsysteme<br />
• DB2<br />
• Informix<br />
• Interbase<br />
• Microsoft <strong>SQL</strong> Server<br />
• Oracle<br />
• Sybase<br />
Microsoft und Oracle bieten auch kostenlose Express-Versionen mit e<strong>in</strong>geschränkten<br />
Möglichkeiten oder Nutzungsrechten an.<br />
3.2.3. Freie Datenbankmanagementsysteme<br />
• Firebird<br />
• My<strong>SQL</strong><br />
• Postgre<strong>SQL</strong><br />
• <strong>SQL</strong>ite<br />
Bei My<strong>SQL</strong> ist das duale Lizenzsystem zu beachten: je nach Nutzungsbed<strong>in</strong>gungen<br />
frei oder kostenpflichtig.<br />
Die Unterscheidung zwischen „frei“ und „kommerziell“ ist nicht korrekt.<br />
Bei den „freien“ DBMS steht die freie Verfügbarkeit im Vordergrund,<br />
auch wenn Kosten anfallen oder es nicht als Open Source-<br />
Projekt entwickelt wird. Bei den „kommerziellen“ DBMS steht das<br />
gewerbliche Interesse des Anbieters im Vordergrund, auch wenn es<br />
kostenlose Lizenzen gibt.<br />
3.2.4. Weitere Systeme zur Datenverwaltung<br />
Die folgenden Dateisysteme enthalten ke<strong>in</strong>e Datenbanken im eigentlichen S<strong>in</strong>ne,<br />
sondern Dateien für strukturierte Daten. Auch diese können (je nach verwendetem<br />
Programm) <strong>in</strong> e<strong>in</strong>geschränktem Umfang mit <strong>SQL</strong>-Befehlen umgehen.<br />
• dBASE und se<strong>in</strong>e Varianten<br />
13
E<strong>in</strong>leitung<br />
• MS-Access<br />
• das Datenbankmodul Base von LibreOffice (OpenOffice.<strong>org</strong>)<br />
• Paradox<br />
Auf diese Systeme gehen wir nicht e<strong>in</strong>. Sie müssen <strong>in</strong> der jeweiligen Programm-<br />
Dokumentation nachlesen, welche Befehle und Optionen möglich s<strong>in</strong>d.<br />
3.3. Schreibweisen im Buch<br />
Das Buch ist grundsätzlich schrittweise aufgebaut. Aber nicht immer können <strong>in</strong><br />
e<strong>in</strong>em Beispiel nur bereits bekannte Begriffe verwendet werden. Wenn Bestandteile<br />
erst <strong>in</strong> e<strong>in</strong>em späteren Kapitel erläutert werden, dann gibt es ausdrückliche<br />
H<strong>in</strong>weise, beispielsweise hier:<br />
• Der INSERT-Befehl <strong>in</strong> DML (2) − Daten speichern 1 muss korrekt mit Datentypen<br />
2 umgehen und benutzt dazu auch Funktionen 3 .<br />
• Auch für das Erstellen e<strong>in</strong>er Tabelle <strong>in</strong> DDL − Struktur der Datenbank 4 muss<br />
genau auf die Datentypen geachtet werden.<br />
Wenn Sie die <strong>SQL</strong>-Begriffe aus dem Englischen <strong>in</strong>s Deutsche übersetzen, sollten<br />
Sie den Zusammenhang auch ohne H<strong>in</strong>- und Herblättern verstehen.<br />
Das Wort Statement bezeichnet e<strong>in</strong>en <strong>SQL</strong>-Befehl, manchmal auch den Teil e<strong>in</strong>es<br />
Befehls.<br />
Die Beispiele für <strong>SQL</strong>-Befehle werden nach den folgenden Regeln geschrieben.<br />
1. Alle <strong>SQL</strong>-Befehle und Schlüsselwörter, wie z. B. SELECT, INSERT, DELETE,<br />
WHERE, ORDER BY, werden vorzugsweise groß geschrieben. <strong>SQL</strong> selbst<br />
verlangt das nicht, sondern arbeitet ohne Berücksichtigung von Groß- und<br />
Kle<strong>in</strong>schreibung (case <strong>in</strong>sensitive); dort werden select, Select und sogar<br />
sElEcT gleich behandelt. 5<br />
2. Eigentlich sollten Tabellen- und Spaltennamen vorzugsweise ebenfalls<br />
groß geschrieben werden, und zwar ohne Anführungszeichen. Aber <strong>in</strong> der<br />
Praxis werden solche Namen meistens „gemischt“ geschrieben.<br />
3. Str<strong>in</strong>g-Literale werden mit e<strong>in</strong>fachen Anführungszeichen gekennzeichnet.<br />
Bitte nicht wundern: Manche DBMS geben für Namen oder Literale andere<br />
Regeln zu den Anführungszeichen vor.<br />
1 Kapitel 9 auf Seite 69<br />
2 Kapitel 13 auf Seite 97<br />
3 Kapitel 14 auf Seite 109<br />
4 Kapitel 10 auf Seite 79<br />
5 Im Buch verh<strong>in</strong>dern zurzeit technische E<strong>in</strong>schränkungen, dass alle Begriffe automatisch großgeschrieben<br />
werden. Wir haben uns aber durchgehend um Großschreibung bemüht.<br />
14
4. <strong>SQL</strong>-Befehle werden mit e<strong>in</strong>em Semikolon abgeschlossen.<br />
Siehe auch<br />
5. Optionale Argumente (d. h. solche, die nicht unbed<strong>in</strong>gt erforderlich s<strong>in</strong>d)<br />
werden <strong>in</strong> [ ] e<strong>in</strong>geschlossen.<br />
6. Variable Argumente (d. h. solche, die mit unterschiedlichem Inhalt vorkommen)<br />
werden <strong>in</strong> < > e<strong>in</strong>geschlossen.<br />
7. Wahlmöglichkeiten werden durch das Pipe-Zeichen | (den senkrechten<br />
Strich) getrennt.<br />
8. Listen werden gekennzeichnet durch , wobei dies e<strong>in</strong>e Kurzform<br />
ist für .<br />
9. Sofern das Ergebnis e<strong>in</strong>er Abfrage im Ausgabefenster aufgeführt wird, handelt<br />
es sich überwiegend nur um e<strong>in</strong>en Teil des Ergebnisses, gleichgültig ob<br />
darauf h<strong>in</strong>gewiesen wird oder nicht.<br />
Die Struktur e<strong>in</strong>es Befehls steht <strong>in</strong> e<strong>in</strong>em Rahmen mit Courier-Schrift:<br />
SELECT <br />
FROM <br />
[ WHERE ]<br />
;<br />
Aufgabe: So wird e<strong>in</strong>e Aufgabenstellung angezeigt, die mit dem danach folgenden<br />
Beispiel erledigt werden soll.<br />
E<strong>in</strong> konkretes Beispiel wird mit e<strong>in</strong>em komplexen Rahmen und unterschiedlichen<br />
Inhalten (zusätzlicher H<strong>in</strong>weis bei Fehlern, mit oder ohne Kopf- und Fußzeile,<br />
mit oder ohne Ausgabefenster) dargestellt:<br />
Quelltext Falsch<br />
SELECT * FROM Beispieltabelle<br />
WHERE Spalte1 = ’Abc’;<br />
Hier steht ggf. e<strong>in</strong>e Meldung.<br />
3.4. Siehe auch<br />
Unter Webl<strong>in</strong>ks 6 stehen viele zusätzliche H<strong>in</strong>weise.<br />
MoWeS 7 bietet e<strong>in</strong>e kostenlose PHP-My<strong>SQL</strong>-Umgebung für den USB-Stick, die<br />
sich gut eignet, My<strong>SQL</strong> zu lernen und zu testen.<br />
6 Anhang D auf Seite 445<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/MoWeS<br />
15
4. Relationale Datenbanken<br />
4.1. Grundstruktur relationaler Datenbanken . . . . . . . . . . . . 17<br />
4.2. Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />
4.3. Spalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
4.4. Verknüpfungen und Schlüssel . . . . . . . . . . . . . . . . . . . . 22<br />
4.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24<br />
Um mit <strong>SQL</strong> auf relationalen Datenbanken zu arbeiten, muss der Anwender e<strong>in</strong><br />
Grundverständnis dafür haben. Dieses soll hier vermittelt werden.<br />
4.1. Grundstruktur relationaler Datenbanken<br />
Bevor man mit der Sprache <strong>SQL</strong> beg<strong>in</strong>nt, muss das Grundpr<strong>in</strong>zip relationaler<br />
Datenbanken geklärt werden. Diese versuchen, e<strong>in</strong>en Bestandteil der Realität <strong>in</strong><br />
e<strong>in</strong>em Datenmodell abzubilden. Für diese Datenmodelle gibt es verschiedene<br />
Abstraktionsebenen. In der Regel unterscheidet man zwischen Entitätenmodell<br />
und Tabellenmodell. Da es sich hier um e<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> handelt, beschränken<br />
wir uns auf das Tabellenmodell, das weniger Theorie voraussetzt.<br />
Grundsätzlich sollen Objekte der Realität betrachtet werden, welche zue<strong>in</strong>ander<br />
<strong>in</strong> Beziehung stehen. Zum e<strong>in</strong>en werden die Objekte mit ihren Eigenschaften untersucht:<br />
Objekte mit gleichen Eigenschaften werden zusammengefasst; Objekte<br />
mit verschiedenen Eigenschaften werden getrennt. Zum anderen werden die<br />
Beziehungen zwischen unterschiedlichen Objekten behandelt. Außerdem geht<br />
es darum, möglichst ke<strong>in</strong>e Informationen unnötigerweise doppelt zu speichern.<br />
4.1.1. Beispielhafte Struktur<br />
In unserer Beispieldatenbank 1 simulieren wir dazu e<strong>in</strong>e Versicherungsgesellschaft.<br />
Unter anderem werden die Verträge mit den dazugehörigen Kunden betrachtet:<br />
1 Kapitel 6 auf Seite 39<br />
17
Relationale Datenbanken<br />
• E<strong>in</strong> Versicherungsvertrag ist durch die Vertragsnummer, das Datum des Abschlusses,<br />
den Versicherungsnehmer, die Art und die Höhe der Police gekennzeichnet.<br />
• E<strong>in</strong> Versicherungsnehmer kann bei der Versicherung e<strong>in</strong>en oder mehrere Verträge<br />
haben. Es kann Kunden geben, die aktuell ke<strong>in</strong>en Vertrag haben; aber es<br />
kann ke<strong>in</strong>en Vertrag ohne zugehörigen Kunden geben.<br />
• E<strong>in</strong> Versicherungsnehmer ist gekennzeichnet durch se<strong>in</strong>en Namen und Anschrift<br />
und bei Personen e<strong>in</strong>en Vornamen und e<strong>in</strong> Geburtsdatum. Außerdem<br />
verfügt er „üblicherweise“ über e<strong>in</strong>e Kundennummer, die ihn e<strong>in</strong>deutig kennzeichnet.<br />
Nun könnte man alle Verträge wie folgt <strong>in</strong> e<strong>in</strong>er e<strong>in</strong>zigen Datei, z. B. e<strong>in</strong>em<br />
Arbeitsblatt e<strong>in</strong>er Tabellenkalkulation, speichern:<br />
NUMMER ABSCHLUSSDATUM ART NAME ANSCHRIFT<br />
BETREUER TELEFON<br />
DG-01 03.05.1974 TK Heckel Obsthandel GmbH 46282 Dorsten<br />
Pohl, Helmut 0201/4014186 Mobil (0171) 4123456<br />
DG-02 11.06.1975 TK Heckel Obsthandel GmbH 46282 Dorsten<br />
Pohl, Helmut 0201/4014186 Mobil (0171) 4123456<br />
DG-03 25.02.1977 TK Heckel Obsthandel GmbH 46282 Dorsten<br />
Pohl, Helmut 0201/4014186 Mobil (0171) 4123456<br />
XC-01 07.08.1974 HP Antonius, Bernhard 45892 Gelsenkirchen<br />
Braun, Christian 0201/4014726 Mobil (0170) 8351647<br />
RH-01 11.12.1976 VK Cornelsen, Dorothea 44577 Castrop-Rauxel<br />
Braun, Christian 0201/4014726 Mobil (0170) 8351647<br />
Dies ist offensichtlich unübersichtlich. Auch werden die persönlichen Daten<br />
e<strong>in</strong>es Versicherungsnehmers und se<strong>in</strong>es Betreuers „zu oft“ gespeichert. Es ist<br />
also s<strong>in</strong>nvoll, dies zu trennen − zum e<strong>in</strong>en die Verträge:<br />
NUMMER ABSCHLUSSDATUM ART KUNDE BETREUER<br />
DG-01 03.05.1974 TK 1 9<br />
DG-02 11.06.1975 TK 1 9<br />
DG-03 25.02.1977 TK 1 9<br />
XC-01 07.08.1974 HP 2 10<br />
RH-01 11.12.1976 VK 3 10<br />
Zum anderen die Kunden:<br />
18<br />
NUMMER NAME ANSCHRIFT<br />
1 Heckel Obsthandel GmbH 46282 Dorsten<br />
2 Antonius, Bernhard 45892 Gelsenkirchen<br />
3 Cornelsen, Dorothea 44577 Castrop-Rauxel
Und schließlich die zuständigen Sachbearbeiter (Betreuer):<br />
NUMMER NAME TELEFON MOBIL<br />
9 Pohl, Helmut 0201/4014186 (0171) 4123456<br />
10 Braun, Christian 0201/4014726 (0170) 8351647<br />
Grundstruktur relationaler Datenbanken<br />
Durch die Angabe der Nummer (Kunde bzw. Betreuer) <strong>in</strong> den Aufstellungen ist<br />
e<strong>in</strong>e klare Verb<strong>in</strong>dung hergestellt. Außerdem zeigt die Wiederholung des Wortes<br />
„Mobil“ an, dass dieser Wert <strong>in</strong> e<strong>in</strong>er eigenen Spalte e<strong>in</strong>getragen werden sollte.<br />
Diese Trennung der Daten erfolgt bei der Normalisierung 2 e<strong>in</strong>er Datenbank.<br />
4.1.2. Eigenschaften der Objekte<br />
Vor allem müssen wir über die Eigenschaften der verschiedene Objekte nachdenken.<br />
Es gibt solche, die e<strong>in</strong> Objekt e<strong>in</strong>deutig kennzeichnen, andere, die immer<br />
anzugeben s<strong>in</strong>d, und weitere, die nur manchmal wichtig s<strong>in</strong>d.<br />
Für e<strong>in</strong>en Versicherungsnehmer gibt es u. a. folgende Eigenschaften:<br />
• NUMMER ist e<strong>in</strong>deutig und e<strong>in</strong>e Pflichtangabe.<br />
• NAME, PLZ, ORT s<strong>in</strong>d Pflichtangaben, ihre Inhalte können aber bei mehreren<br />
Versicherungsnehmern vorkommen.<br />
• VORNAME und GEBURTSDATUM s<strong>in</strong>d bei natürlichen Personen Pflicht, aber<br />
bei juristischen Personen (Firmen) irrelevant.<br />
Für e<strong>in</strong>en Versicherungsvertrag gibt es u. a. folgende Eigenschaften:<br />
• NUMMER ist e<strong>in</strong>deutig und e<strong>in</strong>e Pflichtangabe.<br />
• Auch die anderen bisher genannten Eigenschaften s<strong>in</strong>d Pflicht, aber sie s<strong>in</strong>d<br />
nicht e<strong>in</strong>deutig.<br />
Die verschiedenen Objekte stehen über die Kundennummer mite<strong>in</strong>ander <strong>in</strong><br />
Beziehung. Im Beispiel geht es um die Verknüpfung: „E<strong>in</strong> Kunde kann e<strong>in</strong>en<br />
oder mehrere Verträge oder auch ke<strong>in</strong>en haben.“ Der letzte Fall „ke<strong>in</strong>en Vertrag“<br />
kommt erst am Schluss des Buches vor, wenn wir weitere Testdaten erzeugen 3 .<br />
In e<strong>in</strong>em relationalen Datenbanksystem (DBMS) werden die Objekte als Tabellen<br />
dargestellt. Die Eigenschaften werden über die Spalten der Tabelle abgebildet.<br />
E<strong>in</strong>e Zeile (wahlweise als Datensatz bezeichnet) <strong>in</strong> der Tabelle entspricht<br />
genau e<strong>in</strong>em Objekt <strong>in</strong> der Realität. Die Beziehungen zwischen Tabellen werden<br />
über Fremdschlüssel abgebildet.<br />
2 Kapitel 5 auf Seite 25<br />
3 Kapitel 36 auf Seite 407<br />
19
Relationale Datenbanken<br />
4.2. Tabellen<br />
Tabellen s<strong>in</strong>d zweidimensional gegliederte Informationen. Die Tabelle selbst hat<br />
e<strong>in</strong>en Namen. Anzahl, Bezeichnung und Typ der Spalten (auch Felder oder Attribute<br />
genannt) werden durch die Tabelle def<strong>in</strong>iert. Die Zeilen (Anzahl und Inhalte)<br />
s<strong>in</strong>d variabel und entsprechen jeweils e<strong>in</strong>em wirklichen Objekt des Typs, der<br />
<strong>in</strong> der Tabelle gesammelt wird.<br />
So sieht e<strong>in</strong> Ausschnitt aus der Tabelle Abteilung der Beispieldatenbank aus:<br />
Spaltenname ID KURZBEZEICHNUNG BEZEICHNUNG ORT<br />
Datentyp <strong>in</strong>teger varchar(10) varchar(30) varchar(30)<br />
Zeilen 1 Fibu F<strong>in</strong>anzbuchhaltung Dortmund<br />
2 Albu Anlagenbuchhaltung Dortmund<br />
5 Vert Vertrieb Essen<br />
6 Lagh Lagerhaltung Bochum<br />
Sie enthält also 4 Spalten und 12 Zeilen, von denen hier 4 angezeigt werden.<br />
Dabei handelt es sich um e<strong>in</strong>e Basistabelle (TABLE), die tatsächlich Informationen<br />
speichert. Daneben gibt es „virtuelle“ Arten von Tabellen, nämlich die VIEW<br />
als Sichttabelle und die Ergebnismenge (Resultset) als Ergebnis e<strong>in</strong>er SELECT-<br />
Abfrage.<br />
E<strong>in</strong>e View enthält e<strong>in</strong>e fest vordef<strong>in</strong>ierte Abfrage, die sich auf e<strong>in</strong>e oder mehrere<br />
Tabellen bezieht. Aus Sicht des Anwenders sieht sie wie e<strong>in</strong>e Basistabelle aus,<br />
ist aber nur e<strong>in</strong>e Abbildung realer Tabellen. E<strong>in</strong> Beispiel wäre e<strong>in</strong> Ausschnitt aus<br />
e<strong>in</strong>er View Mitarbeiter_Bochum, nämlich der Mitarbeiter, die zu e<strong>in</strong>er der Abteilungen<br />
<strong>in</strong> Bochum gehören:<br />
PERSNR NAME VORNAME BEZEICHNUNG<br />
60001 Aagenau Karol<strong>in</strong> Lagerhaltung<br />
60002 P<strong>in</strong>kart Petra Lagerhaltung<br />
70001 Olschewski Pjotr Produktion<br />
70002 Nordmann Jörg Produktion<br />
120001 Carlsen Zacharias Forschung und Entwicklung<br />
120002 Baber Yvonne Forschung und Entwicklung<br />
Näheres zu Sichttabellen steht im Kapitel Erstellen von Views 4 .<br />
Jede Ergebnismenge hat zwangsläufig die Struktur e<strong>in</strong>er Tabelle.<br />
Ergänzend sei darauf h<strong>in</strong>gewiesen, dass auch das DBMS selbst sämtliche Schemata<br />
<strong>in</strong> Systemtabellen speichert. Beispielsweise stehen bei Interbase und Firebird<br />
die Def<strong>in</strong>ition von TABLEs und VIEWs <strong>in</strong> der Tabelle RDB$RELATIONS und<br />
die dazugehörigen Felder (Spalten) <strong>in</strong> RDB$RELATION_FIELDS.<br />
4 Kapitel 27 auf Seite 271<br />
20
4.3. Spalten<br />
Spalten<br />
Spalten bezeichnen die Elemente e<strong>in</strong>er Tabellenstruktur. Sie werden e<strong>in</strong>deutig<br />
gekennzeichnet durch ihren Namen; diese E<strong>in</strong>deutigkeit gilt <strong>in</strong>nerhalb e<strong>in</strong>er Tabelle,<br />
verschiedene Tabellen dürfen Spalten mit gleichem Namen (z. B. ID) haben.<br />
Außerdem gehört zur Def<strong>in</strong>ition e<strong>in</strong>er Spalte der Datentyp; dies wird im<br />
Kapitel Datentypen 5 behandelt.<br />
Die Spalten (<strong>in</strong>nerhalb e<strong>in</strong>er Tabelle) werden <strong>in</strong>tern nach Position geordnet.<br />
Spalten an verschiedenen Positionen können denselben Datentyp haben, aber<br />
niemals denselben Namen. Auf e<strong>in</strong>e bestimmte Spalte wird fast immer über den<br />
Namen zugegriffen, nur äußerst selten über die Position.<br />
E<strong>in</strong>e Spalte hat also e<strong>in</strong>en Namen und e<strong>in</strong>en Datentyp. Jede Zeile <strong>in</strong> e<strong>in</strong>er Tabelle<br />
hat genau e<strong>in</strong>en Wert für jede Spalte; wenn mehrere gleichartige Werte e<strong>in</strong>getragen<br />
werden sollen, werden mehrere Spalten benötigt. Jeder Wert <strong>in</strong> e<strong>in</strong>er Zeile<br />
entspricht dem Datentyp der Spalte.<br />
H<strong>in</strong>weis: In dieser H<strong>in</strong>sicht unterscheiden sich Datenbank-Tabellen<br />
ganz wesentlich von denjenigen e<strong>in</strong>er Tabellenkalkulation, bei der<br />
der Datentyp e<strong>in</strong>zelner Zellen abweichen kann von der Spaltendef<strong>in</strong>ition<br />
und e<strong>in</strong>zelne Zellen zusammengezogen werden können.<br />
Die Eigenschaft NULL für e<strong>in</strong>en Wert ist e<strong>in</strong>e Besonderheit, die vor allem E<strong>in</strong>steiger<br />
gerne verwirrt. Dies bedeutet, dass e<strong>in</strong>er Zelle (noch) ke<strong>in</strong> Wert zugeordnet<br />
worden ist. E<strong>in</strong>e bessere Bezeichnung wäre etwas wie UNKNOWN; aber es heißt<br />
nun leider NULL. Bitte beachten Sie deshalb:<br />
• Für e<strong>in</strong> Textfeld werden folgende Werte unterschieden:<br />
1. Der Wert ” ist e<strong>in</strong> leerer Text.<br />
2. Der Wert ’ ’ ist e<strong>in</strong> Text, der genau e<strong>in</strong> Leerzeichen enthält.<br />
3. Der Wert NULL enthält nichts.<br />
• Für e<strong>in</strong> logisches Feld (Datentyp boolean) wird dies unterschieden:<br />
1. Der Wert TRUE bedeutet „wahr“.<br />
2. Der Wert FALSE bedeutet „falsch“.<br />
3. Der Wert NULL bedeutet „unbekannt“.<br />
• Für e<strong>in</strong> Zahlenfeld wird es so unterschieden:<br />
1. Der Wert 0 ist e<strong>in</strong>e bestimmte Zahl, genauso gut wie jede andere.<br />
2. Der Wert NULL bedeutet „unbekannt“.<br />
5 Kapitel 13 auf Seite 97<br />
21
Relationale Datenbanken<br />
i H<strong>in</strong>weis<br />
Der Wert NULL steht nicht für e<strong>in</strong>en bestimmten Wert, sondern kann<br />
immer als „unbekannt“ <strong>in</strong>terpretiert werden.<br />
Dies kann bei jeder Spalte allgeme<strong>in</strong> festgelegt werden: Die Eigenschaft „NOT<br />
NULL“ bestimmt, dass <strong>in</strong> dieser Spalte der NULL-Wert nicht zulässig ist; wenn<br />
Daten gespeichert werden, muss immer e<strong>in</strong> Wert e<strong>in</strong>getragen werden (und sei es<br />
e<strong>in</strong> leerer Text). Wenn dies nicht festgelegt wurde, muss ke<strong>in</strong> Wert e<strong>in</strong>getragen<br />
werden; der Feld<strong>in</strong>halt ist dann NULL.<br />
Bei SELECT-Abfragen (vor allem auch bei Verknüpfungen mehrerer Tabellen)<br />
gibt es unterschiedliche Ergebnisse je nachdem, ob NULL-Werte vorhanden s<strong>in</strong>d<br />
und ob sie berücksichtigt oder ausgeschlossen werden sollen.<br />
4.4. Verknüpfungen und Schlüssel<br />
Mit diesen Verfahren werden die Tabellen <strong>in</strong> Beziehung zue<strong>in</strong>ander gebracht.<br />
Auch dies folgt der Vorstellung, dass die Wirklichkeit abgebildet werden soll.<br />
4.4.1. Verknüpfungen<br />
Diese, nämlich die Beziehungen zwischen den Tabellen, s<strong>in</strong>d e<strong>in</strong> Kern e<strong>in</strong>es relationalen<br />
Datenbanksystems. In der Beispieldatenbank 6 bestehen unter anderem<br />
folgende Beziehungen:<br />
• Die Tabelle Mitarbeiter verweist auf folgende Tabelle:<br />
1. Jeder Mitarbeiter gehört zu e<strong>in</strong>em E<strong>in</strong>trag der Tabelle Abteilung.<br />
• Die Tabelle Zuordnung_SF_FZ verb<strong>in</strong>det Schadensfälle und Fahrzeuge und<br />
verweist auf folgende Tabellen:<br />
1. Jedes beteiligte Fahrzeug gehört zu e<strong>in</strong>em E<strong>in</strong>trag der Tabelle Fahrzeug.<br />
2. Jeder Schadensfall muss <strong>in</strong> der Tabelle Schadensfall registriert se<strong>in</strong>.<br />
• Die Tabelle Versicherungsvertrag verweist auf folgende Tabellen:<br />
1. Jeder Vertrag wird von e<strong>in</strong>er Person aus der Tabelle Mitarbeiter bearbeitet.<br />
2. Zu jedem Vertrag gehört e<strong>in</strong> E<strong>in</strong>trag der Tabelle Fahrzeug.<br />
3. Zu jedem Vertrag gehört e<strong>in</strong> E<strong>in</strong>trag der Tabelle Versicherungsnehmer.<br />
6 Anhang A auf Seite 419<br />
22
Durch diese Verknüpfungen werden mehrere Vorteile erreicht:<br />
Verknüpfungen und Schlüssel<br />
• Informationen werden nur e<strong>in</strong>mal gespeichert.<br />
Beispiel: Der Name und Sitz e<strong>in</strong>er Abteilung muss nicht bei jedem Mitarbeiter<br />
notiert werden.<br />
• Änderungen werden nur e<strong>in</strong>mal v<strong>org</strong>enommen.<br />
Beispiel: Wenn die Abteilung umzieht, muss nur der E<strong>in</strong>trag <strong>in</strong> der Tabelle Abteilung<br />
geändert werden und nicht die Angaben bei jedem Mitarbeiter.<br />
• Der Zusammenhang der Daten wird gewährleistet.<br />
Beispiel: E<strong>in</strong> Versicherungsnehmer kann nicht gelöscht werden, solange er<br />
noch mit e<strong>in</strong>em Vertrag registriert ist.<br />
Damit dies verwirklicht werden kann, werden geeignete Maßnahmen benötigt:<br />
1. Jeder Datensatz muss durch e<strong>in</strong>en Schlüssel e<strong>in</strong>deutig identifiziert werden.<br />
2. Die Schlüssel der verschiedenen mite<strong>in</strong>ander verknüpften Datensätze<br />
müssen sich zuordnen lassen.<br />
4.4.2. Schlüssel<br />
PrimaryKey (PK): Der Primärschlüssel ist e<strong>in</strong>e Spalte <strong>in</strong> der Tabelle, durch die<br />
e<strong>in</strong>deutig jede Zeile identifiziert wird (gerne mit dem Namen ID). Es kann auch<br />
e<strong>in</strong>e Komb<strong>in</strong>ation von Spalten als e<strong>in</strong>deutig festgelegt werden; das ist aber selten<br />
s<strong>in</strong>nvoll. In der Regel sollte diese Spalte auch ke<strong>in</strong>e andere „<strong>in</strong>haltliche“ Bedeutung<br />
haben als die ID.<br />
Beispiele: Die Komb<strong>in</strong>ation Name/Vorname kann bei kle<strong>in</strong>en Datenmengen<br />
zwar praktisch e<strong>in</strong>deutig se<strong>in</strong>, aber niemals theoretisch; irgendwann<br />
kommt e<strong>in</strong> zweiter „Lucas Müller“, und dann? Bei e<strong>in</strong>em<br />
Mehrbenutzersystem werden häufig mehrere E<strong>in</strong>träge „gleichzeitig“<br />
gespeichert; es ist besser, wenn das DBMS die Vergabe der ID selbst<br />
steuert, als dass die Benutzer sich absprechen müssen. In der Beispieldatenbank<br />
wird deshalb <strong>in</strong> der Tabelle Mitarbeiter zwischen der<br />
automatisch vergebenen ID und der ebenfalls e<strong>in</strong>deutigen Personalnummer<br />
unterschieden.<br />
ForeignKey (FK): Über Fremdschlüssel werden die Tabellen mite<strong>in</strong>ander verknüpft.<br />
E<strong>in</strong>em Feld <strong>in</strong> der e<strong>in</strong>en Tabelle wird e<strong>in</strong> Datensatz <strong>in</strong> e<strong>in</strong>er anderen Tabelle<br />
zugeordnet; dieser wird über den Primärschlüssel bereitgestellt. Es kann<br />
auch e<strong>in</strong>e Komb<strong>in</strong>ation von Spalten verwendet werden; da sich der Fremdschlüssel<br />
aber auf e<strong>in</strong>en Primärschlüssel der anderen Tabelle beziehen muss, ist<br />
dies ebenso selten s<strong>in</strong>nvoll. Die „Datenbank-Theorie“ geht sogar soweit, dass die<br />
Schlüsselfelder dem Anwender gar nicht bekannt se<strong>in</strong> müssen.<br />
23
Relationale Datenbanken<br />
Beispiele stehen <strong>in</strong> der obigen Aufstellung. Nähere Erläuterungen<br />
s<strong>in</strong>d im Kapitel Fremdschlüssel-Beziehungen 7 zu f<strong>in</strong>den.<br />
Index: Er dient dazu, e<strong>in</strong>en Suchbegriff schnell <strong>in</strong>nerhalb e<strong>in</strong>er Tabelle zu f<strong>in</strong>den.<br />
Die Mehrzahl lautet nach Duden ‚Indizes‘, auch ‚Indexe‘ ist möglich; <strong>in</strong> der<br />
EDV wird oft auch der englische Plural ‚Indexes‘ verwendet. Dies gehört zwar nicht<br />
zum „Kernbereich“ e<strong>in</strong>es relationalen DBMS, passt aber (auch wegen der umgangssprachlichen<br />
Bedeutung des Wortes „Schlüssel“) durchaus hierher.<br />
• Der Primärschlüssel ist e<strong>in</strong> Suchbegriff, mit dem e<strong>in</strong>deutig e<strong>in</strong> Datensatz gefunden<br />
werden kann.<br />
• Mit e<strong>in</strong>em Index kann die Suche nach e<strong>in</strong>em bestimmten Datensatz oder e<strong>in</strong>er<br />
Datenmenge beschleunigt werden.<br />
Beispiel: die Suche nach Postleitzahl<br />
• Die Werte e<strong>in</strong>er Spalte oder e<strong>in</strong>er Komb<strong>in</strong>ation von Spalten sollen e<strong>in</strong>deutig<br />
se<strong>in</strong>.<br />
Beispiel: die Personalnummer<br />
Nähere Erläuterungen s<strong>in</strong>d <strong>in</strong> den Kapiteln im Teil Erweiterungen ab DDL − E<strong>in</strong>zelheiten<br />
8 zu f<strong>in</strong>den.<br />
4.5. Siehe auch<br />
Über Wikipedia s<strong>in</strong>d weitere Informationen zu f<strong>in</strong>den:<br />
• Relationale Datenbank 9<br />
• Entitätenmodell 10 und Entity-Relationship-Modell 11<br />
• Normalisierung 12<br />
• Tabellenkalkulation 13 im Gegensatz zu Datenbank-Tabellen<br />
• Nullwert 14<br />
7 Kapitel 29 auf Seite 305<br />
8 Kapitel 28 auf Seite 285<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Relationale%20Datenbank<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/Entit%c3%a4t%20(Informatik)<br />
11 http://de.wikipedia.<strong>org</strong>/wiki/Entity-Relationship-Modell<br />
12 http://de.wikipedia.<strong>org</strong>/wiki/Normalisierung%20(Datenbank)<br />
13 http://de.wikipedia.<strong>org</strong>/wiki/Tabellenkalkulation<br />
14 http://de.wikipedia.<strong>org</strong>/wiki/Nullwert<br />
24
5. Normalisierung<br />
5.1. Grundgedanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25<br />
5.2. Die 1. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . 28<br />
5.3. Die 2. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />
5.4. Die 3. Normalform . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
5.5. Zusätzliche Maßnahmen . . . . . . . . . . . . . . . . . . . . . . . 34<br />
5.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
5.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />
In diesem Kapitel werden e<strong>in</strong>ige Überlegungen angestellt, wie e<strong>in</strong>e Datenbank<br />
konzipiert werden soll.<br />
Dieses Kapitel geht zwar über die Anforderungen e<strong>in</strong>er <strong>E<strong>in</strong>führung</strong> h<strong>in</strong>aus. Denn<br />
es richtet sich weniger an „e<strong>in</strong>fache“ Anwender, die e<strong>in</strong>e vorhandene Datenbank<br />
benutzen wollen, sondern an (künftige) Programmentwickler, die e<strong>in</strong>e Datenbank<br />
entwerfen und erstellen. Aber wann soll man darüber sprechen, wenn nicht<br />
am Anfang?<br />
5.1. Grundgedanken<br />
Schon das e<strong>in</strong>fache Muster e<strong>in</strong>er unzureichenden Tabelle wie im vorigen Kapitel<br />
weist auf e<strong>in</strong> paar Forderungen h<strong>in</strong>, die offensichtlich an e<strong>in</strong>e s<strong>in</strong>nvolle Struktur<br />
gestellt werden sollten:<br />
Redundanz vermeiden: E<strong>in</strong>e Information, die an mehreren Stellen benötigt<br />
wird, soll nur e<strong>in</strong>mal gespeichert werden. Dadurch müssen auch sämtliche<br />
Änderungen, die solche Informationen betreffen, nur an e<strong>in</strong>er Stelle<br />
erledigt werden.<br />
Im Beispiel waren das u. a. die Angaben zum Fahrzeughalter und zum<br />
Sachbearbeiter (mit se<strong>in</strong>en Telefonnummern), die bei jedem Vertrag angegeben<br />
werden.<br />
Wiederholungen trennen: Die Zusammenfassung gleichartiger Informationen<br />
ist nicht sehr übersichtlich. Im vorigen Kapitel standen zunächst Festnetzund<br />
Mobil-Rufnummer <strong>in</strong> e<strong>in</strong>er Spalte. Das ist praktisch, wenn man sich<br />
25
Normalisierung<br />
ke<strong>in</strong>e Gedanken machen will, welche und wie viele Kontaktnummern es<br />
gibt. Es ist ziemlich unpraktisch, wenn man gezielt e<strong>in</strong>zelne Nummern suchen<br />
will oder von e<strong>in</strong>er Nummer auf den Sachbearbeiter schließen will.<br />
Besser s<strong>in</strong>d Festnetz- und Mobilnummer getrennt zu speichern.<br />
Primärschlüssel verwenden: Jeder Datensatz muss e<strong>in</strong>deutig identifiziert werden,<br />
damit die Informationen verwendet werden können.<br />
In der ungenügenden Tabelle wurde wegen der Zeilennummern darauf<br />
verzichtet. Aber selbst wenn man die Suche <strong>in</strong> zigtausend Zeilen für machbar<br />
hielte, spätestens bei der Aufteilung <strong>in</strong> mehrere Tabellen braucht man<br />
Werte, um die Zusammenhänge e<strong>in</strong>deutig zu regeln.<br />
Bei den bisherigen Überlegungen handelt es sich nur um e<strong>in</strong> paar Gedanken, die<br />
sich e<strong>in</strong>em aufdrängen. Das wollen wir nun besser strukturieren. Überlegen wir<br />
uns zunächst, welche Informationen benötigt werden.<br />
Grundlegende Daten<br />
Für die Verträge müssen die folgenden Angaben gespeichert werden.<br />
• die Vertragsdaten selbst, also Nummer, Abschlussdatum und Art des Vertrags<br />
(HP = Haftpflicht, TK = Teilkasko, VK = Vollkasko) und der Prämienberechnung<br />
• der Vertragspartner mit Name, Anschrift, Telefonnummern, dazu bei natürlichen<br />
Personen Geschlecht und Geburtsdatum sowie bei juristischen Personen<br />
der E<strong>in</strong>trag im Handelsregister o. ä.<br />
• das versicherte Fahrzeug mit Kennzeichen, Hersteller und Typ (die Farbe ist<br />
e<strong>in</strong>e Zusatz<strong>in</strong>formation, die bei manchen Suchv<strong>org</strong>ängen hilfreich se<strong>in</strong> kann)<br />
• der zuständige Sachbearbeiter mit Name, Abteilung und Telefonnummern<br />
E<strong>in</strong> Teil der Angaben muss immer vorhanden se<strong>in</strong>, e<strong>in</strong> anderer Teil je nach Situation<br />
(Geschlecht bei Personen) und weitere nur bei Bedarf (Mobiltelefon).<br />
Schnell stellen wir fest, dass e<strong>in</strong> Versicherungsnehmer auch mehrere Fahrzeuge<br />
versichern kann. Andererseits kann e<strong>in</strong> Fahrzeug zu mehreren Verträgen gehören,<br />
wenn Haftpflicht und Vollkasko getrennt abgeschlossen werden oder wenn<br />
zur Haftpflicht vorübergehend Vollkasko fürs Ausland h<strong>in</strong>zukommt. Jeder Vertrag<br />
ist e<strong>in</strong> eigener Datensatz; also stehen bei jedem Datensatz die Angaben des<br />
Vertragspartners und des Fahrzeugs. Um alle Verträge e<strong>in</strong>es Kunden geme<strong>in</strong>sam<br />
anzuzeigen, brauchen wir folglich e<strong>in</strong> paar Angaben zur Organisation:<br />
• Kundennummer und laufende Nummer se<strong>in</strong>er Verträge als e<strong>in</strong>deutiger Suchbegriff<br />
(als Index, vielleicht auch als Primärschlüssel)<br />
• Vertragsnummer als e<strong>in</strong>deutiger Wert<br />
• Fahrzeug-Kennzeichen als Wert, der <strong>in</strong> der Regel nur e<strong>in</strong>mal vorkommt<br />
26
Schadensfälle<br />
Grundgedanken<br />
Nun sollen zu e<strong>in</strong>em Vertrag auch die Schadensfälle gespeichert werden. Wir benötigen<br />
also e<strong>in</strong>e oder mehrere Spalten mit den erforderlichen Angaben (Datum<br />
und Art des Schadens, Sachbearbeiter der Schadensabwicklung, andere beteiligte<br />
Fahrzeuge); aber wie wird das am besten gespeichert?<br />
• e<strong>in</strong>e geme<strong>in</strong>same Spalte für alle diese Angaben für alle Schadensfälle (denn die<br />
Mehrzahl der Fahrzeuge fährt schadensfrei)<br />
• oder je e<strong>in</strong>e Spalte für alle Angaben e<strong>in</strong>es Schadensfalls (werden dann zwei<br />
oder drei Spalten benötigt, oder muss man für „Mehrfach-Sünder“ gleich zehn<br />
Spalten vorsehen?)<br />
• oder e<strong>in</strong>zelne Datensätze für jeden Schaden e<strong>in</strong>es Fahrzeugs (sodass sich alle<br />
Fahrzeug- und Vertragsdaten <strong>in</strong> diesen Datensätzen wiederholen und nur die<br />
Schadensangaben unterscheiden)<br />
Ungeklärt bleibt dabei noch dieses Problem: Wie viele versicherte Schadensgegner<br />
gibt es denn − ke<strong>in</strong>e (wenn e<strong>in</strong> Reh angefahren wird), e<strong>in</strong>en (z. B. beim Parken)<br />
oder viele (bei e<strong>in</strong>em Auffahrunfall auf der Autobahn)?<br />
Entscheiden wir uns deshalb provisorisch für e<strong>in</strong> eigenes Arbeitsblatt Schadensfälle<br />
mit folgender Struktur:<br />
• je e<strong>in</strong>e Spalte für die Angaben, die direkt zum Schadensfall gehören<br />
• vorläufig 5 Gruppen für maximal 5 beteiligte Fahrzeuge<br />
• jede dieser Gruppen enthält <strong>in</strong> e<strong>in</strong>zelnen Spalten Fahrzeug-Kennzeichen und<br />
die Halter-Angaben (Name, Anschrift)<br />
Damit stehen die Fahrzeugdaten <strong>in</strong> beiden Arbeitsblättern. Als praktisches Problem<br />
kommt h<strong>in</strong>zu: Für die Schadenshäufigkeit e<strong>in</strong>es Fahrzeugs muss man es <strong>in</strong><br />
fünf Spalten heraussuchen. Die Beschränkung auf fünf beteiligte Fahrzeuge wird<br />
im nächsten Schritt aufgelöst, machen wir uns dazu ke<strong>in</strong>e weiteren Gedanken.<br />
Auf diesem Weg kann jedenfalls noch ke<strong>in</strong>e s<strong>in</strong>nvolle Struktur erreicht werden.<br />
„Update-Anomalien“<br />
Mit diesem Begriff werden die folgenden Unklarheiten bezeichnet. Dabei handelt<br />
es sich um weitere Probleme bei e<strong>in</strong>er ungenügenden Struktur.<br />
E<strong>in</strong>fügen-Anomalie: In der ersten Tabelle s<strong>in</strong>d u. a. die Sachbearbeiter enthalten.<br />
Es wäre s<strong>in</strong>nvoll, auch alle anderen Mitarbeiter der Gesellschaft hier<br />
e<strong>in</strong>zutragen. Aber bei e<strong>in</strong>em Vertrag werden zw<strong>in</strong>gend Vertragsnummer<br />
und Abschlussdatum benötigt; dieser Zwang verh<strong>in</strong>dert das Speichern der<br />
Teil<strong>in</strong>formation Sachbearbeiter ohne Bezug auf e<strong>in</strong>en Vertrag.<br />
27
Normalisierung<br />
Löschen-Anomalie: Wenn e<strong>in</strong> Vertrag gekündigt und abgelaufen ist und deshalb<br />
gelöscht wird, s<strong>in</strong>d <strong>in</strong> der ersten Tabelle auch alle Angaben zum Vertragspartner<br />
verloren. Wenn er drei Tage später e<strong>in</strong>en neuen Vertrag abschließt,<br />
müssen alle Angaben neu aufgenommen werden. Und was soll<br />
mit Schadensfällen geschehen, die zu diesem Vertrag gespeichert s<strong>in</strong>d?<br />
Ändern-Anomalie: Wenn der Sachbearbeiter wechselt, muss se<strong>in</strong> Name bei allen<br />
von ihm betreuten Verträgen geändert werden, obwohl e<strong>in</strong>e e<strong>in</strong>zige Änderung<br />
ausreichend wäre. (Dies ist auf die Datenredundanz zurückzuführen,<br />
dass nämlich dieser H<strong>in</strong>weis vielfach gespeichert ist statt e<strong>in</strong>malig.)<br />
All diesen Problemen wollen wir nun durch e<strong>in</strong>e deutlich verbesserte Datenstruktur<br />
begegnen. Nehmen wir dazu zunächst an, dass alle benötigten Informationen<br />
<strong>in</strong> den beiden Arbeitsblättern Verträge und Schadensfälle der Tabellenkalkulation<br />
stehen, und beg<strong>in</strong>nen wir, dies s<strong>in</strong>nvoll zu strukturieren.<br />
5.2. Die 1. Normalform<br />
Am „grausamsten“ für den Aufbau der Tabelle und die praktische Arbeit ist die<br />
vielfache Wiederholung gleicher Informationen.<br />
Sowohl beim Namen des Fahrzeughalters als auch bei den Mitarbeitern steht der<br />
Name bisher <strong>in</strong> e<strong>in</strong>er Spalte <strong>in</strong> der Form „Nachname, Vorname“, was als Suchbegriff<br />
geeignet ist. Nun benötigen wir aber auch e<strong>in</strong>e persönliche Anrede (Name<br />
mit Titel) und e<strong>in</strong>e Briefanschrift (Vorname, Name). Soll dies <strong>in</strong> weiteren Spalten<br />
gespeichert werden? Ne<strong>in</strong>, denn all dies kann aus den E<strong>in</strong>zelangaben „Name“<br />
und „Vorname“ zusammengesetzt werden.<br />
E<strong>in</strong> Schadensfall ist bisher auf fünf beteiligte Fahrzeuge beschränkt, auch wenn<br />
selten mehr als zwei Beteiligungen benötigt werden. Wenn nun e<strong>in</strong> sechstes,<br />
zehntes oder zwanzigstes Fahrzeug beteiligt ist, muss dann jedesmal die Tabellenstruktur<br />
(und jedes Makro, das auf diese Spalten zugreift) geändert werden?!<br />
Damit haben wir bereits zwei Regeln, die als Def<strong>in</strong>ition der ersten Normalform<br />
gelten. H<strong>in</strong>zu kommt e<strong>in</strong>e dritte Regel, die zwar formal nicht erforderlich ist; aber<br />
aus praktischen Gründen gibt es ke<strong>in</strong>e s<strong>in</strong>nvolle Lösung ohne diese Regel.<br />
28<br />
i Die 1. Normalform<br />
1. Jede Spalte enthält nur unteilbare (atomare, atomische) Werte.<br />
2. Spalten, die gleiche oder gleichartige Informationen enthalten, s<strong>in</strong>d <strong>in</strong><br />
eigene Tabellen (Relationen) auszulagern.<br />
3. Jede Tabelle enthält e<strong>in</strong>en Primärschlüssel.
Verletzung der 1. Normalform<br />
Unsere Ausgangstabelle verstößt an vielen Stellen gegen diese Regeln:<br />
• Zusammengesetzte Werte bef<strong>in</strong>den sich z. B. an folgenden Stellen:<br />
Die 1. Normalform<br />
• Fahrzeughalter: Name und Vorname, PLZ und Ort, Straße und Nummer<br />
• Sachbearbeiter: Name und Vorname, Festnetz- und Mobilnummer<br />
• Wiederholungen f<strong>in</strong>den sich vor allem hier:<br />
• mehrere Fahrzeuge bei e<strong>in</strong>em Schadensfall<br />
V<strong>org</strong>ehen zur Herstellung der 1. Normalform<br />
Es werden also zwei Schritte benötigt:<br />
1. Zusammengesetzte Werte werden <strong>in</strong> E<strong>in</strong>zel<strong>in</strong>formationen aufgeteilt: je<br />
e<strong>in</strong>e Spalte für jeden unteilbaren Wert.<br />
2. Wiederholungen werden <strong>in</strong> getrennte Tabellen ausgelagert; welche Daten<br />
zusammengehören, wird durch e<strong>in</strong>e laufende Nummer angegeben.<br />
E<strong>in</strong>e erste Verbesserung<br />
An jeder Stelle, die Namen oder Anschrift enthält, werden getrennte Spalten verwendet,<br />
beispielsweise im Arbeitsblatt Verträge:<br />
Kundennummer Vertrag Abschluss Halter_Name Halter_PLZ Halter_Ort<br />
Sachbearbeiter_N Sachbearbeiter_V Telefon<br />
1 DG-01 03 05 1974 Heckel Obsthandel GmbH 46282 Dorsten<br />
Pohl Helmut 0201/4014186<br />
1 DG-02 04 07 1974 Heckel Obsthandel GmbH 46282 Dorsten<br />
Pohl Helmut 0201/4014186<br />
Die Tabelle Schadensfälle wird auf die eigentlichen Daten beschränkt; die beteiligten<br />
Fahrzeuge stehen <strong>in</strong> e<strong>in</strong>er eigenen Tabelle Zuordnungen.<br />
Nummer Datum Schadensort Beschreibung<br />
Sachbearbeiter_N Sachbearbeiter_V Telefon<br />
1 02 03 2007 Reckl<strong>in</strong>ghausen, Bergknappenstr 144 Gartenzaun gestreift<br />
Sch<strong>in</strong>dler Christ<strong>in</strong>a 0201/4012151<br />
2 11 07 2007 Haltern, Hauptstr 46 beim Ausparken hat ...<br />
Aliman Zafer 0201/4012161<br />
Die Anzahl der beteiligten Fahrzeuge an e<strong>in</strong>em Schadensfall wird durch e<strong>in</strong>e eigene<br />
Tabelle Zuordnungen berücksichtigt:<br />
29
Normalisierung<br />
Nummer Fahrzeug Hersteller Typ Halter_Name Halter_Vorname<br />
Halter_PLZ Halter_Ort Halter_Straße<br />
1 RE-LM 902 Opel Corsa Heckel Obsthandel GmbH<br />
46282 Dorsten Gahlener Str 40<br />
2 BO-GH 102 Volvo C30 Geissler Helga<br />
44809 Bochum Ste<strong>in</strong>bankstr 15<br />
2 RE-CD 456 Renault Tw<strong>in</strong>go Cornelsen Dorothea<br />
44577 Castrop-Rauxel Kiefernweg 9<br />
Die Zusatzbed<strong>in</strong>gung e<strong>in</strong>es Primärschlüssels wurde gleichzeitig erfüllt; die betreffenden<br />
Spalten wurden unterstrichen.<br />
5.3. Die 2. Normalform<br />
E<strong>in</strong>e weitere Wiederholung <strong>in</strong> den „Monster-Tabellen“ s<strong>in</strong>d die Angaben zum<br />
Halter bei den Verträgen und den Zuordnungen oder die Fahrzeugdaten sowohl<br />
bei den Verträgen als auch bei den Zuordnungen. Auch diese werden <strong>in</strong> eigene<br />
Tabellen ausgelagert; das ergibt sich als Def<strong>in</strong>ition aus der folgenden Regel:<br />
i Die 2. Normalform<br />
1. Die Tabelle erfüllt die 1. Normalform.<br />
2. Alle Informationen <strong>in</strong> den Spalten, die nicht Teil des Primärschlüssels<br />
s<strong>in</strong>d, müssen sich auf den gesamten Primärschlüssel beziehen.<br />
Man sagt auch, dass die Informationen funktional abhängig s<strong>in</strong>d von der Gesamtheit<br />
der Schlüsselwerte. Umgekehrt formuliert bedeutet es: Wenn e<strong>in</strong>e Spalte<br />
nur zu e<strong>in</strong>em e<strong>in</strong>zelnen Schlüsselfeld gehört, ist die 2. Normalform nicht erfüllt.<br />
Während sich die 1. Normalform auf die e<strong>in</strong>zelnen Spalten und Wiederholungen<br />
<strong>in</strong>nerhalb e<strong>in</strong>es Datensatzes bezieht, befasst sich die 2. Normalform mit Wiederholungen<br />
bei verschiedenen Datensätzen.<br />
Verletzung der 2. Normalform<br />
Unsere derzeitigen Tabellen verstoßen mehrfach gegen diese Regel:<br />
• Bei den Verträgen beziehen sich Name und Anschrift des Vertragspartners nur<br />
auf die Kundennummer, aber nicht auf die Vertragsnummer.<br />
30
Die 2. Normalform<br />
• Bei den Verträgen stehen auch die Fahrzeugdaten. (Dies wurde bei der Beschreibung<br />
der grundlegenden Daten erwähnt, fehlt aber <strong>in</strong> den bisherigen<br />
Beispielen.) Diese beziehen sich auf die Vertragsnummer, haben aber nur <strong>in</strong>direkt<br />
etwas mit Name und Anschrift des Vertragspartners zu tun.<br />
• Bei den Zuordnungen der Schadensfälle gehören Fahrzeug- und Halterdaten<br />
nur zu e<strong>in</strong>em Fahrzeug (dem Kennzeichen), aber nicht zu e<strong>in</strong>em Schadensfall.<br />
V<strong>org</strong>ehen zur Herstellung der 2. Normalform<br />
Alle „unpassenden“ Informationen gehören <strong>in</strong> eigene Tabellen. Der Primärschlüssel<br />
bezieht sich nur auf die „eigentlichen“ Informationen e<strong>in</strong>er Tabelle.<br />
• Aus den Verträgen werden alle Angaben des Vertragspartners entfernt und<br />
<strong>in</strong> e<strong>in</strong>e Tabelle Versicherungsnehmer übertragen. Der Primärschlüssel besteht<br />
nur noch aus der Spalte Vertrag. Die Spalte Kundennummer ist nur noch e<strong>in</strong><br />
Fremdschlüssel zur Verknüpfung mit der neuen Tabelle Versicherungsnehmer.<br />
• Aus den Verträgen werden alle Angaben des Fahrzeugs entfernt und <strong>in</strong> e<strong>in</strong>e<br />
neue Tabelle Fahrzeuge übertragen. Das Fahrzeug-Kennzeichen ist hier nur<br />
noch e<strong>in</strong> Fremdschlüssel als Verweis auf die Tabelle Fahrzeuge.<br />
• Aus den Zuordnungen werden alle Angaben der Fahrzeuge entfernt und <strong>in</strong> e<strong>in</strong>e<br />
Tabelle Fahrzeuge übertragen. Die Tabelle Zuordnungen besteht nur noch aus<br />
den Spalten des Primärschlüssels.<br />
Damit stehen die Fahrzeugdaten nur noch <strong>in</strong> e<strong>in</strong>er Tabelle – sowohl für die Verträge<br />
als auch für die Schadensfälle (genauer: die Zuordnungen).<br />
E<strong>in</strong>e zweite Verbesserung<br />
Die ursprüngliche Tabelle Verträge beschränkt sich jetzt auf diese Angaben:<br />
Vertrag Abschluss Typ Kd-Nr Fahrzeug Sachb_N Sachb_V Telefon<br />
DG-01 03.05.1974 HP 1 RE-LM 901 Pohl Helmut 0201/4014186<br />
DG-02 04.07.1974 HP 1 RE-LM 902 Pohl Helmut 0201/4014186<br />
Die neue Tabelle Versicherungsnehmer umfasst dessen Daten:<br />
Kd-Nr Name Vorname Geburtsdatum PLZ Ort Straße Nr<br />
1 Heckel Obsthandel GmbH 46282 Dorsten Gahlener Str. 40<br />
5 Geissler Helga 13.01.1953 44809 Bochum Ste<strong>in</strong>bankstr. 15<br />
Die neue Tabelle Fahrzeuge umfasst nur noch die Daten, die sich auf das Fahrzeug<br />
selbst beziehen. Name und Anschrift des Fahrzeughalters werden durch die<br />
Kundennummer, also den Verweis auf die Tabelle Versicherungsnehmer ersetzt.<br />
31
Normalisierung<br />
Fahrzeug Hersteller Typ Farbe Halter<br />
RE-LM 902 Opel Corsa ocker 1<br />
BO-GH 102 Volvo C30 rot 5<br />
RE-CD 456 Renault Tw<strong>in</strong>go ocker 3<br />
Die Tabelle Schadensfälle muss nicht angepasst werden. Die Tabelle Zuordnungen<br />
vere<strong>in</strong>facht sich radikal:<br />
Nummer Fahrzeug<br />
1 RE-LM 902<br />
2 BO-GH 102<br />
2 RE-CD 456<br />
Die 2. Normalform kann ganz e<strong>in</strong>fach dadurch gewährleistet werden, dass sich<br />
der Primärschlüssel nur auf e<strong>in</strong>e Spalte bezieht.<br />
5.4. Die 3. Normalform<br />
Beseitigen wir noch die übrigen Wiederholungen, nämlich die Sachbearbeiter<br />
bei Verträgen und Schadensfällen sowie die Hersteller bei Fahrzeugen. Diese<br />
kommen ebenfalls <strong>in</strong> eigene Tabellen gemäß Def<strong>in</strong>ition nach folgender Regel:<br />
i Die 3. Normalform<br />
1. Die Tabelle erfüllt die 2. Normalform.<br />
2. Informationen <strong>in</strong> den Spalten, die nicht Teil des Primärschlüssels s<strong>in</strong>d,<br />
dürfen funktional nicht vone<strong>in</strong>ander abhängen.<br />
Die 3. Normalform befasst sich also mit Wiederholungen bei verschiedenen Datensätzen,<br />
die nur zusätzliche Informationen bereitstellen.<br />
Verletzung der 3. Normalform<br />
Unsere derzeitigen Tabellen verstoßen <strong>in</strong> folgender H<strong>in</strong>sicht gegen diese Regel:<br />
• Name, Vorname und Telefonnummer e<strong>in</strong>es Sachbearbeiters hängen vone<strong>in</strong>ander<br />
ab. Sie haben aber nur <strong>in</strong>sgesamt etwas mit dem Vertrag bzw. dem Schadensfall<br />
zu tun, nicht als e<strong>in</strong>zelne Information.<br />
• Hersteller und Typ e<strong>in</strong>es Fahrzeugs hängen vone<strong>in</strong>ander ab. Sie haben aber<br />
nur <strong>in</strong>sgesamt etwas mit dem Fahrzeug zu tun, nicht als e<strong>in</strong>zelne Information.<br />
32
Die 3. Normalform<br />
E<strong>in</strong>e andere Erklärung dafür ist, dass die Zusatz<strong>in</strong>formation auch ohne Bezug<br />
zum eigentlichen Datensatz gültig bleibt. Der Sachbearbeiter gehört zum Unternehmen<br />
unabhängig von e<strong>in</strong>em bestimmten Vertrag. Der Fahrzeughersteller<br />
existiert unabhängig davon, ob e<strong>in</strong> bestimmtes Fahrzeug noch fährt oder <strong>in</strong>zwischen<br />
verschrottet worden ist.<br />
V<strong>org</strong>ehen zur Herstellung der 3. Normalform<br />
Alle „unpassenden“ Informationen kommen wieder <strong>in</strong> eigene Tabellen. Ihre<br />
Spalten werden ersetzt durch e<strong>in</strong>en Fremdschlüssel zur Verknüpfung mit der<br />
neuen Tabelle.<br />
• Aus den Verträgen und den Schadensfällen werden alle Angaben zum Sachbearbeiter<br />
entfernt und <strong>in</strong> e<strong>in</strong>e Tabelle Mitarbeiter übertragen. Die Spalte Sachbearbeiter<br />
verweist als Fremdschlüssel auf die neue Tabelle Mitarbeiter.<br />
Dies löst automatisch auch das oben erwähnte Problem: Wir können<br />
nun alle Mitarbeiter <strong>in</strong> e<strong>in</strong>er geme<strong>in</strong>samen Tabelle speichern.<br />
• Aus den Fahrzeugen werden alle Angaben zu Hersteller und Typ entfernt und<br />
<strong>in</strong> e<strong>in</strong>e neue Tabelle Fahrzeugtypen übertragen. Die Spalten Hersteller und Typ<br />
werden ersetzt durch e<strong>in</strong>en Fremdschlüssel zur Verknüpfung mit der Tabelle<br />
Fahrzeugtypen.<br />
Um es korrekt zu machen, gehört der Hersteller <strong>in</strong> e<strong>in</strong>e weitere Tabelle<br />
Fahrzeughersteller; <strong>in</strong> der Tabelle Fahrzeugtypen verweist er als<br />
Fremdschlüssel auf diese weitere Tabelle.<br />
E<strong>in</strong>e weitere Verbesserung<br />
Die Tabellen Verträge und Schadensfälle werden also nochmals vere<strong>in</strong>facht:<br />
Vertrag Abschluss Typ Kundennummer Fahrzeug Sachbearbeiter<br />
DG-01 03.05.1974 HP 1 RE-LM 901 9<br />
DG-02 04.07.1974 HP 1 RE-LM 902 9<br />
Nummer Datum Schadensort Beschreibung Sachbearb<br />
1 02.03.2007 Reckl<strong>in</strong>ghausen, Bergknappe... Gartenzaun gestreift 14<br />
2 11.07.2007 Haltern, Hauptstr. 46 beim Ausparken hat ... 15<br />
H<strong>in</strong>zu kommt die neue Tabelle Mitarbeiter:<br />
Nummer Nachname Vorname Telefon<br />
9 Pohl Helmut 0201/4014186<br />
33
Normalisierung<br />
14 Sch<strong>in</strong>dler Christ<strong>in</strong>a 0201/4012151<br />
15 Aliman Zafer 0201/4012161<br />
In gleicher Weise wird die Tabelle Fahrzeuge gekürzt; die Angaben werden <strong>in</strong> die<br />
neuen Tabellen Fahrzeugtypen und Fahrzeughersteller ausgelagert.<br />
5.5. Zusätzliche Maßnahmen<br />
In der Theorie gibt es noch e<strong>in</strong>e 4. und e<strong>in</strong>e 5. Normalform (und e<strong>in</strong>e Alternative<br />
zur 3. Normalform). Dazu sei auf den Wikipedia-Artikel und die dortigen<br />
H<strong>in</strong>weise (Quellen, Literatur, Webl<strong>in</strong>ks) verwiesen. In der Praxis ist aber e<strong>in</strong>e Datenbank,<br />
die den Bed<strong>in</strong>gungen der 3. Normalform entspricht, bereits sehr gut<br />
konzipiert. Weitere Normalisierungen br<strong>in</strong>gen kaum noch Vorteile; stattdessen<br />
können sie die Übersichtlichkeit und die Geschw<strong>in</strong>digkeit beim Datenzugriff bee<strong>in</strong>trächtigen.<br />
5.5.1. Verzicht auf Zusatztabellen<br />
Beispielsweise wiederholen sich <strong>in</strong> der Tabelle Versicherungsnehmer die Ortsangaben.<br />
Dabei gehören die Komb<strong>in</strong>ationen PLZ/Ort immer zusammen; auch<br />
wenn der Kunde umzieht, ist e<strong>in</strong>e solche Komb<strong>in</strong>ation unverändert gültig. Also<br />
könnte man <strong>in</strong> der Tabelle die Adresse wie folgt speichern:<br />
Kd-Nr Name Vorname Geburtsdatum PLZ Alort Straße Hausnummer<br />
1 Heckel Obsthandel GmbH 46282 10884500 Gahlener Str. 40<br />
5 Geissler Helga 13.01.1953 44809 05902500 Ste<strong>in</strong>bankstr. 15<br />
Der Ortsname ist dann zu f<strong>in</strong>den über die PL-Datei der Deutschen Post AG (mit<br />
PLZ/Alort als Primärschlüssel):<br />
Dateiversion Geltung PLZ Alort PLZ-Arten Ortsname<br />
PL 0509 244 20010101 44809 05902500 06 2 2 Bochum<br />
PL 0509 244 20050329 46282 10884500 06 2 2 Dorsten<br />
Das gleiche Verfahren ist möglich für die Straßennamen. Es s<strong>org</strong>t dafür, dass nur<br />
gültige Anschriften gespeichert s<strong>in</strong>d und auch bei E<strong>in</strong>geme<strong>in</strong>dungen die neuen<br />
Angaben e<strong>in</strong>deutig übernommen werden. Aber selbst wenn man wegen der<br />
Datensicherheit mit Alort arbeiten will, wird man für die praktische Arbeit den<br />
Ortsnamen <strong>in</strong> der Adressendatei behalten wollen.<br />
34
5.5.2. Primärschlüssel nur zur Identifizierung<br />
Zusätzliche Maßnahmen<br />
Im vorigen Kapitel hatten wir kurz darauf h<strong>in</strong>gewiesen, dass der Primärschlüssel<br />
nur die Bedeutung als ID haben sollte.<br />
E<strong>in</strong>e Begründung liefert die obige Tabelle Fahrzeuge, bei der das Kennzeichen<br />
zur Identifizierung benutzt wurde. Wenn der Fahrzeughalter <strong>in</strong> e<strong>in</strong>en anderen<br />
Kreis umzieht, bekommt er e<strong>in</strong> neues Kennzeichen. Dann müsste es an allen<br />
Stellen geändert werden, an denen es gespeichert ist. Nun darf die Vertragsabteilung<br />
nur die Verträge ändern, die Schadensabwicklung die Schadensfälle (und<br />
wer weiß, wo noch darauf Bezug genommen wird). Jede Ummeldung verursacht<br />
also erheblichen Mehraufwand.<br />
S<strong>org</strong>en wir also für mehr Datensicherheit und Arbeitsvere<strong>in</strong>fachung:<br />
• Der Primärschlüssel ist e<strong>in</strong>e automatisch zugewiesene ID. Diese ID wird niemals<br />
geändert.<br />
• Die Identifizierung, die der Benutzer kennt, ist e<strong>in</strong>e e<strong>in</strong>zelne Spalte <strong>in</strong> e<strong>in</strong>er<br />
e<strong>in</strong>zigen Tabelle.<br />
Beispiele: Vertragsnummer, Fahrzeug-Kennzeichen, Personalnummer<br />
• Die Verknüpfungen mit anderen Tabellen regelt die Datenbank mit Hilfe der<br />
ID selbständig.<br />
5.5.3. Änderungsdaten<br />
In vielen Fällen ist es s<strong>in</strong>nvoll, wenn <strong>in</strong> e<strong>in</strong>er Tabelle nicht nur der aktuelle Zustand<br />
gespeichert ist, sondern der Verlauf.<br />
• Beispiel mit Änderung des Kennzeichens: Wenn die Polizei nach e<strong>in</strong>em Unfallverursacher<br />
sucht, der <strong>in</strong>zwischen umgezogen ist, wird für das alte (nicht<br />
mehr gültige) Kennzeichen ke<strong>in</strong> Fahrzeug gefunden.<br />
• Adressenänderungen auf Term<strong>in</strong> legen: Es wäre viel zu aufwändig, wenn alle<br />
Änderungen gleichzeitig an dem Stichtag, an dem sie gelten sollen, e<strong>in</strong>gegeben<br />
werden müssten. Besser ist es, sie sofort zu speichern mit Angabe des<br />
Geltungsdatums; die Datenbank holt abhängig vom aktuellen und dem Geltungsdatum<br />
immer die richtige Version.<br />
• Aktueller Steuersatz: Die Abrechnung e<strong>in</strong>er Versandfirma muss immer mit<br />
dem richtigen Steuersatz rechnen, auch wenn er mitten im Jahr geändert wird.<br />
Als Steuersatz 1 für die Mehrwertsteuer wird dann bei e<strong>in</strong>er „älteren“ Rechnung<br />
mit 16 % und bei e<strong>in</strong>er Rechnung neueren Datums mit 19 % gerechnet.<br />
Für alle solche Fälle erhalten Tabellen gerne zusätzliche Spalten gültig von und<br />
gültig bis. Anstelle e<strong>in</strong>er Änderung werden Datensätze verdoppelt; die neuen Informationen<br />
ersetzen dabei die bisher gültigen.<br />
35
Normalisierung<br />
Selbstverständlich erfordert e<strong>in</strong>e solche Datenbankstruktur höheren Aufwand<br />
sowohl beim Entwickler der Datenbank als auch beim Programmierer der Anwendung.<br />
Der zusätzliche Nutzen rechtfertigt diesen Aufwand.<br />
5.5.4. Reihenfolge ohne Bedeutung<br />
Beim Aufbau e<strong>in</strong>er Datenbank darf es nicht relevant se<strong>in</strong>, <strong>in</strong> welcher Reihenfolge<br />
die Zeilen und Spalten vorkommen: Die Funktionalität der Datenbank darf<br />
nicht davon abhängen, dass zwei Spalten <strong>in</strong> e<strong>in</strong>er bestimmten Folge nache<strong>in</strong>ander<br />
kommen oder dass nach e<strong>in</strong>em Datensatz e<strong>in</strong> bestimmter zweiter folgt.<br />
• E<strong>in</strong> Datensatz (also e<strong>in</strong>e Zeile) steht nur über den Primärschlüssel bereit.<br />
• E<strong>in</strong>e Spalte steht nur über den Spaltennamen zur Verfügung.<br />
Nur ausnahmsweise wird mit der Nummer e<strong>in</strong>er Zeile oder Spalte gearbeitet,<br />
z. B. wenn e<strong>in</strong> „Programmierer-Werkzeug“ nur <strong>in</strong>dizierte Spalten kennt.<br />
5.6. Zusammenfassung<br />
Fassen wir diese Erkenntnisse zusammen zu e<strong>in</strong>igen Regeln, die bei der Entwicklung<br />
e<strong>in</strong>er Datenbankstruktur zu beachten s<strong>in</strong>d.<br />
5.6.1. Allgeme<strong>in</strong>e Regeln<br />
Von der Realität ausgehen: Erstellen Sie Tabellen danach, mit welchen D<strong>in</strong>gen<br />
(Objekten) Sie arbeiten. Benutzen Sie aussagekräftige Namen für Tabellen<br />
und Spalten.<br />
E<strong>in</strong>zelne Informationen klar trennen: Alle Informationen werden <strong>in</strong> e<strong>in</strong>zelne<br />
Spalten mit unteilbaren Werten aufgeteilt.<br />
Vollständigkeit: Alle möglichen Informationen sollten von vornhere<strong>in</strong> berücksichtigt<br />
werden. Nachträgliche Änderungen s<strong>in</strong>d zu vermeiden.<br />
Ke<strong>in</strong>e berechneten Werte speichern: E<strong>in</strong>e Information, die aus vorhandenen<br />
Angaben zusammengestellt werden kann, wird nicht als eigene Spalte gespeichert.<br />
Kle<strong>in</strong>e Tabellen bevorzugen: Wenn e<strong>in</strong>e Tabelle sehr viele Informationen umfasst,<br />
ist es besser, sie zu unterteilen und über e<strong>in</strong>en e<strong>in</strong>heitlichen Primärschlüssel<br />
zu verb<strong>in</strong>den.<br />
Ke<strong>in</strong>e doppelten Speicherungen: Es darf weder ganze doppelte Datensätze geben<br />
noch wiederholte Speicherungen gleicher Werte (Redundanz von Daten).<br />
Doppelte Datensätze werden durch Primärschlüssel und Regeln zur<br />
36
Siehe auch<br />
E<strong>in</strong>deutigkeit <strong>in</strong> Feldern vermieden; Datenredundanz wird durch getrennte<br />
Tabellen ersetzt, sodass es nur die Fremdschlüssel mehrfach gibt.<br />
Wichtig ist auch, dass der Aufbau der Datenbank beschrieben und ggf. begründet<br />
wird. Hilfreich s<strong>in</strong>d dabei graphische Darstellungen, wie sie von vielen Datenbankprogrammen<br />
angeboten werden.<br />
5.6.2. Abweichungen<br />
Die vorstehenden Überlegungen werden nicht immer beachtet. Für fast jede Regel<br />
gibt es begründete Ausnahmen<br />
Beispielsweise enthält e<strong>in</strong>e Datenbank mit Adressen <strong>in</strong> der Regel die Adressen<br />
im Klartext und nicht nur als Verweise auf die Nummern von Orten, Ortsteilen,<br />
Straßen und Straßenabschnitten der Deutschen Post.<br />
Entscheiden Sie erst nach bewusster Abwägung von Vor- und Nachteilen, wenn<br />
Sie von e<strong>in</strong>er der Regeln abweichen wollen.<br />
5.7. Siehe auch<br />
Über Wikipedia s<strong>in</strong>d weitere Informationen zu f<strong>in</strong>den:<br />
• Normalisierung 1<br />
• Relationale Datenbank 2<br />
• Edgar F. Codd 3 und se<strong>in</strong>e 12 Regeln 4 für relationale Datenbanken<br />
• Update-Anomalien 5<br />
• Redundanz von Daten 6<br />
Für e<strong>in</strong>e s<strong>org</strong>fältige Planung e<strong>in</strong>er Adressen-Datenbank hilft die Datenstruktur<br />
der Deutschen Post:<br />
• Broschüre Datafactory 7 (360 kB) als Überblick<br />
Unter Post Direkt 8 kann die Datei 20101230_HB_Leitdaten.pdf mit der Datensatzbeschreibung<br />
kostenfrei bestellt werden.<br />
1 http://de.wikipedia.<strong>org</strong>/wiki/Normalisierung%20(Datenbank)<br />
2 http://de.wikipedia.<strong>org</strong>/wiki/Relationale%20Datenbank<br />
3 http://de.wikipedia.<strong>org</strong>/wiki/Edgar%20F.%20Codd<br />
4 http://de.wikipedia.<strong>org</strong>/wiki/Onl<strong>in</strong>e_Analytical_Process<strong>in</strong>g%2312_Regeln_nach_Codd<br />
5 http://de.wikipedia.<strong>org</strong>/wiki/Anomalie%20(Informatik)<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/Redundanz%20(Informationstheorie)<br />
7 http://www.deutschepost.de/downloadServlet?target=/mlm.nf/dpag/images/d/datafactory<br />
/20100205_datafactory_<strong>in</strong>ternet.pdf<br />
8 mailto:<strong>in</strong>fo@postdirekt.de<br />
37
6. Beispieldatenbank<br />
6.1. Sachverhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />
6.2. Schematische Darstellung . . . . . . . . . . . . . . . . . . . . . . 41<br />
6.3. Tabellenstruktur und Datenbank . . . . . . . . . . . . . . . . . . 42<br />
6.4. Anmerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43<br />
Dieses Kapitel bespricht die Grundlagen der Beispieldatenbank. Alle E<strong>in</strong>zelheiten<br />
der Tabellen stehen im Anhang unter Tabellenstruktur der Beispieldatenbank<br />
1 .<br />
i H<strong>in</strong>weis<br />
Alle Beispiele, Erläuterungen und Aufgaben beziehen sich auf diese<br />
Datenstruktur. Bitte schlagen Sie (soweit erforderlich) immer dort nach.<br />
6.1. Sachverhalt<br />
Die Beispieldatenbank <strong>in</strong> diesem Buch versucht, e<strong>in</strong>e Versicherungsgesellschaft<br />
für Kfz-Versicherungen abzubilden. Das Beispiel ist e<strong>in</strong>e starke Vere<strong>in</strong>fachung<br />
der Realität; zum Beispiel fehlen alle Teile der laufenden Abrechnung der Prämien<br />
und Schadensfälle.<br />
Die relevanten Begriffe s<strong>in</strong>d für e<strong>in</strong>e bessere Übersichtlichkeit fett gekennzeichnet.<br />
Folgender Sachverhalt wird <strong>in</strong> der Datenbank abgebildet:<br />
Die Versicherungsgesellschaft UnsereFirma verwaltet mit dieser Datenbank ihre<br />
Kundendaten und die Schadensfälle. Wegen der Schadensfälle müssen auch<br />
Daten „fremder“ Kunden und Versicherungen gespeichert werden.<br />
Jeder Versicherungsvertrag wird mit e<strong>in</strong>em Versicherungsnehmer über genau<br />
e<strong>in</strong> Fahrzeug abgeschlossen. Der Versicherungsvertrag wird durch folgende Eigenschaften<br />
gekennzeichnet; zu jeder Eigenschaft gehört e<strong>in</strong>e Spalte:<br />
1 Anhang A auf Seite 419<br />
39
Beispieldatenbank<br />
• Vertragsnummer (Pflicht, e<strong>in</strong>deutig)<br />
• Datum des Abschlusses, Art des Vertrages (Pflicht); dabei gibt es die Arten Haftpflicht<br />
(HP), Haftpflicht mit Teilkasko (TK), Vollkasko (VK).<br />
• Verweis auf den Versicherungsnehmer (Pflicht)<br />
• Verweis auf das Fahrzeug (Pflicht, e<strong>in</strong>deutig)<br />
• Verweis auf den Mitarbeiter, der den Vertrag bearbeitet (Pflicht)<br />
Der Versicherungsnehmer ist gekennzeichnet durch diese Eigenschaften:<br />
• Kundennummer (Pflicht, e<strong>in</strong>deutig)<br />
• Name und Anschrift: PLZ, Ort, Straße, Hausnummer (Pflicht)<br />
• bei natürlichen Personen zusätzlich durch Vorname, Geburtsdatum, Datum<br />
des Führersche<strong>in</strong>s (optional)<br />
• Verweis auf e<strong>in</strong>e „Fremdversicherung“, wenn e<strong>in</strong> (fremdes) Fahrzeug an e<strong>in</strong>em<br />
Unfall beteiligt ist (optional)<br />
Das Fahrzeug ist gekennzeichnet durch diese Eigenschaften:<br />
• polizeiliches Kennzeichen (Pflicht, e<strong>in</strong>deutig)<br />
• Farbe (optional)<br />
• Fahrzeugtyp und damit <strong>in</strong>direkt auch den Fahrzeughersteller (Pflicht)<br />
E<strong>in</strong> Mitarbeiter ist gekennzeichnet durch diese Eigenschaften:<br />
• Name, Vorname, Geburtsdatum (Pflicht)<br />
• Personalnummer (Pflicht, e<strong>in</strong>deutig)<br />
• Verweis auf Abteilung, Vermerk, ob es sich um den Leiter der Abteilung handelt<br />
(Pflicht)<br />
• Kontaktdaten wie Telefon, Mobiltelefon, Email, Raum (optional)<br />
Die Abteilung ist gekennzeichnet durch diese Eigenschaften:<br />
• Nummer (Pflicht, e<strong>in</strong>deutig)<br />
• Kurzbezeichnung, Bezeichnung (Pflicht, e<strong>in</strong>deutig)<br />
• Ort (optional)<br />
Zusätzlich gibt es Dienstwagen. Dabei handelt es sich um e<strong>in</strong>e Tabelle mit den<br />
gleichen Eigenschaften wie bei Fahrzeug und zusätzlich:<br />
• Verweis auf den Mitarbeiter, zu dem e<strong>in</strong> Dienstwagen gehört (optional)<br />
• denn es gibt auch Firmenwagen, die ke<strong>in</strong>em Mitarbeiter persönlich zugeordnet<br />
s<strong>in</strong>d<br />
E<strong>in</strong> Schadensfall ist gekennzeichnet durch diese Eigenschaften:<br />
40
• Datum, Ort und Umstände des Unfalls (Pflicht)<br />
Schematische Darstellung<br />
• Vermerk, ob es Verletzte gab, sowie Höhe des Gesamtschadens (optional, denn<br />
die Angaben könnten erst später bekannt werden)<br />
• Verweis auf den Mitarbeiter, der den Schadensfall bearbeitet (Pflicht)<br />
An e<strong>in</strong>em Schadensfall können mehrere Fahrzeuge unterschiedlicher Versicherungen<br />
beteiligt se<strong>in</strong>. (Unfälle mit Radfahrern und Fußgängern werden nicht betrachtet.)<br />
Deshalb gibt es e<strong>in</strong>e weitere Tabelle Zuordnung_SF_FZ mit diesen Eigenschaften:<br />
• Liste aller Schadensfälle und aller beteiligten Fahrzeuge (Pflicht)<br />
• anteilige Schadenshöhe e<strong>in</strong>es Fahrzeugs an dem betreffenden Unfall (optional)<br />
Über die Verb<strong>in</strong>dung Schadensfall → Fahrzeug → Versicherungsvertrag → Versicherungsnehmer<br />
→ Versicherungsgesellschaft können alle beteiligten Gesellschaften<br />
festgestellt und <strong>in</strong> die Schadensabwicklung e<strong>in</strong>gebunden werden.<br />
Bitte beachten Sie auch die unten stehenden H<strong>in</strong>weise über „Fehlende Spalten<br />
und E<strong>in</strong>schränkungen“.<br />
6.2. Schematische Darstellung<br />
Die vorstehende Struktur kann im folgenden Diagramm dargestellt werden. Die<br />
Verweise, nämlich die Verknüpfungen zwischen den Tabellen s<strong>in</strong>d daraus so abzulesen:<br />
• Von jeder Tabelle gibt es e<strong>in</strong>en Verweis auf e<strong>in</strong>en E<strong>in</strong>trag <strong>in</strong> der direkt darunter<br />
stehenden Tabelle.<br />
• Zu jedem Vertrag gehört e<strong>in</strong> Sachbearbeiter; das wird durch den Pfeil nach<br />
l<strong>in</strong>ks außen angedeutet, der <strong>in</strong> der Tabelle Mitarbeiter von rechts here<strong>in</strong>kommt.<br />
• Zu jedem Vertrag gehört genau e<strong>in</strong> Fahrzeug; zu jedem Fahrzeug gehört e<strong>in</strong><br />
Vertrag.<br />
• Jeder E<strong>in</strong>trag <strong>in</strong> der Liste der Zuordnungen Schadensfall, Fahrzeug bezieht sich<br />
auf e<strong>in</strong>en Schadensfall und e<strong>in</strong> Fahrzeug; dabei gilt:<br />
• Zu jedem Schadensfall gehören e<strong>in</strong> oder mehrere Fahrzeuge.<br />
• Nicht jedes Fahrzeug hat e<strong>in</strong>en Schadensfall.<br />
• E<strong>in</strong> Dienstwagen kann e<strong>in</strong>em Mitarbeiter zugeordnet se<strong>in</strong>, muss es aber nicht:<br />
Es gibt auch nicht-persönliche Dienstwagen.<br />
41
Beispieldatenbank<br />
Tabellen und ihre Zusammenhänge<br />
Doppelte Pfeile zeigen e<strong>in</strong>e Muss-Verknüpfung, e<strong>in</strong>fache e<strong>in</strong>e Kann-Verknüpfung.<br />
Die Kürzel bei den Tabellennamen werden künftig immer wieder als „Alias“ anstelle<br />
des „Langnamens“ verwendet. In dieser Übersicht wurden sie wegen der<br />
Deutlichkeit großgeschrieben; künftig werden sie kle<strong>in</strong>geschrieben.<br />
6.3. Tabellenstruktur und Datenbank<br />
Im Anhang ist die Tabellenstruktur der Beispieldatenbank beschrieben, nämlich<br />
die Liste der Tabellen und aller ihrer Spalten. Innerhalb des Buches wird die Datenbankstruktur<br />
erweitert; dabei werden e<strong>in</strong>ige der folgenden Anmerkungen beachtet.<br />
Im Anhang stehen auch Wege, wie Sie über die Download-Seite 2 die Beispieldatenbank<br />
erhalten. E<strong>in</strong>e kurze Beschreibung für die Installation steht dort. Für<br />
Details nutzen Sie bitte die Dokumentation des jeweiligen Datenbankmanagementsystems;<br />
L<strong>in</strong>ks dazu stehen <strong>in</strong> der E<strong>in</strong>leitung 3 sowie unter Webl<strong>in</strong>ks 4 .<br />
Zum Abschluss werden auch e<strong>in</strong> paar Verfahren behandelt, schnell viele Datensätze<br />
als Testdaten 5 zu erzeugen. Für das Verständnis der Inhalte des Buchs s<strong>in</strong>d<br />
diese Verfahren nicht wichtig. Sie s<strong>in</strong>d aber äußerst nützlich, wenn Sie weitere<br />
Möglichkeiten der <strong>SQL</strong>-Befehle ausprobieren wollen.<br />
2 Anhang B auf Seite 423<br />
3 Kapitel 3 auf Seite 11<br />
4 Anhang D auf Seite 445<br />
5 Kapitel 36 auf Seite 407<br />
42
6.4. Anmerkungen<br />
Anmerkungen<br />
Die Beispieldatenbank wurde erstellt und e<strong>in</strong>gefügt, als sich das Buch noch<br />
ziemlich am Anfang befand. Im Laufe der Zeit wurden bei der Konzeption und<br />
der Umsetzung <strong>in</strong> Beispielen Unstimmigkeiten und Mängel festgestellt. Hier<br />
wird darauf h<strong>in</strong>gewiesen: Teilweise wurden sie beseitigt, teilweise werden wir die<br />
Datenstruktur <strong>in</strong> späteren Kapiteln ändern, teilweise belassen wir es dabei.<br />
Namen von Tabellen und Spalten<br />
Für die Bezeichner haben sich e<strong>in</strong> paar Regeln als s<strong>in</strong>nvoll herausgestellt.<br />
S<strong>in</strong>gular verwenden: Es ist üblicher, e<strong>in</strong>e Tabelle im S<strong>in</strong>gular zu bezeichnen.<br />
Es ist e<strong>in</strong>facher zu sagen: E<strong>in</strong> Versicherungsvertrag hat diese Eigenschaften.<br />
. . Umständlicher wäre: E<strong>in</strong> Datensatz der Tabelle Versicherungsverträge<br />
hat diese Eigenschaften. . .<br />
Vor allem beim Programmieren werden die Namen mit anderen Begriffen<br />
verknüpft; es käme dann etwas wie VertraegePositionenRow zustande<br />
− also die Positionen von Verträgen −, wo nur e<strong>in</strong>e e<strong>in</strong>zelne Zeile, nämlich<br />
e<strong>in</strong>e Position e<strong>in</strong>es Vertrags geme<strong>in</strong>t ist.<br />
In e<strong>in</strong>em Programmiererforum gab es e<strong>in</strong>mal e<strong>in</strong>e Diskussion über<br />
„S<strong>in</strong>gular oder Plural“. Dort wurde das als uraltes Streitthema bezeichnet,<br />
das zu heftigsten Ause<strong>in</strong>andersetzungen führen kann. Im<br />
Ergebnis sollte es eher als Geschmackssache angesehen werden.<br />
Also hat der Autor <strong>in</strong> diesem Buch se<strong>in</strong>en Geschmack durchgesetzt.<br />
Beschreibender Name: Die Spalten von Tabellen sollen den Inhalt klar angeben;<br />
das ist durchgehend berücksichtigt worden. Es ist nicht üblich, e<strong>in</strong><br />
Tabellenkürzel als Teil des Spaltennamens zu verwenden; denn e<strong>in</strong> Spaltenname<br />
wird immer <strong>in</strong> Zusammenhang mit e<strong>in</strong>er bestimmten Tabelle benutzt.<br />
Englische Begriffe: Es ist häufig praktischer, englische Begriffe für Tabellen und<br />
Spalten zu verwenden. Programmierer müssen die Namen weiterverarbeiten;<br />
weil sowohl Datenbanken als auch Programmiersprachen mit Englisch<br />
arbeiten, entstehen ständig komplexe Namen. Dabei ist e<strong>in</strong> Begriff wie CustomerNumberChanged<br />
(also re<strong>in</strong>es Englisch) immer noch besser als etwas<br />
wie KundeNummerChanged, also denglischer Mischmasch.<br />
Auf diese Änderung wird verzichtet, weil es ziemlich umständlich<br />
gewesen wäre. Außerdem spielt die Weiterverwendung durch Programmierer<br />
<strong>in</strong> diesem Buch eigentlich ke<strong>in</strong>e Rolle.<br />
43
Beispieldatenbank<br />
Aufteilung der Tabellen<br />
Die Aufgaben der Versicherungsgesellschaft werden nur e<strong>in</strong>geschränkt behandelt.<br />
Es fehlen Tabellen für Abrechnung und Zahlung der Prämien sowie Abrechnung<br />
der Kosten für Schadensfälle. Auf diese Aufgaben wird hier verzichtet.<br />
Die Tarife und damit die Höhe der Basisprämie gehören <strong>in</strong> e<strong>in</strong>e eigene Tabelle;<br />
e<strong>in</strong> Versicherungsvertrag müsste darauf verweisen. Auf diese Aufteilung wird<br />
wegen der Vere<strong>in</strong>fachung verzichtet.<br />
Sowohl Mitarbeiter als auch Versicherungsnehmer s<strong>in</strong>d Personen: Auch zu e<strong>in</strong>em<br />
Versicherungsnehmer passen Kontaktdaten; auch bei e<strong>in</strong>em Mitarbeiter ist<br />
die Privatanschrift von Bedeutung. Es wäre deshalb vernünftig, e<strong>in</strong>e geme<strong>in</strong>same<br />
Tabelle Person e<strong>in</strong>zurichten; die Tabelle Mitarbeiter würde Verweise auf diese<br />
Tabelle erhalten. Auf diese Änderung wird wegen der Vere<strong>in</strong>fachung verzichtet.<br />
Man kann streiten, ob der Verweis auf e<strong>in</strong>e fremde Versicherungsgesellschaft<br />
besser zum Versicherungsvertrag oder zum Versicherungsnehmer gehört.<br />
Die Verknüpfungen zwischen den Tabellen fehlten <strong>in</strong> der Ursprungsversion<br />
der Beispieldatenbank völlig. Sie werden <strong>in</strong> den Kapiteln Fremdschlüssel-<br />
Beziehungen 6 besprochen und <strong>in</strong> Änderung der Datenbankstruktur 7 e<strong>in</strong>gebaut.<br />
Fehlende Spalten und E<strong>in</strong>schränkungen<br />
Neben den genannten Punkten s<strong>in</strong>d weitere Spalten s<strong>in</strong>nvoll bzw. notwendig:<br />
• Tabelle Versicherungsvertrag: Basisprämie, Prämiensatz, letzte Änderung des<br />
Prämiensatzes<br />
• Tabelle Zuordnung_SC_FZ: Anteil am Verschulden<br />
• Tabellen Versicherungsnehmer und Mitarbeiter: Geschlecht m/w wegen der<br />
Anrede <strong>in</strong> Briefen<br />
Viele E<strong>in</strong>schränkungen fehlen <strong>in</strong> der ursprünglichen Version:<br />
• Spalten, deren Werte e<strong>in</strong>deutig se<strong>in</strong> müssen<br />
• Spalten, die nur bestimmte Werte annehmen dürfen<br />
• Tabellen, bei denen Spaltenwerte <strong>in</strong> gewisser Beziehung stehen müssen<br />
Diese Änderungen werden <strong>in</strong> DDL – E<strong>in</strong>zelheiten 8 besprochen und <strong>in</strong> Änderung<br />
der Datenbankstruktur 9 e<strong>in</strong>gebaut.<br />
6 Kapitel 29 auf Seite 305<br />
7 Kapitel 35 auf Seite 397<br />
8 Kapitel 28 auf Seite 285<br />
9 Kapitel 35 auf Seite 397<br />
44
Teil II.<br />
Grundlagen<br />
45
7. <strong>SQL</strong>-Befehle<br />
7.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . 48<br />
7.2. DML − Data Manipulation Language . . . . . . . . . . . . . . . 49<br />
7.3. DDL − Data Def<strong>in</strong>ition Language . . . . . . . . . . . . . . . . . . 51<br />
7.4. TCL − Transaction Control Language . . . . . . . . . . . . . . . 52<br />
7.5. DCL − Data Control Language . . . . . . . . . . . . . . . . . . . . 52<br />
7.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 52<br />
7.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53<br />
7.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />
Manchen E<strong>in</strong>steigern mag <strong>SQL</strong> sehr sperrig ersche<strong>in</strong>en. <strong>SQL</strong> ist weniger für improvisierte<br />
E<strong>in</strong>malabfragen gedacht als vielmehr zur Entwicklung von stabilen,<br />
dauerhaft nutzbaren Abfragen. Wenn die Abfrage e<strong>in</strong>mal entwickelt ist, wird sie<br />
meistens <strong>in</strong> e<strong>in</strong>e GUI- oder HTML-Umgebung e<strong>in</strong>gebunden, sodass der Benutzer<br />
mit dem <strong>SQL</strong>-Code gar nicht mehr <strong>in</strong> Berührung kommt.<br />
Es gibt zwar e<strong>in</strong>en <strong>SQL</strong>-Standard (siehe das Kapitel E<strong>in</strong>leitung 1 ), der möglichst<br />
von allen Datenbanken übernommen werden soll, aber leider hält sich<br />
ke<strong>in</strong> angebotenes DBMS (Datenbank-Management-System) vollständig daran.<br />
Es kommt also je nach DBMS zu leichten bis großen Abweichungen, und für Sie<br />
führt ke<strong>in</strong> Weg daran vorbei, immer wieder <strong>in</strong> der Dokumentation ihrer persönlichen<br />
Datenbank nachzulesen. Das gilt natürlich besonders für Verfahren, die im<br />
<strong>SQL</strong>-Standard gar nicht enthalten s<strong>in</strong>d, von Ihrem DBMS trotzdem angeboten<br />
werden.<br />
i H<strong>in</strong>weis<br />
Die Abweichungen e<strong>in</strong>es bestimmten DBMS vom <strong>SQL</strong>-Standard werden<br />
üblicherweise als <strong>SQL</strong>-Dialekt bezeichnet.<br />
1 Abschnitt 3.1 auf Seite 12<br />
47
<strong>SQL</strong>-Befehle<br />
7.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise<br />
Die gesamte Menge an Befehlen ist recht überschaubar; Schwierigkeiten machen<br />
die vielen Parameter mit zahlreichen Varianten.<br />
Der Übersicht halber wurde <strong>SQL</strong> <strong>in</strong> Teilbereiche gegliedert; allerd<strong>in</strong>gs gibt es<br />
auch für diese Aufteilung Unterschiede bei den DBMS, den Dokumentationen<br />
und <strong>in</strong> Fachbüchern. Diese Aufteilung (siehe Inhaltsverzeichnis dieses Kapitels)<br />
dient aber nicht nur der Übersicht, sondern hat auch praktische Gründe:<br />
• DML-Befehle werden vor allem von „e<strong>in</strong>fachen“ Anwendern benutzt.<br />
• DDL- und TCL-Befehle dienen Programmierern.<br />
• DCL-Befehle gehören zum Aufgabenbereich von Systemadm<strong>in</strong>istratoren.<br />
Dies s<strong>in</strong>d die Bestandteile e<strong>in</strong>es e<strong>in</strong>zelnen Befehls:<br />
• der Name des Befehls<br />
• der Name des Objekts (Datenbank, Tabelle, Spalte usw.)<br />
• e<strong>in</strong> H<strong>in</strong>weis zur Maßnahme, soweit diese nicht durch den Befehl klar ist, sowie<br />
E<strong>in</strong>zelheiten<br />
• das Semikolon als Zeichen für den Abschluss e<strong>in</strong>es <strong>SQL</strong>-Befehls<br />
Damit dies alles e<strong>in</strong>deutig ist, gibt es e<strong>in</strong>e Reihe von Schlüsselwörtern 2 (key<br />
words, reserved words, non-reserved words), anhand derer das DBMS die Informationen<br />
<strong>in</strong>nerhalb e<strong>in</strong>es Befehls erkennt.<br />
Die Schreibweise e<strong>in</strong>es Befehls ist flexibel.<br />
• Groß- und Kle<strong>in</strong>schreibung der Schlüsselwörter werden nicht unterschieden.<br />
• E<strong>in</strong> Befehl kann beliebig auf e<strong>in</strong>e oder mehrere Zeilen verteilt werden; der<br />
wichtigste Gesichtspunkt dabei ist die Lesbarkeit auch für Sie selbst.<br />
• Das Semikolon ist nicht immer erforderlich, wird aber empfohlen. Bei manchen<br />
DBMS wird der Befehl erst nach e<strong>in</strong>em folgenden GO o. ä. ausgeführt.<br />
Für eigene Bezeichner, d. h. die Namen von Tabellen, Spalten oder eigenen<br />
Funktionen gilt:<br />
• Vermeiden Sie unbed<strong>in</strong>gt, Schlüsselwörter dafür zu verwenden; dies führt<br />
schnell zu Problemen auch dort, wo es möglich wäre.<br />
• Das Wort muss <strong>in</strong> der Regel mit e<strong>in</strong>em Buchstaben oder dem Unterstrich a...z<br />
A...Z _ beg<strong>in</strong>nen. Danach folgen beliebig Ziffern und Buchstaben.<br />
• Inwieweit andere Zeichen und länderspezifische Buchstaben (Umlaute) möglich<br />
s<strong>in</strong>d, hängt vom DBMS ab.<br />
2 Anhang C.5 auf Seite 440<br />
48
DML − Data Manipulation Language<br />
In diesem Buch wird von Umlauten und dergleichen abgeraten. Bei<br />
den Erläuterungen zur Beispieldatenbank 3 (Namen von Tabellen und<br />
Spalten) wird sowieso für englische Bezeichner plädiert.<br />
Dieses Buch geht davon aus, dass Schlüsselwörter nicht als Bezeichner dienen<br />
und auch Umlaute nicht benutzt werden.<br />
Kommentare können <strong>in</strong> <strong>SQL</strong>-Befehle fast beliebig e<strong>in</strong>gefügt werden (nur die<br />
Schlüsselwörter dürfen natürlich nicht „zerrissen“ werden). Es gibt zwei Arten<br />
von Kommentaren:<br />
-- (doppelter B<strong>in</strong>destrich, am besten mit Leerzeichen dah<strong>in</strong>ter)<br />
Alles von den beiden Strichen an (e<strong>in</strong>schließlich) bis zum Ende dieser Zeile gilt<br />
als Kommentar und nicht als Bestandteil des Befehls.<br />
/* (längerer Text, gerne auch über mehrere Zeilen) */<br />
Alles, was zwischen /* und */ steht (e<strong>in</strong>schließlich dieser Begrenzungszeichen),<br />
gilt als Kommentar und nicht als Bestandteil des Befehls.<br />
7.2. DML − Data Manipulation Language<br />
DML beschäftigt sich mit dem Inhalt des Datenbestandes. „Manipulation“ ist<br />
dabei nicht nur im S<strong>in</strong>ne von „Manipulieren“ zu verstehen, sondern allgeme<strong>in</strong>er<br />
im S<strong>in</strong>ne von „<strong>in</strong> die Hand nehmen“ (lat. manus = Hand).<br />
Das Kapitel DML (1) – Daten abfragen 4 befasst sich mit dem SELECT-Befehl.<br />
• Gelegentlich f<strong>in</strong>den Sie dafür auch den Begriff Data Query Language (DQL).<br />
Diese <strong>E<strong>in</strong>führung</strong> fasst ihn als Teilgebiet der DML auf; nur aus Gründen der<br />
Übersicht gibt es getrennte Kapitel.<br />
Die Befehle INSERT, UPDATE, DELETE dienen der Speicherung von Daten und<br />
werden <strong>in</strong> DML (2) – Daten speichern 5 behandelt.<br />
Bei diesen Befehlen ist immer genau e<strong>in</strong>e Tabelle − nur bei SELECT auch mehrere<br />
− anzugeben, dazu Art und Umfang der Arbeiten sowie <strong>in</strong> aller Regel mit<br />
WHERE e<strong>in</strong>e Liste von Bed<strong>in</strong>gungen, welche Datensätze zu bearbeitet s<strong>in</strong>d.<br />
Die folgenden Erläuterungen s<strong>in</strong>d e<strong>in</strong>heitlich für alle DML-Befehle zu beachten.<br />
3 Kapitel 6 auf Seite 39<br />
4 Kapitel 8 auf Seite 57<br />
5 Kapitel 9 auf Seite 69<br />
49
<strong>SQL</strong>-Befehle<br />
7.2.1. Datenmengen, nicht e<strong>in</strong>zelne Datensätze<br />
Bitte beachten Sie, dass <strong>SQL</strong> grundsätzlich mengenorientiert arbeitet. DML-<br />
Befehle wirken sich meistens nicht nur auf e<strong>in</strong>en Datensatz aus, sondern auf e<strong>in</strong>e<br />
ganze Menge, die aus 0, e<strong>in</strong>em oder mehreren Datensätzen bestehen kann. Auch<br />
die WHERE-Bed<strong>in</strong>gungen s<strong>org</strong>en „nur“ für den Umfang der Datenmenge; aber<br />
das Ergebnis ist immer e<strong>in</strong>e Datenmenge.<br />
Im E<strong>in</strong>zelfall wissen Sie als Anwender oder Programmierer natürlich häufig, ob<br />
die Datenmenge 0, 1 oder n Datensätze enthalten kann oder soll. Aber Sie müssen<br />
selbst darauf achten, denn <strong>SQL</strong> oder das DBMS können das nicht wissen.<br />
Die Struktur als Datenmenge führt auch dazu, dass es bei e<strong>in</strong>em SELECT-Befehl<br />
ke<strong>in</strong>e „natürliche“ Reihenfolge gibt, <strong>in</strong> der die Daten angezeigt werden. Manchmal<br />
kommen sie <strong>in</strong> der Reihenfolge, <strong>in</strong> der sie gespeichert wurden; aber bei umfangreicheren<br />
Tabellen mit vielen Änderungen sieht es eher nach e<strong>in</strong>em großen<br />
Durche<strong>in</strong>ander aus − es sei denn, Sie verwenden die ORDER BY-Klausel.<br />
7.2.2. <strong>SQL</strong>-Ausdrücke<br />
In vielen Fällen wird der Begriff Ausdruck verwendet. Dies ist e<strong>in</strong> allgeme<strong>in</strong>er<br />
Begriff für verschiedene Situationen:<br />
• An Stellen, an denen e<strong>in</strong> e<strong>in</strong>zelner Wert angegeben werden muss, kann auch<br />
e<strong>in</strong> Werte-Ausdruck verwendet werden: e<strong>in</strong> konstanter Wert (Zahl, Text, boolescher<br />
Wert), das Ergebnis e<strong>in</strong>er Funktion, die e<strong>in</strong>en solchen Wert zurückgibt,<br />
oder e<strong>in</strong>e Abfrage, die als Ergebnis e<strong>in</strong>en e<strong>in</strong>zigen Wert liefert.<br />
• An Stellen, an denen e<strong>in</strong>e oder mehrere Zeilen e<strong>in</strong>er Datenmenge angegeben<br />
werden müssen, kann auch e<strong>in</strong> <strong>SQL</strong>-Ausdruck (im <strong>SQL</strong>-Standard als query expression<br />
bezeichnet) verwendet werden. Dabei handelt es sich um e<strong>in</strong>en <strong>SQL</strong>-<br />
Befehl (meistens SELECT), der e<strong>in</strong>e Menge von Datensätzen liefert.<br />
• An Stellen, an denen e<strong>in</strong>e Liste e<strong>in</strong>zelner Werte angegeben werden muss, kann<br />
ebenfalls e<strong>in</strong> <strong>SQL</strong>-Ausdruck verwendet werden; dieser muss dann als Ergebnis<br />
e<strong>in</strong>e Menge passender Werte liefern.<br />
7.2.3. Daten<strong>in</strong>tegrität<br />
Bei allen Datenmanipulationen ist zu beachten, dass die Bed<strong>in</strong>gungen für Querverweise<br />
erhalten bleiben, siehe das Kapitel Fremdschlüssel-Beziehungen 6 . Beispielsweise<br />
darf e<strong>in</strong> Datensatz <strong>in</strong> der Tabelle Abteilung erst dann gelöscht werden,<br />
wenn alle zugeordneten Mitarbeiter gelöscht oder versetzt wurden.<br />
6 Kapitel 29 auf Seite 305<br />
50
DDL − Data Def<strong>in</strong>ition Language<br />
7.2.4. H<strong>in</strong>weis für Programmierer: Parameter benutzen!<br />
E<strong>in</strong> <strong>SQL</strong>-Befehl <strong>in</strong> e<strong>in</strong>em Programm sollte niemals als „langer Str<strong>in</strong>g“ mit festen<br />
Texten erstellt werden, sondern mit Parametern. Das erleichtert die Wiederverwendung,<br />
die E<strong>in</strong>b<strong>in</strong>dung von Werten, vermeidet Probleme mit der Formatierung<br />
von Zahlen und Datumsangaben und verh<strong>in</strong>dert <strong>SQL</strong>-Injection.<br />
Im Kapitel DML (2) – Daten speichern 7 steht bei UPDATE so e<strong>in</strong> Beispiel:<br />
Dies korrigiert die Schreibweise des Namens beim Mitarbeiter mit der Personalnummer 20001<br />
UPDATE Mitarbeiter<br />
SET Name = ’Mayer’<br />
WHERE Personalnummer = 20001;<br />
Dieses Beispiel sieht besser so aus:<br />
UPDATE Mitarbeiter<br />
SET Name = @Name<br />
WHERE Personalnummer = @PersNr;<br />
Bei ADO.NET und Visual Basic für MS-<strong>SQL</strong> wird dieser Befehl so verwendet.<br />
Visual Basic-Quelltext<br />
Dim sel As Str<strong>in</strong>g = "UPDATE ... @PersNr;"<br />
Dim cmd As SqlCommand = new SqlCommand(sel)<br />
cmd.Parameters.AddWithValue("@Name", "Mayer")<br />
cmd.Parameters.AddWithValue("@PersNr", 20001)<br />
Zwar unterscheidet sich die Angabe der Parameter nach Programmiersprache<br />
und DBMS. Auch ist es etwas mehr Schreibarbeit. Aber viel wichtiger s<strong>in</strong>d die<br />
Unterschiede nach den Datentypen; <strong>in</strong>sgesamt wird die Arbeit sehr viel sicherer.<br />
Parameter gibt es nur für DML-Befehle, nicht für DDL oder DCL.<br />
7.3. DDL − Data Def<strong>in</strong>ition Language<br />
DDL def<strong>in</strong>iert die Struktur e<strong>in</strong>er Datenbank.<br />
Hierzu gibt es die Befehle CREATE, ALTER, DROP; diese werden für Datenbank-<br />
Objekte DATABASE, TABLE, VIEW usw. verwendet.<br />
E<strong>in</strong>zelheiten werden <strong>in</strong> DDL – Struktur der Datenbank 8 behandelt.<br />
7 Kapitel 9 auf Seite 69<br />
8 Kapitel 10 auf Seite 79<br />
51
<strong>SQL</strong>-Befehle<br />
7.4. TCL − Transaction Control Language<br />
Damit die Daten dauerhaft zusammenpassen, also die Integrität der Daten gewahrt<br />
bleibt, sollen Änderungen, die zusammengehören, auch „am Stück“ übertragen<br />
und gespeichert werden. Falls e<strong>in</strong>e e<strong>in</strong>zelne dieser Änderungen nicht<br />
funktioniert, muss der gesamte V<strong>org</strong>ang rückgängig gemacht werden.<br />
Dies wird durch Transaktionen gesteuert, die mit COMMIT oder ROLLBACK abgeschlossen<br />
werden.<br />
E<strong>in</strong> paar Grundlagen werden <strong>in</strong> TCL – Ablaufsteuerung 9 behandelt.<br />
7.5. DCL − Data Control Language<br />
E<strong>in</strong>e „vollwertige“ <strong>SQL</strong>-Datenbank regelt umfassend die Rechte für den Zugriff<br />
auf Objekte (Tabellen, e<strong>in</strong>zelne Felder, <strong>in</strong>terne Funktionen usw.). Dafür kommen<br />
die Befehle GRANT und REVOKE zum E<strong>in</strong>satz.<br />
E<strong>in</strong>ige H<strong>in</strong>weise stehen <strong>in</strong> DCL – Zugriffsrechte 10 .<br />
7.6. Zusammenfassung<br />
In diesem Kapitel lernten wir grundlegende Informationen zu <strong>SQL</strong>-Befehlen<br />
kennen:<br />
• Die Befehle der DML (Data Manipulation Language) bearbeiten die Daten und<br />
gehören zum Aufgabenbereich e<strong>in</strong>es jeden Benutzers.<br />
• Mit SELECT werden Daten abgerufen, mit INSERT und UPDATE gespeichert<br />
und mit DELETE gelöscht.<br />
• Die DML-Befehle arbeiten mengenorientiert; anstelle konkreter Werte werden<br />
häufig Ausdrücke verwendet.<br />
• Die Befehle der DDL (Data Def<strong>in</strong>ition Language) steuern die <strong>in</strong>terne Struktur<br />
der Datenbank und gehören zum Arbeitsbereich von Programmentwicklern.<br />
• Die Befehle der TCL (Transaction Control Language) s<strong>org</strong>en für die Integrität<br />
der Daten beim Speichern und gehören ebenfalls zum Aufgabenbereich von<br />
Programmentwicklern.<br />
• Die Befehle der DCL (Data Control Language) s<strong>org</strong>en für die Zugriffssicherheit<br />
und gehören zum Aufgabenbereich von Systemadm<strong>in</strong>istratoren.<br />
9 Kapitel 11 auf Seite 89<br />
10 Kapitel 12 auf Seite 95<br />
52
Übungen<br />
Die Bestandteile der <strong>SQL</strong>-Befehle werden anhand von Schlüsselwörtern erkannt<br />
und ausgewertet.<br />
7.7. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 54.<br />
Übung 1 – Begriffsklärung<br />
Was versteht man unter „<strong>SQL</strong>“?<br />
1. Süß − Quadratisch − Lecker<br />
2. Server’s Quick Library<br />
3. Structured Query Language<br />
4. Standard Quarterly Lectures<br />
Übung 2 – Begriffsklärung<br />
Was versteht man unter „DBMS“?<br />
1. Datenbankmanagementsprache<br />
2. Datenbankmanagementsystem<br />
3. Data-Base Manipulation Standard<br />
4. Deutsche Bahn Mobilstation<br />
Übung 3 – Begriffsklärung<br />
Was versteht man unter „<strong>SQL</strong>-Dialekt“?<br />
1. die Sprache des Computers<br />
2. die Sprache des Benutzers<br />
3. die Sprache des Programmierers e<strong>in</strong>es DBMS<br />
4. die Abweichungen der <strong>SQL</strong>-Befehle vom <strong>SQL</strong>-Standard<br />
5. die jeweilige Version e<strong>in</strong>es DBMS<br />
Übung 4 – Teilbereiche von <strong>SQL</strong><br />
Zu welchem der <strong>SQL</strong>-Teilbereiche DML (Data Manipulation Language), DDL<br />
(Data Def<strong>in</strong>ition Language), TCL (Transaction Control Language), DCL (Data<br />
Control Language) gehören die folgenden Befehle?<br />
1. wähle Daten aus<br />
53
<strong>SQL</strong>-Befehle<br />
2. erzeuge e<strong>in</strong>e Tabelle <strong>in</strong> der Datenbank<br />
3. erzeuge e<strong>in</strong>e Prozedur<br />
4. ändere die Informationen, die zu e<strong>in</strong>em Mitarbeiter gespeichert s<strong>in</strong>d<br />
5. ändere die Def<strong>in</strong>ition e<strong>in</strong>er Spalte<br />
6. bestätige e<strong>in</strong>e Gruppe von zusammengehörenden Anweisungen<br />
7. gewähre e<strong>in</strong>em Benutzer Zugriffsrechte auf e<strong>in</strong>e Tabelle<br />
8. SELECT ID FROM Mitarbeiter<br />
9. ROLLBACK<br />
10. UPDATE Versicherungsvertrag SET . . .<br />
11. lösche e<strong>in</strong>en Datensatz <strong>in</strong> e<strong>in</strong>er Tabelle<br />
12. lösche e<strong>in</strong>e Tabelle <strong>in</strong>sgesamt<br />
Übung 5 – <strong>SQL</strong>-Kommentare<br />
Welche der folgenden Zeilen enthalten korrekte Kommentare? Mit ... werden<br />
weitere Teile angedeutet, die für die Frage unwichtig s<strong>in</strong>d.<br />
1. SELECT * FROM Mitarbeiter;<br />
2. UPDATE Mitarbeiter SET Name = ’neu’; -- ändert alle Namen<br />
3. DEL/*löschen*/ETE FROM Mitarbeiter WHERE ...<br />
4. -- DELETE FROM Mitarbeiter WHERE ...<br />
5. UPDATE Mitarbeiter /* ändern */ SET Name = ...; /* usw. */<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 53.<br />
Lösung zu Übung 1 – Begriffsklärung<br />
Antwort 3 ist richtig.<br />
Lösung zu Übung 2 – Begriffsklärung<br />
Antwort 2 ist richtig.<br />
Lösung zu Übung 3 – Begriffsklärung<br />
Antwort 4 ist richtig.<br />
54
Lösung zu Übung 4 – Teilbereiche von <strong>SQL</strong><br />
1. DML − 2. DDL − 3. DDL − 4. DML<br />
5. DDL − 6. TCL − 7. DCL − 8. DML<br />
9. TCL − 10. DML − 11. DML − 12. DDL<br />
Lösung zu Übung 5 – <strong>SQL</strong>-Kommentare<br />
1. Diese Zeile enthält ke<strong>in</strong>en Kommentar.<br />
2. Der letzte Teil nach dem Semikolon ist e<strong>in</strong> Kommentar.<br />
Siehe auch<br />
3. Dieser Versuch e<strong>in</strong>es Kommentars zerstört das Befehlswort DELETE und<br />
ist deshalb unzulässig.<br />
4. Die gesamte Zeile zählt als Kommentar.<br />
5. Diese Zeile enthält zwei Kommentare: zum e<strong>in</strong>en zwischen dem Tabellennamen<br />
und dem Schlüsselwort SET sowie den Rest der Zeile h<strong>in</strong>ter dem<br />
Semikolon.<br />
7.8. Siehe auch<br />
Bei Wikipedia gibt es Erläuterungen zu Fachbegriffen:<br />
• Grafische Benutzeroberfläche 11 (GUI)<br />
• HTML 12<br />
• Parameter 13 <strong>in</strong> der Informatik<br />
• <strong>SQL</strong>-Injection 14<br />
• .NET 15 und ADO.NET 16<br />
• Daten<strong>in</strong>tegrität 17<br />
Zur Arbeit mit <strong>SQL</strong>-Parametern unter C# und ADO.NET gibt es e<strong>in</strong>e ausführliche<br />
Darstellung:<br />
• [Artikelserie] Parameter von <strong>SQL</strong>-Befehlen 18<br />
11 http://de.wikipedia.<strong>org</strong>/wiki/Grafische%20Benutzeroberfl%c3%a4che<br />
12 http://de.wikipedia.<strong>org</strong>/wiki/Hypertext%20Markup%20Language<br />
13 http://de.wikipedia.<strong>org</strong>/wiki/Parameter%20(Informatik)<br />
14 http://de.wikipedia.<strong>org</strong>/wiki/<strong>SQL</strong>-Injection<br />
15 http://de.wikipedia.<strong>org</strong>/wiki/.NET<br />
16 http://de.wikipedia.<strong>org</strong>/wiki/ADO.NET<br />
17 http://de.wikipedia.<strong>org</strong>/wiki/Daten<strong>in</strong>tegrit%c3%a4t<br />
18 http://www.mycsharp.de/wbb2/thread.php?threadid=66704<br />
55
8. DML (1) – Daten abfragen<br />
8.1. SELECT – Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . 57<br />
8.2. Die e<strong>in</strong>fachsten Abfragen . . . . . . . . . . . . . . . . . . . . . . . 58<br />
8.3. DISTINCT – Ke<strong>in</strong>e doppelten Zeilen . . . . . . . . . . . . . . . . 59<br />
8.4. WHERE – E<strong>in</strong>grenzen der Ergebnismenge . . . . . . . . . . . . 60<br />
8.5. ORDER BY – Sortieren . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
8.6. FROM – Mehrere Tabellen verknüpfen . . . . . . . . . . . . . . 62<br />
8.7. Ausblick auf komplexe Abfragen . . . . . . . . . . . . . . . . . . 64<br />
8.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />
8.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65<br />
E<strong>in</strong>e Datenbank enthält e<strong>in</strong>e Vielzahl verschiedener Daten. Abfragen dienen dazu,<br />
bestimmte Daten aus der Datenbank auszugeben. Dabei kann die Ergebnismenge<br />
gemäß den Anforderungen e<strong>in</strong>gegrenzt und genauer gesteuert werden.<br />
Dieser Teilbereich der Data Manipulation Language (DML) behandelt den <strong>SQL</strong>-<br />
Befehl SELECT, mit dem Abfragen durchgeführt werden.<br />
Zunächst geht es um e<strong>in</strong>fache Abfragen. Vertieft wird der SELECT-Befehl unter<br />
„Mehr zu Abfragen“ behandelt, beg<strong>in</strong>nend mit Ausführliche SELECT-Struktur 1 ;<br />
unten im Abschnitt „Ausblick auf komplexe Abfragen“ gibt es H<strong>in</strong>weise auf diese<br />
weiteren Möglichkeiten.<br />
8.1. SELECT – Allgeme<strong>in</strong>e H<strong>in</strong>weise<br />
SELECT ist <strong>in</strong> der Regel der erste und wichtigste Befehl, den der <strong>SQL</strong>-Neul<strong>in</strong>g<br />
kennenlernt, und das aus gutem Grund: Man kann damit ke<strong>in</strong>en Schaden anrichten.<br />
E<strong>in</strong> Fehler im Befehl führt höchstens zu e<strong>in</strong>er Fehlermeldung oder dem<br />
Ausbleiben des Abfrageergebnisses, aber nicht zu Schäden am Datenbestand.<br />
Trotzdem erlaubt der Befehl das Herantasten an die wichtigsten Konzepte von<br />
DML, und die anderen Befehle müssen nicht mehr so <strong>in</strong>tensiv erläutert werden.<br />
Dieser Befehl enthält die folgenden Bestandteile („Klauseln“ genannt).<br />
1 Kapitel 15 auf Seite 129<br />
57
DML (1) – Daten abfragen<br />
SELECT [DISTINCT | ALL]<br />
| *<br />
FROM <br />
[WHERE ]<br />
[GROUP BY ]<br />
[HAVING ]<br />
[UNION ]<br />
[ORDER BY ]<br />
;<br />
Die Reihenfolge der Klauseln ist fest im <strong>SQL</strong>-Standard v<strong>org</strong>egeben. Klauseln, die<br />
<strong>in</strong> [ . . . ] stehen, s<strong>in</strong>d nicht nötig, sondern können entfallen; der Name des Befehls<br />
und die FROM-Angaben s<strong>in</strong>d unbed<strong>in</strong>gt erforderlich, das Semikolon als<br />
Standard empfohlen.<br />
Die wichtigsten Teile werden <strong>in</strong> den folgenden Abschnitten erläutert.<br />
Die folgenden Punkte verlangen dagegen vertiefte Beschäftigung mit <strong>SQL</strong>:<br />
• GROUP BY − Daten gruppieren<br />
• HAVING − weitere E<strong>in</strong>schränkungen<br />
• UNION − mehrere Abfragen verb<strong>in</strong>den<br />
Dies sowie weitere E<strong>in</strong>zelheiten zu den wichtigsten Bestandteilen stehen <strong>in</strong> Ausführliche<br />
SELECT-Struktur 2 und anderen „fortgeschrittenen“ Kapiteln.<br />
Die Beispiele beziehen sich auf den Anfangsbestand der Beispieldatenbank; auf<br />
die Ausgabe der selektierten Datensätze wird <strong>in</strong> der Regel verzichtet. Bitte probieren<br />
Sie alle Beispiele aus und nehmen Sie verschiedene Änderungen vor, um<br />
die Auswirkungen zu erkennen.<br />
8.2. Die e<strong>in</strong>fachsten Abfragen<br />
Aufgabe: Gesucht wird der Inhalt der Tabelle der Fahrzeughersteller mit all<br />
ihren Spalten und Datensätzen (Zeilen).<br />
SELECT * FROM Fahrzeughersteller;<br />
Schauen wir uns das Beispiel etwas genauer an:<br />
• Die beiden Begriffe SELECT und FROM s<strong>in</strong>d <strong>SQL</strong>-spezifische Bezeichner.<br />
• Fahrzeughersteller ist der Name der Tabelle, aus der die Daten selektiert und<br />
ausgegeben werden sollen.<br />
2 Kapitel 15 auf Seite 129<br />
58
DISTINCT – Ke<strong>in</strong>e doppelten Zeilen<br />
• Das Sternchen, Asterisk genannt, ist e<strong>in</strong>e Kurzfassung für „alle Spalten“.<br />
Nun wollen wir nur bestimmte Spalten ausgeben, nämlich e<strong>in</strong>e Liste aller Fahrzeughersteller;<br />
das Land <strong>in</strong>teressiert uns dabei nicht. Dazu müssen zwischen<br />
den <strong>SQL</strong>-Bezeichnern SELECT und FROM die auszugebenden Spalten angegeben<br />
werden. S<strong>in</strong>d es mehrere, dann werden diese durch jeweils e<strong>in</strong> Komma getrennt.<br />
Aufgabe: So erhält man die Namensliste aller Fahrzeughersteller:<br />
SELECT Name FROM Fahrzeughersteller;<br />
Aufgabe: Folgendes Beispiel gibt die beiden Spalten für Name und Land des<br />
Herstellers aus. Die Spalten werden durch Komma getrennt.<br />
SELECT Name, Land FROM Fahrzeughersteller;<br />
Für die Ausgabe kann e<strong>in</strong>e (abweichende) Spaltenüberschrift festgelegt werden.<br />
Diese wird als Spalten-Alias bezeichnet. Der Alias kann dem Spaltennamen direkt<br />
folgen oder mit dem B<strong>in</strong>dewort AS angegeben werden. Das vorherige Beispiel<br />
kann also wie folgt mit dem Alias Hersteller für Name und dem Alias Staat<br />
für Land versehen werden:<br />
SELECT Name Hersteller, Land AS Staat<br />
FROM Fahrzeughersteller;<br />
8.3. DISTINCT – Ke<strong>in</strong>e doppelten Zeilen<br />
Wenn Sie bei e<strong>in</strong>em SELECT-Befehl den DISTINCT-Parameter angeben, erhalten<br />
Sie nur e<strong>in</strong>deutige Ergebnisse:<br />
Aufgabe: Gesucht werden die Fahrzeuge, für die Schadensfälle aufgetreten<br />
s<strong>in</strong>d.<br />
SELECT Fahrzeug_ID<br />
FROM Zuordnung_SF_FZ;<br />
Tatsächlich wird für jeden Schadensfall e<strong>in</strong>e Zeile ausgegeben, die Fahrzeuge 2, 5<br />
und 7 ersche<strong>in</strong>en mehrmals. Damit ke<strong>in</strong>e doppelten Zeilen ausgegeben werden,<br />
wird DISTINCT vor den Spaltennamen <strong>in</strong> den <strong>SQL</strong>-Befehl e<strong>in</strong>gefügt:<br />
SELECT DISTINCT Fahrzeug_ID<br />
FROM Zuordnung_SF_FZ;<br />
59
DML (1) – Daten abfragen<br />
Fahrzeug_ID<br />
3<br />
4<br />
5<br />
6<br />
7<br />
Damit ersche<strong>in</strong>t jede Fahrzeug-ID nur e<strong>in</strong>mal <strong>in</strong> der Liste.<br />
Bitte beachten Sie: Als „e<strong>in</strong>deutig“ gilt immer die gesamte Zeile, also alle aufgeführten<br />
Spalten geme<strong>in</strong>sam. Die folgende Abfrage liefert alle Datensätze; Fahrzeuge<br />
mit mehreren Schadensfällen stehen auch mehrfach <strong>in</strong> der Liste.<br />
Quelltext Falsch<br />
SELECT DISTINCT Fahrzeug_ID, ID<br />
FROM Zuordnung_SF_FZ;<br />
Nur theoretisch DISTINCT, praktisch nicht<br />
Die Alternative zu DISTINCT ist das <strong>in</strong> der Syntax genannte ALL, das ausdrücklich<br />
alle Datensätze abfragt. Da dies der Standardwert ist, wird er äußerst selten<br />
benutzt, sondern kann weggelassen werden.<br />
SELECT ALL Fahrzeug_ID<br />
FROM Zuordnung_SF_FZ<br />
8.4. WHERE – E<strong>in</strong>grenzen der Ergebnismenge<br />
Fast immer soll nicht der komplette Inhalt e<strong>in</strong>er Tabelle ausgegeben werden. Dazu<br />
wird die Ergebnismenge mittels Bed<strong>in</strong>gungen <strong>in</strong> der WHERE-Klausel e<strong>in</strong>gegrenzt,<br />
welche nach dem Tabellennamen im SELECT-Befehl steht.<br />
E<strong>in</strong>e Bed<strong>in</strong>gung ist e<strong>in</strong> logischer Ausdruck, dessen Ergebnis WAHR oder FALSCH<br />
ist. In diesen logischen Ausdrücken werden die Inhalte der Spalten (vorwiegend)<br />
mit konstanten Werten verglichen. Hierbei stehen verschiedene Operatoren zur<br />
Verfügung, vor allem:<br />
= gleich ungleich; seltener auch: !=<br />
< kle<strong>in</strong>er als größer als >= größer als oder gleich<br />
Bed<strong>in</strong>gungen können durch die logischen Operatoren OR und AND und die<br />
Klammern () verknüpft werden. Je komplizierter solche Verknüpfungen werden,<br />
desto sicherer ist es, die Bed<strong>in</strong>gungen durch Klammern zu gliedern. Mit diesen<br />
Mitteln lässt sich die Abfrage entsprechend e<strong>in</strong>grenzen.<br />
60
ORDER BY – Sortieren<br />
Aufgabe: Beispielsweise sollen alle Hersteller angezeigt werden, die ihren Sitz<br />
<strong>in</strong> Schweden oder Frankreich haben:<br />
SELECT * FROM Fahrzeughersteller<br />
WHERE ( Land = ’Schweden’ ) OR ( Land = ’Frankreich’ );<br />
Auf die Klammern kann hier verzichtet werden.<br />
H<strong>in</strong>ter der WHERE-Klausel kann man also e<strong>in</strong>e oder mehrere Bed<strong>in</strong>gungen (mit<br />
e<strong>in</strong>em booleschen Operator verknüpft) e<strong>in</strong>fügen. Jede e<strong>in</strong>zelne besteht aus dem<br />
Namen der Spalte, deren Inhalt überprüft werden soll, und e<strong>in</strong>em Wert, wobei<br />
beide mit e<strong>in</strong>em Vergleichsoperator verknüpft s<strong>in</strong>d.<br />
Für die Umkehrung e<strong>in</strong>er solchen Bed<strong>in</strong>gung gibt es mehrere Möglichkeiten:<br />
• Man kann alle anderen Fälle e<strong>in</strong>zeln auflisten.<br />
• Man dreht e<strong>in</strong>fach den Vergleichsoperator um.<br />
• Man kehrt die Bed<strong>in</strong>gung mit NOT um.<br />
Aufgabe: Es sollen alle Fahrzeughersteller angezeigt werden, die außerhalb<br />
Deutschlands sitzen.<br />
- Vergleichsoperator ändern<br />
SELECT * FROM Fahrzeughersteller<br />
WHERE Land ’Deutschland’;<br />
- Bed<strong>in</strong>gung mit NOT umkehren<br />
SELECT * FROM Fahrzeughersteller<br />
WHERE NOT (Land = ’Deutschland’);<br />
Vertiefte Erläuterungen s<strong>in</strong>d unter WHERE-Klausel im Detail 3 zu f<strong>in</strong>den.<br />
8.5. ORDER BY – Sortieren<br />
Nachdem wir nun die Zeilen und Spalten der Ergebnismenge e<strong>in</strong>grenzen können,<br />
wollen wir die Ausgabe der Zeilen sortieren. Hierfür wird die ORDER BY-<br />
Klausel genutzt. Diese ist die letzte im <strong>SQL</strong>-Befehl vor dem abschließenden Semikolon<br />
und enthält die Spalten, nach denen sortiert werden soll.<br />
Aufgabe: Die Liste der Hersteller wird nach dem Namen sortiert:<br />
SELECT * FROM Fahrzeughersteller<br />
ORDER BY Name;<br />
3 Kapitel 17 auf Seite 155<br />
61
DML (1) – Daten abfragen<br />
Anstatt des Spaltennamens kann auch die Nummer der Spalte genutzt werden.<br />
Mit dem folgenden Statement erreichen wir also das gleiche Ergebnis, da Name<br />
die zweite Spalte <strong>in</strong> unserer Ausgabe ist:<br />
SELECT * FROM Fahrzeughersteller<br />
ORDER BY 2;<br />
Die Angabe nach Spaltennummer ist unüblich; sie wird eigentlich höchstens<br />
dann verwendet, wenn die Spalten genau aufgeführt werden und komplizierte<br />
Angaben – z. B. Berechnete Spalten 4 – enthalten.<br />
Die Sortierung erfolgt standardmäßig aufsteigend; das kann auch durch ASC<br />
ausdrücklich angegeben werden. Die Sortierreihenfolge kann mit dem DESC-<br />
Bezeichner <strong>in</strong> absteigend verändert werden.<br />
SELECT * FROM Fahrzeughersteller<br />
ORDER BY Name DESC;<br />
In <strong>SQL</strong> kann nicht nur nach e<strong>in</strong>er Spalte sortiert werden, sondern nach mehreren<br />
Spalten mit e<strong>in</strong>er eigenen Regel für jede Spalte. Dabei werden die Zeilen zuerst<br />
nach der ersten Spalte sortiert und <strong>in</strong>nerhalb dieser Spalte nach der zweiten<br />
Spalte usw. Bei der Sortierung nach Land und Name wird also zuerst nach dem<br />
Land und dann je Land nach Name sortiert. E<strong>in</strong>e Neusortierung nach Name, die<br />
jene Sortierung nach Land wieder verwirft, f<strong>in</strong>det also nicht statt.<br />
Aufgabe: Der folgende Befehl liefert die Hersteller − zuerst absteigend nach<br />
Land und dann aufsteigend sortiert nach dem Namen − zurück.<br />
SELECT * FROM Fahrzeughersteller<br />
ORDER BY Land DESC, Name ASC;<br />
8.6. FROM – Mehrere Tabellen verknüpfen<br />
In fast allen Abfragen werden Informationen aus mehreren Tabellen zusammengefasst.<br />
Die s<strong>in</strong>nvolle Speicherung von Daten <strong>in</strong> getrennten Tabellen ist e<strong>in</strong>es der<br />
Merkmale e<strong>in</strong>es relationalen DBMS; deshalb müssen die Daten bei e<strong>in</strong>er Abfrage<br />
nach praktischen Gesichtspunkten zusammengeführt werden.<br />
4 Kapitel 24 auf Seite 235<br />
62
8.6.1. Traditionell mit FROM und WHERE<br />
FROM – Mehrere Tabellen verknüpfen<br />
Beim „traditonellen“ Weg werden e<strong>in</strong>fach alle Tabellen <strong>in</strong> der FROM-Klausel aufgeführt<br />
und durch jeweils e<strong>in</strong>e Bed<strong>in</strong>gung <strong>in</strong> der WHERE-Klausel verknüpft.<br />
Aufgabe: Ermittle die Angaben der Mitarbeiter, deren Abteilung ihren Sitz <strong>in</strong><br />
Dortmund oder Bochum hat.<br />
SELECT mi.Name, mi.Vorname, mi.Raum, ab.Ort<br />
FROM Mitarbeiter mi, Abteilung ab<br />
WHERE mi.Abteilung_ID = ab.ID<br />
AND ab.Ort <strong>in</strong> (’Dortmund’, ’Bochum’)<br />
ORDER BY mi.Name, mi.Vorname;<br />
Es werden also Daten aus den Tabellen Mitarbeiter (Name und Raum) sowie Abteilung<br />
(Ort) gesucht. Für die Verknüpfung werden diese Bestandteile benötigt:<br />
• In der FROM-Klausel stehen die benötigten Tabellen.<br />
• Zur Vere<strong>in</strong>fachung wird jeder Tabelle e<strong>in</strong> Kürzel als Tabellen-Alias zugewiesen.<br />
• In der Spaltenliste wird jede e<strong>in</strong>zelne Spalte mit dem Namen der betreffenden<br />
Tabelle bzw. dem Alias verbunden. (Der Tabellenname bzw. Alias kann oft weggelassen<br />
werden; aber schon zur Klarheit sollte er immer benutzt werden.)<br />
• Die WHERE-Klausel enthält die Verknüpfungsbed<strong>in</strong>gung „mi.Abteilung_ID =<br />
ab.ID“ − zusätzlich zur E<strong>in</strong>schränkung nach dem Sitz der Abteilung.<br />
Jede Tabelle <strong>in</strong> e<strong>in</strong>er solchen Abfrage benötigt m<strong>in</strong>destens e<strong>in</strong>e direkte Verknüpfung<br />
zu e<strong>in</strong>er anderen Tabelle. Alle Tabellen müssen zum<strong>in</strong>dest <strong>in</strong>direkt mite<strong>in</strong>ander<br />
verknüpft se<strong>in</strong>. Falsche Verknüpfungen s<strong>in</strong>d e<strong>in</strong>e häufige Fehlerquelle.<br />
Vertiefte Erläuterungen s<strong>in</strong>d unter E<strong>in</strong>fache Tabellenverknüpfung 5 zu f<strong>in</strong>den.<br />
8.6.2. Modern mit JOIN . . . ON<br />
Beim „modernen“ Weg steht e<strong>in</strong>e Tabelle <strong>in</strong> der FROM-Klausel, nämlich diejenige,<br />
die als wichtigste oder „Haupttabelle“ der Abfrage anzusehen ist. E<strong>in</strong>e weitere<br />
Tabelle wird durch JOIN und e<strong>in</strong>e Bed<strong>in</strong>gung <strong>in</strong> der ON-Klausel verknüpft.<br />
Das obige Beispiel sieht dann so aus:<br />
SELECT mi.Name, mi.Vorname, mi.Raum, ab.Ort<br />
FROM Mitarbeiter mi<br />
JOIN Abteilung ab<br />
ON mi.Abteilung_ID = ab.ID<br />
WHERE ab.Ort <strong>in</strong> (’Dortmund’, ’Bochum’)<br />
ORDER BY mi.Name, mi.Vorname;<br />
5 Kapitel 19 auf Seite 173<br />
63
DML (1) – Daten abfragen<br />
Für die Verknüpfung der Tabellen werden folgende Bestandteile benötigt:<br />
• In der FROM-Klausel steht e<strong>in</strong>e der benötigten Tabellen.<br />
• In der JOIN-Klausel steht jeweils e<strong>in</strong>e weitere Tabelle.<br />
• Die ON-Klausel enthält die Verknüpfungsbed<strong>in</strong>gung „mi.Abteilung_ID =<br />
ab.ID“.<br />
• Die WHERE-Klausel beschränkt sich auf die wirklich gewünschten E<strong>in</strong>schränkungen<br />
für die Ergebnismenge.<br />
E<strong>in</strong> Tabellen-Alias ist wiederum für alle Tabellen s<strong>in</strong>nvoll. In der Spaltenliste und<br />
auch zur Sortierung können alle Spalten aller Tabellen benutzt werden.<br />
Vertiefte Erläuterungen s<strong>in</strong>d unter Arbeiten mit JOIN 6 zu f<strong>in</strong>den.<br />
8.7. Ausblick auf komplexe Abfragen<br />
Das folgende Beispiel ist erheblich umfangreicher und geht über „Anfängerbedürfnisse“<br />
weit h<strong>in</strong>aus. Es zeigt aber sehr schön, was alles mit <strong>SQL</strong> möglich ist:<br />
Aufgabe: Gesucht werden die Daten der Versicherungsnehmer im Jahr 2008,<br />
und zwar die Adresse, die Höhe des Gesamtschadens und die Anzahl der Schadensfälle.<br />
SELECT vn.Name,<br />
vn.Vorname,<br />
vn.Strasse,<br />
vn.Hausnummer AS HNR,<br />
vn.PLZ,<br />
vn.Ort,<br />
SUM(sf.Schadenshoehe) AS Schaden,<br />
COUNT(sf.ID) AS Anzahl<br />
FROM Versicherungsnehmer vn<br />
JOIN Versicherungsvertrag vv ON vv.Versicherungsnehmer_ID = vn.ID<br />
JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Zuordnung_SF_FZ zu ON zu.Fahrzeug_ID = fz.ID<br />
JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
WHERE EXTRACT(YEAR FROM sf.Datum) = 2008<br />
GROUP BY vn.Name, vn.Vorname, vn.Strasse, vn.Hausnummer, vn.PLZ, vn.Ort<br />
ORDER BY Gesamtschaden, Anzahl;<br />
NAME VORNAME STRASSE HNR PLZ ORT SCHADEN ANZAHL<br />
Heckel Obsthandel GmbH Gahlener Str. 40 46282 Dorsten 1.438,75 1<br />
Antonius Bernhard Coesfelder Str. 23 45892 Gelsenkirchen 1.983,00 1<br />
6 Kapitel 20 auf Seite 181<br />
64
Zusammenfassung<br />
Hierbei kommen die Funktionen SUM (Summe) und COUNT (Anzahl) zum E<strong>in</strong>satz.<br />
Diese können nur e<strong>in</strong>gesetzt werden, wenn die Datenmenge richtig gruppiert<br />
wurde. Deshalb wird mit GROUP BY das Datenmaterial nach allen verbliebenen,<br />
zur Ausgabe v<strong>org</strong>esehenen Datenfeldern gruppiert.<br />
Vertiefte Erläuterungen s<strong>in</strong>d zu f<strong>in</strong>den <strong>in</strong> den Kapiteln Funktionen 7 sowie Gruppierungen<br />
8 .<br />
8.8. Zusammenfassung<br />
In diesem Kapitel lernten wir die Grundlagen e<strong>in</strong>es SELECT-Befehls kennen:<br />
• SELECT-Befehle werden zur Abfrage von Daten aus Datenbanken genutzt.<br />
• Die auszugebenden Spalten können festgelegt werden, <strong>in</strong>dem die Liste der<br />
Spalten zwischen den Bezeichnern SELECT und FROM angegeben wird.<br />
• Mit DISTINCT werden identische Zeilen <strong>in</strong> der Ergebnismenge nur e<strong>in</strong>mal<br />
ausgegeben.<br />
• Die Ergebnismenge wird mittels der WHERE-Klausel e<strong>in</strong>gegrenzt.<br />
• Die WHERE-Klausel enthält logische Ausdrücke. Diese können mit AND und<br />
OR verknüpft werden.<br />
• Mittels der ORDER BY-Klausel kann die Ergebnismenge sortiert werden.<br />
Die Reihenfolge <strong>in</strong>nerhalb e<strong>in</strong>es SELECT-Befehls ist zu beachten. SELECT und<br />
FROM s<strong>in</strong>d hierbei Pflicht, das abschließende Semikolon als Standard empfohlen.<br />
Alle anderen Klauseln s<strong>in</strong>d optional.<br />
8.9. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 67.<br />
Bei den Übungen 3–6 ist jeweils e<strong>in</strong>e Abfrage zur Tabelle Abteilung zu erstellen.<br />
Übung 1 – Richtig oder falsch?<br />
Welche der folgenden Aussagen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
1. Fehlerhafte Verwendung von SELECT kann ke<strong>in</strong>en Schaden am Datenbestand<br />
hervorrufen.<br />
7 Kapitel 14 auf Seite 109<br />
8 Kapitel 25 auf Seite 241<br />
65
DML (1) – Daten abfragen<br />
2. Die gewünschten Spalten werden <strong>in</strong> e<strong>in</strong>er Liste jeweils durch Leerzeichen<br />
getrennt.<br />
3. Wenn alle Spalten gewünscht werden, s<strong>in</strong>d auch alle Spalten <strong>in</strong> dieser Liste<br />
aufzuführen.<br />
4. Es ist Standard, den Parameter ALL bei e<strong>in</strong>em SELECT-Befehl anzugeben.<br />
5. Bei SELECT DISTINCT werden nur solche Zeilen angezeigt, die sich <strong>in</strong> m<strong>in</strong>destens<br />
e<strong>in</strong>er Spalte unterscheiden.<br />
6. Die WHERE-Klausel dient dazu, das Ergebnis der Abfrage wunschgemäß<br />
zu formatieren.<br />
7. E<strong>in</strong>e WHERE-Bed<strong>in</strong>gung ist stets e<strong>in</strong>e Prüfung auf WAHR/FALSCH: Entweder<br />
e<strong>in</strong> Datensatz erfüllt die Bed<strong>in</strong>gung und gehört zum Abfrageergebnis,<br />
oder er gehört nicht dazu.<br />
8. WHERE-Bed<strong>in</strong>gungen können mit AND/OR komb<strong>in</strong>iert und mit NOT umgekehrt<br />
werden.<br />
9. Solche Komb<strong>in</strong>ationen von Bed<strong>in</strong>gungen müssen durch Klammern gegliedert<br />
werden.<br />
10. Um Daten aus mehreren Tabellen zu verknüpfen, ist es möglich, diese Tabellen<br />
e<strong>in</strong>fach <strong>in</strong> der FROM-Klausel aufzuführen.<br />
Übung 2 – Pflichtangaben<br />
Welche Bestandteile e<strong>in</strong>es SELECT-Befehls s<strong>in</strong>d unbed<strong>in</strong>gt erforderlich und können<br />
nicht weggelassen werden?<br />
Übung 3 – Alle Angaben<br />
Geben Sie alle Informationen zu allen Abteilungen aus.<br />
Übung 4 – Angaben mit E<strong>in</strong>schränkung<br />
Geben Sie alle Abteilungen aus, deren Standort Bochum ist.<br />
Übung 5 – Angaben mit E<strong>in</strong>schränkungen<br />
Geben Sie alle Abteilungen aus, deren Standort Bochum oder Essen ist. Hierbei<br />
soll nur der Name der Abteilung ausgegeben werden.<br />
Übung 6 – Abfrage mit Sortierung<br />
Geben Sie nur die Kurzbezeichnungen aller Abteilungen aus. Hierbei sollen die<br />
Abteilungen nach den Standorten sortiert werden.<br />
66
Übung 7 – DISTINCT und ALL<br />
Bitte überprüfen Sie die folgenden Befehle:<br />
-- Variante 1<br />
SELECT DISTINCT COUNT(*)<br />
FROM Mitarbeiter<br />
GROUP BY Abteilung_ID<br />
-- Variante 2<br />
SELECT ALL COUNT(*)<br />
FROM Mitarbeiter<br />
GROUP BY Abteilung_ID<br />
Übungen<br />
Wor<strong>in</strong> unterscheidet sich die Ausgabe? Welche wichtige Information fehlt vor allem<br />
bei Variante 2?<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 65.<br />
Lösung zu Übung 1 – Richtig oder falsch?<br />
Die Aussagen 1, 5, 7, 8, 10 s<strong>in</strong>d richtig. Die Aussagen 2, 3, 4, 6, 9 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – Pflichtangaben<br />
SELECT, Spaltenliste oder '*', FROM, Tabellenname.<br />
Lösung zu Übung 3 – Alle Angaben<br />
SELECT * FROM Abteilung;<br />
Lösung zu Übung 4 – Angaben mit E<strong>in</strong>schränkung<br />
SELECT * FROM Abteilung<br />
WHERE Ort = ’Bochum’;<br />
Lösung zu Übung 5 – Angaben mit E<strong>in</strong>schränkungen<br />
SELECT Bezeichnung FROM Abteilung<br />
WHERE Ort = ’Bochum’ OR Ort = ’Essen’;<br />
67
DML (1) – Daten abfragen<br />
Alternativ ist es auch so möglich:<br />
SELECT Bezeichnung FROM Abteilung<br />
WHERE Ort IN (’Bochum’, ’Essen’);<br />
Lösung zu Übung 6 – Abfrage mit Sortierung<br />
SELECT Kuerzel FROM Abteilung<br />
ORDER BY Ort;<br />
Lösung zu Übung 7 – DISTINCT und ALL<br />
Variante 1 nennt jede Anzahl von Mitarbeitern pro Abteilung genau e<strong>in</strong>mal. Bei<br />
Variante 2 gibt es diese Anzeige für jede Abteilung e<strong>in</strong>zeln. Dabei fehlt vor allem<br />
die Angabe der Abteilung_ID, ohne die die Ausgabe ziemlich s<strong>in</strong>nlos ist.<br />
68
9. DML (2) – Daten speichern<br />
9.1. INSERT – Daten e<strong>in</strong>fügen . . . . . . . . . . . . . . . . . . . . . . . 69<br />
9.2. UPDATE – Daten ändern . . . . . . . . . . . . . . . . . . . . . . . 72<br />
9.3. DELETE – Daten löschen . . . . . . . . . . . . . . . . . . . . . . . 74<br />
9.4. TRUNCATE – Tabelle leeren . . . . . . . . . . . . . . . . . . . . . 75<br />
9.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 75<br />
9.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75<br />
Dieser Teilbereich der Data Manipulation Language (DML) behandelt die Befehle,<br />
mit denen die Inhalte der Datenbank geändert werden: Neuaufnahme, Änderung,,<br />
Löschung.<br />
Bitte beachten Sie, dass mit den Befehlen INSERT, UPDATE, DELETE (fast) immer<br />
nur Daten genau e<strong>in</strong>er Tabelle bearbeitet werden können − anders als beim<br />
SELECT-Befehl, der Daten mehrerer Tabellen zusammenfassen kann.<br />
9.1. INSERT – Daten e<strong>in</strong>fügen<br />
Der INSERT-Befehl dient dem Erstellen von neuen Datensätzen. Es gibt ihn <strong>in</strong><br />
zwei Versionen − zum e<strong>in</strong>en durch die Angabe e<strong>in</strong>zelner Werte, zum anderen<br />
mit Hilfe e<strong>in</strong>es SELECT-Befehls.<br />
In beiden Versionen müssen die Datentypen 1 der Werte zu den Datentypen der<br />
Spalten passen. Man sollte nicht versuchen, e<strong>in</strong>er Spalte, die e<strong>in</strong>e Zahl erwartet,<br />
e<strong>in</strong>e Zeichenkette zuzuweisen. Man wird nur selten das Ergebnis erhalten,<br />
welches man erwartet. Das Kapitel Funktionen 2 erläutert im Abschnitt „Konvertierungen“<br />
Möglichkeiten, wie Werte implizit (also automatisch) oder explizit<br />
durch CAST oder CONVERT angepasst werden können.<br />
1 Kapitel 13 auf Seite 97<br />
2 Abschnitt 14.6 auf Seite 117<br />
69
DML (2) – Daten speichern<br />
9.1.1. E<strong>in</strong>zeln mit VALUES<br />
Wenn e<strong>in</strong> e<strong>in</strong>zelner Datensatz durch die Liste se<strong>in</strong>er Werte gespeichert werden<br />
soll, gilt folgende Syntax:<br />
INSERT INTO <br />
[ ( ) ]<br />
VALUES ( )<br />
;<br />
Zu diesem Befehl gehören folgende Angaben:<br />
• INSERT als Name des Befehls, INTO als feststehender Begriff<br />
• als Name der Tabelle, die diesen Datensatz erhalten soll<br />
• <strong>in</strong> Klammern ( ) gesetzt e<strong>in</strong>e Liste von Spalten (Feldnamen), denen Werte zugewiesen<br />
werden<br />
• der Begriff VALUES als H<strong>in</strong>weis darauf, dass e<strong>in</strong>zelne Werte angegeben werden<br />
• <strong>in</strong> Klammern ( ) gesetzt e<strong>in</strong>e Liste von Werten, die <strong>in</strong> den entsprechenden Spalten<br />
gespeichert werden sollen<br />
Wenn e<strong>in</strong>e Liste von Spalten fehlt, bedeutet das, dass alle Spalten dieser Tabelle<br />
<strong>in</strong> der Reihenfolge der Struktur mit Werten versehen werden müssen.<br />
Aufgabe: So wird (wie im Skript der Beispieldatenbank) e<strong>in</strong> E<strong>in</strong>trag <strong>in</strong> der Tabelle<br />
Mitarbeiter gespeichert:<br />
INSERT INTO Mitarbeiter<br />
( Personalnummer, Name, Vorname,<br />
Telefon, Email, Raum, Ist_Leiter, Abteilung_ID, Geburtsdatum )<br />
VALUES ( ’20002’, ’Schmitz’, ’Michael’,<br />
’0231/5556187’, ’michael.schmitz@unserefirma.de’,<br />
’212’, ’N’, 2, ’1959-08-25’ );<br />
Wenn Sie diesen Befehl mit der Tabellenstruktur vergleichen, werden Sie feststellen:<br />
• Die Spalte ID fehlt. Dieser Wert wird von der Datenbank automatisch vergeben.<br />
• Die Spalte Mobil fehlt. In dieser Spalte wird folglich e<strong>in</strong> NULL-Wert gespeichert.<br />
• Die Reihenfolge der Spalten weicht von der Tabellendef<strong>in</strong>ition ab; das ist also<br />
durchaus möglich.<br />
In der Beschreibung der Beispieldatenbank werden sehr viele Spalten als<br />
„Pflicht“ festgelegt. Folgender Befehl wird deshalb zurückgewiesen:<br />
70
INSERT – Daten e<strong>in</strong>fügen<br />
<strong>SQL</strong> Quelltext Falsch<br />
INSERT INTO Mitarbeiter<br />
( Personalnummer, Name, Vorname, Ist_Leiter, Abteilung_ID )<br />
VALUES ( ’17999’, ’Liebich’, ’Andrea’, ’N’, 17);<br />
validation error for column GEBURTSDATUM, value "*** null ***".<br />
Die Spalte Geburtsdatum darf laut Def<strong>in</strong>ition nicht NULL se<strong>in</strong>. E<strong>in</strong>e Angabe fehlt<br />
<strong>in</strong> diesem Befehl: das wird als NULL <strong>in</strong>terpretiert, also mit e<strong>in</strong>er Fehlermeldung<br />
quittiert.<br />
9.1.2. Mengen mit SELECT<br />
Wenn e<strong>in</strong>e Menge von Datensätzen mit Hilfe e<strong>in</strong>es SELECT-Befehls gespeichert<br />
werden soll, gilt folgende Syntax:<br />
INSERT INTO <br />
[ ( ) ]<br />
SELECT <br />
;<br />
Zu diesem Befehl gehören die folgenden Angaben:<br />
• INSERT INTO (wie oben)<br />
• <strong>in</strong> Klammern ( ) gesetzt e<strong>in</strong>e Liste von Spalten (Feldnamen), sofern v<strong>org</strong>esehen<br />
• dazu e<strong>in</strong> vollständiger SELECT-Befehl, mit dem die passenden Inhalte geliefert<br />
werden<br />
Da e<strong>in</strong> SELECT-Befehl auch ohne Bezug auf e<strong>in</strong>e Tabelle nur mit konstanten Werten<br />
möglich ist, kann das obige Beispiel auch so formuliert werden:<br />
INSERT INTO Mitarbeiter<br />
( Personalnummer, Name, Vorname,<br />
Telefon, Email, Raum, Ist_Leiter, Abteilung_ID, Geburtsdatum )<br />
SELECT ’20002’, ’Schmitz’, ’Michael’,<br />
’0231/5556187’, ’michael.schmitz@unserefirma.de’,<br />
’212’, ’N’, 2, ’1959-08-25’<br />
/* from rdb%database (bei Firebird) */ ;<br />
/* from dual (bei Oracle) */ ;<br />
H<strong>in</strong>weis: Firebird und Oracle kennen diese Kurzform des SELECT-Befehls nicht;<br />
dort ist die als Kommentar jeweils e<strong>in</strong>gefügte FROM-Klausel erforderlich.<br />
Wichtig ist diese Art des INSERT-Befehls, wenn neue Datensätze aus vorhandenen<br />
anderen Daten abgeleitet werden wie im Skript der Beispieldatenbank:<br />
71
DML (2) – Daten speichern<br />
Aufgabe: Für jeden Abteilungsleiter aus der Tabelle Mitarbeiter wird e<strong>in</strong> E<strong>in</strong>trag<br />
<strong>in</strong> der Tabelle Dienstwagen gespeichert:<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID )<br />
SELECT ’DO-WB 42’ || Abteilung_ID, ’elfenbe<strong>in</strong>’, 14, ID<br />
FROM Mitarbeiter<br />
WHERE Ist_Leiter = ’J’;<br />
Die Spalte ID wird automatisch zugewiesen. Die anderen Spalten erhalten Werte:<br />
• Farbe und Fahrzeugtyp als Konstante<br />
• dazu natürlich die ID des Mitarbeiters, dem der Dienstwagen zugeordnet wird<br />
• und e<strong>in</strong> Kfz-Kennzeichen, das aus e<strong>in</strong>em konstanten Teil mit der ID der Abteilung<br />
zusammengesetzt wird<br />
Manches DBMS erlaubt auch die Erstellung von Tabellen aus e<strong>in</strong>em SELECT-<br />
Ausdruck wie bei MS-<strong>SQL</strong> (nächstes Beispiel) oder Teradata (anschließend):<br />
SELECT [ ( ) ]<br />
INTO <br />
FROM <br />
CREATE TABLE AS<br />
SELECT [ ( ) ]<br />
FROM <br />
9.2. UPDATE – Daten ändern<br />
Der UPDATE-Befehl dient zum Ändern e<strong>in</strong>er Menge von Datensätzen:<br />
UPDATE <br />
SET <br />
[WHERE ];<br />
Jede Änderung e<strong>in</strong>es Feldes ist so e<strong>in</strong>zutragen:<br />
= ,<br />
Zu diesem Befehl gehören die folgenden Angaben:<br />
• UPDATE als Name des Befehls<br />
• als Name der Tabelle, <strong>in</strong> der die Daten zu ändern s<strong>in</strong>d<br />
72
• SET als Anfang der Liste von Änderungen<br />
UPDATE – Daten ändern<br />
• als Name der Spalte, die e<strong>in</strong>en neuen Inhalt erhalten soll, dazu<br />
das Gleichheitszeichen und die Zuweisung von als neuer Inhalt<br />
• e<strong>in</strong> Komma als H<strong>in</strong>weis, dass e<strong>in</strong> weiteres Feld zu ändern ist; vor der WHERE-<br />
Klausel oder dem abschließenden Semikolon muss das Komma entfallen<br />
• die WHERE-Klausel mit Bed<strong>in</strong>gungen, welche Datensätze zu ändern s<strong>in</strong>d: e<strong>in</strong>er<br />
oder e<strong>in</strong>e bestimmte Menge<br />
Die Struktur der WHERE-Klausel ist identisch mit derjenigen beim SELECT-<br />
Befehl. Wenn alle Datensätze geändert werden sollen, kann die WHERE-<br />
Bed<strong>in</strong>gung entfallen; aber beachten Sie unbed<strong>in</strong>gt:<br />
Ohne WHERE-Bed<strong>in</strong>gung wird wirklich alles sofort geändert.<br />
An den Beispielen ist zu sehen, dass die Änderung aller Datensätze nur selten<br />
s<strong>in</strong>nvoll ist und meistens mit WHERE-Bed<strong>in</strong>gung gearbeitet wird.<br />
Wie beim INSERT-Befehl muss der Datentyp e<strong>in</strong>es Wertes zum Datentyp der<br />
Spalte passen. Beispiele:<br />
Aufgabe: Korrigiere die Schreibweise des Namens bei e<strong>in</strong>em Mitarbeiter.<br />
UPDATE Mitarbeiter<br />
SET Name = ’Mayer’<br />
WHERE Personalnummer = 20001;<br />
Aufgabe: Ändere nach e<strong>in</strong>er E<strong>in</strong>geme<strong>in</strong>dung PLZ und Ortsname für alle betroffenen<br />
Adressen.<br />
UPDATE Versicherungsnehmer<br />
SET Ort = ’Leipzig’,<br />
PLZ = ’04178’<br />
WHERE PLZ = ’04430’;<br />
Aufgabe: Erhöhe bei allen Schadensfällen die Schadenshöhe um 10 % (das ist<br />
natürlich ke<strong>in</strong>e s<strong>in</strong>nvolle Maßnahme):<br />
UPDATE Schadensfall<br />
SET Schadenshoehe = Schadenshoehe * 1.1;<br />
Aufgabe: Berichtige das Geburtsdatum für e<strong>in</strong>en Versicherungsnehmer:<br />
UPDATE Versicherungsnehmer<br />
SET Geburtsdatum = ’14.03.1963’<br />
WHERE Name = ’Zenep’ AND Geburtsdatum = ’13.02.1963’;<br />
0 row(s) affected.<br />
73
DML (2) – Daten speichern<br />
Nanu, ke<strong>in</strong>e Zeilen wurden geändert? Bei diesem Befehl wurde zur Kontrolle,<br />
welcher Datensatz geändert werden sollte, nicht nur der Nachname, sondern<br />
auch das bisher notierte Geburtsdatum angegeben − und dieses war falsch.<br />
Tatsächlich ändert der UPDATE-Befehl also e<strong>in</strong>e Menge von Datensätzen: je<br />
nach WHERE-Klausel null, e<strong>in</strong>en, mehrere oder alle Zeilen der Tabelle.<br />
9.3. DELETE – Daten löschen<br />
Der DELETE-Befehl löscht e<strong>in</strong>e Menge von Datensätzen <strong>in</strong> e<strong>in</strong>er Tabelle:<br />
DELETE FROM <br />
[ WHERE ] ;<br />
Zu diesem Befehl gehören folgende Angaben:<br />
• DELETE als Name des Befehls, FROM als feststehender Begriff<br />
• für die Tabelle, aus der die Datenmenge zu entfernen ist<br />
• die WHERE-Klausel mit Bed<strong>in</strong>gungen, welche Datensätze zu löschen s<strong>in</strong>d: e<strong>in</strong>er<br />
oder e<strong>in</strong>e bestimmte Menge<br />
Die Struktur der WHERE-Klausel ist identisch mit derjenigen beim SELECT-<br />
Befehl. Wenn alle Datensätze gelöscht werden sollen, kann die WHERE-<br />
Bed<strong>in</strong>gung entfallen; aber beachten Sie unbed<strong>in</strong>gt:<br />
Ohne WHERE-Bed<strong>in</strong>gung wird wirklich alles sofort gelöscht.<br />
Beispiele:<br />
Aufgabe: Der Mitarbeiter mit der Personalnummer 20001 ist ausgeschieden.<br />
DELETE FROM Mitarbeiter<br />
WHERE Personalnummer = 20001;<br />
Aufgabe: Die Abteilung 1 wurde ausgelagert, alle Mitarbeiter gehören nicht<br />
mehr zum Unternehmen.<br />
DELETE FROM Mitarbeiter<br />
WHERE Abteilung_ID = 1;<br />
Aufgabe: Entferne den gesamten Inhalt der Tabelle.<br />
DELETE FROM Schadensfall;<br />
Achtung: Dies löscht ohne weitere Rückfrage alle Schadensfälle; die Struktur der<br />
Tabelle selbst bleibt erhalten. E<strong>in</strong> solcher Befehl sollte unbed<strong>in</strong>gt nur nach e<strong>in</strong>er<br />
74
TRUNCATE – Tabelle leeren<br />
vorherigen Datensicherung ausgeführt werden. Auch der Versuch ist „strafbar“<br />
und führt zum sofortigen Datenverlust.<br />
9.4. TRUNCATE – Tabelle leeren<br />
Wenn Sie entgegen den oben genannten H<strong>in</strong>weisen wirklich alle Datensätze<br />
e<strong>in</strong>er Tabelle löschen wollen, können Sie (soweit vorhanden) anstelle von<br />
DELETE den TRUNCATE-Befehl benutzen. Damit werden (ohne Verb<strong>in</strong>dung mit<br />
WHERE) immer alle Datensätze gelöscht; dies geschieht schneller und e<strong>in</strong>facher,<br />
weil auf das <strong>in</strong>terne Änderungsprotokoll der Datenbank verzichtet wird.<br />
TRUNCATE TABLE Schadensfall;<br />
9.5. Zusammenfassung<br />
In diesem Kapitel lernten wir die <strong>SQL</strong>-Befehle kennen, mit denen der Datenbestand<br />
geändert wird:<br />
• Mit INSERT + VALUES wird e<strong>in</strong> e<strong>in</strong>zelner Datensatz e<strong>in</strong>gefügt.<br />
• Mit INSERT + SELECT wird e<strong>in</strong>e Menge von Datensätzen mit Hilfe e<strong>in</strong>er Abfrage<br />
e<strong>in</strong>gefügt.<br />
• Mit UPDATE wird e<strong>in</strong>e Menge von Datensätzen geändert; die Menge wird<br />
durch WHERE festgelegt.<br />
• Mit DELETE wird e<strong>in</strong>e Menge von Datensätzen gelöscht; die Menge wird durch<br />
WHERE festgelegt.<br />
• Mit TRUNCATE werden alle Datensätze e<strong>in</strong>er Tabelle gelöscht.<br />
Die WHERE-Bed<strong>in</strong>gungen s<strong>in</strong>d hier besonders wichtig, damit ke<strong>in</strong>e falschen<br />
Speicherungen erfolgen.<br />
9.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 76.<br />
Übung 1 – Daten e<strong>in</strong>zeln e<strong>in</strong>fügen<br />
Welche Angaben werden benötigt, wenn e<strong>in</strong> e<strong>in</strong>zelner Datensatz <strong>in</strong> der Datenbank<br />
gespeichert werden soll?<br />
75
DML (2) – Daten speichern<br />
Übung 2 – Daten e<strong>in</strong>zeln e<strong>in</strong>fügen<br />
Speichern Sie <strong>in</strong> der Tabelle Mitarbeiter e<strong>in</strong>en neuen Datensatz und lassen Sie<br />
alle Spalten und Werte weg, die nicht benötigt werden.<br />
Übung 3 – Daten e<strong>in</strong>fügen<br />
Begründen Sie, warum der Spalte ID beim E<strong>in</strong>fügen <strong>in</strong> der Regel ke<strong>in</strong> Wert zugewiesen<br />
wird.<br />
Übung 4 – Daten e<strong>in</strong>fügen<br />
Begründen Sie, warum die folgenden Befehle nicht ausgeführt werden können.<br />
/* Mitarbeiter */<br />
INSERT INTO Mitarbeiter<br />
( ID, Personalnummer, Name, Vorname, Geburtsdatum, Ist_Leiter, Abteilung_ID )<br />
VALUES ( 4, ’PD-348’, ’Çiçek’, ’Yasem<strong>in</strong>’, ’23.08.1984’, ’J’, 9 );<br />
/* Dienstwagen 1 */<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID )<br />
VALUES ( ’DO-UF 1234’, null, null, null );<br />
/* Dienstwagen 2 */<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID )<br />
VALUES ( ’HAM-AB 1234’, ’rot’, 7, null );<br />
Übung 5 – Daten ändern und löschen<br />
Warum gibt es selten UPDATE- oder DELETE-Befehle ohne e<strong>in</strong>e WHERE-<br />
Klausel?<br />
Übung 6 – Daten ändern<br />
Schreiben Sie e<strong>in</strong>en <strong>SQL</strong>-Befehl für folgende Änderung: Alle Mitarbeiter, die bisher<br />
noch ke<strong>in</strong>en Mobil-Anschluss hatten, sollen unter e<strong>in</strong>er e<strong>in</strong>heitlichen Nummer<br />
erreichbar se<strong>in</strong>.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 75.<br />
Lösung zu Übung 1 – Daten e<strong>in</strong>zeln e<strong>in</strong>fügen<br />
• der INSERT-Befehl selbst<br />
76
• INTO mit Angabe der Tabelle<br />
Übungen<br />
• bei Bedarf <strong>in</strong> Klammern die Liste der Spalten, die mit Werten versehen werden<br />
• VALUES zusammen mit (<strong>in</strong> Klammern) der Liste der zugeordneten Werte<br />
Lösung zu Übung 2 – Daten e<strong>in</strong>zeln e<strong>in</strong>fügen<br />
Beispielsweise so:<br />
INSERT INTO Mitarbeiter<br />
( Personalnummer, Name, Vorname, Geburtsdatum, Ist_Leiter, Abteilung_ID )<br />
VALUES ( ’PD-348’, ’Çiçek’, ’Yasem<strong>in</strong>’, ’23.08.1984’, ’J’, 9 );<br />
Lösung zu Übung 3 – Daten e<strong>in</strong>fügen<br />
Dieser soll von der Datenbank automatisch zugewiesen werden; er muss deshalb<br />
weggelassen oder mit NULL v<strong>org</strong>egeben werden.<br />
Lösung zu Übung 4 – Daten e<strong>in</strong>fügen<br />
• Mitarbeiter: Diese ID ist schon vergeben; sie muss aber e<strong>in</strong>deutig se<strong>in</strong>.<br />
• Dienstwagen 1: Der Fahrzeugtyp ist e<strong>in</strong>e Pflicht-Angabe; die Fahrzeugtyp_ID<br />
darf nicht null se<strong>in</strong>.<br />
• Dienstwagen 2: Das Kennzeichen ist zu lang; es darf maximal e<strong>in</strong>e Länge von<br />
10 Zeichen haben.<br />
Lösung zu Übung 5 – Daten ändern und löschen<br />
Dies würde die gleiche Änderung bzw. die Löschung für alle Datensätze ausführen;<br />
das ist selten s<strong>in</strong>nvoll bzw. gewünscht.<br />
Lösung zu Übung 6 – Daten ändern<br />
UPDATE Mitarbeiter<br />
SET Mobil = ’(0177) 44 55 66 77’<br />
WHERE Mobil IS NULL OR Mobil = ”;<br />
77
10. DDL – Struktur der Datenbank<br />
10.1. Allgeme<strong>in</strong>e Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . 79<br />
10.2. Hauptteile der Datenbank . . . . . . . . . . . . . . . . . . . . . . 80<br />
10.3. Ergänzungen zu Tabellen . . . . . . . . . . . . . . . . . . . . . . . 83<br />
10.4. Programmieren mit <strong>SQL</strong> . . . . . . . . . . . . . . . . . . . . . . . 85<br />
10.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 85<br />
10.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />
10.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87<br />
Mit den Befehlen der Data Def<strong>in</strong>ition Language (DDL) wird die Struktur der Datenbank<br />
gesteuert. Diese Aufgaben gehen über e<strong>in</strong>e <strong>E<strong>in</strong>führung</strong> <strong>in</strong> <strong>SQL</strong> h<strong>in</strong>aus.<br />
Hier werden deshalb nur e<strong>in</strong>ige grundlegende Informationen und Beispiele behandelt,<br />
für welche Objekte e<strong>in</strong>er Datenbank diese Befehle verwendet werden.<br />
Vor allem bei DDL-Befehlen muss e<strong>in</strong>e Benutzer<strong>in</strong> über die nötigen Zugriffsrechte<br />
1 verfügen. E<strong>in</strong>ige Erweiterungen zu DDL folgen gegen Ende des Buches:<br />
• DDL − E<strong>in</strong>zelheiten 2<br />
• Änderung der Datenbankstruktur 3<br />
• <strong>SQL</strong>-Programmierung 4<br />
10.1. Allgeme<strong>in</strong>e Syntax<br />
Die DDL-Befehle s<strong>in</strong>d grundsätzlich so aufgebaut:<br />
BEFEHL OBJEKTTYP []<br />
CREATE erzeugt e<strong>in</strong> Objekt wie e<strong>in</strong>e Datentabelle oder gar e<strong>in</strong>e Datenbank.<br />
ALTER ermöglicht die Änderung e<strong>in</strong>es Objekts, z. B. e<strong>in</strong>er Tabelle. In neueren<br />
Versionen gibt es auch den geme<strong>in</strong>samen Aufruf (unterschiedlich je nach<br />
1 Kapitel 12 auf Seite 95<br />
2 Kapitel 28 auf Seite 285<br />
3 Kapitel 35 auf Seite 397<br />
4 Kapitel 30 auf Seite 323<br />
79
DDL – Struktur der Datenbank<br />
DBMS); das DBMS entscheidet dann selbst: Wenn das Objekt schon existiert,<br />
wird es geändert, andernfalls erzeugt.<br />
• CREATE OR ALTER<br />
• CREATE OR REPLACE<br />
• RECREATE<br />
DROP dient dazu, das Objekt, z. B. e<strong>in</strong>e Tabelle wieder zu löschen.<br />
10.2. Hauptteile der Datenbank<br />
10.2.1. DATABASE – die Datenbank selbst<br />
Der Befehl zum Erstellen e<strong>in</strong>er Datenbank lautet:<br />
CREATE DATABASE [ ] ;<br />
Der ist meistens e<strong>in</strong> vollständiger Name e<strong>in</strong>schließlich Pfad; <strong>in</strong> e<strong>in</strong>er<br />
solchen Datei werden alle Teile der Datenbank zusammengefasst. Zu den<br />
gehören z. B. der Benutzername des Eigentümers der Datenbank<br />
mit se<strong>in</strong>em Passwort, der Zeichensatz mit Angaben zur Standardsortierung, die<br />
Aufteilung <strong>in</strong> e<strong>in</strong>e oder mehrere Dateien usw.<br />
Jedes DBMS bietet sehr verschiedene Optionen; wir können hier ke<strong>in</strong>e Geme<strong>in</strong>samkeiten<br />
vorstellen und müssen deshalb ganz auf Beispiele verzichten.<br />
Wegen der vielen Möglichkeiten ist zu empfehlen, dass e<strong>in</strong>e Datenbank nicht per<br />
<strong>SQL</strong>-Befehl, sondern <strong>in</strong>nerhalb e<strong>in</strong>er Benutzeroberfläche erstellt wird.<br />
Mit ALTER DATABASE werden die Optionen geändert, mit DROP DATABASE<br />
wird die Datenbank gelöscht. Diese Befehle kennt nicht jedes DBMS.<br />
10.2.2. TABLE – e<strong>in</strong>e e<strong>in</strong>zelne Tabelle<br />
CREATE TABLE<br />
Um e<strong>in</strong>e Tabelle zu erzeugen, s<strong>in</strong>d wesentlich konkretere umfangreiche Angaben<br />
nötig.<br />
80<br />
CREATE TABLE <br />
( <br />
[ , ]<br />
);
Zum Erzeugen e<strong>in</strong>er Tabelle werden folgende Angaben benutzt:<br />
Hauptteile der Datenbank<br />
• der Name der Tabelle, mit dem die Daten über die DML-Befehle gespeichert<br />
und abgerufen werden<br />
• die Liste der Spalten (Felder), und zwar vor allem mit dem jeweiligen Datentyp<br />
• Angaben wie der Primärschlüssel (PRIMARY KEY, PK) oder weitere Indizes<br />
Die Spalten und Zusatzangaben werden <strong>in</strong> Klammern ( ) zusammengefasst. Jede<br />
Angabe wird mit e<strong>in</strong>em Komma abgeschlossen; dieses entfällt vor der schließenden<br />
Klammer. Die Zusatzangaben werden häufig nicht sofort festgelegt, sondern<br />
durch anschließende ALTER TABLE-Befehle; sie werden deshalb weiter unten<br />
besprochen.<br />
Aufgabe: In der Beispieldatenbank wird e<strong>in</strong>e Tabelle so erzeugt:<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE TABLE Dienstwagen<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Kennzeichen VARCHAR(30) NOT NULL,<br />
Farbe VARCHAR(30),<br />
Fahrzeugtyp_ID INTEGER NOT NULL,<br />
Mitarbeiter_ID INTEGER<br />
);<br />
Die e<strong>in</strong>zelnen Spalten berücksichtigen mit ihren Festlegungen unterschiedliche<br />
Anforderungen:<br />
• ID ist e<strong>in</strong>e ganze Zahl, darf nicht NULL se<strong>in</strong>, wird automatisch hochgezählt<br />
und dient dadurch gleichzeitig als Primärschlüssel.<br />
• Das Kennzeichen ist e<strong>in</strong>e Zeichenkette von variabler Länge (maximal 30 Zeichen),<br />
die unbed<strong>in</strong>gt erforderlich ist.<br />
• Die Farbe ist ebenfalls e<strong>in</strong>e Zeichenkette, deren Angabe auch fehlen kann.<br />
• Für den Fahrzeugtyp wird dessen ID benötigt, wie er <strong>in</strong> der Tabelle Fahrzeugtyp<br />
gespeichert ist; diese Angabe muss se<strong>in</strong> − e<strong>in</strong> „unbekannter“ Fahrzeugtyp<br />
macht bei e<strong>in</strong>em Dienstwagen ke<strong>in</strong>en S<strong>in</strong>n.<br />
• Für den Mitarbeiter, dem e<strong>in</strong> Dienstwagen zugeordnet ist, wird dessen ID aus<br />
der Tabelle Mitarbeiter benötigt. Dieser Wert kann entfallen, wenn es sich nicht<br />
um e<strong>in</strong>en „persönlichen“ Dienstwagen handelt.<br />
E<strong>in</strong>e besondere Bedeutung hat die Spalte ID als automatischer Zähler, der durch<br />
AUTO_INCREMENT e<strong>in</strong>gerichtet wird. Er gehört genau zu der betreffenden Tabelle<br />
und hat als Primärschlüssel ke<strong>in</strong>e weitere <strong>in</strong>haltliche Bedeutung.<br />
81
DDL – Struktur der Datenbank<br />
ALTER TABLE<br />
Die Struktur e<strong>in</strong>er Tabelle wird mit dieser Syntax geändert:<br />
ALTER TABLE <br />
Mit der Aufgabe ADD CONSTRAINT wird e<strong>in</strong>e <strong>in</strong>terne E<strong>in</strong>schränkung − Constra<strong>in</strong>t<br />
genannt − h<strong>in</strong>zugefügt:<br />
Aufgabe: E<strong>in</strong> Primärschlüssel kann auch nachträglich festgelegt werden, z. B.<br />
wie folgt:<br />
Firebird-Quelltext<br />
ALTER TABLE Dienstwagen<br />
ADD CONSTRAINT Dienstwagen_PK PRIMARY KEY (ID);<br />
Die E<strong>in</strong>schränkung bekommt den Namen Dienstwagen_PK und legt fest, dass es<br />
sich dabei um den PRIMARY KEY unter Verwendung der Spalte ID handelt.<br />
Aufgabe: In der Tabelle Mitarbeiter muss auch die Personalnummer e<strong>in</strong>deutig<br />
se<strong>in</strong> (zusätzlich zur ID, die als PK sowieso e<strong>in</strong>deutig ist):<br />
ALTER TABLE Mitarbeiter<br />
ADD CONSTRAINT Mitarbeiter_PersNr UNIQUE (Personalnummer);<br />
Aufgabe: In der Tabelle Zuordnung_SF_FZ − Verknüpfung zwischen den Schadensfällen<br />
und den Fahrzeugen − wird e<strong>in</strong> Feld für sehr lange Texte e<strong>in</strong>gefügt:<br />
ALTER TABLE Zuordnung_SF_FZ<br />
ADD Beschreibung BLOB;<br />
Mit ALTER . . . DROP Beschreibung kann dieses Feld auch wieder gelöscht werden.<br />
DROP TABLE<br />
Aufgabe: Damit wird e<strong>in</strong>e Tabelle mit allen Daten gelöscht (die folgende Tabelle<br />
gab es <strong>in</strong> e<strong>in</strong>er früheren Version der Beispieldatenbank):<br />
DROP TABLE ZUORD_VNE_SCF;<br />
Warnung: Dies löscht die Tabelle e<strong>in</strong>schließlich aller Daten unwiderruflich!<br />
82
10.2.3. USER – Benutzer<br />
Ergänzungen zu Tabellen<br />
Aufgabe: Auf diese Weise wird e<strong>in</strong> neuer Benutzer für die Arbeit mit der aktuellen<br />
Datenbank registriert.<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE USER Hans_Dampf IDENTIFIED BY ’cH4y37X1P’;<br />
Dieser Benutzer muss sich mit dem Namen Hans_Dampf und dem Passwort<br />
'cH4y37X1P' anmelden. − Jedes DBMS kennt eigene Optionen für die Zuordnung<br />
des Passworts und die Verwendung von Anführungszeichen.<br />
10.3. Ergänzungen zu Tabellen<br />
Weitere Objekte <strong>in</strong> der Datenbank erleichtern die Arbeit mit Tabellen.<br />
10.3.1. VIEW – besondere Ansichten<br />
E<strong>in</strong>e VIEW ist e<strong>in</strong>e Sicht auf e<strong>in</strong>e oder mehrere Tabellen. Für den Anwender sieht<br />
es wie e<strong>in</strong>e eigene Tabelle aus; es handelt sich aber „nur“ um e<strong>in</strong>e fest gespeicherte<br />
Abfrage. Dabei wird nur die Abfrage fest gespeichert, nicht die Ergebnismenge;<br />
diese wird bei jedem neuen Aufruf nach den aktuellen Daten neu erstellt.<br />
E<strong>in</strong>zelheiten werden unter Erstellen von Views 5 behandelt.<br />
10.3.2. INDEX – Datenzugriff beschleunigen<br />
E<strong>in</strong> Index beschleunigt die Suche nach Datensätzen. Um <strong>in</strong> der Tabelle Versicherungsnehmer<br />
nach dem Namen „Schulze“ zu suchen, würde es zu lange dauern,<br />
wenn das DBMS alle Zeilen durchgehen müsste, bis es auf diesen Namen träfe.<br />
Stattdessen wird e<strong>in</strong> Index angelegt (ähnlich wie <strong>in</strong> e<strong>in</strong>em Telefon- oder Wörterbuch),<br />
sodass schnell alle passenden Datensätze gefunden werden.<br />
Aufgabe: So wird e<strong>in</strong> Index mit der Bezeichnung Versicherungsnehmer_Name<br />
für die Komb<strong>in</strong>ation „Name, Vorname“ angelegt:<br />
CREATE INDEX Versicherungsnehmer_Name<br />
ON Versicherungsnehmer (Name, Vorname);<br />
Es ist dr<strong>in</strong>gend zu empfehlen, dass Indizes für alle Spalten bzw. Komb<strong>in</strong>ationen<br />
von Spalten angelegt werden, die immer wieder zum Suchen benutzt werden.<br />
5 Kapitel 27 auf Seite 271<br />
83
DDL – Struktur der Datenbank<br />
Weitere E<strong>in</strong>zelheiten werden unter DDL − E<strong>in</strong>zelheiten 6 behandelt.<br />
10.3.3. IDENTITY – auch e<strong>in</strong> automatischer Zähler<br />
Anstelle von AUTO_INCREMENT verwendet MS-<strong>SQL</strong> diese Erweiterung für die<br />
automatische Nummerierung neuer Datensätze:<br />
MS-<strong>SQL</strong>-Quelltext<br />
CREATE TABLE Fahrzeug<br />
(ID INTEGER NOT NULL IDENTITY(1,1),<br />
Kennzeichen VARCHAR(10) NOT NULL,<br />
Farbe VARCHAR(30),<br />
Fahrzeugtyp_ID INTEGER NOT NULL,<br />
CONSTRAINT Fahrzeug_PK PRIMARY KEY (ID)<br />
);<br />
Der erste Parameter bezeichnet den Startwert, der zweite Parameter die Schrittweite<br />
zum nächsten ID-Wert.<br />
10.3.4. SEQUENCE – Ersatz für automatischen Zähler<br />
Wenn das DBMS für Spalten ke<strong>in</strong>e automatische Zählung kennt (Firebird,<br />
Oracle), steht dies als Ersatz zur Verfügung.<br />
Firebird-Quelltext<br />
/* zuerst die Folge def<strong>in</strong>ieren */<br />
CREATE SEQUENCE Versicherungsnehmer_ID;<br />
/* dann den Startwert festlegen */<br />
ALTER SEQUENCE Versicherungsnehmer_ID RESTART WITH 1;<br />
/* und im Trigger (s.u.) ähnlich wie e<strong>in</strong>e Funktion benutzen */<br />
NEXT VALUE FOR Versicherungsnehmer_ID<br />
Während e<strong>in</strong>e AUTO_INCREMENT-Spalte genau zu der betreffenden Tabelle gehört,<br />
bezieht sich e<strong>in</strong>e „Sequenz“ auf die gesamte Datenbank. Es ist ohne Weiteres<br />
möglich, e<strong>in</strong>e e<strong>in</strong>zige Sequenz AllMyIDs zu def<strong>in</strong>ieren und die neue ID e<strong>in</strong>er<br />
jeden Tabelle daraus abzuleiten. Dies ist durchaus s<strong>in</strong>nvoll, weil die ID als<br />
Primärschlüssel sowieso ke<strong>in</strong>e weitere <strong>in</strong>haltliche Bedeutung haben darf. In der<br />
Beispieldatenbank benutzen wir getrennte Sequenzen, weil sie für die verschiedenen<br />
DBMS „ähnlich“ aussehen soll.<br />
Oracle arbeitet (natürlich) mit anderer Syntax (mit mehr Möglichkeiten) und benutzt<br />
dann NEXTVAL.<br />
6 Kapitel 28 auf Seite 285<br />
84
10.4. Programmieren mit <strong>SQL</strong><br />
Programmieren mit <strong>SQL</strong><br />
Die Funktionalität e<strong>in</strong>er <strong>SQL</strong>-Datenbank kann erweitert werden, und zwar auch<br />
mit Bestandteilen e<strong>in</strong>er „vollwertigen“ Programmiersprache, z. B. Schleifen und<br />
IF-Abfragen.<br />
Dies wird unter Programmierung 7 behandelt; dabei gibt es relativ wenig Geme<strong>in</strong>samkeiten<br />
zwischen den DBMS.<br />
FUNCTION − Benutzerdef<strong>in</strong>ierte Funktionen<br />
Eigene Funktionen ergänzen die (<strong>in</strong>ternen) Skalarfunktionen des DBMS.<br />
PROCEDURE − Gespeicherte Prozeduren<br />
E<strong>in</strong>e Prozedur − gespeicherte Prozedur, engl. StoredProcedure (SP) − ist<br />
v<strong>org</strong>esehen für Arbeitsabläufe, die „immer wiederkehrende“ Arbeiten ausführen<br />
sollen. Es gibt sie mit und ohne Argumente und Rückgabewerte.<br />
TRIGGER − Ereignisse beim Speichern<br />
E<strong>in</strong> Trigger ist e<strong>in</strong> Arbeitsablauf, der automatisch beim Speichern <strong>in</strong> e<strong>in</strong>er<br />
Tabelle ausgeführt wird. Dies dient E<strong>in</strong>gabeprüfungen oder zusätzlichen<br />
Maßnahmen; beispielsweise holt e<strong>in</strong> Trigger den NEXT VALUE e<strong>in</strong>er SE-<br />
QUENCE (siehe oben).<br />
TRIGGER werden unterschieden nach INSERT-, UPDATE- oder DELETE-<br />
Befehlen und können vor oder nach dem Speichern sowie <strong>in</strong> e<strong>in</strong>er bestimmten<br />
Reihenfolge ausgeführt werden.<br />
10.5. Zusammenfassung<br />
In diesem Kapitel lernten wir die Grundbegriffe für e<strong>in</strong>e Datenbankstruktur<br />
kennen:<br />
• DATABASE selbst sowie TABLE s<strong>in</strong>d die wichtigsten Objekte, e<strong>in</strong> USER arbeitet<br />
damit.<br />
• E<strong>in</strong>e VIEW ist e<strong>in</strong>e gespeicherte Abfrage, die wie e<strong>in</strong>e Tabelle abgerufen wird.<br />
• E<strong>in</strong> INDEX beschleunigt den Datenzugriff.<br />
Darüber h<strong>in</strong>aus wurde auf weitere Möglichkeiten wie SEQUENCE, Funktionen,<br />
Prozeduren und Trigger h<strong>in</strong>gewiesen.<br />
7 Kapitel 30 auf Seite 323<br />
85
DDL – Struktur der Datenbank<br />
10.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 86.<br />
Übung 1 – Objekte bearbeiten<br />
Welche der folgenden <strong>SQL</strong>-Befehle enthalten Fehler?<br />
1. CREATE DATABASE C:\DBFILES\employee.gdb DEFAULT CHARACTER SET<br />
UTF8;<br />
2. DROP DATABASE;<br />
3. CREATE TABLE Person<br />
( ID PRIMARY KEY, Name VARCHAR(30), Vorname VARCHAR(30) );<br />
4. ALTER TABLE ADD UNIQUE KEY (Name);<br />
Übung 2 – Tabelle erstellen<br />
Auf welchen zwei Wegen kann der Primärschlüssel (Primary Key, PK) e<strong>in</strong>er Tabelle<br />
festgelegt werden? E<strong>in</strong> weiterer Weg ist ebenfalls üblich, aber noch nicht erwähnt<br />
worden.<br />
Übung 3 – Tabelle ändern<br />
Skizzieren Sie e<strong>in</strong>en Befehl, mit dem die Tabelle Mitarbeiter um Felder für die<br />
Anschrift erweitert werden kann.<br />
Übung 4 – Tabellen<br />
Wor<strong>in</strong> unterscheiden sich TABLE und VIEW <strong>in</strong> erster L<strong>in</strong>ie?<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 86.<br />
Lösung zu Übung 1 – Objekte bearbeiten<br />
Bei 2. fehlt der Name der Datenbank.<br />
Bei 3. fehlt der Datentyp zur Spalte ID.<br />
Bei 4. fehlt der Name der Tabelle, die geändert werden soll.<br />
86
Lösung zu Übung 2 – Tabelle erstellen<br />
Siehe auch<br />
1. Im CREATE TABLE-Befehl zusammen mit der Spalte, die als PK benutzt<br />
wird, z. B.:<br />
ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
2. Durch e<strong>in</strong>en ALTER TABLE-Befehl, der den PK h<strong>in</strong>zufügt:<br />
ALTER TABLE Dienstwagen ADD PRIMARY KEY (ID);<br />
Lösung zu Übung 3 – Tabelle ändern<br />
ALTER TABLE Mitarbeiter<br />
ADD PLZ CHAR(5),<br />
ADD Ort VARCHAR(24),<br />
ADD Strasse VARCHAR(24),<br />
ADD Hausnummer VARCHAR(10);<br />
Lösung zu Übung 4 – Tabellen<br />
E<strong>in</strong>e TABLE (Tabelle) ist real <strong>in</strong> der Datenbank gespeichert. E<strong>in</strong>e VIEW (Sichttabelle)<br />
ist e<strong>in</strong>e Sicht auf e<strong>in</strong>e oder mehrere tatsächlich vorhandene Tabellen; sie<br />
enthält e<strong>in</strong>e Abfrage auf diese Tabellen, die als Abfrage <strong>in</strong> der Datenbank gespeichert<br />
ist, aber für den Anwender wie e<strong>in</strong>e eigene Tabelle aussieht.<br />
10.7. Siehe auch<br />
Bei diesem Kapitel s<strong>in</strong>d die folgenden Erläuterungen zu beachten:<br />
• Datentypen 8<br />
Manche Themen werden <strong>in</strong> den folgenden Kapiteln genauer behandelt:<br />
• Eigene Funktionen 9 als Ergänzung zu denen, die <strong>in</strong> Funktionen 10 und Funktionen<br />
(2) 11 behandelt werden.<br />
• Prozeduren 12<br />
• Trigger 13<br />
8 Kapitel 13 auf Seite 97<br />
9 Kapitel 31 auf Seite 347<br />
10 Kapitel 14 auf Seite 109<br />
11 Kapitel 16 auf Seite 141<br />
12 Kapitel 32 auf Seite 357<br />
13 Kapitel 33 auf Seite 377<br />
87
11. TCL – Ablaufsteuerung<br />
11.1. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89<br />
11.2. Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90<br />
11.3. Misserfolg regeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91<br />
11.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />
11.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />
Dieses Kapitel gibt e<strong>in</strong>e kurze <strong>E<strong>in</strong>führung</strong> <strong>in</strong> die Transaction Control Language<br />
(TCL). Deren Befehle s<strong>org</strong>en für die Datensicherheit <strong>in</strong>nerhalb e<strong>in</strong>er Datenbank.<br />
My<strong>SQL</strong> verfolgt e<strong>in</strong>e „offenere“ Philosophie und arbeitet neben Transaktionen<br />
auch mit anderen Sicherungsmaßnahmen. Der Ersteller e<strong>in</strong>er Datenbank muss<br />
sich für e<strong>in</strong> Verfahren entscheiden, kann aber auch danach noch variieren.<br />
11.1. Beispiele<br />
E<strong>in</strong>e <strong>SQL</strong>-Datenbank speichert Daten <strong>in</strong> der Regel <strong>in</strong> verschiedenen Tabellen.<br />
Dabei ist es nötig, dass die betreffenden Befehle immer „gleichzeitig“ ausgeführt<br />
werden. Da das nicht möglich ist − zwei Befehle können nur nache<strong>in</strong>ander erledigt<br />
werden −, muss sichergestellt se<strong>in</strong>, dass nicht der e<strong>in</strong>e Befehl ausgeführt<br />
wird, während der andere Befehl scheitert.<br />
• Zu e<strong>in</strong>er Überweisung bei der Bank gehören immer zwei Buchungen: die Gutschrift<br />
auf dem e<strong>in</strong>en und die Lastschrift auf dem anderen Konto; häufig gehören<br />
die Konten zu verschiedenen Banken. Es wäre völlig unerträglich, wenn die<br />
Gutschrift ausgeführt würde und die (externe) Lastschrift nicht, weil <strong>in</strong> diesem<br />
Moment die Datenleitung unterbrochen wird.<br />
• Wenn <strong>in</strong> dem Versicherungsunternehmen der Beispieldatenbank e<strong>in</strong> neuer<br />
Vertrag abgeschlossen wird, gehören dazu mehrere INSERT-Befehle, und zwar<br />
<strong>in</strong> die Tabellen Fahrzeug, Versicherungsnehmer, Versicherungsvertrag. Zuerst<br />
müssen Fahrzeug und Versicherungsnehmer gespeichert werden; aber wenn<br />
das Speichern des Vertrags „schiefgeht“, hängen die beiden anderen Datensätze<br />
nutzlos <strong>in</strong> der Datenbank herum.<br />
89
TCL – Ablaufsteuerung<br />
• Wenn dort e<strong>in</strong>e Abteilung ausgelagert wird, werden alle ihre Mitarbeiter gestrichen,<br />
weil sie nicht mehr zum Unternehmen gehören. Wie soll verfahren<br />
werden, wenn nur e<strong>in</strong> Teil der DELETE-Befehle erfolgreich war?<br />
• E<strong>in</strong>e Menge e<strong>in</strong>zelner Befehle (z. B. 1000 INSERTs <strong>in</strong>nerhalb e<strong>in</strong>er Schleife)<br />
dauert „ewig lange“.<br />
Solche Probleme können nicht nur durch die Hardware entstehen, sondern auch<br />
dadurch, dass parallel andere Nutzer denselben Datenbestand ändern wollen.<br />
11.2. Transaktionen<br />
Alle solche Ungereimtheiten werden vermieden, <strong>in</strong>dem <strong>SQL</strong>-Befehle <strong>in</strong> Transaktionen<br />
zusammengefasst und ausgeführt werden:<br />
• Entweder alle Befehle können ausgeführt werden. Dann wird die Transaktion<br />
bestätigt und erfolgreich abgeschlossen.<br />
• Oder (m<strong>in</strong>destens) e<strong>in</strong> Befehl kann nicht ausgeführt werden. Dann wird die<br />
Transaktion für ungültig erklärt; alle Befehle werden rückgängig gemacht.<br />
• Auch das Problem mit der langen Arbeitszeit von 1000 INSERTs wird vermieden,<br />
wenn das DBMS nicht jeden Befehl e<strong>in</strong>zeln prüft und bestätigt,<br />
Es gibt verschiedene Arten von Transaktionen. Diese hängen vom DBMS und<br />
dessen Version, der Hardware (E<strong>in</strong>zel- oder Mehrplatzsystem) und dem Datenzugriff<br />
(direkt oder über Anwenderprogramme) ab.<br />
Der Beg<strong>in</strong>n e<strong>in</strong>er Transaktion wird durch e<strong>in</strong>e dieser Maßnahmen geregelt.<br />
• Wenn die Datenbank auf AUTOCOMMIT e<strong>in</strong>gestellt ist, wird jeder <strong>SQL</strong>-Befehl<br />
als e<strong>in</strong>zelne Transaktion behandelt und sofort gültig. (Das wäre die Situation<br />
mit der langen Arbeitszeit von 1000 INSERTs.)<br />
• Wenn e<strong>in</strong> Stapel von Befehlen mit COMMIT bestätigt oder mit ROLLBACK verworfen<br />
wird, dann wird mit dem nächsten Befehl implizit e<strong>in</strong>e neue Transaktion<br />
begonnen.<br />
• Mit e<strong>in</strong>em ausdrücklichen TRANSACTION-Befehl wird explizit e<strong>in</strong>e neue<br />
Transaktion begonnen:<br />
BEGIN TRANSACTION ; /* bei MS-<strong>SQL</strong> */<br />
SET TRANSACTION ; /* bei Firebird */<br />
START TRANSACTION; /* bei My<strong>SQL</strong> */<br />
E<strong>in</strong>e Transaktion kann mit e<strong>in</strong>em Namen versehen werden. Dies ist vor allem<br />
dann nützlich, wenn Transaktionen geschachtelt werden. Außerdem gibt es je<br />
nach DBMS viele weitere Optionen zur detaillierten Steuerung.<br />
90
Misserfolg regeln<br />
• E<strong>in</strong>e Transaktion, die implizit oder explizit begonnen wird, ist ausdrücklich abzuschließen<br />
durch COMMIT oder ROLLBACK. Andernfalls wird sie erst dann<br />
beendet, wenn die Verb<strong>in</strong>dung mit der Datenbank geschlossen wird.<br />
Der erfolgreiche Abschluss e<strong>in</strong>er Transaktion wird so geregelt:<br />
COMMIT [ TRANSACTION | WORK ] ;<br />
Die genaue Schreibweise und Varianten müssen <strong>in</strong> der DBMS-Dokumentation<br />
nachgelesen werden.<br />
Dieser Befehl bestätigt alle vorangegangenen Befehle e<strong>in</strong>er Transaktion und<br />
s<strong>org</strong>t dafür, dass sie „am Stück“ gespeichert werden.<br />
Auszug aus dem Skript zum Erstellen der Beispieldatenbank:<br />
Firebird-Quelltext<br />
COMMIT;<br />
/* damit wird die vorherige Transaktion abgeschlossen und implizit e<strong>in</strong>e<br />
neue Transaktion gestartet */<br />
INSERT INTO Dienstwagen (Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID)<br />
VALUES (’DO-WB 111’, ’elfenbe<strong>in</strong>’, 16, NULL);<br />
INSERT INTO Dienstwagen (Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID)<br />
SELECT ’DO-WB 3’ || Abteilung_ID || SUBSTRING(Personalnummer FROM 5 FOR 1),<br />
’gelb’, SUBSTRING(Personalnummer FROM 5 FOR 1), ID<br />
FROM Mitarbeiter<br />
WHERE Abteilung_ID IN (5, 8) AND Ist_Leiter = ’N’;<br />
/* damit wird diese Transaktion abgeschlossen */<br />
COMMIT;<br />
11.3. Misserfolg regeln<br />
Mit dem folgenden Befehl wird e<strong>in</strong>e Transaktion <strong>in</strong> „sichere“ Abschnitte geteilt:<br />
SAVEPOINT ;<br />
Bis zu diesem Sicherungspunkt werden die Befehle auch dann als gültig abgeschlossen,<br />
wenn die Transaktion am Ende für ungültig erklärt wird.<br />
Wenn e<strong>in</strong>e Transaktion missglückt, wird sie für ungültig erklärt:<br />
ROLLBACK [ TRANSACTION | WORK ] [ TO ] ;<br />
91
TCL – Ablaufsteuerung<br />
Damit werden alle Befehle der Transaktion für ungültig erklärt und<br />
rückgängig gemacht. Sofern e<strong>in</strong> Sicherungspunkt angegeben ist, werden<br />
die Befehle bis zu diesem Sicherungspunkt für gültig erklärt und erst alle<br />
folgenden für ungültig.<br />
Schreibweise und Varianten s<strong>in</strong>d <strong>in</strong> der DBMS-Dokumentation nachzulesen.<br />
Aufgabe: Im folgenden Beispiel werden zu Testzwecken e<strong>in</strong>ige Daten geändert<br />
und abgerufen; abschließend werden die Änderungen rückgängig gemacht.<br />
UPDATE Dienstwagen<br />
SET Farbe = ’goldgelb/violett gestreift’<br />
WHERE ID >= 14;<br />
SELECT * FROM Dienstwagen;<br />
ROLLBACK;<br />
11.4. Zusammenfassung<br />
In diesem Kapitel lernten Sie die Grundbegriffe von Transaktionen kennen:<br />
• E<strong>in</strong>e Transaktion wird implizit oder explizit begonnen.<br />
• E<strong>in</strong>e Transaktion wird mit e<strong>in</strong>em ausdrücklichen Befehl (oder durch Ende der<br />
Verb<strong>in</strong>dung) abgeschlossen.<br />
• Mit COMMIT wird e<strong>in</strong>e Transaktion erfolgreich abgeschlossen; die Daten werden<br />
abschließend gespeichert.<br />
• Mit ROLLBACK werden die Änderungen verworfen, ggf. ab e<strong>in</strong>em bestimmten<br />
SAVEPOINT.<br />
11.5. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 93.<br />
Übung 1 – Zusammengehörende Befehle<br />
Skizzieren Sie die Befehle, die geme<strong>in</strong>sam ausgeführt werden müssen, wenn<br />
e<strong>in</strong> neues Fahrzeug mit e<strong>in</strong>em noch nicht registrierten Fahrzeugtyp und Fahrzeughersteller<br />
gespeichert werden soll.<br />
Übung 2 – Zusammengehörende Befehle<br />
Skizzieren Sie die Befehle, die geme<strong>in</strong>sam ausgeführt werden müssen, wenn e<strong>in</strong><br />
neuer Schadensfall ohne weitere beteiligte Fahrzeuge registriert werden soll.<br />
92
Übung 3 – Zusammengehörende Befehle<br />
Übungen<br />
Skizzieren Sie die Befehle, die geme<strong>in</strong>sam ausgeführt werden müssen, wenn e<strong>in</strong><br />
neuer Schaden durch e<strong>in</strong>en „Eigenen Kunden“ gemeldet wird; dabei sollen e<strong>in</strong><br />
zweiter „Eigener Kunde“ sowie e<strong>in</strong> „Fremdkunde“ e<strong>in</strong>er bisher nicht gespeicherten<br />
Versicherungsgesellschaft beteiligt se<strong>in</strong>. Erwähnen Sie dabei auch den Inhalt<br />
des betreffenden E<strong>in</strong>trags.<br />
Übung 4 – Transaktionen<br />
Welche der folgenden Maßnahmen starten immer e<strong>in</strong>e neue Transaktion, welche<br />
unter Umständen, welche s<strong>in</strong>d unzulässig?<br />
1. das Herstellen der Verb<strong>in</strong>dung zur Datenbank<br />
2. der Befehl START TRANSACTION;<br />
3. der Befehl SET TRANSACTION ACTIVE;<br />
4. der Befehl SAVEPOINT wird ausgeführt<br />
5. der Befehl ROLLBACK wird ausgeführt<br />
Übung 5 – Transaktionen<br />
Welche der folgenden Maßnahmen beenden immer e<strong>in</strong>e Transaktion, welche<br />
unter Umständen, welche s<strong>in</strong>d unzulässig?<br />
1. das Schließen der Verb<strong>in</strong>dung zur Datenbank<br />
2. der Befehl END TRANSACTION;<br />
3. der Befehl SET TRANSACTION INACTIVE;<br />
4. der Befehl SAVEPOINT wird ausgeführt<br />
5. der Befehl ROLLBACK wird ausgeführt<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 92.<br />
Lösung zu Übung 1 – Zusammengehörende Befehle<br />
• INSERT INTO Fahrzeughersteller<br />
• INSERT INTO Fahrzeugtyp<br />
• INSERT INTO Fahrzeug<br />
93
TCL – Ablaufsteuerung<br />
Lösung zu Übung 2 – Zusammengehörende Befehle<br />
• INSERT INTO Schadensfall<br />
• INSERT INTO Zuordnung_SF_FZ<br />
Lösung zu Übung 3 – Zusammengehörende Befehle<br />
• INSERT INTO Schadensfall<br />
• INSERT INTO Versicherungsgesellschaft /* für den weiteren<br />
Fremdkunden */<br />
• INSERT INTO Versicherungsnehmer /* für den weiteren Fremdkunden */<br />
• INSERT INTO Fahrzeug /* für den weiteren Fremdkunden */<br />
• INSERT INTO Zuordnung_SF_FZ /* für den weiteren Fremdkunden */<br />
• INSERT INTO Zuordnung_SF_FZ /* für den beteiligten Eigenen Kunden */<br />
• INSERT INTO Zuordnung_SF_FZ /* für den Eigenen Kunden laut<br />
Schadensmeldung */<br />
94<br />
Lösung zu Übung 4 – Transaktionen<br />
1. ja, sofern AUTOCOMMIT festgelegt ist<br />
2. ja, aber nur, wenn das DBMS diese Variante vorsieht (wie My<strong>SQL</strong>)<br />
3. ja, aber nur, wenn das DBMS diese Variante vorsieht (wie Firebird); denn<br />
das Wort „ACTIVE“ wird nicht als Schlüsselwort, sondern als Name der<br />
Transaction <strong>in</strong>terpretiert<br />
4. ne<strong>in</strong>, das ist nur e<strong>in</strong> Sicherungspunkt <strong>in</strong>nerhalb e<strong>in</strong>er Transaktion<br />
5. ja, nämlich implizit für die folgenden Befehle<br />
Lösung zu Übung 5 – Transaktionen<br />
1. ja, und zwar immer<br />
2. ne<strong>in</strong>, diesen Befehl gibt es nicht<br />
3. ne<strong>in</strong>, dieser Befehl ist unzulässig; wenn das DBMS diese Variante kennt<br />
(wie Firebird), dann ist es der Start e<strong>in</strong>er Transaktion namens „INACTIVE“<br />
4. teilweise, das ist e<strong>in</strong> Sicherungspunkt <strong>in</strong>nerhalb e<strong>in</strong>er Transaktion und bestätigt<br />
die bisherigen Befehle, setzen aber dieselbe Transaktion fort<br />
5. ja, nämlich explizit durch Widerruf aller Befehle
12. DCL – Zugriffsrechte<br />
E<strong>in</strong>e „vollwertige“ <strong>SQL</strong>-Datenbank enthält umfassende Regelungen über die<br />
Vergabe von Rechten für den Zugriff auf Objekte (Tabellen, e<strong>in</strong>zelne Felder, <strong>in</strong>terne<br />
Funktionen usw.). Am Anfang stehen diese Rechte nur dem Ersteller der Datenbank<br />
und dem Systemadm<strong>in</strong>istrator zu. Andere Benutzer müssen ausdrücklich<br />
zu e<strong>in</strong>zelnen Handlungen ermächtigt werden.<br />
Da es sich dabei nicht um Maßnahmen für E<strong>in</strong>steiger handelt, beschränken wir<br />
uns auf e<strong>in</strong> paar Beispiele.<br />
GRANT<br />
Dieser Befehl gestattet e<strong>in</strong>e bestimmte Aktion mit Zugriff auf e<strong>in</strong> bestimmtes Objekt.<br />
Aufgabe: Der Benutzer Herr_Mueller darf Abfragen auf die Tabelle Abteilungen<br />
ausführen.<br />
GRANT SELECT ON Abteilung TO Herr_Mueller<br />
Aufgabe: Die Benutzer<strong>in</strong> Frau_Schulze darf Daten <strong>in</strong> der Tabelle Abteilungen<br />
ändern.<br />
GRANT UPDATE ON Abteilung TO Frau_Schulze<br />
REVOKE<br />
Dieser Befehl verweigert e<strong>in</strong>e bestimmte Aktion mit Zugriff auf e<strong>in</strong> bestimmtes<br />
Objekt.<br />
Aufgabe: Herr_Mueller darf künftig ke<strong>in</strong>e solche Abfragen mehr ausführen.<br />
REVOKE SELECT ON Abteilung FROM Herr_Mueller<br />
95
13. Datentypen<br />
13.1. Vordef<strong>in</strong>ierte Datentypen . . . . . . . . . . . . . . . . . . . . . . 97<br />
13.2. Konstruierte und benutzerdef<strong>in</strong>ierte Datentypen . . . . . . . 101<br />
13.3. Spezialisierte Datentypen . . . . . . . . . . . . . . . . . . . . . . 102<br />
13.4. Nationale und <strong>in</strong>ternationale Zeichensätze . . . . . . . . . . . 103<br />
13.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
13.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />
13.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108<br />
<strong>SQL</strong> kennt verschiedene Arten von Datentypen: vordef<strong>in</strong>ierte, konstruierte und<br />
benutzerdef<strong>in</strong>ierte. Dieses Kapitel erklärt Arten und Verwendungen.<br />
13.1. Vordef<strong>in</strong>ierte Datentypen<br />
Laut <strong>SQL</strong>-Standard s<strong>in</strong>d die folgenden Datentypen vordef<strong>in</strong>iert. Die fettgedruckten<br />
Begriffe s<strong>in</strong>d die entsprechenden reservierten Schlüsselwörter 1 .<br />
i Achtung<br />
Bei allen Datentypen weichen die <strong>SQL</strong>-Dialekte mehr oder weniger vom<br />
Standard ab.<br />
Die Abweichungen betreffen vor allem die folgenden Punkte:<br />
• bei den möglichen Werten, z. B. dem maximalen Wert e<strong>in</strong>er ganzen Zahl oder<br />
der Länge e<strong>in</strong>er Zeichenkette<br />
• bei der Bearbeitung e<strong>in</strong>zelner Typen; z. B. kennt Firebird noch nicht sehr lange<br />
den BOOLEAN-Typ und musste mit dem Ersatz „ganze Zahl gleich 1“ leben<br />
• bei der Art der Werte, z. B. ob Datum und Zeit getrennt oder geme<strong>in</strong>sam gespeichert<br />
werden<br />
1 Anhang C.5 auf Seite 440<br />
97
Datentypen<br />
13.1.1. Zeichen und Zeichenketten<br />
Es gibt Zeichenketten mit fester, variabler und sehr großer Länge.<br />
• CHARACTER(n), CHAR(n)<br />
Hierbei handelt es sich um Zeichenketten mit fester Länge von genau n Zeichen.<br />
Für e<strong>in</strong> e<strong>in</strong>zelnes Zeichen muss die Länge (1) nicht angegeben werden.<br />
• CHARACTER VARYING(n), VARCHAR(n)<br />
Dies s<strong>in</strong>d Zeichenketten mit variabler Länge bei maximal n Zeichen.<br />
• CHARACTER LARGE OBJECT (CLOB)<br />
Hierbei handelt es sich um beliebig große Zeichenketten. Diese Variante ist<br />
relativ umständlich zu nutzen; sie wird vorwiegend für die Speicherung ganzer<br />
Textdateien verwendet.<br />
Zu allen diesen Varianten gibt es auch die Festlegung e<strong>in</strong>es nationalen Zeichensatzes<br />
durch NATIONAL CHARACTER bzw. NCHAR oder NATIONAL CHARAC-<br />
TER VARYING bzw. NVARCHAR. Erläuterungen dazu siehe unten im Abschnitt<br />
über Zeichensätze.<br />
Die maximale Länge von festen und variablen Zeichenketten hängt vom DBMS<br />
ab. In früheren Versionen betrug sie oft nur 255, heute s<strong>in</strong>d 32767 verbreitet.<br />
Die maximale Feldlänge bei Zeichenketten ist für E<strong>in</strong>gabe, Ausgabe und <strong>in</strong>terne<br />
Speicherung wichtig. Die DBMS verhalten sich unterschiedlich, ob am Anfang<br />
oder Ende stehende Leerzeichen gespeichert oder entfernt werden. Wichtig ist,<br />
dass bei fester Feldlänge e<strong>in</strong> gelesener Wert immer mit genau dieser Anzahl von<br />
Zeichen zurückgegeben wird und bei Bedarf rechts mit Leerzeichen aufgefüllt<br />
wird.<br />
In der Praxis s<strong>in</strong>d folgende Gesichtspunkte von Bedeutung:<br />
• Für <strong>in</strong>dizierte Felder (also Spalten, denen e<strong>in</strong> Index zugeordnet wird) s<strong>in</strong>d feste<br />
Längen vorzuziehen; beachten Sie aber die nächsten H<strong>in</strong>weise.<br />
• Als CHAR(n), also mit fester Länge, s<strong>in</strong>d Felder vorzusehen, deren Länge bei<br />
der überwiegenden Zahl der Datensätze konstant ist. Beispiel: die deutschen<br />
Postleitzahlen mit CHAR(5).<br />
• Als VARCHAR(n), also mit variabler Länge, s<strong>in</strong>d Felder vorzusehen, deren Länge<br />
stark variiert. Beispiel: Namen und Vornamen mit VARCHAR(30).<br />
• In Zweifelsfällen ist pragmatisch vorzugehen. Beispiel: Die <strong>in</strong>ternationalen<br />
Postleitzahlen (Post-Code, Zip-Code) benötigen bis zu 10 Zeichen. Wenn e<strong>in</strong>e<br />
Datenbank überwiegend nur deutsche Adressen enthält, passt VARCHAR(10)<br />
besser, bei hohem Anteil von britischen, US-amerikanischen, kanadischen<br />
und ähnlichen Adressen ist CHAR(10) zu empfehlen.<br />
98
13.1.2. Zahlen mit exakter Größe<br />
Vordef<strong>in</strong>ierte Datentypen<br />
Werte dieser Datentypen werden mit der genauen Größe gespeichert.<br />
• INTEGER bzw. INT<br />
Ganze Zahl mit Vorzeichen. Der Größenbereich hängt von der Implementierung<br />
ab; auf e<strong>in</strong>em 32-bit-System entspricht es meistens ±2 31 -1, genauer von<br />
–2 147 483 648 bis +2 147 483 647.<br />
• SMALLINT<br />
Ebenfalls e<strong>in</strong> Datentyp für ganze Zahlen, aber mit kle<strong>in</strong>erem Wertebereich als<br />
INTEGER, oft von -32 768 bis +32 767.<br />
• BIGINT<br />
Ebenfalls e<strong>in</strong> Datentyp für ganze Zahlen, aber mit größerem Wertebereich als<br />
INTEGER. Auch der <strong>SQL</strong>-Standard akzeptiert, dass e<strong>in</strong> DBMS diesen Typ nicht<br />
kennt.<br />
• NUMERIC(p,s) sowie DECIMAL(p,s)<br />
Datentypen für Dezimalzahlen mit exakter Speicherung, also „Festkommazahlen“,<br />
wobei p die Genauigkeit und s die Anzahl der Dezimalstellen angibt.<br />
Dabei muss 0 ≤ s ≤ p se<strong>in</strong>, und s hat e<strong>in</strong>en V<strong>org</strong>abewert von 0. Der Parameter<br />
s = 0 kann entfallen; der V<strong>org</strong>abewert für p hängt vom DBMS ab.<br />
Diese Dezimalzahlen s<strong>in</strong>d wegen der genauen Speicherung z. B. für Daten der<br />
Buchhaltung geeignet. Bei vielen DBMS gibt es ke<strong>in</strong>en Unterschied zwischen<br />
NUMERIC und DECIMAL.<br />
13.1.3. Zahlen mit „näherungsweiser“ Größe<br />
Werte dieser Datentypen werden nicht unbed<strong>in</strong>gt mit der genauen Größe gespeichert,<br />
sondern <strong>in</strong> vielen Fällen nur näherungsweise.<br />
• FLOAT, REAL, DOUBLE PRECISION<br />
Diese Datentypen haben grundsätzlich die gleiche Bedeutung. Je nach DBMS<br />
gibt FLOAT(p,s) die Genauigkeit oder die Anzahl der Dezimalstellen an; auch<br />
der Wertebereich und die Genauigkeit hängen vom DBMS ab.<br />
Diese „Gleitkommazahlen“ s<strong>in</strong>d für technisch-wissenschaftliche Werte geeignet<br />
und umfassen auch die Exponentialdarstellung. Wegen der Speicherung im B<strong>in</strong>ärformat<br />
s<strong>in</strong>d sie aber für Geldbeträge nicht geeignet, weil sich beispielsweise<br />
der Wert 0,10 =C (entspricht 10 Cent) nicht exakt abbilden lässt. Es kommt immer<br />
wieder zu Rundungsfehlern.<br />
99
Datentypen<br />
13.1.4. Zeitpunkte und Zeit<strong>in</strong>tervalle<br />
Für Datum und Uhrzeit gibt es die folgenden Datentypen:<br />
• DATE<br />
Das Datum enthält die Bestandteile YEAR, MONTH, DAY, wobei die Monate<br />
<strong>in</strong>nerhalb e<strong>in</strong>es Jahres und die Tage <strong>in</strong>nerhalb e<strong>in</strong>es Monats geme<strong>in</strong>t s<strong>in</strong>d.<br />
• TIME<br />
Die Uhrzeit enthält die Bestandteile HOUR, MINUTE, SECOND, wobei die M<strong>in</strong>ute<br />
<strong>in</strong>nerhalb e<strong>in</strong>er Stunde und die Sekunden <strong>in</strong>nerhalb e<strong>in</strong>er M<strong>in</strong>ute geme<strong>in</strong>t<br />
s<strong>in</strong>d. Sehr oft gibt es auch Millisekunden als Bruchteile von Sekunden.<br />
• TIMESTAMP<br />
Der „Zeitstempel“ enthält Datum und Uhrzeit zusammen.<br />
Zeitangaben können WITH TIME ZONE oder WITHOUT TIME ZONE deklariert<br />
werden. Ohne die Zeitzone ist <strong>in</strong> der Regel die lokale Zeit geme<strong>in</strong>t, mit der Zeitzone<br />
wird die Koord<strong>in</strong>ierte Weltzeit (UTC) gespeichert.<br />
Bei Datum und Uhrzeit enden die Geme<strong>in</strong>samkeiten der <strong>SQL</strong>-Dialekte endgültig;<br />
sie werden unterschiedlich mit „eigenen“ Datentypen realisiert. Man kann<br />
allenfalls annehmen, dass e<strong>in</strong> Tag <strong>in</strong>tern mit e<strong>in</strong>er ganzen Zahl und e<strong>in</strong> Zeitwert<br />
mit e<strong>in</strong>em Bruchteil e<strong>in</strong>er ganzen Zahl gespeichert wird.<br />
Beispiele:<br />
DBMS Datentyp Geltungsbereich Genauigkeit<br />
MS-<strong>SQL</strong> datetime 01.01.1753 bis 31.12.9999 3,33 Millisek.<br />
Server 2005 smalldatetime 01.01.1900 bis 06.06.2079 1 M<strong>in</strong>ute<br />
MS-<strong>SQL</strong> date 01.01.0001 bis 31.12.9999 1 Tag<br />
Server 2008 time 00:00:00.00 bis 23:59:59.99 100 Nanosek.<br />
datetime 01.01.0001 bis 31.12.9999 3,33 Millisek.<br />
smalldatetime 01.01.1900 bis 06.06.2079 1 M<strong>in</strong>ute<br />
Firebird DATE 01.01.0100 bis 29.02.32768 1 Tag<br />
TIME 00:00 bis 23:59.9999 6,67 Millisek.<br />
My<strong>SQL</strong> 5.x DATETIME 01.01.1000 00:00:00 bis<br />
31.12.9999 23:59:59<br />
1 Sekunde<br />
DATE 01.01.1000 bis 31.12.9999 1 Tag<br />
TIME –838:59:59 bis 838:59:59 1 Sekunde<br />
YEAR 1901 bis 2055 1 Jahr<br />
Bitte wundern Sie sich nicht: bei jedem DBMS gibt es noch weitere Datentypen<br />
und Bezeichnungen.<br />
Die Deklaration von TIME bei My<strong>SQL</strong> zeigt schon: Statt e<strong>in</strong>er Uhrzeit kann es<br />
auch e<strong>in</strong>en Zeitraum, d. h. e<strong>in</strong> Intervall darstellen.<br />
100
Konstruierte und benutzerdef<strong>in</strong>ierte Datentypen<br />
• INTERVAL<br />
E<strong>in</strong> Intervall setzt sich − je nach betrachteter Zeitdauer − zusammen aus:<br />
• YEAR, MONTH für längere Zeiträume (der <strong>SQL</strong>-Standard kennt auch nur die<br />
Bezeichnung "year-month-<strong>in</strong>terval")<br />
• DAY, HOUR, MINUTE, SECOND für Zeiträume <strong>in</strong>nerhalb e<strong>in</strong>es Tages oder<br />
über mehrere Tage h<strong>in</strong>weg<br />
13.1.5. Große Objekte<br />
BLOB (B<strong>in</strong>ary Large Object, b<strong>in</strong>äre große Objekte) ist die allgeme<strong>in</strong>e Bezeichnung<br />
für unbestimmt große Objekte.<br />
• BLOB<br />
Allgeme<strong>in</strong> werden b<strong>in</strong>äre Objekte z. B. für Bilder oder Bilddateien verwendet,<br />
nämlich dann, wenn der Inhalt nicht näher strukturiert ist und auch Bytes enthalten<br />
kann, die ke<strong>in</strong>e Zeichen s<strong>in</strong>d.<br />
• CLOB<br />
Speziell werden solche Objekte, die nur „echte“ Zeichen enthalten, zum Speichern<br />
von großen Texten oder Textdateien verwendet.<br />
Je nach DBMS werden BLOB-Varianten durch Sub_Type oder spezielle Datentypen<br />
für unterschiedliche Maximalgrößen oder Verwendung gekennzeichnet.<br />
13.1.6. Boolean<br />
Der Datentyp BOOLEAN ist für logische Werte v<strong>org</strong>esehen. Solche Felder können<br />
die Werte TRUE (wahr) und FALSE (falsch) annehmen; auch NULL ist möglich<br />
und wird als UNKNOWN (unbekannt) <strong>in</strong>terpretiert.<br />
Wenn e<strong>in</strong> DBMS diesen Datentyp (noch) nicht kennt − wie My<strong>SQL</strong> −, dann ist<br />
mit e<strong>in</strong>em der numerischen Typen e<strong>in</strong>e e<strong>in</strong>fache Ersatzlösung möglich (wie früher<br />
bei Interbase und Firebird); siehe unten im Abschnitt über Doma<strong>in</strong>s.<br />
13.2. Konstruierte und benutzerdef<strong>in</strong>ierte<br />
Datentypen<br />
Diese Datentypen, die aus den vordef<strong>in</strong>ierten Datentypen zusammengesetzt<br />
werden, werden hier nur der Vollständigkeit halber erwähnt; sie s<strong>in</strong>d <strong>in</strong> der Praxis<br />
e<strong>in</strong>es Anwenders ziemlich unwichtig.<br />
101
Datentypen<br />
• ROW<br />
E<strong>in</strong>e Zeile ist e<strong>in</strong>e Sammlung von Feldern; jedes Feld besteht aus dem Namen<br />
und dem Datentyp. Nun ja, e<strong>in</strong>e Zeile <strong>in</strong> e<strong>in</strong>er Tabelle ist (natürlich) von diesem<br />
Typ.<br />
• REF<br />
Referenztypen s<strong>in</strong>d zwar im <strong>SQL</strong>-Standard v<strong>org</strong>esehen, treten aber <strong>in</strong> der Praxis<br />
nicht auf.<br />
• ARRAY, MULTISET<br />
Felder und Mengen s<strong>in</strong>d Typen von Sammlungen ("collection type"), <strong>in</strong> denen<br />
jedes Element vom gleichen Datentyp ist. E<strong>in</strong> Array gibt die Elemente durch<br />
die Position an, e<strong>in</strong> Multiset ist e<strong>in</strong>e ungeordnete Menge. Wegen der Notwendigkeit,<br />
Tabellen zu normalisieren, s<strong>in</strong>d diese Typen <strong>in</strong> der Praxis unwichtig.<br />
• Benutzerdef<strong>in</strong>ierte Typen<br />
Dazu gehören nicht nur e<strong>in</strong> Typ, sondern auch Methoden zu ihrer Verwendung.<br />
Auch für solche Typen s<strong>in</strong>d ke<strong>in</strong>e s<strong>in</strong>nvollen Anwendungen zu f<strong>in</strong>den.<br />
13.3. Spezialisierte Datentypen<br />
Datentypen können mit E<strong>in</strong>schränkungen, also CONSTRAINTs versehen werden;<br />
auch der Begriff Doma<strong>in</strong> wird verwendet. (E<strong>in</strong>en vernünftigen deutschen<br />
Begriff gibt es dafür nicht.) Der <strong>SQL</strong>-Standard macht nicht viele V<strong>org</strong>aben.<br />
Der Befehl zum Erstellen e<strong>in</strong>er Doma<strong>in</strong> sieht allgeme<strong>in</strong> so aus:<br />
CREATE DOMAIN [] []<br />
Unter Interbase/Firebird wurde auf diese Weise e<strong>in</strong> Ersatz für den BOOLEAN-<br />
Datentyp erzeugt; weitere Beispiele stehen bei der Def<strong>in</strong>ition von CHECK 2 .<br />
Firebird-Quelltext<br />
CREATE DOMAIN BOOLEAN<br />
-- def<strong>in</strong>iere diesen Datentyp<br />
AS INT<br />
-- als Integer<br />
DEFAULT 0 NOT NULL<br />
-- V<strong>org</strong>abewert 0, hier ohne NULL-Werte<br />
CHECK (VALUE BETWEEN 0 AND 1);<br />
-- Werte können nur 0 (= false) und 1 (= true) se<strong>in</strong><br />
Bei My<strong>SQL</strong> können Spalten mit ENUM oder SET auf bestimmte Werte e<strong>in</strong>geschränkt<br />
werden, allerd<strong>in</strong>gs nur auf Zeichen.<br />
2 Abschnitt 28.4.5 auf Seite 299<br />
102
Nationale und <strong>in</strong>ternationale Zeichensätze<br />
13.4. Nationale und <strong>in</strong>ternationale Zeichensätze<br />
Aus der Frühzeit der EDV ist das Problem der nationalen Zeichen geblieben: Mit<br />
1 Byte (= 8 Bit) können höchstens 256 Zeichen (abzüglich 32 Steuerzeichen, Ziffern<br />
und e<strong>in</strong>er Reihe von Satz- und Sonderzeichen) dargestellt werden; und das<br />
reicht nicht e<strong>in</strong>mal für die Akzentbuchstaben (Umlaute) aller westeuropäischen<br />
Sprachen. Erst mit Unicode gibt es e<strong>in</strong>en Standard, der weltweit alle Zeichen<br />
(z. B. auch die ch<strong>in</strong>esischen Zeichen) darstellen und speichern soll.<br />
Da ältere EDV-Systeme (Computer, Programme, Programmiersprachen, Datenbanken)<br />
weiterh<strong>in</strong> benutzt werden, muss die Verwendung nationaler Zeichensätze<br />
nach wie vor berücksichtigt werden. Dafür gibt es verschiedene Maßnahmen<br />
− jedes DBMS folgt eigenen Regeln für CHARACTER SET (Zeichensatz) und<br />
COLLATE (alphabetische Sortierung) und benutzt eigene Bezeichner für die Zeichensätze<br />
und die Regeln der Reihenfolge.<br />
Vor allem wegen der DBMS-spezifischen Bezeichnungen kommen Sie nicht um<br />
<strong>in</strong>tensive Blicke <strong>in</strong> die jeweilige Dokumentation herum.<br />
13.4.1. Zeichensatz festlegen mit CHARACTER SET / CHARSET<br />
Wenn e<strong>in</strong>e Datenbank erstellt wird, muss der Zeichensatz festgelegt werden.<br />
Ohne ausdrückliche Festlegung regelt jedes DBMS selbst je nach Version, welcher<br />
Zeichensatz als Standard gelten soll. Wenn e<strong>in</strong> Programm Zugriff auf e<strong>in</strong>e<br />
vorhandene Datenbank nimmt, muss ebenso der Zeichensatz angegeben werden;<br />
dieser muss mit dem ursprünglich festgelegten übere<strong>in</strong>stimmen. Wenn<br />
Umlaute falsch angezeigt werden, dann stimmen <strong>in</strong> der Regel diese Angaben<br />
nicht.<br />
E<strong>in</strong>e neue Datenbank sollte, wenn das DBMS und die Programmiersprache dies<br />
unterstützen, möglichst mit Unicode (<strong>in</strong> der Regel als UTF8) angelegt werden.<br />
In neueren Versionen steht die Bezeichnung NCHAR (= NATIONAL CHAR) oft<br />
nicht für e<strong>in</strong>en speziellen nationalen Zeichensatz, sondern für den allgeme<strong>in</strong>en<br />
Unicode-Zeichensatz. Bei CHAR bzw. VARCHAR wird e<strong>in</strong> spezieller Zeichensatz<br />
verwendet, abhängig von der Installation oder der Datenbank.<br />
In diesem Abschnitt kann deshalb nur beispielhaft gezeigt werden, wie Zeichensätze<br />
behandelt werden.<br />
Firebird, Interbase; My<strong>SQL</strong><br />
CHARACTER SET beschreibt den Zeichensatz e<strong>in</strong>er Datenbank. Dieser gilt als<br />
V<strong>org</strong>abewert für alle Zeichenketten: CHAR(n), VARCHAR(n), CLOB. Für e<strong>in</strong>e e<strong>in</strong>zelne<br />
Spalte kann e<strong>in</strong> abweichender Zeichensatz festgelegt werden. Beispiel:<br />
103
Datentypen<br />
Firebird-Quelltext<br />
CREATE DATABASE ’europe.fb’ DEFAULT CHARACTER SET ISO8859_1;<br />
ALTER TABLE xyz ADD COLUMN lname VARCHAR(30) NOT NULL CHARACTER SET CYRL;<br />
Es kommt auch vor, dass e<strong>in</strong> Programm mit e<strong>in</strong>em anderen Zeichensatz arbeitet<br />
als die Datenbank. Dann können die Zeichensätze angepasst werden:<br />
Firebird-Quelltext<br />
SET NAMES DOS437;<br />
CONNECT ’europe.fb’ USER ’JAMES’ PASSWORD ’U4EEAH’;<br />
-- die Datenbank selbst arbeitet mit ISO8859_1, das Programm mit DOS-Codepage 437<br />
MS-<strong>SQL</strong><br />
Die Dokumentation geht nur allgeme<strong>in</strong> auf „Nicht-Unicode-Zeichendaten“ e<strong>in</strong>.<br />
Es gibt ke<strong>in</strong>erlei Erläuterung, wie e<strong>in</strong> solcher Zeichensatz festgelegt wird.<br />
13.4.2. Sortierungen mit COLLATE<br />
COLLATE legt fest, nach welchen Regeln die Reihenfolge von Zeichenketten<br />
(englisch: Collation Order) bestimmt wird. Der V<strong>org</strong>abewert für die Datenbank<br />
bzw. Tabelle hängt direkt vom Zeichensatz ab. Abweichende Regeln können getrennt<br />
gesteuert werden für Spalten, Vergleiche sowie Festlegungen bei ORDER<br />
BY und GROUP BY.<br />
Firebird-Quelltext<br />
CREATE DATABASE ’europe.fb’ DEFAULT CHARACTER SET ISO8859_1;<br />
-- dies legt automatisch die Sortierung nach ISO8859_1 fest<br />
ALTER TABLE xyz ADD COLUMN lname VARCHAR(30) NOT NULL COLLATE FR_CA;<br />
-- dies legt die Sortierung auf kanadisches Französisch fest<br />
SELECT ... WHERE lname COLLATE FR_FR
13.5. Zusammenfassung<br />
In diesem Kapitel lernten Sie die Datentypen unter <strong>SQL</strong> kennen:<br />
Zusammenfassung<br />
• Bei Zeichenketten ist zwischen fester und variabler Länge zu unterscheiden<br />
und der Zeichensatz − UNICODE oder national − zu beachten.<br />
• Für Zahlen ist zwischen exakter und näherungsweiser Speicherung zu unterscheiden<br />
und die Genauigkeit zu beachten.<br />
• Für Datums- und Zeitwerte ist vor allem auf den jeweiligen Geltungsbereich<br />
und die Genauigkeit zu achten.<br />
• Für spezielle Zwecke gibt es weitere Datentypen wie BLOB oder BOOLEAN.<br />
13.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 106.<br />
Zahlen und Datumsangaben verwenden immer die im deutschsprachigen Raum<br />
üblichen Schreibweisen. Zeichen werden mit Hochkommata begrenzt.<br />
Übung 1 – Texte und Zahlen<br />
Geben Sie zu den Werten jeweils an, welche Datentypen passen, welche fraglich<br />
s<strong>in</strong>d (also u. U. möglich, aber nicht s<strong>in</strong>nvoll oder unklar) und welche falsch s<strong>in</strong>d.<br />
1. 'A' als Char, Char(20), Varchar, Varchar(20)<br />
2. der Ortsname 'Bietigheim-Biss<strong>in</strong>gen' als Char, Char(20), Varchar, Varchar(20)<br />
3. das Wort 'Übungen' als Varchar(20), NVarchar(20)<br />
4. 123.456 als Integer, Small<strong>in</strong>t, Float, Numeric, Varchar(20)<br />
5. 123,456 als Integer, Small<strong>in</strong>t, Float, Numeric, Varchar(20)<br />
6. 789,12 [ =C] als Integer, Small<strong>in</strong>t, Float, Numeric, Varchar(20)<br />
Übung 2 – Datum und Zeit<br />
Geben Sie jeweils an, welche Datentypen passen, welche fraglich s<strong>in</strong>d (also u. U.<br />
möglich, aber nicht s<strong>in</strong>nvoll oder unklar) und welche falsch s<strong>in</strong>d.<br />
1. '27.11.2009' als Date, Time, Timestamp, Char(10), Varchar(20)<br />
2. '11:42:53' als Date, Time, Timestamp, Char(10), Varchar(20)<br />
3. '27.11.2009 11:42:53' als Date, Time, Timestamp, Char(10), Varchar(20)<br />
4. 'November 2009' als Date, Time, Timestamp, Char(10), Varchar(20)<br />
105
Datentypen<br />
Übung 3 – Personen<br />
Bereiten Sie e<strong>in</strong>e Tabelle Person vor: Notieren Sie mit möglichst konsequenter<br />
Aufteilung der Bestandteile die möglichen Spaltennamen und deren Datentypen;<br />
berücksichtigen Sie dabei auch <strong>in</strong>ternationale Adressen, aber ke<strong>in</strong>e Kontaktdaten<br />
(wie Telefon).<br />
Übung 4 – Buchhaltung<br />
Bereiten Sie e<strong>in</strong>e Tabelle Kassenbuch vor: Notieren Sie die möglichen Spaltennamen<br />
und deren Datentypen; berücksichtigen Sie dabei auch, dass Angaben der<br />
Buchhaltung geprüft werden müssen.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 105.<br />
Lösung zu Übung 1 – Texte und Zahlen<br />
Die „richtige“ Festlegung hängt vom Zusammenhang <strong>in</strong>nerhalb e<strong>in</strong>er Tabelle ab.<br />
1. Char und Varchar(20) passen, Varchar ist nicht s<strong>in</strong>nvoll, Char(20) ist falsch.<br />
2. Char(20) und Varchar(20) passen, Char und Varchar s<strong>in</strong>d falsch.<br />
3. Je nach verwendetem Zeichensatz können beide Varianten richtig, ungeeignet<br />
oder falsch se<strong>in</strong>.<br />
4. Integer und Numeric s<strong>in</strong>d richtig, Float ist möglich, Small<strong>in</strong>t ist falsch, Varchar(20)<br />
ist nicht ganz ausgeschlossen.<br />
5. Float und Numeric s<strong>in</strong>d richtig, Integer und Small<strong>in</strong>t s<strong>in</strong>d falsch, Varchar(20)<br />
ist nicht ganz ausgeschlossen.<br />
6. Numeric ist richtig, Float ist möglich, Integer und Small<strong>in</strong>t s<strong>in</strong>d falsch, Varchar(20)<br />
ist nicht ganz ausgeschlossen.<br />
Lösung zu Übung 2 – Datum und Zeit<br />
106<br />
1. Date und Timestamp s<strong>in</strong>d richtig, Char(10) und Varchar(20) s<strong>in</strong>d möglich,<br />
Time ist falsch.<br />
2. Time und Timestamp s<strong>in</strong>d richtig, Varchar(20) ist möglich, Char(10) und<br />
Date s<strong>in</strong>d falsch.<br />
3. Timestamp ist richtig, Varchar(20) ist möglich, Date, Time und Char(10)<br />
s<strong>in</strong>d falsch.<br />
4. Varchar(20) ist richtig, alles andere falsch.
Lösung zu Übung 3 – Personen<br />
Übungen<br />
Bitte wundern Sie sich nicht über unerwartete Unterteilungen: Bei der folgenden<br />
Lösung werden auch Erkenntnisse der Datenbank-Theorie und der praktischen<br />
Arbeit mit Datenbanken berücksichtigt.<br />
• ID Integer<br />
• Titel Varchar(15)<br />
• Vorname Varchar(30)<br />
• Adelszusatz Varchar(15) − e<strong>in</strong>e getrennte Spalte ist wegen der alphabetischen<br />
Sortierung s<strong>in</strong>nvoll<br />
• Name Varchar(30)<br />
• Adresszusatz Varchar(30)<br />
• Strasse Varchar(24)<br />
• Hausnr Integer (oder Small<strong>in</strong>t)<br />
• HausnrZusatz Varchar(10) − Trennung ist wegen der numerischen Sortierung<br />
s<strong>in</strong>nvoll<br />
• Länderkennung Char(2) − nach ISO 3166, auch Char(3) möglich, Integer oder<br />
Small<strong>in</strong>t denkbar; der Ländername ist auf jeden Fall unpraktisch und allenfalls<br />
als zusätzliche Spalte s<strong>in</strong>nvoll<br />
• PLZ Char(10) oder Varchar(10) − <strong>in</strong>ternational s<strong>in</strong>d bis zu 10 Zeichen möglich<br />
• Geburtsdatum Date<br />
Lösung zu Übung 4 – Buchhaltung<br />
• ID Integer<br />
• Buchungsjahr Integer oder Small<strong>in</strong>t<br />
• Buchungsnummer Integer<br />
• Buchungsterm<strong>in</strong> Timestamp − je nach Arbeitsweise genügt auch Date<br />
• Betrag Numeric oder Decimal<br />
• V<strong>org</strong>ang Varchar(50) − als Beschreibung der Buchung<br />
• Bearbeiter Varchar(30) − derjenige, der den Kassenbestand ändert; auch<br />
Bearbeiter_ID Integer mit Fremdschlüssel auf e<strong>in</strong>e Tabelle Mitarbeiter ist s<strong>in</strong>nvoll<br />
• Nutzer Varchar(30) − derjenige, der die Buchung registriert; auch Nutzer_ID<br />
Integer ist s<strong>in</strong>nvoll<br />
• Buchhaltung Timestamp − Term<strong>in</strong>, zu dem die Buchung registriert wird<br />
Wenn das Kassenbuch explizit e<strong>in</strong> Teil der Buchhaltung ist, werden auch Spalten<br />
wie Buchungskonto (Haupt- und Gegenkonten) benötigt.<br />
107
Datentypen<br />
13.7. Siehe auch<br />
In Wikipedia gibt es zusätzliche H<strong>in</strong>weise:<br />
• Gleitkommazahlen 3 mit ausführlicher Erläuterung ihrer Ungenauigkeiten<br />
• Koord<strong>in</strong>ierte Weltzeit (UTC) 4<br />
• Unicode 5 als umfassender Zeichensatz<br />
• ISO 3166 6 − Postleitzahl 7 − Liste der Postleitsysteme 8<br />
3 http://de.wikipedia.<strong>org</strong>/wiki/Gleitkommazahlen<br />
4 http://de.wikipedia.<strong>org</strong>/wiki/Koord<strong>in</strong>ierte%20Weltzeit<br />
5 http://de.wikipedia.<strong>org</strong>/wiki/Unicode<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/ISO%203166<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Postleitzahl<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Liste%20der%20Postleitsysteme<br />
108
14. Funktionen<br />
14.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . 110<br />
14.2. Funktionen für Zahlen . . . . . . . . . . . . . . . . . . . . . . . . 110<br />
14.3. Funktionen für Zeichenketten . . . . . . . . . . . . . . . . . . . 113<br />
14.4. Funktionen für Datums- und Zeitwerte . . . . . . . . . . . . . . 115<br />
14.5. Funktionen für logische und NULL-Werte . . . . . . . . . . . . 116<br />
14.6. Konvertierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117<br />
14.7. Spaltenfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . 120<br />
14.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 123<br />
14.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123<br />
14.10. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126<br />
Im <strong>SQL</strong>-Standard werden e<strong>in</strong>ige wenige Funktionen festgelegt, die <strong>in</strong> jedem <strong>SQL</strong>-<br />
Dialekt vorkommen. In aller Regel ergänzt jedes DBMS diese Funktionen durch<br />
weitere eigene. E<strong>in</strong>e ganze Reihe davon kann als Standard angesehen werden,<br />
weil sie immer (oder fast immer) vorhanden s<strong>in</strong>d. An vielen Stellen ist aber auf<br />
Unterschiede im Namen oder <strong>in</strong> der Art des Aufrufs h<strong>in</strong>zuweisen.<br />
• Skalarfunktionen verarbeiten Werte oder Ausdrücke aus e<strong>in</strong>zelnen Zahlen,<br />
Zeichenketten oder Datums- und Zeitwerten. Sofern die Werte aus e<strong>in</strong>er Spalte<br />
geholt werden, handelt es sich immer um e<strong>in</strong>en Wert aus e<strong>in</strong>er bestimmten<br />
Zeile. – Das Ergebnis e<strong>in</strong>er solchen Funktion wird auch als Rückgabewert bezeichnet.<br />
• Spaltenfunktionen verarbeiten alle Werte aus e<strong>in</strong>er Spalte.<br />
• Ergänzend kann man für beide Varianten auch benutzerdef<strong>in</strong>ierte Funktionen<br />
erstellen; dies wird unter Programmierung 1 angesprochen.<br />
Dieses Kapitel enthält als Grundlage nur die wichtigsten Skalar- und Spaltenfunktionen.<br />
Unter Funktionen (2) 2 gibt es viele Ergänzungen.<br />
1 Kapitel 30 auf Seite 323<br />
2 Kapitel 16 auf Seite 141<br />
109
Funktionen<br />
14.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise<br />
Die Funktionen können überall dort verwendet werden, wo e<strong>in</strong> <strong>SQL</strong>-Ausdruck<br />
möglich ist. Wichtig ist, dass das Ergebnis der Funktion zu dem Datentyp passt,<br />
der an der betreffenden Stelle erwartet wird.<br />
Firebird kennt ab Version 2.1 viele Funktionen, die vorher als benutzerdef<strong>in</strong>ierte<br />
Funktionen (user def<strong>in</strong>ed functions, UDF) erstellt werden mussten.<br />
Schreibweise für Funktionen: Bei Funktionen müssen die Parameter (auch „Argumente“<br />
genannt) immer <strong>in</strong> Klammern gesetzt werden. Diese Klammern s<strong>in</strong>d<br />
auch bei e<strong>in</strong>er Funktion mit konstantem Wert wie PI erforderlich. E<strong>in</strong>e Ausnahme<br />
s<strong>in</strong>d lediglich die Systemfunktionen CURRENT_DATE u. ä.<br />
Schreibweise der Beispiele: Aus Platzgründen werden Beispiele meistens nur<br />
e<strong>in</strong>fach <strong>in</strong> e<strong>in</strong>en Rahmen gesetzt. Für Funktionen ohne Bezug auf Tabellen und<br />
Spalten genügt <strong>in</strong> der Regel e<strong>in</strong> e<strong>in</strong>facher SELECT-Befehl; <strong>in</strong> manchen Fällen<br />
muss e<strong>in</strong>e Tabelle oder e<strong>in</strong>e fiktive Quelle angegeben werden:<br />
SELECT 2 * 3; /* Normalfall */<br />
SELECT 2 * 3 FROM rdb$database; /* bei Firebird und Interbase */<br />
SELECT 2 * 3 FROM dual; /* bei Oracle */<br />
In diesem Kapitel und auch im zweiten Kapitel zu Funktionen werden diese Verfahren<br />
durch e<strong>in</strong>e verkürzte Schreibweise zusammengefasst:<br />
SELECT 2 * 3 [from fiktiv];<br />
Das ist so zu lesen: Die FROM-Klausel ist für Firebird, Interbase und Oracle notwendig<br />
und muss die jeweils benötigte Tabelle angeben; bei allen anderen DBMS<br />
muss sie entfallen.<br />
14.2. Funktionen für Zahlen<br />
Bei allen numerischen Funktionen müssen Sie auf den genauen Typ achten. Es<br />
ist beispielsweise e<strong>in</strong> Unterschied, ob das Ergebnis e<strong>in</strong>er Division zweier ganzer<br />
Zahlen als ganze Zahl oder als Dezimalzahl behandelt werden soll.<br />
14.2.1. Operatoren<br />
Es gibt die üblichen Operatoren für die Grundrechenarten:<br />
110
+ Addition<br />
- Subtraktion, Negation<br />
* Multiplikation<br />
/ Division|(<br />
Funktionen für Zahlen<br />
Dafür gelten die üblichen mathematischen Regeln (Punkt vor Strich, Klammern<br />
zuerst). Bitte beachten Sie auch folgende Besonderheit:<br />
• Bei der Division ganzer Zahlen ist auch das Ergebnis e<strong>in</strong>e ganze Zahl; man<br />
nennt das „Integer-Division“:<br />
SELECT 8 / 5 [from fiktiv]; /* Ergebnis: 1 */<br />
• Wenn Sie das Ergebnis als Dezimalzahl haben wollen, müssen Sie (m<strong>in</strong>destens)<br />
e<strong>in</strong>e der beiden Zahlen als Dezimalzahl v<strong>org</strong>eben:<br />
SELECT 8.0 / 5 [from fiktiv]; /* Ergebnis: 1.6 */<br />
• Ausnahme: My<strong>SQL</strong> liefert auch bei 8/5 e<strong>in</strong>e Dezimalzahl. Für die „Integer-<br />
Division“ gibt es die DIV-Funktion:<br />
SELECT 8 DIV 5; /* Ergebnis: 1 */<br />
Division durch 0 liefert zunächst e<strong>in</strong>e Fehlermeldung "division by zero has occurred"<br />
und danach das Ergebnis NULL.<br />
14.2.2. MOD – der Rest e<strong>in</strong>er Division<br />
Die Modulo-Funktion MOD bestimmt den Rest bei e<strong>in</strong>er Division ganzer Zahlen:<br />
MOD( , ) /* allgeme<strong>in</strong> */<br />
% /* bei MS-<strong>SQL</strong> und My<strong>SQL</strong> */<br />
Beispiele:<br />
SELECT MOD(7, 3) [from fiktiv]; /* Ergebnis: 1 */<br />
SELECT ID FROM Mitarbeiter WHERE MOD(ID, 10) = 0; /* listet IDs mit ’0’ am Ende ’/<br />
111
Funktionen<br />
14.2.3. CEILING, FLOOR, ROUND, TRUNCATE – die nächste<br />
ganze Zahl<br />
Es gibt mehrere Möglichkeiten, zu e<strong>in</strong>er Dezimalzahl die nächste ganze Zahl zu<br />
bestimmen.<br />
CEILING oder CEIL liefert die nächstgrößere ganze Zahl, genauer: die kle<strong>in</strong>ste<br />
Zahl, die größer oder gleich der gegebenen Zahl ist.<br />
SELECT CEILING(7.3), CEILING(-7.3) [from fiktiv]; /* Ergebnis: 8, -7 */<br />
FLOOR ist das Gegenstück dazu und liefert die nächstkle<strong>in</strong>ere ganze Zahl, genauer:<br />
die größte Zahl, die kle<strong>in</strong>er oder gleich der gegebenen Zahl ist.<br />
SELECT FLOOR(7.3), FLOOR(-7.3) [from fiktiv]; /* Ergebnis: 7, -8 */<br />
TRUNCATE oder TRUNC schneidet den Dezimalanteil ab.<br />
SELECT TRUNCATE(7.3),TRUNCATE(-7.3) [from fiktiv]; /* Ergebnis: 7, -7 */<br />
SELECT TRUNCATE(Schadenshoehe) FROM Schadensfall; /* Euro-Werte ohne Cent */<br />
ROUND liefert e<strong>in</strong>e mathematische Rundung: ab 5 wird aufgerundet, darunter<br />
wird abgerundet.<br />
ROUND( [ , ] )<br />
ist e<strong>in</strong>e beliebige Zahl oder e<strong>in</strong> Ausdruck, der e<strong>in</strong>e beliebige Zahl<br />
liefert.<br />
• Wenn nicht angegeben ist, wird 0 angenommen.<br />
• Bei e<strong>in</strong>em positiven Wert für wird auf entsprechend viele Dezimalstellen<br />
gerundet.<br />
• Bei e<strong>in</strong>em negativen Wert für wird l<strong>in</strong>ks vom Dezimaltrenner<br />
auf entsprechend viele Nullen gerundet.<br />
Beispiele:<br />
SELECT ROUND(12.248,-2) [from fiktiv]; /* Ergebnis: 0,000 */<br />
SELECT ROUND(12.248,-1) [from fiktiv]; /* Ergebnis: 10,000 */<br />
SELECT ROUND(12.248, 0) [from fiktiv]; /* Ergebnis: 12,000 */<br />
SELECT ROUND(12.248, 1) [from fiktiv]; /* Ergebnis: 12,200 */<br />
SELECT ROUND(12.248, 2) [from fiktiv]; /* Ergebnis: 12,250 */<br />
SELECT ROUND(12.248, 3) [from fiktiv]; /* Ergebnis: 12,248 */<br />
SELECT ROUND(12.25, 1) [from fiktiv]; /* Ergebnis: 12,300 */<br />
SELECT ROUND(Schadenshoehe) FROM Schadensfall; /* Euro-Werte gerundet */<br />
112
14.3. Funktionen für Zeichenketten<br />
Funktionen für Zeichenketten<br />
Zur Bearbeitung und Prüfung von Zeichenketten (Str<strong>in</strong>gs) werden viele Funktionen<br />
angeboten.<br />
14.3.1. Verknüpfen von Str<strong>in</strong>gs<br />
Als Operatoren, um mehrere Zeichenketten zu verb<strong>in</strong>den, stehen zur Verfügung:<br />
|| als <strong>SQL</strong>-Standard<br />
+ für MS-<strong>SQL</strong> oder My<strong>SQL</strong><br />
CONCAT für My<strong>SQL</strong> oder Oracle<br />
Der senkrechte Strich wird als „Verkettungszeichen“ bezeichnet und oft „Pipe“-<br />
Zeichen genannt. Es wird auf der deutschen PC-Tastatur unter W<strong>in</strong>dows durch<br />
die Tastenkomb<strong>in</strong>ation Alt Gr + < > erzeugt.<br />
E<strong>in</strong> Beispiel <strong>in</strong> allen diesen Varianten:<br />
SELECT Name || ’, ’ || Vorname from Mitarbeiter;<br />
SELECT Name + ’, ’ + Vorname from Mitarbeiter;<br />
SELECT CONCAT(Name, ’, ’, Vorname) from Mitarbeiter;<br />
Jede Variante liefert das gleiche Ergebnis: Für jeden Datensatz der Tabelle Mitarbeiter<br />
werden Name und Vorname verbunden und dazwischen e<strong>in</strong> weiterer<br />
Str<strong>in</strong>g gesetzt, bestehend aus Komma und e<strong>in</strong>em Leerzeichen.<br />
14.3.2. Länge von Str<strong>in</strong>gs<br />
Um die Länge e<strong>in</strong>er Zeichenkette zu ermitteln, gibt es folgende Funktionen:<br />
CHARACTER_LENGTH( ) <strong>SQL</strong>-Standard<br />
CHAR_LENGTH( ) <strong>SQL</strong>-Standard Kurzfassung<br />
LEN( ) nur für MS-<strong>SQL</strong><br />
Beispiele:<br />
SELECT CHAR_LENGTH(’Hello World’) [from fiktiv]; /* Ergebnis: 11 */<br />
SELECT CHAR_LENGTH(”) [from fiktiv]; /* Ergebnis: 0 */<br />
SELECT CHAR_LENGTH( NULL ) [from fiktiv]; /* Ergebnis: */<br />
SELECT Name FROM Mitarbeiter ORDER BY CHAR_LENGTH(Name) DESC;<br />
/* liefert die Namen der Mitarbeiter, absteigend sortiert nach Länge */<br />
113
Funktionen<br />
14.3.3. UPPER, LOWER – Groß- und Kle<strong>in</strong>buchstaben<br />
UPPER konvertiert den gegebenen Str<strong>in</strong>g zu Großbuchstaben; LOWER gibt<br />
e<strong>in</strong>en Str<strong>in</strong>g zurück, der nur aus Kle<strong>in</strong>buchstaben besteht.<br />
SELECT UPPER(’Abc Äöü Xyzß ÀÉÇ àéç’) [from fiktiv];<br />
/* Ergebnis: ’ABC ÄÖÜ XYZß ÀÉÇ ÀÉÇ’ */<br />
SELECT LOWER(’Abc Äöü Xyzß ÀÉÇ àéç’) [from fiktiv];<br />
/* Ergebnis: ’abc äöü xyzß àéç àéç’ */<br />
SELECT UPPER(Kuerzel), Bezeichnung FROM Abteilung;<br />
/* Kurzbezeichnungen <strong>in</strong> Großbuchstaben */<br />
Ob die Konvertierung bei Umlauten richtig funktioniert, hängt vom verwendeten<br />
Zeichensatz ab.<br />
14.3.4. SUBSTRING – Teile von Zeichenketten<br />
SUBSTRING ist der <strong>SQL</strong>-Standard, um aus e<strong>in</strong>em Str<strong>in</strong>g e<strong>in</strong>en Teil zu holen:<br />
SUBSTRING( FROM FOR ) /* <strong>SQL</strong>-Standard */<br />
SUBSTRING( , , ) /* MS-<strong>SQL</strong>, My<strong>SQL</strong>, Oracle */<br />
Diese Funktion heißt unter Oracle SUBSTR und kann auch bei My<strong>SQL</strong> so bezeichnet<br />
werden.<br />
Der Ausgangstext wird von Position 1 an gezählt. Der Teilstr<strong>in</strong>g beg<strong>in</strong>nt an der<br />
h<strong>in</strong>ter FROM genannten Position und übernimmt so viele Zeichen wie h<strong>in</strong>ter<br />
FOR angegeben ist:<br />
SELECT SUBSTRING(’Abc Def Ghi’ FROM 6 FOR 4) [from fiktiv]; /* Ergebnis: ’ef G’ */<br />
SELECT CONCAT(Name, ’, ’, SUBSTRING(Vorname FROM 1 FOR 1), ’.’) FROM Mitarbeiter;<br />
/* liefert den Namen und vom Vornamen den Anfangsbuchstaben */<br />
Wenn der -Parameter fehlt, wird alles bis zum Ende von übernommen:<br />
SELECT SUBSTRING(’Abc Def Ghi’ FROM 6) [from fiktiv]; /* Ergebnis: ’ef Ghi’ */<br />
Wenn der -Parameter 0 lautet, werden 0 Zeichen übernommen, man<br />
erhält also e<strong>in</strong>e leere Zeichenkette.<br />
My<strong>SQL</strong> bietet noch e<strong>in</strong>e Reihe weiterer Varianten.<br />
114
Funktionen für Datums- und Zeitwerte<br />
H<strong>in</strong>weis: Nach <strong>SQL</strong>-Standard liefert das Ergebnis von SUBSTRING seltsamerweise<br />
e<strong>in</strong>en Text von gleicher Länge wie der ursprüngliche Text; die jetzt zwangsläufig<br />
folgenden Leerzeichen müssen ggf. mit der TRIM-Funktion (im zweiten<br />
Kapitel über Funktionen 3 ) entfernt werden.<br />
14.4. Funktionen für Datums- und Zeitwerte<br />
Bei den Datums- und Zeitfunktionen gilt das gleiche wie für Datum und Zeit als<br />
Datentyp: Jeder <strong>SQL</strong>-Dialekt hat sich se<strong>in</strong>en eigenen „Standard“ ausgedacht. Wir<br />
beschränken uns deshalb auf die wichtigsten Funktionen, die es so ähnlich „immer“<br />
gibt, und verweisen auf die DBMS-Dokumentationen.<br />
Vor allem My<strong>SQL</strong> bietet viele zusätzliche Funktionen an. Teilweise s<strong>in</strong>d es nur<br />
verkürzte und spezialisierte Schreibweisen der Standardfunktionen, teilweise<br />
liefern sie zusätzliche Möglichkeiten.<br />
14.4.1. Systemdatum und -uhrzeit<br />
Nach <strong>SQL</strong>-Standard werden aktuelle Uhrzeit und Datum abgefragt:<br />
CURRENT_DATE<br />
CURRENT_TIME<br />
CURRENT_TIMESTAMP<br />
In Klammern kann als die Anzahl der Dezimalstellen bei den Sekunden<br />
angegeben werden.<br />
Beispiele:<br />
SELECT CURRENT_TIMESTAMP [from fiktiv]; /* Ergebnis: ’19.09.2009 13:47:49’ */<br />
UPDATE Versicherungsvertrag SET Aenderungsdatum = CURRENT_DATE WHERE irgendetwas;<br />
Bei MS-<strong>SQL</strong> gibt es nur CURRENT_TIMESTAMP als Standardfunktion, dafür<br />
aber andere Funktionen mit höherer Genauigkeit.<br />
14.4.2. Teile von Datum oder Uhrzeit bestimmen<br />
Für diesen Zweck gibt es vor allem EXTRACT als Standardfunktion:<br />
3 Kapitel 16 auf Seite 141<br />
115
Funktionen<br />
EXTRACT ( FROM )<br />
ist der Wert des betreffenden Datums und/oder der Uhrzeit, die aufgeteilt<br />
werden soll. Als wird der gewünschte Teil des Datums angegeben:<br />
YEAR | MONTH | DAY | HOUR | MINUTE | SECOND | MILLISECOND<br />
Bei e<strong>in</strong>em DATE-Feld ohne Uhrzeit s<strong>in</strong>d HOUR usw. natürlich unzulässig, bei<br />
e<strong>in</strong>em TIME-Feld nur mit Uhrzeit die Bestandteile YEAR usw. Beispiele:<br />
SELECT ID, Datum, EXTRACT(YEAR FROM Datum) AS Jahr, EXTRACT(MONTH FROM Datum) AS Monat<br />
FROM Schadensfall ORDER BY Jahr, Monat;<br />
SELECT ’Stunde = ’ || EXTRACT(HOUR FROM CURRENT_TIME) [from fiktiv];<br />
/* Ergebnis: ’Stunde = 14’ */<br />
Bei MS-<strong>SQL</strong> heißt diese Standardfunktion DATEPART; als können viele<br />
weitere Varianten genutzt werden.<br />
Sehr oft gibt es weitere Funktionen, die direkt e<strong>in</strong>en Bestandteil abfragen, zum<br />
Beispiel:<br />
YEAR( ) liefert das Jahr<br />
MONTH( ) liefert den Monat usw.<br />
MINUTE( ) liefert die M<strong>in</strong>ute usw.<br />
DAYOFWEEK( ) gibt den Wochentag an (als Zahl, 1 für Sonntag usw.)<br />
Wie gesagt: Lesen Sie <strong>in</strong> der DBMS-Dokumentation nach, was es sonst noch gibt.<br />
14.5. Funktionen für logische und NULL-Werte<br />
Wenn man es genau nimmt, gehören dazu auch Prüfungen wie „Ist e<strong>in</strong> Wert<br />
NULL?“. Der <strong>SQL</strong>-Standard hat dazu und zu anderen Prüfungen verschiedene<br />
spezielle Ausdrücke v<strong>org</strong>esehen. Diese gehören vor allem zur WHERE-Klausel 4<br />
des SELECT-Befehls:<br />
= < > usw. Größenvergleich zweier Werte<br />
BETWEEN AND Werte zwischen zwei Grenzen<br />
LIKE Ähnlichkeiten (1)<br />
CONTAINS u.a. Ähnlichkeiten (2)<br />
IS NULL null-Werte prüfen<br />
4 Kapitel 17 auf Seite 155<br />
116
IN genauer Vergleich mit e<strong>in</strong>er Liste<br />
EXISTS schneller Vergleich mit e<strong>in</strong>er Liste<br />
Konvertierungen<br />
Alle diese Ausdrücke liefern e<strong>in</strong>en der logischen Werte TRUE, FALSE − also<br />
WAHR oder FALSCH − und ggf. NULL − also − zurück und können<br />
als boolescher Wert weiterverarbeitet oder ausgewertet werden.<br />
14.5.1. Operatoren<br />
Zur Verknüpfung logischer Werte gibt es die „booleschen Operatoren“:<br />
NOT als Negation<br />
AND als Konjunktion<br />
OR als Adjunktion<br />
XOR als Kontravalenz /* nur bei My<strong>SQL</strong> */<br />
Auch diese Operatoren werden bei der WHERE-Klausel behandelt.<br />
Zur Verknüpfung von NULL-Werten gibt es vielfältige Regeln, je nach dem Zusammenhang,<br />
<strong>in</strong> dem das von Bedeutung ist. Man kann sich aber folgende Regel<br />
merken:<br />
Wenn e<strong>in</strong> Ausgangswert NULL ist, also , und dieser<br />
„Wert“ mit etwas verknüpft wird (z. B. mit e<strong>in</strong>er Zahl addiert wird),<br />
kann das Ergebnis nur se<strong>in</strong>, also NULL lauten.<br />
14.6. Konvertierungen<br />
In der EDV − also auch bei <strong>SQL</strong>-Datenbanken − ist der verwendete Datentyp<br />
immer von Bedeutung. Mit Zahlen kann gerechnet werden, mit Zeichenketten<br />
nicht. Größenvergleiche von Zahlen gelten immer und überall; bei Zeichenketten<br />
hängt die Sortierung auch von der Sprache ab. E<strong>in</strong> Datum wird <strong>in</strong> Deutschland<br />
durch '11.09.2001' beschrieben, <strong>in</strong> England durch '11/09/2001' und <strong>in</strong> den<br />
USA durch '09/11/2001'. Wenn wir etwas aufschreiben (z. B. e<strong>in</strong>en <strong>SQL</strong>-Befehl),<br />
dann benutzen wir zwangsläufig immer Zeichen bzw. Zeichenketten, auch wenn<br />
wir Zahlen oder e<strong>in</strong> Datum me<strong>in</strong>en.<br />
In vielen Fällen „versteht“ das DBMS, was wir mit e<strong>in</strong>er solchen Schreibweise<br />
me<strong>in</strong>en; dann werden durch „implizite Konvertierung“ die Datentypen automatisch<br />
<strong>in</strong>e<strong>in</strong>ander übertragen. In anderen Fällen muss der Anwender dem DBMS<br />
durch e<strong>in</strong>e Konvertierungsfunktion explizit erläutern, was wie zu verstehen ist.<br />
117
Funktionen<br />
14.6.1. Implizite Konvertierung<br />
Datentypen, die problemlos vergleichbar s<strong>in</strong>d, werden „automatisch“ <strong>in</strong>e<strong>in</strong>ander<br />
übergeführt.<br />
• Die Zahl 17 kann je nach Situation e<strong>in</strong> INTEGER, e<strong>in</strong> SMALLINT, e<strong>in</strong> BIGINT,<br />
aber auch NUMERIC oder FLOAT se<strong>in</strong>.<br />
• Die Zahl 17 kann <strong>in</strong> e<strong>in</strong>em SELECT-Befehl auch als Str<strong>in</strong>g '17' erkannt und verarbeitet<br />
werden.<br />
SELECT ID, Name, Vorname FROM Mitarbeiter WHERE ID = ’17’;<br />
• Je nach DBMS kann e<strong>in</strong> Str<strong>in</strong>g wie '11.09.2001' als Datum erkannt und verarbeitet<br />
werden. Vielleicht verlangt es aber auch e<strong>in</strong>e andere Schreibweise wie<br />
'11/09/2001'. Die Schreibweise '2009-09-11' nach ISO 8601 sollte dagegen immer<br />
richtig verstanden werden (aber auch da gibt es Abweichungen).<br />
Es gibt bereits durch den <strong>SQL</strong>-Standard ausführliche Regeln, welche Typen immer,<br />
unter bestimmten Umständen oder niemals <strong>in</strong>e<strong>in</strong>ander übergeführt werden<br />
können. Jedes DBMS ergänzt diese allgeme<strong>in</strong>en Regeln durch eigene.<br />
Wenn e<strong>in</strong> solcher Befehl ausgeführt wird, dürfte es niemals Missverständnisse<br />
geben, sondern er wird „mit an Sicherheit grenzender Wahrsche<strong>in</strong>lichkeit“ korrekt<br />
erledigt. Wenn e<strong>in</strong> Wert nicht e<strong>in</strong>deutig ist, wird das DBMS eher zu früh als<br />
zu spät mit e<strong>in</strong>er Fehlermeldung wie "conversion error from str<strong>in</strong>g. . . " reagieren.<br />
Dann ist es Zeit für e<strong>in</strong>e explizite Konvertierung, meistens durch CAST.<br />
14.6.2. CAST<br />
Die CAST-Funktion ist der <strong>SQL</strong>-Standard für die Überführung e<strong>in</strong>es Wertes von<br />
e<strong>in</strong>em Datentyp <strong>in</strong> e<strong>in</strong>en anderen.<br />
CAST ( AS )<br />
Als ist etwas angegeben, was den „falschen“ Typ hat, nämlich e<strong>in</strong><br />
Wert oder Ausdruck. Mit wird der Datentyp angegeben, der an der betreffenden<br />
Stelle gewünscht oder benötigt wird. Das Ergebnis des CAST<strong>in</strong>gs (der<br />
Konvertierung) ist dann genau von diesem Typ.<br />
Beispiele für Datum:<br />
SELECT EXTRACT(DAY FROM ’07.14.2009’) [from fiktiv];<br />
/* expression evaluation not supported */<br />
SELECT EXTRACT(DAY FROM CAST(’07.14.2009’ AS DATE)) [from fiktiv];<br />
118
Konvertierungen<br />
/* conversion error from str<strong>in</strong>g "07.14.2009" */<br />
SELECT EXTRACT(DAY FROM CAST(’14.07.2009’ AS DATE)) [from fiktiv];<br />
/* Ergebnis: ’14’ */<br />
SELECT Name, Vorname, CAST(Geburtsdatum AS CHAR(10)) FROM Versicherungsnehmer;<br />
/* Ergebnis wird <strong>in</strong> der Form ’1953-01-13’ angezeigt */<br />
Kürzere Zeichenketten können schnell verlängert werden, wenn es nötig ist:<br />
SELECT ID, CAST(Kuerzel AS CHAR(20)) FROM Abteilung;<br />
-- 20 Zeichen Länge statt eigentlich 10 Zeichen<br />
Das Verkürzen funktioniert nicht immer so e<strong>in</strong>fach. Ob bei Überschreitung e<strong>in</strong>er<br />
Maximallänge e<strong>in</strong>fach abgeschnitten wird oder ob es zu e<strong>in</strong>er Fehlermeldung<br />
"str<strong>in</strong>g truncation" kommt, hängt vom DBMS ab; dann müssen Sie ggf. e<strong>in</strong>e<br />
SUBSTRING-Variante benutzen.<br />
-- vielleicht funktioniert es so:<br />
SELECT CAST(Name AS CHAR(15)) || Vorname from Versicherungsnehmer;<br />
-- aber sicherer klappt es so:<br />
SELECT SUBSTRING(Name FROM 1 FOR 15) || Vorname from Versicherungsnehmer;<br />
-- ggf. unter Berücksichtigung des H<strong>in</strong>weises bei SUBSTRING:<br />
SELECT TRIM( SUBSTRING(Name FROM 1 FOR 15) ) || Vorname from Versicherungsnehmer;<br />
Bitte lesen Sie <strong>in</strong> Ihrer <strong>SQL</strong>-Dokumentation nach, zwischen welchen Datentypen<br />
implizite Konvertierung möglich ist und wie die explizite Konvertierung mit<br />
CAST ausgeführt wird.<br />
14.6.3. CONVERT<br />
Nach dem <strong>SQL</strong>-Standard ist CONVERT v<strong>org</strong>esehen zum Konvertieren von Zeichenketten<br />
<strong>in</strong> verschiedenen Zeichensätzen:<br />
CONVERT ( USING ) <strong>SQL</strong>-Standard<br />
CONVERT ( , ) alternative Schreibweise<br />
Firebird kennt diese Funktion überhaupt nicht. MS-<strong>SQL</strong> benutzt e<strong>in</strong>e andere<br />
Syntax und bietet vor allem für Datums- und Zeitformate viele weitere Möglichkeiten:<br />
CONVERT ( , [ , ] )<br />
Wegen dieser starken Abweichungen verzichten wir auf weitere Erläuterungen<br />
und verweisen auf die jeweilige Dokumentation.<br />
119
Funktionen<br />
14.6.4. Datum und Zeit<br />
Vor allem für die Konvertierung mit Datums- und Zeitangaben bieten die verschiedenen<br />
DBMS Erweiterungen. Beispiele:<br />
• MS-<strong>SQL</strong> hat die Syntax von CONVERT für diesen Zweck erweitert.<br />
• My<strong>SQL</strong> konvertiert vor allem mit den Funktionen STR_TO_DATE und<br />
DATE_FORMAT.<br />
• Oracle kennt e<strong>in</strong>e Reihe von Funktionen wie TO_DATE usw.<br />
Noch e<strong>in</strong> Grund für das Studium der Dokumentation. . .<br />
14.7. Spaltenfunktionen<br />
Die Spaltenfunktionen werden auch als Aggregatfunktionen bezeichnet, weil<br />
sie e<strong>in</strong>e Menge von Werten − nämlich aus allen Zeilen e<strong>in</strong>er Spalte − zusammenfassen<br />
und daraus e<strong>in</strong>en Wert bestimmen. In der Regel geht es um e<strong>in</strong>e Spalte aus<br />
e<strong>in</strong>er der beteiligten Tabellen; es kann aber auch e<strong>in</strong> sonstiger <strong>SQL</strong>-Ausdruck mit<br />
e<strong>in</strong>em e<strong>in</strong>zelnen Wert als Ergebnis se<strong>in</strong>. Das Ergebnis der Funktion ist dann e<strong>in</strong><br />
Wert, der aus allen passenden Zeilen der Abfrage berechnet wird.<br />
Bei Abfragen kann das Ergebnis e<strong>in</strong>er Spaltenfunktion auch nach e<strong>in</strong>er oder<br />
mehreren Spalten oder Berechnungen gruppiert werden. Die Funktionen liefern<br />
dann für jede Gruppe e<strong>in</strong> Teilergebnis – Näheres unter Gruppierungen 5 .<br />
14.7.1. COUNT – Anzahl<br />
Die Funktion COUNT zählt alle Zeilen, die e<strong>in</strong>en e<strong>in</strong>deutigen Wert enthalten,<br />
also nicht NULL s<strong>in</strong>d. Sie kann auf alle Datentypen angewendet werden, da für<br />
jeden Datentyp NULL def<strong>in</strong>iert ist. Beispiel:<br />
SELECT COUNT(Farbe) AS Anzahl_Farbe FROM Fahrzeug;<br />
Die Spalte Farbe ist als VARCHAR(30), also als Text variabler Länge def<strong>in</strong>iert und<br />
optional. Hier werden also alle Zeilen gezählt, die <strong>in</strong> dieser Spalte e<strong>in</strong>en E<strong>in</strong>trag<br />
haben. Dasselbe funktioniert auch mit e<strong>in</strong>er numerischen Spalte:<br />
SELECT COUNT(Schadenshoehe) AS Anzahl_Schadenshoehe FROM Schadensfall;<br />
Hier ist die Spalte numerisch und optional. Die Zahl 0 ist bekanntlich nicht<br />
NULL. Wenn <strong>in</strong> der Spalte e<strong>in</strong>e 0 steht, wird sie mitgezählt.<br />
5 Kapitel 25 auf Seite 241<br />
120
Spaltenfunktionen<br />
E<strong>in</strong> Spezialfall ist der Asterisk '*' als Parameter. Dies bezieht sich dann nicht auf<br />
e<strong>in</strong>e e<strong>in</strong>zelne Spalte, sondern auf e<strong>in</strong>e ganze Zeile. So wird also die Anzahl der<br />
Zeilen <strong>in</strong> der Tabelle gezählt:<br />
SELECT COUNT(*) AS Anzahl_Zeilen FROM Schadensfall;<br />
Die Funktion COUNT liefert niemals NULL zurück, sondern immer e<strong>in</strong>e Zahl;<br />
wenn alle Werte <strong>in</strong> e<strong>in</strong>er Spalte NULL s<strong>in</strong>d, ist das Ergebnis die Zahl 0 (es gibt 0<br />
Zeilen mit e<strong>in</strong>em Wert ungleich NULL <strong>in</strong> dieser Spalte).<br />
14.7.2. SUM – Summe<br />
Die Funktion SUM kann (natürlich) nur auf numerische Datentypen angewendet<br />
werden. Im Gegensatz zu COUNT liefert SUM nur dann e<strong>in</strong>en Wert zurück,<br />
wenn wenigstens e<strong>in</strong> E<strong>in</strong>gabewert nicht NULL ist. Als Beispiel für e<strong>in</strong>e e<strong>in</strong>zelne<br />
numerische Spalte werden alle Werte der Spalte Schadenshoehe aufsummiert:<br />
SELECT SUM(Schadenshoehe) AS Summe_Schadenshoehe<br />
FROM Schadensfall;<br />
Als Parameter kann nicht nur e<strong>in</strong>e e<strong>in</strong>zelne numerische Spalte, sondern auch<br />
e<strong>in</strong>e Berechnung übergeben werden, die als Ergebnis e<strong>in</strong>e e<strong>in</strong>zelne Zahl liefert.<br />
Aufgabe: Hier werden Euro-Beträge aus Schadenshoehe zuerst <strong>in</strong> US-Dollar<br />
nach e<strong>in</strong>em Tageskurs umgerechnet und danach aufsummiert.<br />
SELECT SUM(Schadenshoehe * 1.5068) AS Summe_Schadenshoehe_-<br />
Dollar FROM Schadensfall;<br />
E<strong>in</strong>e Besonderheit ist das Berechnen von Vergleichen. E<strong>in</strong> Vergleich wird als<br />
WAHR oder FALSCH ausgewertet. Sofern das DBMS (wie bei My<strong>SQL</strong> oder Access)<br />
das Ergebnis als Zahl benutzt, ist das Ergebnis e<strong>in</strong>es Vergleichs daher 1 oder<br />
0 (bei Access –1 oder 0). Um alle Fälle zu zählen, deren Schadenshöhe größer als<br />
1000 ist, müsste der Befehl so aussehen:<br />
SELECT SUM(Schadenshoehe > 1000) AS Anzahl_Schadenshoehe_gt_1000<br />
FROM Schadensfall;<br />
Dabei werden nicht etwa die Schäden aufsummiert, sondern nur das Ergebnis<br />
des Vergleichs, also 0 oder 1, im Grunde also gezählt. Die Funktion COUNT kann<br />
hier nicht genommen werden, da sie sowohl die 1 als auch die 0 zählen würde.<br />
E<strong>in</strong>ige DBMS (z. B. DB2, Oracle) haben e<strong>in</strong>e strengere Typenkontrolle; Firebird<br />
nimmt e<strong>in</strong>e Zwischenstellung e<strong>in</strong>. Dabei haben Vergleichsausdrücke grundsätzlich<br />
e<strong>in</strong> boolesches Ergebnis, das nicht summiert werden kann. Dann kann man<br />
sich mit der CASE-Konstruktion behelfen, die dem Wahrweitswert TRUE e<strong>in</strong>e 1<br />
zuordnet und dem Wert FALSE e<strong>in</strong>e 0:<br />
121
Funktionen<br />
SELECT SUM(CASE WHEN Schadenshoehe > 1000 THEN 1<br />
ELSE 0<br />
END) AS Anzahl_Schadenshoehe_gt_1000<br />
FROM Schadensfall;<br />
14.7.3. MAX, MIN – Maximum, M<strong>in</strong>imum<br />
Diese Funktionen können auf jeden Datentyp angewendet werden, für den e<strong>in</strong><br />
Vergleich e<strong>in</strong> gültiges Ergebnis liefert. Dies gilt für numerische Werte, Datumswerte<br />
und Textwerte, nicht aber für z. B. BLOBs (B<strong>in</strong>ary Large Objects). Bei Textwerten<br />
ist zu bedenken, dass die Sortierung je nach verwendetem Betriebssystem,<br />
DBMS und Zeichensatze<strong>in</strong>stellungen der Tabelle oder Spalte unterschiedlich<br />
ist, die Funktion demnach auch unterschiedliche Ergebnisse liefern kann.<br />
Aufgabe: Suche den kle<strong>in</strong>sten, von NULL verschiedenen Schadensfall.<br />
SELECT MIN(Schadenshoehe) AS M<strong>in</strong>imum_Schadenshoehe FROM Schadensfall;<br />
Kommen nur NULL-Werte vor, wird NULL zurückgegeben. Gibt es mehrere Zeilen,<br />
die den kle<strong>in</strong>sten Wert haben, wird trotzdem nur e<strong>in</strong> Wert zurückgegeben.<br />
Welche Zeile diesen Wert liefert, ist nicht def<strong>in</strong>iert.<br />
Für MAX gilt Entsprechendes wie für MIN.<br />
14.7.4. AVG – Mittelwert<br />
AVG (average = Durchschnitt) kann nur auf numerische Werte angewendet werden.<br />
Das für SUM Gesagte gilt analog auch für AVG. Um die mittlere Schadenshöhe<br />
zu berechnen, schreibt man:<br />
SELECT AVG(Schadenshoehe) AS Mittlere_Schadenshoehe FROM Schadensfall;<br />
NULL-Werte fließen dabei nicht <strong>in</strong> die Berechnung e<strong>in</strong>, Nullen aber sehr wohl.<br />
14.7.5. STDDEV – Standardabweichung<br />
Die Standardabweichung STDDEV oder STDEV kann auch nur für numerische<br />
Werte berechnet werden. NULL-Werte fließen nicht mit <strong>in</strong> die Berechnung e<strong>in</strong>,<br />
Nullen schon. Wie bei SUM können auch Berechnungen als Werte genommen<br />
werden. Die Standardabweichung der Schadensfälle wird so berechnet:<br />
122<br />
SELECT STDDEV(Schadenshoehe) AS StdAbw_Schadenshoehe FROM Schadensfall;
14.8. Zusammenfassung<br />
Zusammenfassung<br />
In diesem Kapitel lernten wir die wichtigsten „e<strong>in</strong>gebauten“ Funktionen kennen:<br />
• Für Zahlen gibt es vor allem die Operatoren, dazu die modulo-Funktionen und<br />
Möglichkeiten für Rundungen.<br />
• Für Zeichenketten gibt es vor allem das Verknüpfen und Aufteilen, dazu die<br />
Längenbestimmung und die Umwandlung <strong>in</strong> Groß- und Kle<strong>in</strong>buchstaben.<br />
• Für Datums- und Zeitwerte gibt es neben Systemfunktionen die Verwendung<br />
e<strong>in</strong>zelner Teile.<br />
• Für logische und NULL-Werte gibt es vor allem Vergleiche und Komb<strong>in</strong>ationen<br />
durch Operatoren.<br />
• Konvertierungen − implizite, CAST, CONVERT − dienen dazu, dass e<strong>in</strong> Wert<br />
des e<strong>in</strong>en Datentyps als Wert e<strong>in</strong>es anderen Typs verwendet werden kann.<br />
Mit Spaltenfunktionen werden alle Werte e<strong>in</strong>er Spalte geme<strong>in</strong>sam ausgewertet.<br />
14.9. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 125.<br />
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Feststellungen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
1. E<strong>in</strong>e Skalarfunktion bestimmt aus allen Werten e<strong>in</strong>es Feldes e<strong>in</strong>en geme<strong>in</strong>samen<br />
Wert.<br />
2. E<strong>in</strong>e Spaltenfunktion bestimmt aus allen Werten e<strong>in</strong>es Feldes e<strong>in</strong>en geme<strong>in</strong>samen<br />
Wert.<br />
3. E<strong>in</strong>e Skalarfunktion kann ke<strong>in</strong>e Werte aus den Spalten e<strong>in</strong>er Tabelle verarbeiten.<br />
4. E<strong>in</strong>e benutzerdef<strong>in</strong>ierte Funktion kann als Skalarfunktion, aber nicht als<br />
Spaltenfunktion dienen.<br />
5. Wenn e<strong>in</strong>er Funktion ke<strong>in</strong>e Argumente übergeben werden, kann auf die<br />
Klammern h<strong>in</strong>ter dem Funktionsnamen verzichtet werden.<br />
6. Wenn e<strong>in</strong>e Funktion für e<strong>in</strong>en <strong>SQL</strong>-Ausdruck benutzt wird, muss das Ergebnis<br />
der Funktion vom Datentyp her mit dem übere<strong>in</strong>stimmen, der an<br />
der betreffenden Stelle erwartet wird.<br />
123
Funktionen<br />
Übung 2 – Def<strong>in</strong>itionen<br />
Welche der folgenden Funktionen s<strong>in</strong>d Spaltenfunktionen? Welche s<strong>in</strong>d Skalarfunktionen,<br />
welche davon s<strong>in</strong>d Konvertierungen?<br />
1. Bestimme den Rest bei e<strong>in</strong>er Division ganzer Zahlen.<br />
2. Bestimme die Länge des Namens des Mitarbeiters mit der ID 13.<br />
3. Bestimme die maximale Länge aller Mitarbeiter-Namen.<br />
4. Bestimme die Gesamtlänge aller Mitarbeiter-Namen.<br />
5. Verwandle e<strong>in</strong>e Zeichenkette, die nur Ziffern enthält, <strong>in</strong> e<strong>in</strong>e ganze Zahl.<br />
6. Verwandle e<strong>in</strong>e Zeichenkette, die ke<strong>in</strong>e Ziffern enthält, <strong>in</strong> e<strong>in</strong>en Str<strong>in</strong>g, der<br />
nur Großbuchstaben enthält.<br />
7. Bestimme das aktuelle Datum.<br />
Übung 3 – Funktionen mit Zahlen<br />
Benutzen Sie für die folgenden Berechnungen die Spalte Schadenshoehe der<br />
Tabelle Schadensfall. Welche Datensätze benutzt werden, also der Inhalt der<br />
WHERE-Klausel, soll uns dabei nicht <strong>in</strong>teressieren. Auch geht es nur um die Formeln,<br />
nicht um e<strong>in</strong>en SELECT-Befehl.<br />
1. Berechnen Sie (ohne AVG-Funktion) die durchschnittliche Schadenshöhe.<br />
2. Bestimmen Sie den prozentualen Anteil e<strong>in</strong>es bestimmten Schadensfalls<br />
an der gesamten Schadenshöhe.<br />
Übung 4 – Funktionen mit Zeichenketten<br />
Schreiben Sie Name, Vorname und Abteilung der Mitarbeiter <strong>in</strong> tabellarischer<br />
Form (nehmen wir an, dass das Kuerzel der Abteilung <strong>in</strong> der Tabelle Mitarbeiter<br />
stünde); benutzen Sie dazu nache<strong>in</strong>ander die folgenden Teilaufgaben:<br />
1. Br<strong>in</strong>gen Sie die Namen auf e<strong>in</strong>e e<strong>in</strong>heitliche Länge von 20 Zeichen.<br />
2. Br<strong>in</strong>gen Sie die Namen auf e<strong>in</strong>e e<strong>in</strong>heitliche Länge von 10 Zeichen.<br />
3. Br<strong>in</strong>gen Sie die Vornamen ebenso auf e<strong>in</strong>e Länge von 10 Zeichen.<br />
4. Setzen Sie diese Teilergebnisse zusammen und fügen Sie dazwischen je<br />
zwei Leerzeichen e<strong>in</strong>.<br />
Übung 5 – Funktionen mit Datum und Zeit<br />
Gegeben ist e<strong>in</strong> Timestamp-Wert mit dem Spaltennamen Zeitstempel und dem<br />
Inhalt "16. Dezember 2009 um 19:53 Uhr". Zeigen Sie diesen Wert als Zeichenkette<br />
im Format "12/2009; 16. Tag; 7 M<strong>in</strong>uten vor 20 Uhr" an. (Für die Str<strong>in</strong>g-<br />
Verknüpfung benutzen wir jetzt das Plus-Zeichen. Die Leerzeichen zwischen den<br />
124
Übungen<br />
Bestandteilen können Sie ignorieren. Auch muss es ke<strong>in</strong>e allgeme<strong>in</strong>gültige Lösung<br />
se<strong>in</strong>, die alle Eventualitäten beachtet.)<br />
Übung 6 – Funktionen mit Datum und Zeit<br />
Gegeben ist e<strong>in</strong>e Zeichenkette datum mit dem Inhalt "16122009". S<strong>org</strong>en Sie dafür,<br />
dass das Datum für jedes DBMS gültig ist. Zusatzfrage: Muss dafür CAST verwendet<br />
werden und warum bzw. warum nicht?<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 123.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Richtig s<strong>in</strong>d 2 und 6, falsch s<strong>in</strong>d 1, 3, 4, 5.<br />
Lösung zu Übung 2 – Def<strong>in</strong>itionen<br />
Spaltenfunktionen s<strong>in</strong>d 3 und 4; beide benutzen das Ergebnis von Skalarfunktionen.<br />
Alle anderen s<strong>in</strong>d Skalarfunktionen, wobei 5 e<strong>in</strong>e Konvertierung ist und 7<br />
e<strong>in</strong>e Systemfunktion, die ohne Klammern geschrieben wird.<br />
Lösung zu Übung 3 – Funktionen mit Zahlen<br />
1. SUM(Schadenshoehe) / COUNT(Schadenshoehe)<br />
2. ROUND( Schadenshoehe * 100 / SUM(Schadenshoehe) )<br />
Lösung zu Übung 4 – Funktionen mit Zeichenketten<br />
1. CAST(Name AS CHAR(20))<br />
2. SUBSTRING( CAST(Name AS CHAR(20)) FROM 1 FOR 10 )<br />
3. SUBSTRING( CAST(Vorname AS CHAR(20)) FROM 1 FOR 10 )<br />
4. SUBSTRING( CAST(Name AS CHAR(20)) FROM 1 FOR 10 ) {{!!}} ’ ’ {{!!}}<br />
SUBSTRING( CAST(Vorname AS CHAR(20)) FROM 1 FOR 10 ) {{!!}} ’ ’ {{!!}} Kuerzel<br />
Lösung zu Übung 5 – Funktionen mit Datum und Zeit<br />
EXTRACT(MONTH FROM CURRENT_TIMESTAMP) + ’/’<br />
+ EXTRACT(YEAR FROM CURRENT_TIMESTAMP) + ’;’<br />
+ EXTRACT(DAY FROM CURRENT_TIMESTAMP) + ’.Tag; ’<br />
125
Funktionen<br />
+ CAST((60 - EXTRACT(MINUTE FROM CURRENT_TIMESTAMP)) AS Varchar(2)) + ’ M<strong>in</strong>uten vor ’<br />
+ CAST( (EXTRACT(HOUR FROM CURRENT_TIMESTAMP) + 1) AS Varchar(2)) + ’ Uhr’<br />
Lösung zu Übung 6 – Funktionen mit Datum und Zeit<br />
SUBSTRING(datum FROM 5 FOR 4) + ’-’<br />
+ SUBSTRING(datum FROM 3 FOR 2) + ’-’<br />
+ SUBSTRING(datum FROM 1 FOR 2)<br />
Auf CAST kann (fast immer) verzichtet werden, weil mit dieser Substr<strong>in</strong>g-<br />
Verwendung die Standardschreibweise '2009-12-16' nach ISO 8601 erreicht wird.<br />
14.10. Siehe auch<br />
E<strong>in</strong>ige H<strong>in</strong>weise s<strong>in</strong>d <strong>in</strong> den folgenden Kapiteln zu f<strong>in</strong>den:<br />
• <strong>SQL</strong>-Befehle 6 beschreibt auch den Begriff „<strong>SQL</strong>-Ausdruck“.<br />
• Datentypen 7<br />
Weitere Erläuterungen stehen bei Wikipedia:<br />
• Senkrechter Strich 8 oder „Pipe“-Zeichen<br />
• Boolesche Operatoren 9<br />
• ISO 8601 10 zur Schreibweise von Datumsangaben<br />
6 Kapitel 7 auf Seite 47<br />
7 Kapitel 13 auf Seite 97<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Senkrechter%20Strich<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Boolescher%20Operator<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/ISO%208601<br />
126
Teil III.<br />
Mehr zu Abfragen<br />
127
15. Ausführliche SELECT-Struktur<br />
15.1. Allgeme<strong>in</strong>e Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . 129<br />
15.2. Set Quantifier – Mengenquantifizierer . . . . . . . . . . . . . . 130<br />
15.3. Select List – Auswahlliste . . . . . . . . . . . . . . . . . . . . . . . 130<br />
15.4. Table Reference List − Tabellen-Verweise . . . . . . . . . . . . . 132<br />
15.5. WHERE-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133<br />
15.6. GROUP BY- und HAVING-Klausel . . . . . . . . . . . . . . . . . . 134<br />
15.7. UNION-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135<br />
15.8. ORDER BY-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
15.9. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
15.10. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136<br />
Dieses Kapitel erläutert die Syntax des SELECT-Befehls. Anstelle von Beispielen<br />
gibt es Verweise auf diejenigen Kapitel, die die betreffenden Klauseln genauer<br />
behandeln.<br />
Bitte beachten Sie: Der SELECT-Befehl bietet so umfangreiche Möglichkeiten,<br />
dass auch bei dieser Übersicht nicht alle E<strong>in</strong>zelheiten v<strong>org</strong>estellt werden können.<br />
15.1. Allgeme<strong>in</strong>e Syntax<br />
Der SELECT-Befehl wird als oder , also<br />
Abfrage-Ausdruck bezeichnet und setzt sich grundsätzlich aus diesen Bestandteilen<br />
zusammen:<br />
SELECT<br />
[ DISTINCT | ALL ]<br />
<br />
FROM <br />
[ ]<br />
[ ]<br />
[ ]<br />
[ UNION [ALL] ]<br />
[ ]<br />
129
Ausführliche SELECT-Struktur<br />
Diese Bestandteile setzen sich je nach Bedarf aus weiteren e<strong>in</strong>fachen oder komplexen<br />
Teilen zusammen und werden <strong>in</strong> den folgenden Abschnitten erläutert.<br />
Unbed<strong>in</strong>gt erforderlich s<strong>in</strong>d:<br />
• das Schlüsselwort SELECT<br />
• e<strong>in</strong>e Liste von Spalten<br />
• das Schlüsselwort FROM mit e<strong>in</strong>em oder mehreren Verweisen auf Tabellen<br />
Alle anderen Bestandteile s<strong>in</strong>d optional, können also auch weggelassen werden.<br />
Andernfalls müssen sie <strong>in</strong> genau der genannten Reihenfolge verwendet werden.<br />
15.2. Set Quantifier – Mengenquantifizierer<br />
Als Mengenquantifizierer stehen DISTINCT und ALL zur Verfügung. Alles Nötige<br />
wurde schon im Kapitel DML (1) – Daten abfragen 1 erwähnt.<br />
Außerdem ist es sehr oft möglich (wenn auch nicht im <strong>SQL</strong>-Standard v<strong>org</strong>eschrieben),<br />
das Ergebnis auf e<strong>in</strong>e bestimmte Anzahl von Zeilen zu beschränken,<br />
z. B. FIRST 3 oder LIMIT 7.<br />
Erläuterungen zu dieser Beschränkung stehen unter Nützliche Erweiterungen 2 .<br />
15.3. Select List – Auswahlliste<br />
Die Liste der Spalten, die ausgewählt werden sollen, wird angegeben durch den<br />
Asterisk oder mit e<strong>in</strong>er .<br />
15.3.1. Asterisk *<br />
Der Asterisk, d. h. das Sternchen *, ist e<strong>in</strong>e Kurzfassung und steht für die Liste<br />
aller Spalten (Felder) e<strong>in</strong>er e<strong>in</strong>zelnen Tabelle.<br />
Wenn mehrere Tabellen verknüpft werden, ist der Asterisk zusammen mit dem<br />
Namen oder dem Alias e<strong>in</strong>er Tabelle anzugeben.<br />
.* /* oder */<br />
.*<br />
Erläuterungen und Beispiele dazu f<strong>in</strong>den sich <strong>in</strong> allen Kapiteln, die sich mit Abfragen<br />
befassen.<br />
1 Kapitel 8 auf Seite 57<br />
2 Kapitel 23 auf Seite 213<br />
130
15.3.2. Column List – Spaltenliste<br />
Select List – Auswahlliste<br />
Die ist e<strong>in</strong>e Liste von e<strong>in</strong>em oder mehreren Elementen, die <strong>in</strong> der<br />
<strong>SQL</strong>-Dokumentation als bezeichnet werden:<br />
[ , , , ... ]<br />
Jedes e<strong>in</strong>zelne Element ist e<strong>in</strong>e Spalte e<strong>in</strong>er Tabelle oder e<strong>in</strong>e abgeleitete Spalte<br />
− siehe den nächsten Abschnitt. Jedem Element kann e<strong>in</strong><br />
Spalten-Alias zugewiesen, das Wort AS kann dabei auch weggelassen werden:<br />
[ [AS] ]<br />
Bei Spalten aus Tabellen wird e<strong>in</strong>fach deren Name angegeben. Wenn mehrere<br />
Tabellen verknüpft werden und e<strong>in</strong> Spaltenname nicht e<strong>in</strong>deutig ist, ist der Spaltenname<br />
zusammen mit dem Namen oder dem Alias e<strong>in</strong>er Tabelle anzugeben.<br />
/* oder */<br />
. /* oder */<br />
.<br />
H<strong>in</strong>weis: In manchen DBMS darf der Tabellenname nicht mehr benutzt werden,<br />
wenn e<strong>in</strong> Tabellen-Alias angegeben ist.<br />
Erläuterungen und Beispiele dazu f<strong>in</strong>den sich <strong>in</strong> allen Kapiteln, die sich mit Abfragen<br />
befassen.<br />
15.3.3. Abgeleitete Spalte<br />
E<strong>in</strong>e abgeleitete Spalte bezeichnet das Ergebnis e<strong>in</strong>es . Das kann e<strong>in</strong> beliebiger Ausdruck se<strong>in</strong>, der für jede Zeile genau<br />
e<strong>in</strong>en Wert liefert – vor allem e<strong>in</strong>e Funktion oder e<strong>in</strong>e passende Unterabfrage.<br />
Erläuterungen dazu f<strong>in</strong>den sich vor allem <strong>in</strong> den Kapiteln Berechnete Spalten 3<br />
und Unterabfragen 4 .<br />
3 Kapitel 24 auf Seite 235<br />
4 Kapitel 26 auf Seite 251<br />
131
Ausführliche SELECT-Struktur<br />
15.4. Table Reference List − Tabellen-Verweise<br />
Als Bestandteil der FROM-Klausel werden die beteiligten Tabellen und Verweise<br />
darauf aufgeführt:<br />
FROM /* nämlich */<br />
FROM [ , , ... ]<br />
In der stehen e<strong>in</strong>e oder (mit Komma getrennt) mehrere Verweise<br />
(Referenzen); diese können direkt aus der Datenbank übernommen oder auf<br />
verschiedene Arten abgeleitet werden.<br />
Jede dieser Varianten kann erweitert werden:<br />
[ [ AS ] <br />
[ ( ) ] ]<br />
Mit AS kann e<strong>in</strong> Alias-Name dem Verweis h<strong>in</strong>zugefügt werden; das Schlüsselwort<br />
AS kann auch entfallen. Diesem Tabellen-Alias kann (je nach DBMS) <strong>in</strong> Klammern<br />
e<strong>in</strong>e Liste von Spaltennamen h<strong>in</strong>zugefügt werden, die anstelle der eigentlichen<br />
Spaltennamen angezeigt werden sollen.<br />
Die folgenden Varianten s<strong>in</strong>d als Verweise möglich.<br />
Tabellen, Views: Primär verwendet man die Tabellen sowie die VIEWs (fest def<strong>in</strong>ierte<br />
Sichten).<br />
Erläuterungen dazu f<strong>in</strong>den sich <strong>in</strong> allen Kapiteln, die sich mit Abfragen<br />
befassen, sowie <strong>in</strong> Erstellen von Views 5 .<br />
Derived Table − Abgeleitete Tabellen: Vor allem können Unterabfragen wie<br />
e<strong>in</strong>e Tabelle benutzt werden.<br />
H<strong>in</strong>weis: Es gibt e<strong>in</strong>e Reihe weiterer Varianten, um andere Tabellen „vorübergehend“<br />
abzuleiten. Wegen der DBMS-Unterschiede würde die Übersicht<br />
zu kompliziert; wir verzichten deshalb auf e<strong>in</strong>e ausführlichere Darstellung.<br />
Erläuterungen dazu f<strong>in</strong>den sich im Kapitel Unterabfragen 6 .<br />
Jo<strong>in</strong>ed Table − Verknüpfte Tabelle: Wie e<strong>in</strong>e Tabelle kann auch e<strong>in</strong>e Verknüpfung<br />
verwendet werden. Dabei handelt es sich um zwei Tabellen, die nach<br />
bestimmten Bed<strong>in</strong>gungen verbunden werden.<br />
5 Kapitel 27 auf Seite 271<br />
6 Kapitel 26 auf Seite 251<br />
132
WHERE-Klausel<br />
Für die Verknüpfung zweier (oder mehrerer) Tabellen gibt es zwei Verfahren:<br />
Beim (traditionellen) direkten Weg werden die beiden Tabellen e<strong>in</strong>fach<br />
nache<strong>in</strong>ander aufgeführt. Beim (modernen) Weg wird zu e<strong>in</strong>er Tabelle<br />
e<strong>in</strong>e weitere mit JOIN genannt, die durch e<strong>in</strong>e Verknüpfungsbed<strong>in</strong>gung<br />
über ON verbunden wird.<br />
Erläuterungen dazu f<strong>in</strong>den sich ab dem Kapitel Mehrere Tabellen 7 .<br />
15.5. WHERE-Klausel<br />
Mit der WHERE-Klausel wird e<strong>in</strong>e Suchbed<strong>in</strong>gung festgelegt, welche Zeilen der<br />
Ergebnistabelle zur Auswahl gehören sollen. Dieser Filter wird zuerst erstellt; erst<br />
anschließend werden Bed<strong>in</strong>gungen wie GROUP BY und ORDER BY ausgewertet.<br />
WHERE <br />
Die Suchbed<strong>in</strong>gung ist e<strong>in</strong>e Konstruktion mit e<strong>in</strong>em e<strong>in</strong>deutigen<br />
Prüfergebnis: Entweder die Zeile gehört zur Auswahl, oder sie gehört nicht<br />
zur Auswahl. Es handelt sich also um e<strong>in</strong>e logische Verknüpfung von e<strong>in</strong>er oder<br />
mehreren booleschen Variablen.<br />
Erläuterungen zu den folgenden E<strong>in</strong>zelheiten f<strong>in</strong>den sich vor allem <strong>in</strong> den Kapiteln<br />
WHERE-Klausel im Detail 8 und Unterabfragen 9 .<br />
15.5.1. E<strong>in</strong>e e<strong>in</strong>zelne Suchbed<strong>in</strong>gung<br />
E<strong>in</strong>e Suchbed<strong>in</strong>gung hat e<strong>in</strong>e der folgenden Formen, deren Ergebnis immer<br />
WAHR oder FALSCH (TRUE bzw. FALSE) lautet:<br />
[ NOT ] <br />
[ NOT ] BETWEEN AND <br />
[ NOT ] LIKE [ ESCAPE ]<br />
[ NOT ] CONTAINING <br />
[ NOT ] STARTING [ WITH ] <br />
IS [ NOT ] NULL<br />
[ NOT ] IN <br />
[ NOT ] EXISTS <br />
NOT <br />
7 Kapitel 18 auf Seite 167<br />
8 Kapitel 17 auf Seite 155<br />
9 Kapitel 26 auf Seite 251<br />
133
Ausführliche SELECT-Struktur<br />
Anstelle e<strong>in</strong>es Wertes kann auch e<strong>in</strong> Wertausdruck stehen,<br />
also e<strong>in</strong>e Unterabfrage, die genau e<strong>in</strong>en Wert als Ergebnis liefert.<br />
Anstelle e<strong>in</strong>er Werteliste kann auch e<strong>in</strong> Auswahl-Ausdruck <br />
stehen, also e<strong>in</strong>e Unterabfrage, die e<strong>in</strong>e Liste mehrerer Werte als Ergebnis liefert.<br />
Als s<strong>in</strong>d folgende Vergleiche möglich, und zwar sowohl für Zahlen als<br />
auch für Zeichenketten und Datumsangaben:<br />
= < > = <br />
Wenn das Prüfergebnis „umgekehrt“ werden soll – statt TRUE soll FALSE und<br />
statt FALSE soll TRUE gelten –, hat man mehrere Möglichkeiten:<br />
• Man ersetzt den Vergleichsoperator: Statt nach Gleichheit wird nach Ungleichheit<br />
geprüft usw.<br />
• Man setzt NOT vor den Operator, wie <strong>in</strong> der Übersicht bei jeder e<strong>in</strong>zelnen<br />
Suchbed<strong>in</strong>gung notiert. Dies bewirkt die Umkehrung des Operators und dadurch<br />
des Prüfergebnisses.<br />
• Man setzt NOT vor e<strong>in</strong>e Suchbed<strong>in</strong>gung, wie <strong>in</strong> der Übersicht bei der letzten<br />
Suchbed<strong>in</strong>gung notiert. Dies bewirkt die Umkehrung des Prüfergebnisses.<br />
15.5.2. Mehrere Suchbed<strong>in</strong>gungen<br />
Mehrere Suchbed<strong>in</strong>gungen können mite<strong>in</strong>ander verbunden werden:<br />
NOT <br />
AND <br />
OR <br />
Bitte beachten Sie: NOT hat die stärkste Verb<strong>in</strong>dung und wird zuerst ausgewertet.<br />
Danach hat AND e<strong>in</strong>e stärkere Verb<strong>in</strong>dung und wird als nächstes untersucht.<br />
Erst zum Schluss kommen die OR-Verb<strong>in</strong>dungen. Um Unklarheiten zu vermeiden,<br />
wird dr<strong>in</strong>gend empfohlen, zusammengesetzte Suchbed<strong>in</strong>gungen <strong>in</strong> Klammern<br />
zu setzen, um Prioritäten deutlich zu machen.<br />
15.6. GROUP BY- und HAVING-Klausel<br />
Mit der GROUP BY-Klausel werden alle Zeilen, die <strong>in</strong> e<strong>in</strong>er oder mehreren Spalten<br />
den gleichen Wert enthalten, <strong>in</strong> jeweils e<strong>in</strong>er Gruppe zusammengefasst. Dies<br />
macht <strong>in</strong> der Regel nur dann S<strong>in</strong>n, wenn <strong>in</strong> der Spaltenliste des SELECT-Befehls<br />
e<strong>in</strong>e gruppenweise Auswertung, also e<strong>in</strong>e der Spaltenfunktionen enthalten ist.<br />
134
Die allgeme<strong>in</strong>e Syntax lautet:<br />
GROUP BY <br />
UNION-Klausel<br />
Die Spaltenliste enthält, durch Komma getrennt, die Namen von e<strong>in</strong>er oder mehreren<br />
Spalten. Bei jeder Spalte kann e<strong>in</strong>e eigene Sortierung angegeben werden<br />
(wie bei den Datentypen 10 erläutert):<br />
<br />
-- oder<br />
COLLATE <br />
Die HAVING-Klausel dient dazu, nicht alle ausgewählten Zeilen <strong>in</strong> die Ausgabe<br />
zu übernehmen, sondern nur diejenigen, die den zusätzlichen Bed<strong>in</strong>gungen<br />
entsprechen. Sie wird <strong>in</strong> der Praxis überwiegend als Ergänzung zur GROUP BY-<br />
Klausel verwendet und folgt ggf. direkt danach.<br />
HAVING <br />
Erläuterungen dazu f<strong>in</strong>den sich vor allem im Kapitel Gruppierungen 11 .<br />
15.7. UNION-Klausel<br />
Mit der UNION-Klausel werden mehrere eigentlich getrennte Abfragen zusammengefasst,<br />
um e<strong>in</strong> e<strong>in</strong>heitliches Ergebnis zu liefern. Dabei s<strong>in</strong>d die e<strong>in</strong>zelnen<br />
Bed<strong>in</strong>gungen zu komplex, um sie zusammenzufassen; oder sie können nicht<br />
s<strong>in</strong>nvoll verbunden werden. Es setzt e<strong>in</strong>e weitgehend identische Spaltenliste<br />
voraus.<br />
SELECT FROM WHERE <br />
UNION<br />
SELECT FROM WHERE <br />
Erläuterungen dazu f<strong>in</strong>den sich im Kapitel Nützliche Erweiterungen 12 .<br />
10 Kapitel 13 auf Seite 97<br />
11 Kapitel 25 auf Seite 241<br />
12 Kapitel 23 auf Seite 213<br />
135
Ausführliche SELECT-Struktur<br />
15.8. ORDER BY-Klausel<br />
Mit der ORDER BY-Klausel werden die Datensätze der Ergebnismenge <strong>in</strong> e<strong>in</strong>er<br />
bestimmten Sortierung ausgegeben.<br />
Erläuterungen und Beispiele dazu f<strong>in</strong>den sich <strong>in</strong> allen Kapiteln, die sich mit Abfragen<br />
befassen.<br />
15.9. Zusammenfassung<br />
In diesem Kapitel erhielten Sie e<strong>in</strong>en umfangreichen Überblick über die Syntax<br />
des SELECT-Befehls:<br />
• Die Listen der gewünschten Spalten und der beteiligten Tabellen s<strong>in</strong>d Pflichtangaben,<br />
alle anderen Klauseln s<strong>in</strong>d optional.<br />
• Für die Verknüpfung mehrerer Tabellen gibt es e<strong>in</strong>en (traditionellen) direkten<br />
Weg und den (modernen) Weg über JOIN.<br />
• Die WHERE-Klausel ermöglicht komplexe Bed<strong>in</strong>gungen darüber, welche Datensätze<br />
abgefragt werden sollen.<br />
• Mit Gruppierung, Sortierung und Zusammenfassung gibt es weitere Möglichkeiten<br />
für Abfragen.<br />
15.10. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 138.<br />
Übung 1 – Allgeme<strong>in</strong>e Syntax<br />
Br<strong>in</strong>gen Sie die folgenden Bestandteile des SELECT-Befehls <strong>in</strong> die richtige Reihenfolge<br />
(es gibt Begriffe, die an zwei bzw. drei Stellen gehören):<br />
− DISTINCT − FROM − GROUP BY − HAVING −<br />
ORDER BY − SELECT − − − WHERE<br />
Übung 2 – Allgeme<strong>in</strong>e Syntax<br />
Welche der genannten Bestandteile e<strong>in</strong>es SELECT-Befehls s<strong>in</strong>d unbed<strong>in</strong>gt erforderlich?<br />
136
Übung 3 – Spaltenliste<br />
Übungen<br />
Welche der folgenden Spaltenlisten aus der Beispieldatenbank s<strong>in</strong>d richtig, welche<br />
nicht?<br />
1. SELECT * FROM Mitarbeiter;<br />
2. SELECT ID, Name FROM Mitarbeiter;<br />
3. SELECT ID, Name FROM Mitarbeiter, Abteilung;<br />
4. SELECT ID, Name, Kuerzel FROM Mitarbeiter, Abteilung;<br />
5. SELECT ab.ID, Name FROM Mitarbeiter, Abteilung ab;<br />
6. SELECT ab.ID, Name, Krz Kuerzel FROM Mitarbeiter, Abteilung ab;<br />
7. SELECT ab.ID, Name, Kuerzel Krz FROM Mitarbeiter, Abteilung ab;<br />
8. SELECT ab.ID, mi.Name, ab.Kuerzel FROM Mitarbeiter mi, Abteilung<br />
ab;<br />
Übung 4 – Spaltenliste<br />
Schreiben Sie für folgende Abfragen die Spalten und Tabellen auf.<br />
1. Zeige alle Informationen zu den Mitarbeitern.<br />
2. Zeige zu jedem Mitarbeiter Name, Vorname und Nummer der Abteilung.<br />
3. Zeige zu jedem Mitarbeiter Name, Vorname und Kuerzel der Abteilung.<br />
4. Zeige zu jedem Mitarbeiter ID, Name, Vorname sowie das Kennzeichen des<br />
Dienstwagens.<br />
Übung 5 – Suchbed<strong>in</strong>gungen<br />
Welche der folgenden Suchbed<strong>in</strong>gungen s<strong>in</strong>d richtig, welche nicht? Welche korrekten<br />
Bed<strong>in</strong>gungen liefern immer FALSE als Ergebnis?<br />
1. WHERE Name NOT = ’Meyer’;<br />
2. WHERE 1 = 2;<br />
3. WHERE NOT Name LIKE ’M%’;<br />
4. WHERE Geburtsdatum LIKE ’1980’;<br />
5. WHERE ID BETWEEN 20 AND 10;<br />
6. WHERE Mobil IS NULL;<br />
7. WHERE Name IS NULL;<br />
8. WHERE Name STARTING WITH ’L’ AND CONTAINING ’a’;<br />
9. WHERE ID IN (1, 3, ’A’);<br />
10. WHERE ID IN (1, 3, ’5’);<br />
137
Ausführliche SELECT-Struktur<br />
Übung 6 – Suchbed<strong>in</strong>gungen<br />
Formulieren Sie die folgenden Aussagen als Bed<strong>in</strong>gungen der WHERE-Klausel<br />
zur Tabelle Mitarbeiter.<br />
1. Der Vorname lautet 'Petra'.<br />
2. Der Name enthält die Zeichen 'mann'.<br />
3. Der Name beg<strong>in</strong>nt mit 'A', es handelt sich um Abteilung 8.<br />
4. Es ist ke<strong>in</strong>e Mobil-Nummer gespeichert.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 136.<br />
Lösung zu Übung 1 – Allgeme<strong>in</strong>e Syntax<br />
Richtig ist dies (mit Vervollständigung zu e<strong>in</strong>em Befehl):<br />
SELECT DISTINCT <br />
FROM <br />
WHERE <br />
GROUP BY <br />
HAVING <br />
ORDER BY ;<br />
Lösung zu Übung 2 – Allgeme<strong>in</strong>e Syntax<br />
SELECT FROM <br />
Lösung zu Übung 3 – Spaltenliste<br />
Richtig s<strong>in</strong>d 1, 2, 5, 7, 8.<br />
Falsch s<strong>in</strong>d 3, 4 (ID ist mehrdeutig), 6 (Spaltenname und -Alias <strong>in</strong> falscher Reihenfolge).<br />
Lösung zu Übung 4 – Spaltenliste<br />
138<br />
1. SELECT * FROM Mitarbeiter<br />
2. SELECT Name, Vorname, Abteilung_ID FROM Mitarbeiter<br />
3. SELECT Name, Vorname, Kuerzel FROM Mitarbeiter, Abteilung<br />
/* oder mit Tabellen-Alias: */
SELECT mi.Name, mi.Vorname, ab.Kuerzel FROM Mitarbeiter mi,<br />
Abteilung ab<br />
4. SELECT Name, Vorname, Dienstwagen.ID, Kennzeichen FROM<br />
Mitarbeiter, Dienstwagen<br />
/* auch e<strong>in</strong>heitlich mit Tabellen-Namen oder Tabellen-Alias<br />
möglich */<br />
Lösung zu Übung 5 – Suchbed<strong>in</strong>gungen<br />
1. Richtig.<br />
2. Richtig, immer FALSE: 1 ist immer ungleich 2.<br />
3. Falsch, das NOT gehört h<strong>in</strong>ter , also h<strong>in</strong>ter Name.<br />
4. Richtig, weil das Jahr laut ISO 8601 am Anfang steht.<br />
Übungen<br />
5. Richtig, immer FALSE: es gibt ke<strong>in</strong>e Zahl „größer/gleich 20“ und gleichzeitig<br />
„kle<strong>in</strong>er/gleich 10“.<br />
6. Richtig.<br />
7. Richtig, immer FALSE, weil der Name als Pflichtangabe niemals NULL se<strong>in</strong><br />
kann.<br />
8. Falsch, weil Name <strong>in</strong> der zweiten Bed<strong>in</strong>gung h<strong>in</strong>ter AND fehlt.<br />
9. Falsch, weil 'A' ke<strong>in</strong>e Zahl ist, aber zu ID bei IN e<strong>in</strong>e Liste von Zahlen gehört.<br />
10. Richtig, weil '5' automatisch als Zahl konvertiert wird.<br />
Lösung zu Übung 6 – Suchbed<strong>in</strong>gungen<br />
1. WHERE Vorname = ’Petra’;<br />
2. WHERE Name CONTAINING ’mann’;<br />
3. WHERE Name STARTING WITH ’A’ AND Abteilung_ID = 8;<br />
4. WHERE Mobil IS NULL;<br />
139
16. Funktionen (2)<br />
16.1. Funktionen für Zahlen . . . . . . . . . . . . . . . . . . . . . . . . 141<br />
16.2. Funktionen für Zeichenketten . . . . . . . . . . . . . . . . . . . 144<br />
16.3. Funktionen für Datums- und Zeitwerte . . . . . . . . . . . . . . 146<br />
16.4. Funktionen für logische und NULL-Werte . . . . . . . . . . . . 148<br />
16.5. Verschiedene Funktionen . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150<br />
16.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154<br />
Dieses Kapitel behandelt weitere Skalarfunktionen <strong>in</strong> Ergänzung zu den grundlegenden<br />
Funktionen 1 . Auch hier gelten die dort aufgeführten H<strong>in</strong>weise:<br />
• Jedes DBMS bietet eigene Funktionen sowie Varianten.<br />
• Die Klammern werden <strong>in</strong> den Beschreibungen oft nicht angegeben.<br />
• Die Beispiele benutzen e<strong>in</strong>e verkürzte Schreibweise, wobei der Zusatz [from<br />
fiktiv] als optional gekennzeichnet ist; für Firebird/Interbase ist er durch<br />
[from rdb$database] und für Oracle durch [from dual] zu ersetzen.<br />
SELECT 2 * 3 [from fiktiv];<br />
16.1. Funktionen für Zahlen<br />
Auch bei diesen weiteren Funktionen müssen Sie auf den genauen Datentyp<br />
(ganze Zahl oder Dezimalzahl und den Größenbereich) achten.<br />
POWER und SQRT – Potenzen und Wurzeln<br />
Mit POWER wird e<strong>in</strong>e beliebige Potenz oder Wurzel berechnet:<br />
POWER( , )<br />
1 Kapitel 14 auf Seite 109<br />
141
Funktionen (2)<br />
Sowohl für als auch für s<strong>in</strong>d nicht nur ganze positive Zahlen,<br />
sondern alle Zahlen zulässig. Mit Dezimalzahlen als werden (genau<br />
nach mathematischen Regeln) beliebige Wurzeln berechnet; <strong>in</strong> diesem Fall s<strong>in</strong>d<br />
als negative Zahlen unzulässig. Beispiele:<br />
SELECT POWER( 5, 3 ) [from fiktiv]; /* 125,000 */<br />
SELECT POWER( 5, 2.5) [from fiktiv]; /* 55,902 */<br />
SELECT POWER( 5, 0.5) [from fiktiv]; /* 2,236 also Wurzel aus 5 */<br />
SELECT POWER( 0.5, -3 ) [from fiktiv]; /* 8,000 also 3.Potenz zu 2 */<br />
SELECT POWER( 12.35, 1.5) [from fiktiv]; /* 43,401 */<br />
SELECT POWER(-12.35, 1.5) [from fiktiv]; /* expression evaluation not supported */<br />
SELECT POWER( 12.35,-1.5) [from fiktiv]; /* 0,023 */<br />
SELECT POWER(-12.35,-1.5) [from fiktiv]; /* expression evaluation not supported */<br />
Mit SQRT (= Square Root) gibt es für die Quadratwurzel e<strong>in</strong>e kürzere Schreibweise<br />
anstelle von POWER(x,0.5):<br />
SELECT SQRT(12.25) [from fiktiv]; /* 3,500 */<br />
EXP und LOG – Exponentialfunktion und Logarithmen<br />
Mit EXP wird die Exponentialfunktion im engeren S<strong>in</strong>ne bezeichnet, also mit der<br />
Eulerschen Zahl e als Basis.<br />
SELECT EXP(1) [from fiktiv]; /* 2,71828182845905 */<br />
Mit LOG(, ) wird umgekehrt e<strong>in</strong> Logarithmus bestimmt, mit LN<br />
der natürliche und mit LOG10 der dekadische Logarithmus.<br />
SELECT LOG(10, EXP(1)) [from fiktiv]; /* 2,30258509299405 */<br />
SELECT LOG(10, 10) [from fiktiv]; /* 1,000 */<br />
SELECT LN(10) [from fiktiv]; /* 2,30258509299405 */<br />
W<strong>in</strong>kelfunktionen<br />
Die trigonometrischen Funktionen arbeiten mit dem Bogenmaß (nicht mit e<strong>in</strong>er<br />
Grad-Angabe).<br />
SIN S<strong>in</strong>us<br />
COS Cos<strong>in</strong>us<br />
TAN Tangens<br />
COT Cotangens<br />
ASIN Arcuss<strong>in</strong>us als Umkehrfunktion des S<strong>in</strong>us<br />
142
ACOS Arcuscos<strong>in</strong>us als Umkehrfunktion des Cos<strong>in</strong>us<br />
ATAN Arcustangens als Umkehrfunktion des Tangens<br />
Funktionen für Zahlen<br />
Mit DEGREES wird e<strong>in</strong> Bogenmaß <strong>in</strong> Grad umgerechnet, mit RADIANS e<strong>in</strong> Gradmaß<br />
<strong>in</strong> das Bogenmaß.<br />
PI liefert die entsprechende Zahl und kann auch für die trigonometrischen Funktionen<br />
verwendet werden:<br />
SELECT SIN( PI()/6 ) [from fiktiv]; /* π/6 s<strong>in</strong>d 30°, also Ergebnis 0,5 */<br />
ABS, RAND, SIGN – verschiedene Funktionen<br />
Mit ABS wird der absolute Betrag der gegebenen Zahl zurückgegeben.<br />
SIGN liefert als H<strong>in</strong>weis auf das Vorzeichen der gegebenen Zahl e<strong>in</strong>en der Werte<br />
1, 0, –1 − je nachdem, ob die gegebene Zahl positiv, 0 oder negativ ist.<br />
SELECT SIGN(12.34), SIGN(0), SIGN(-5.67) [from fiktiv];<br />
/* Ergebnis: 1 0 -1 */<br />
RAND liefert e<strong>in</strong>e Zufallszahl im Bereich zwischen 0 und 1 (jeweils e<strong>in</strong>schließlich).<br />
Dies s<strong>in</strong>d aber ke<strong>in</strong>e echten Zufallszahlen, sondern Pseudozufallszahlen.<br />
Mit RAND() wird <strong>in</strong>nerhalb e<strong>in</strong>er Sitzung immer dieselbe Zufallszahl<br />
erzeugt.<br />
Mit e<strong>in</strong>er Komb<strong>in</strong>ation von RAND und FLOOR erhält man e<strong>in</strong>e „zufällige“ Folge<br />
ganzer Zahlen:<br />
FLOOR( + ( RAND() * - + 1 ) )<br />
Beispielsweise liefert die mehrfache Wiederholung der folgenden Abfrage diese<br />
Zahlen zwischen 7 und 12:<br />
SELECT FLOOR(7 + (RAND() * 6)) [from fiktiv];<br />
/* Ergebnisse: 10 9 9 7 8 7 9 9 10 12 usw. */<br />
Diese Funktion ist geeignet, um Datensätze mit SELECT <strong>in</strong> beliebiger Sortierung<br />
oder e<strong>in</strong>en zufällig ausgewählten Datensatz abzurufen:<br />
SELECT * FROM ORDER BY RAND();<br />
SELECT FIRST 1 * FROM ORDER BY RAND();<br />
143
Funktionen (2)<br />
16.2. Funktionen für Zeichenketten<br />
Auch zur Bearbeitung und Prüfung von Str<strong>in</strong>gs gibt es weitere Funktionen.<br />
Verknüpfen von Str<strong>in</strong>gs<br />
Zu den Standardverfahren || + CONCAT gibt es Ergänzungen.<br />
My<strong>SQL</strong> bietet mit CONCAT_WS e<strong>in</strong>e nützliche Erweiterung, bei der zwischen<br />
den Teiltexten e<strong>in</strong> Trennzeichen gesetzt wird.<br />
SPACE(n) − für MS-<strong>SQL</strong> und My<strong>SQL</strong> − erzeugt e<strong>in</strong>en Str<strong>in</strong>g, der aus n Leerzeichen<br />
besteht.<br />
REPEAT( , ) bei My<strong>SQL</strong> und REPLICATE( , ) bei MS-<strong>SQL</strong><br />
erzeugen e<strong>in</strong>e Zeichenkette, <strong>in</strong> der der n-mal wiederholt wird.<br />
Mit LPAD wird , sofern erforderlich, auf die gewünschte gebracht<br />
und dabei von l<strong>in</strong>ks mit bzw. Leerzeichen aufgefüllt. Mit RPAD wird von<br />
rechts aufgefüllt. MS-<strong>SQL</strong> kennt diese Funktionen nur für Access.<br />
LPAD ( , [ , ] )<br />
RPAD ( , [ , ] )<br />
Wenn der dadurch erzeugte Text zu lang wird, wird zuerst und notfalls<br />
auch abgeschnitten. Das erste Beispiel verwirrt zunächst:<br />
SELECT LPAD( CAST(12345 AS CHAR(8)), 10, ’0’) [from fiktiv]; -- also: ’0012345 ’<br />
Nanu, das s<strong>in</strong>d doch nur 7 statt 10 Ziffern? Ach so, zuerst wird mit CAST e<strong>in</strong> 8<br />
Zeichen langer Str<strong>in</strong>g erzeugt; dann ist nur noch Platz für 2 Nullen. Also muss es<br />
mit e<strong>in</strong>er dieser Varianten gehen:<br />
SELECT LPAD( CAST(12345 AS CHAR(5)), 10, ’0’) [from fiktiv]; -- also: ’0000012345’<br />
SELECT LPAD( 12345, 10, ’0’ ) [from fiktiv]; -- also: ’0000012345’<br />
Noch e<strong>in</strong> paar Beispiele:<br />
SELECT LPAD( ’Hilfe’, 10, ’-_/’) [from fiktiv]; -- also: ’-_/-_Hilfe’<br />
SELECT LPAD( ’Ich brauche Hilfe’, 10, ’-_/’) [from fiktiv]; -- also: ’Ich brauch’<br />
SELECT RPAD( ’Hilfe’, 10, ’-_/’) [from fiktiv]; -- also: ’Hilfe-_/-_’<br />
SELECT RPAD( ’Ich brauche Hilfe’, 10, ’-_/’) [from fiktiv]; -- also: ’Ich brauch’<br />
144
LEFT, RIGHT – Teile von Zeichenketten<br />
Funktionen für Zeichenketten<br />
Als Ergänzung zu SUBSTRING wird mit LEFT( , ) der l<strong>in</strong>ke Teil,<br />
also der Anfang e<strong>in</strong>es Textes mit der Länge ausgegeben. Ebenso erhält<br />
man mit RIGHT( , ) den rechten Teil, also das Ende e<strong>in</strong>es Textes.<br />
SELECT LEFT (’Abc Def Ghi’, 5) [from fiktiv]; /* Ergebnis: ’Abc D’ */<br />
SELECT RIGHT(’Abc Def Ghi’, 5) [from fiktiv]; /* Ergebnis: ’f Ghi’ */<br />
TRIM, LTRIM, RTRIM – Leerzeichen u. a. entfernen<br />
Mit der TRIM-Funktion werden bestimmte Zeichen − meistens Leerzeichen −<br />
am Anfang und/oder am Ende e<strong>in</strong>es Textes entfernt:<br />
TRIM( [ [ LEADING | TRAILING | BOTH ] [ ] FROM ] )<br />
Die Parameter werden wie folgt benutzt:<br />
• Es soll entfernt werden. Es kann sich um e<strong>in</strong> e<strong>in</strong>zelnes Zeichen, aber<br />
auch um e<strong>in</strong>en Text handeln.<br />
• Ohne diesen Parameter wird immer nach Leerzeichen gesucht.<br />
• Mit LEADING werden nur führende Zeichen geprüft, bei TRAILING nachfolgende<br />
und bei BOTH sowohl als auch. Ohne Angabe wird BOTH angenommen.<br />
Beispiele:<br />
SELECT TRIM( ’ Dies ist e<strong>in</strong> Text. ’ ) [from fiktiv];<br />
/* Ergebnis: ’Dies ist e<strong>in</strong> Text.’ */<br />
SELECT TRIM( LEADING ’a’ FROM ’abcde’ ) [from fiktiv]; /* Ergebnis: ’bcde’ */<br />
SELECT TRIM( TRAILING ’e’ FROM ’abcde’ ) [from fiktiv]; /* Ergebnis: ’abcd’ */<br />
SELECT TRIM( ’Test’ FROM ’Test als Test’) [from fiktiv]; /* Ergebnis: ’ als ’ */<br />
LTRIM (= Left-Trim) und RTRIM (= Right-Trim) s<strong>in</strong>d Kurzfassungen, bei denen<br />
Leerzeichen am Anfang bzw. am Ende entfernt werden; MS-<strong>SQL</strong> kennt nur diese<br />
beiden Kurzfassungen.<br />
Suchen und Ersetzen<br />
Mit POSITION wird der Anfang e<strong>in</strong>es Textes <strong>in</strong>nerhalb e<strong>in</strong>es anderen gesucht.<br />
POSITION( IN ) <strong>SQL</strong>-Standard<br />
POSITION( , [, ] ) nicht bei My<strong>SQL</strong><br />
LOCATE ( , [, ] ) bei My<strong>SQL</strong><br />
145
Funktionen (2)<br />
Die Bedeutung der Parameter dürfte offensichtlich se<strong>in</strong>:<br />
• ist der Text, <strong>in</strong> dem gesucht werden soll.<br />
• ist e<strong>in</strong> Text, der als Teil <strong>in</strong> gesucht wird.<br />
• Sofern angegeben ist, wird erst ab dieser Position <strong>in</strong>nerhalb <br />
gesucht. Wenn fehlt, wird ab Position 1 gesucht.<br />
• Die Funktion gibt die Startposition von <strong>in</strong>nerhalb von an.<br />
Wenn nicht gefunden wird, lautet das Ergebnis 0.<br />
Beispiele:<br />
SELECT POSITION( ’ch’, ’Ich suche Text’ ) [from fiktiv]; /* Ergebnis: 2 */<br />
SELECT POSITION( ’ch’, ’Ich suche Text’, 3 ) [from fiktiv]; /* Ergebnis: 7 */<br />
SELECT POSITION(’sch’, ’Ich suche Text’ ) [from fiktiv]; /* Ergebnis: 0 */<br />
Bei MS-<strong>SQL</strong> gibt es diese Funktionen nicht; stattdessen kann (mit abweichender<br />
Bedeutung) PATINDEX verwendet werden. Oracle bietet zusätzlich INSTR<br />
(= "<strong>in</strong> str<strong>in</strong>g") an.<br />
REPLACE dient zum Ersetzen e<strong>in</strong>es Teiltextes durch e<strong>in</strong>en anderen <strong>in</strong>nerhalb<br />
e<strong>in</strong>es Gesamttextes:<br />
REPLACE( , , )<br />
Die verschiedenen <strong>SQL</strong>-Dialekte verhalten sich unterschiedlich, ob NULL-Werte<br />
oder leere Zeichenketten zulässig s<strong>in</strong>d.<br />
SELECT REPLACE(’Ich suche Text’, ’ch’, ’sch’) [from fiktiv];<br />
/* Ergebnis: ’Isch susche Text’ */<br />
SELECT REPLACE(’Die liebe Seele’, ’e’, ’’) [from fiktiv];<br />
/* Ergebnis: ’Di lib Sl’ */<br />
REVERSE passt zwar nicht zu dem, was man <strong>in</strong> diesem Zusammenhang erwartet;<br />
aber auch diese Funktion ändert e<strong>in</strong>en vorhandenen Str<strong>in</strong>g, und zwar dadurch,<br />
dass die Reihenfolge aller Zeichen umgekehrt wird:<br />
SELECT REVERSE( ’Hilfe’ ) [from fiktiv]; /* Ergebnis: ’efliH’ */<br />
16.3. Funktionen für Datums- und Zeitwerte<br />
Bitte beachten Sie wiederum, dass die Datentypen je nach DBMS verschieden<br />
def<strong>in</strong>iert s<strong>in</strong>d und sich deshalb unterschiedlich verhalten.<br />
146
Differenzen bei Datum oder Uhrzeit<br />
Funktionen für Datums- und Zeitwerte<br />
Dafür gibt es vor allem die DATEDIFF-Funktion <strong>in</strong> unterschiedlicher Version:<br />
DATEDIFF ( , , ) /* bei MS-<strong>SQL</strong> oder Firebird */<br />
DATEDIFF ( , ) /* bei My<strong>SQL</strong> nur die Anzahl der Tage */<br />
Das Ergebnis ist vom gleichen Typ wie die gesuchte Differenz (also meistens e<strong>in</strong><br />
ganzzahliger Wert). Als gibt es die bekannten Varianten:<br />
YEAR | MONTH | DAY | HOUR | MINUTE | SECOND | MILLISECOND<br />
Beim Vergleich von Start- und Enddatum gilt:<br />
• Die Differenz ist positiv, wenn der zweite Wert später liegt als der erste.<br />
• Die Differenz ist 0, wenn beide Werte gleich s<strong>in</strong>d.<br />
• Die Differenz ist negativ, wenn der zweite Wert früher liegt als der erste.<br />
Bitte beachten Sie: Die DBMS verhalten sich unterschiedlich, ob die Datumsangaben<br />
verglichen werden oder der jeweilige Bestandteil. Beispielsweise kann das<br />
Ergebnis für beide der folgenden Prüfungen 1 lauten, obwohl die „echte“ Differenz<br />
im e<strong>in</strong>en Fall e<strong>in</strong> Tag, im anderen fast zwei Jahre s<strong>in</strong>d:<br />
DATEDIFF( YEAR, ’31.12.2008’, ’01.01.2009’ )<br />
DATEDIFF( YEAR, ’01.01.2008’, ’31.12.2009’ )<br />
Aufgabe: Bestimme die Anzahl der Tage seit dem letzten Schadensfall.<br />
SELECT DATEDIFF(DAY, MAX(Datum), CURRENT_DATE)<br />
FROM Schadensfall; /* Ergebnis: 49 */<br />
Aufgabe: Bestimme die Anzahl der M<strong>in</strong>uten seit Tagesbeg<strong>in</strong>n.<br />
SELECT DATEDIFF(MINUTE, CAST(’00:00’ AS TIME), CURRENT_TIME) [from fiktiv];<br />
/* Ergebnis: 967 */<br />
Datumsangaben können grundsätzlich auch per Subtraktion verglichen werden,<br />
weil „<strong>in</strong>tern“ häufig e<strong>in</strong> Tag gleich 1 ist. Darauf kann man sich aber nicht immer<br />
verlassen; und es ist schwierig, die Bruchteile e<strong>in</strong>es Tages zu berücksichtigen.<br />
Beispiel:<br />
SELECT (CURRENT_DATE - MAX(Datum)) FROM Schadensfall; /* Ergebnis: 49 */<br />
147
Funktionen (2)<br />
Werte für Datum oder Uhrzeit ändern<br />
Sehr häufig muss aus e<strong>in</strong>em vorhandenen Datum oder Uhrzeit e<strong>in</strong> neuer Wert<br />
berechnet werden. Der <strong>SQL</strong>-Standard sieht dazu die direkte Addition und Subtraktion<br />
vor:<br />
+ <br />
- <br />
+ <br />
steht für den gegebenen Wert, für den Zeitraum, der addiert<br />
oder subtrahiert werden soll.<br />
Aufgabe: Aus dem aktuellen Zeitwert '19.09.2009 16:10' wird e<strong>in</strong> neuer Wert<br />
bestimmt, der e<strong>in</strong>en halben Tag <strong>in</strong> der Zukunft liegt:<br />
SELECT CURRENT_TIMESTAMP + 0.5 [from fiktiv]; /* Ergebnis: ’20.09.2009 04:10:39’ */<br />
My<strong>SQL</strong> akzeptiert nur ganze Zahlen; deshalb ist explizit die Art des Intervalls anzugeben<br />
(siehe Dokumentation).<br />
Da das Umrechnen, also die Konvertierung von Zahlen <strong>in</strong> Datums- und Zeitwerte<br />
und umgekehrt für den Anwender umständlich ist, werden viele zusätzliche<br />
Funktionen bereitgestellt. Sehr verbreitet ist DATEADD:<br />
DATEADD( , , ) /* Firebird, MS-<strong>SQL</strong> */<br />
DATE_ADD( , INTERVAL ) /* My<strong>SQL</strong> */<br />
Aufgabe: Welche Versicherungsverträge laufen schon mehr als 10 Jahre?<br />
SELECT ID, Vertragsnummer, Abschlussdatum FROM Versicherungsvertrag<br />
WHERE DATEADD(YEAR, 10, Abschlussdatum)
COALESCE – Suche Wert ungleich NULL<br />
Funktionen für logische und NULL-Werte<br />
Die COALESCE-Funktion sucht <strong>in</strong> e<strong>in</strong>er Liste von Werten (bzw. Ausdrücken) den<br />
ersten, der nicht NULL ist. Wenn alle Werte NULL s<strong>in</strong>d, ist das Ergebnis zwangsläufig<br />
NULL.<br />
Aufgabe: Nenne zu jedem Mitarbeiter e<strong>in</strong>e Kontaktmöglichkeit: vorzugsweise<br />
Mobilnummer, dann Telefonnummer, dann Email-Adresse.<br />
SELECT Name, Vorname, COALESCE(Mobil, Telefon, Email) AS Kontakt FROM Mitarbeiter;<br />
Das Ergebnis überrascht zunächst, denn e<strong>in</strong>ige Mitarbeiter hätten danach ke<strong>in</strong>e<br />
Kontaktmöglichkeit. Zusammen mit IS NULL bei der WHERE-Klausel wird aber<br />
erläutert, dass e<strong>in</strong>e leere Zeichenkette ungleich NULL ist; bei diesen Mitarbeitern<br />
wird also e<strong>in</strong> leerer E<strong>in</strong>trag, aber nicht "nichts" angezeigt.<br />
NULLIF<br />
Bitte nehmen Sie diesen H<strong>in</strong>weis als Empfehlung, lieber NULL zu<br />
speichern als den leeren Str<strong>in</strong>g ’’.<br />
Die Funktion NULLIF vergleicht zwei Werte und liefert NULL zurück, wenn beide<br />
Werte gleich s<strong>in</strong>d; andernfalls liefert der erste Wert das Ergebnis.<br />
Aufgabe: Suche alle Versicherungsnehmer, die im Alter von 18 Jahren ihren<br />
Führersche<strong>in</strong> gemacht haben.<br />
SELECT Name, Vorname, EXTRACT(YEAR FROM Geburtsdatum) + 18 AS Jahr<br />
FROM Versicherungsnehmer<br />
WHERE Geburtsdatum IS NOT NULL<br />
AND NULLIF( EXTRACT(YEAR FROM Geburtsdatum) + 18,<br />
EXTRACT(YEAR FROM Fuehrersche<strong>in</strong>) ) IS NULL;<br />
NAME VORNAME JAHR<br />
Liebermann Maria 1988<br />
H<strong>in</strong>weis: Sowohl COALESCE als auch NULLIF s<strong>in</strong>d Kurzfassungen für spezielle<br />
Fallunterscheidungen mit CASE WHEN, <strong>in</strong> denen zusätzlich IS NULL e<strong>in</strong>gebunden<br />
wird − siehe dazu Nützliche Erweiterungen 2 .<br />
2 Kapitel 23 auf Seite 213<br />
149
Funktionen (2)<br />
16.5. Verschiedene Funktionen<br />
Auch wenn die Funktionen <strong>in</strong> diesem Abschnitt beim <strong>SQL</strong>-Standard v<strong>org</strong>esehen<br />
s<strong>in</strong>d, s<strong>in</strong>d sie nicht immer vorhanden. Wir verzichten deshalb wiederum auf nähere<br />
Erläuterungen und verweisen auf die jeweilige Dokumentation.<br />
ROW_NUMBER – Zeilen nummerieren<br />
Mit der ROW_NUMBER-Funktion werden die Zeilen im Ergebnis e<strong>in</strong>er Abfrage<br />
nach der Sortierung durchnummeriert. MS-<strong>SQL</strong> verwendet folgende Syntax:<br />
ROW_NUMBER() OVER ( [ ] )<br />
CURRENT_USER – der aktuelle Benutzer<br />
Mit CURRENT_USER (<strong>in</strong> der Regel ohne Klammer) wird der aktuelle Benutzername<br />
abgefragt. Dieser kann auch als V<strong>org</strong>abewert bei Neuaufnahmen automatisch<br />
<strong>in</strong> e<strong>in</strong>er Spalte e<strong>in</strong>er Tabelle e<strong>in</strong>getragen werden.<br />
SELECT CURRENT_USER [from fiktiv]; /* Ergebnis: SYSDBA */<br />
16.6. Zusammenfassung<br />
In diesem Kapitel lernten Sie weitere e<strong>in</strong>gebaute Funktionen kennen:<br />
• Für Zahlen gibt es viele mathematische Funktionen wie Potenzen, Wurzeln,<br />
Exponential- oder W<strong>in</strong>kelfunktionen.<br />
• Zeichenketten können auf vielfache Weise verknüpft oder zum Erstellen neuer<br />
Str<strong>in</strong>gs bearbeitet werden.<br />
• Datums- und Zeitwerte können im Detail verglichen und verrechnet werden.<br />
16.7. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 152.<br />
Übung 1 – Funktionen für Zahlen<br />
Geben Sie mit <strong>SQL</strong>-Funktionen die Formeln für die folgenden Aufgaben an. Es<br />
geht nur um die Formeln, nicht um e<strong>in</strong>en passenden SELECT-Befehl. Bei den Aufgaben<br />
1 bis 3 s<strong>in</strong>d jeweils zwei Lösungen möglich.<br />
150
1. Gegeben seien zwei Zahlen a, b. Berechnen Sie a² + 2ab + b².<br />
2. Berechnen Sie die Quadratwurzel von 216,09.<br />
Übungen<br />
3. E<strong>in</strong> Auftragsverwaltungsprogramm speichert <strong>in</strong> der Spalte Bestellung die<br />
Bestellwerte und <strong>in</strong> der Spalte Zahlung die E<strong>in</strong>zahlungen. Bestimmen Sie<br />
mit e<strong>in</strong>em e<strong>in</strong>zigen Ausdruck die Prüfung, ob der Kunde Guthaben oder<br />
Schulden (Zahlungsverpflichtung) hat.<br />
4. Bestimmen Sie den Betrag von Aufgabe 3 (unabhängig davon, ob es sich<br />
um Guthaben oder Schulden handelt).<br />
Übung 2 – Zufallszahlen<br />
Bestimmen Sie mit <strong>SQL</strong>-Funktionen die folgenden Zufallszahlen.<br />
1. e<strong>in</strong>e beliebige Zufallszahl zwischen 0 und 1<br />
2. e<strong>in</strong>e beliebige Zufallszahl zwischen 0 und 4<br />
3. e<strong>in</strong>e beliebige Zufallszahl zwischen 1 und 5<br />
4. e<strong>in</strong>e Zufallszahl als ganze Zahl zwischen 1 und 5<br />
5. e<strong>in</strong>e Zufallszahl als ganze Zahl zwischen 1 und 26<br />
6. e<strong>in</strong>en zufällig ausgewählten Buchstaben aus der Liste letters der Großbuchstaben<br />
'ABC...XYZ'<br />
Übung 3 – Zeichenketten bearbeiten<br />
Aus der Tabelle Mitarbeiter sollen die Werte ID und Abteilung_ID zusammengesetzt<br />
werden. Dabei soll die ID immer 4-stellig und die Abteilung_ID immer 2stellig<br />
geschrieben werden, bei Bedarf sollen die Teile mit '0' aufgefüllt werden.<br />
Übung 4 – Zeichenketten bearbeiten<br />
Geben Sie für die Tabelle Mitarbeiter e<strong>in</strong>e der vorhandenen Telefonnummern an<br />
− vorrangig die Mobilnummer; berücksichtigen Sie dabei auch, ob überhaupt<br />
e<strong>in</strong>e Nummer gespeichert ist.<br />
Übung 5 – Zeichenketten bearbeiten<br />
Zeigen Sie für die Spalte Kennzeichen der Tabelle Fahrzeug den jeweils zugehörigen<br />
Kreis an.<br />
Übung 6 – Zeichenketten bearbeiten<br />
In der Beschreibung für den Schadensfall mit der ID 6 ist das Kennzeichen 'RE-<br />
LM 903' durch 'RE-LM 902' zu berichtigen.<br />
151
Funktionen (2)<br />
Übung 7 – Datum und Zeit bearbeiten<br />
Die folgenden Teilaufgaben werden benutzt, um im Kapitel Testdaten erzeugen 3<br />
Geburtsdatum, Führersche<strong>in</strong>-Erwerb oder Abschluss des Versicherungsvertrags<br />
zufällig zu bestimmen.<br />
1. Bestimmen Sie aus dem Geburtsdatum das Datum des 18. Geburtstags.<br />
2. Bestimmen Sie (ausgehend vom 01.01.1950) Datumsangaben bis zum<br />
31.12.1990, bei denen der Monat und das Jahr per Zufallsfunktion bestimmt<br />
werden.<br />
3. Bestimmen Sie ebenso Datumsangaben, bei denen auch der Tag zufällig<br />
festgelegt wird und immer e<strong>in</strong> gültiges Datum erzeugt wird.<br />
4. Prüfen Sie, ob e<strong>in</strong> neuer Kunde se<strong>in</strong>en Führersche<strong>in</strong> bei Vertragsabschluss<br />
bereits drei Jahre besitzt. Sie können annehmen, dass sowohl Fuehrersche<strong>in</strong><br />
als auch Abschlussdatum <strong>in</strong> derselben Tabelle liegen; Schaltjahre können<br />
ignoriert werden.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 150.<br />
Lösung zu Übung 1 – Funktionen für Zahlen<br />
1. a. POWER(a, 2) + 2*a*b + POWER(b, 2)<br />
b. POWER(a + b, 2) als e<strong>in</strong>fachste B<strong>in</strong>omische Formel<br />
2. a. SQRT(216.09)<br />
b. POWER(216.09, 0.5)<br />
3. a. IF( SUM(Bestellung) > SUM(Zahlung)<br />
b. IF( SIGN( SUM(Bestellung) − SUM(Zahlung) ) = 1)<br />
4. ABS( SUM(Bestellung) − SUM(Zahlung) )<br />
Lösung zu Übung 2 – Zufallszahlen<br />
1. RAND()<br />
2. RAND() * 4<br />
3. 1 + RAND() * 4<br />
4. FLOOR( 1 + RAND() * 4 )<br />
5. FLOOR( 1 + RAND() * 25 )<br />
6. SUBSTRING( letters FROM FLOOR( 1 + RAND() * 25 ) FOR 1 )<br />
3 Kapitel 36 auf Seite 407<br />
152
Lösung zu Übung 3 – Zeichenketten bearbeiten<br />
SELECT LPAD( ID, 4, ’0’) + LPAD( Abteilung_ID, 2, ’0’) FROM Mitarbeiter;<br />
Lösung zu Übung 4 – Zeichenketten bearbeiten<br />
SELECT COALESCE( NULLIF(Mobil, ”), Telefon) AS Tel FROM Mitarbeiter;<br />
Übungen<br />
Erklärung: Wenn Mobil e<strong>in</strong>en Wert enthält, kommt bei NULLIF dieser Wert heraus;<br />
andernfalls wird immer NULL geliefert − entweder als Feld<strong>in</strong>halt oder als<br />
Ergebnis des Vergleichs mit der leeren Zeichenkette.<br />
Lösung zu Übung 5 – Zeichenketten bearbeiten<br />
SELECT SUBSTRING( Kennzeichen FROM 1 FOR Position(’-’, Kennzeichen) - 1)<br />
FROM Fahrzeug;<br />
Lösung zu Übung 6 – Zeichenketten bearbeiten<br />
UPDATE Schadensfall<br />
SET Beschreibung = REPLACE( Beschreibung, ’RE-LM 903’, ’RE-LM 902’ )<br />
WHERE ID = 6;<br />
Lösung zu Übung 7 – Datum und Zeit bearbeiten<br />
Die Teilaufgaben liefern diese E<strong>in</strong>zellösungen.<br />
1. DATEADD( YEAR, 18, Geburtsdatum )<br />
2. DATEADD( MONTH, FLOOR(RAND()*11), DATEADD( YEAR,<br />
FLOOR(RAND()*40), ’1950-01-01’))<br />
Es handelt sich um e<strong>in</strong>e verschachtelte Funktion: Zuerst wird das Jahr neu<br />
bestimmt, es wird maximal e<strong>in</strong> Wert von 40 addiert. Zu diesem Ergebnis<br />
wird der Monat neu bestimmt, es wird maximal 11 addiert. In manchen<br />
Fällen s<strong>in</strong>d e<strong>in</strong>zelne Angaben per CAST genauer zu def<strong>in</strong>ieren.<br />
3. DATEADD( DAY, FLOOR(RAND()*(41*365+10)), ’1950-01-01’)<br />
Es handelt sich um 41 Jahre zu je 365 Tagen, dazu 10 Schalttage.<br />
4. SELECT Fuehrersche<strong>in</strong>, Abschlussdatum FROM (Vertrag/Kunde)<br />
WHERE DATEDIFF( DAY, Fuehrersche<strong>in</strong>, Abschlussdatum) < 3*365<br />
H<strong>in</strong>weis: DATEDIFF(YEAR...) ist nicht unbed<strong>in</strong>gt geeignet, weil e<strong>in</strong> DBMS<br />
nicht die Term<strong>in</strong>e, sondern die Jahreszahlen vergleichen könnte.<br />
153
Funktionen (2)<br />
16.8. Siehe auch<br />
Bei Wikipedia gibt es fachliche Erläuterungen:<br />
• Potenzen 4<br />
• Exponentialfunktion 5<br />
• Trigonometrische Funktionen 6 und Bogenmaß 7<br />
• Betragsfunktion 8 − der absolute Betrag<br />
• Zufallszahl 9 gibt auch H<strong>in</strong>weise zu „Pseudozufallszahlen“.<br />
• B<strong>in</strong>omische Formeln 10<br />
4 http://de.wikipedia.<strong>org</strong>/wiki/Potenz%20(Mathematik)<br />
5 http://de.wikipedia.<strong>org</strong>/wiki/Exponentialfunktion<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/Trigonometrische%20Funktionen<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Bogenma%c3%9f<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Betragsfunktion<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Zufallszahl<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/B<strong>in</strong>omische%20Formel<br />
154
17. WHERE-Klausel im Detail<br />
17.1. E<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung . . . . . . . . . . . . . . . . . . . . . . . 155<br />
17.2. Mehrere Bed<strong>in</strong>gungen verknüpfen . . . . . . . . . . . . . . . . . 160<br />
17.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 162<br />
17.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163<br />
17.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165<br />
In diesem Kapitel werden die E<strong>in</strong>zelheiten der WHERE-Klausel genauer behandelt.<br />
Diese Angaben s<strong>in</strong>d vor allem für den SELECT-Befehl, aber auch für<br />
UPDATE und DELETE von Bedeutung.<br />
Die WHERE-Klausel ist (neben der Verknüpfung mehrerer Tabellen) der wichtigste<br />
Bestandteil des SELECT-Befehls: Je s<strong>org</strong>fältiger die Auswahlbed<strong>in</strong>gungen<br />
formuliert werden, desto genauer ist das Ergebnis der Abfrage.<br />
Neben den hier erläuterten Varianten bietet jedes DBMS noch andere, z. B.<br />
STARTING WITH oder SIMILAR.<br />
Die Beispiele beziehen sich auf den Anfangsbestand der Beispieldatenbank; auf<br />
die Ausgabe der selektierten Datensätze wird verzichtet. Anstelle konstanter<br />
Werte können auch passende Ausdrücke angegeben werden, z. B. Funktionen<br />
oder Unterabfragen.<br />
Bitte probieren Sie alle Beispiele aus und nehmen Sie Änderungen vor, um die<br />
Auswirkungen zu erkennen. Dazu gehört möglichst auch die Umkehrung der<br />
Auswahl mit bzw. ohne NOT. Wie im Kapitel Ausführliche SELECT-Struktur zur<br />
WHERE-Klausel angegeben, gehört das NOT vor e<strong>in</strong>e Suchbed<strong>in</strong>gung, vor e<strong>in</strong>en<br />
Vergleichsoperator oder vor den Parameter-Namen wie BETWEEN.<br />
17.1. E<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung<br />
17.1.1. Größenvergleich zweier Werte<br />
Der e<strong>in</strong>fachste Weg ist der direkte Vergleich zweier Werte, nämlich der Inhalt e<strong>in</strong>er<br />
Spalte mit e<strong>in</strong>em konstanten Wert. Dies ist möglich mit den folgenden Vergleichsoperatoren,<br />
und zwar für alle Datentypen, die verglichen werden können<br />
− Zahlen, Zeichenketten, Datumsangaben.<br />
155
WHERE-Klausel im Detail<br />
= < > = <br />
Beispiele:<br />
Aufgabe: Suche e<strong>in</strong>en Datensatz, bei dem der Wert <strong>in</strong> der Spalte ID gleich ist<br />
zu e<strong>in</strong>em v<strong>org</strong>egebenen Wert.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE ID = 10;<br />
Aufgabe: Suche Datensätze, bei denen der Name kle<strong>in</strong>er als 'B' ist, also mit 'A'<br />
anfängt.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Name < ’B’;<br />
Aufgabe: Suche Führersche<strong>in</strong>-Neul<strong>in</strong>ge.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Fuehrersche<strong>in</strong> >= ’01.01.2007’;<br />
Aufgabe: Suche Fahrzeugtypen mit kurzer Bezeichnung.<br />
SELECT * FROM Fahrzeugtyp<br />
WHERE Char_Length(Bezeichnung)
17.1.3. LIKE – Ähnlichkeiten (1)<br />
E<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung<br />
Die LIKE-Bed<strong>in</strong>gung vergleicht Zeichenketten „ungenau“: Der gesuchte Text soll<br />
<strong>in</strong> e<strong>in</strong>er Spalte enthalten se<strong>in</strong>; dazu werden „Wildcards“ benutzt: Der '_' steht für<br />
e<strong>in</strong> e<strong>in</strong>zelnes Zeichen, das an der betreffenden Stelle vorkommen kann. Das Prozentzeichen<br />
'%' steht für e<strong>in</strong>e beliebige Zeichenkette mit 0 oder mehr Zeichen.<br />
Diese Bed<strong>in</strong>gung wird vor allem <strong>in</strong> zwei Situationen gerne benutzt:<br />
• Der Suchbegriff ist sehr lang; dem Anwender soll es genügen, den Anfang e<strong>in</strong>zugeben.<br />
• Der Suchbegriff ist nicht genau bekannt (z. B. nicht richtig lesbar).<br />
Beispiele:<br />
Aufgabe: Der Ortsname beg<strong>in</strong>nt nicht mit 'B'; der Inhalt dah<strong>in</strong>ter ist beliebig.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Ort NOT LIKE ’B%’;<br />
Aufgabe: Der Ortsname enthält irgendwo 'alt' mit beliebigem Inhalt davor<br />
und dah<strong>in</strong>ter.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Ort LIKE ’%alt%’;<br />
Aufgabe: Der Anfangsbuchstabe des Namens ist unklar, aber danach folgen<br />
die Buchstaben 'ei' und noch etwas mehr.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Name LIKE ’_ei%’;<br />
Schwierig wird es, wenn e<strong>in</strong>es der Wildcard-Zeichen Teil des Suchbegriffs se<strong>in</strong><br />
soll. Dann ist dem LIKE-Parameter mitzuteilen, dass '%' bzw. '_' als „echtes“ Zeichen<br />
zu verstehen ist. Das geschieht dadurch, dass e<strong>in</strong> spezielles Zeichen davor<br />
gesetzt und als „ESCAPE-Zeichen“ angegeben wird:<br />
Aufgabe: Innerhalb der Beschreibung kommt die Zeichenfolge '10%' vor.<br />
SELECT * FROM Schadensfall<br />
WHERE Beschreibung LIKE ’%10\%%’ ESCAPE ’\’;<br />
Das erste und das letzte Prozentzeichen stehen dafür, dass vorher und nachher<br />
beliebige Inhalte möglich s<strong>in</strong>d. Das mittlere Prozentzeichen wird mit dem<br />
Escape-Zeichen '\' verbunden und ist damit Teil der gesuchten Zeichenfolge.<br />
Diese Angabe '\%' ist als e<strong>in</strong> Zeichen zu verstehen.<br />
Vergleichen Sie das Ergebnis, wenn der ESCAPE-Parameter weggelassen wird<br />
oder wenn e<strong>in</strong>es oder mehrere der Sonderzeichen im LIKE-Parameter fehlen.<br />
157
WHERE-Klausel im Detail<br />
17.1.4. CONTAINS u. a. – Ähnlichkeiten (2)<br />
E<strong>in</strong> Problem des LIKE-Parameters ist die Verwendung der Wildcard-Zeichen '%'<br />
und '_', die man gerne vergisst oder (wie im letzten Beispiel) nicht genau genug<br />
beachtet. Deshalb gibt es verschiedene Vere<strong>in</strong>fachungen.<br />
CONTAINS − <strong>in</strong> Firebird CONTAINING − prüft, ob e<strong>in</strong>e Zeichenkette im Feld<strong>in</strong>halt<br />
enthalten ist.<br />
SELECT * FROM Schadensfaelle<br />
WHERE Beschreibung CONTAINS ’10%’;<br />
Teil der Beschreibung ist die Zeichenkette '10%'<br />
Bitte prüfen Sie <strong>in</strong> der Beschreibung Ihres DBMS, welche Möglichkeiten für die<br />
Suche nach Ähnlichkeiten außerdem angeboten werden.<br />
17.1.5. IS NULL – null-Werte prüfen<br />
Wie schon bei den relationalen Datenbanken besprochen, haben NULL-Werte<br />
e<strong>in</strong>e besondere Bedeutung. Mit den folgenden beiden Abfragen werden nicht<br />
alle Datensätze gefunden:<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE Mobil ”;<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE Mobil = ”;<br />
8 Mitarbeiter mit Mobil-Nummer<br />
10 Mitarbeiter ohne Mobil-Nummer<br />
Nanu, es gibt doch 28 Mitarbeiter; wo s<strong>in</strong>d die übrigen geblieben? Für diese Fälle<br />
gibt es mit IS NULL e<strong>in</strong>e spezielle Abfrage:<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE Mobil IS NULL;<br />
10 Mitarbeiter ohne Angabe<br />
Der Vollständigkeit halber sei darauf h<strong>in</strong>gewiesen, dass die folgende Abfrage tatsächlich<br />
die richtige Gegenprobe liefert.<br />
158<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE Mobil IS NOT NULL;<br />
18 Mitarbeiter mit irgende<strong>in</strong>er Angabe (auch mit "leerer" Angabe)
E<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung<br />
Die folgende Abfrage liefert e<strong>in</strong>e leere Ergebnismenge zurück, weil NULL eben<br />
ke<strong>in</strong> Wert ist.<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE Mobil = NULL;<br />
Es gibt ke<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung, die alle Datensätze ohne explizite Mobil-<br />
Angabe auf e<strong>in</strong>mal angibt. Es gibt nur die Möglichkeit, die beiden Bed<strong>in</strong>gungen<br />
"IS NULL" und "ist leer" zu komb<strong>in</strong>ieren:<br />
SELECT ID, Name, Vorname, Mobil<br />
FROM Mitarbeiter<br />
WHERE ( Mobil IS NULL ) OR ( Mobil = ” );<br />
20 Mitarbeiter ohne ausdrückliche Angabe<br />
Beachten Sie auch bei "WHERE ... IS [NOT] NULL" die Bedeutung von NULL:<br />
• Bei Zeichenketten ist zu unterscheiden zwischen dem „leeren“ Str<strong>in</strong>g und dem<br />
NULL-Wert.<br />
• Bei Zahlen ist zu unterscheiden zwischen der Zahl '0' (null) und dem NULL-<br />
Wert.<br />
• Bei Datumsangaben ist zu unterscheiden zwischen e<strong>in</strong>em vorhandenen Datum<br />
und dem NULL-Wert; e<strong>in</strong> Datum, das der Zahl 0 entspräche, gibt es nicht.<br />
(Man könnte allenfalls das kle<strong>in</strong>ste mögliche Datum wie '01.01.0100' benutzen,<br />
aber dies ist bereits e<strong>in</strong> Datum.)<br />
17.1.6. IN – genauer Vergleich mit e<strong>in</strong>er Liste<br />
Der IN-Parameter vergleicht, ob der Inhalt e<strong>in</strong>er Spalte <strong>in</strong> der angegebenen Liste<br />
enthalten ist. Die Liste kann mit beliebigen Datentypen arbeiten.<br />
Aufgabe: Hole die Liste aller Fahrzeuge, deren Typen als „VW-Kle<strong>in</strong>wagen“ registriert<br />
s<strong>in</strong>d.<br />
SELECT * FROM Fahrzeug<br />
WHERE Fahrzeugtyp_ID IN (1, 2);<br />
Aufgabe: Suche nach e<strong>in</strong>em Unfall Fahrzeuge mit e<strong>in</strong>er von mehreren möglichen<br />
Farben.<br />
SELECT * FROM Fahrzeug<br />
WHERE Farbe IN (’ocker’, ’gelb’);<br />
Vor allem das erste Beispiel wird sehr oft mit e<strong>in</strong>er Unterabfrage versehen; vergleichen<br />
Sie dazu auch den folgenden Abschnitt zu EXISTS.<br />
159
WHERE-Klausel im Detail<br />
Aufgabe: Hole die Liste aller Fahrzeuge vom Typ „Volkswagen“.<br />
SELECT * FROM Fahrzeug<br />
WHERE Fahrzeugtyp_ID IN<br />
( SELECT ID FROM Fahrzeugtyp<br />
WHERE Hersteller_ID = 1 );<br />
Dabei wird zuerst mit der Unterabfrage e<strong>in</strong>e Liste aller Fahrzeugtypen-IDs für<br />
den Hersteller 1 (= Volkswagen) zusammengestellt; diese wird dann für den Vergleich<br />
über den IN-Parameter benutzt.<br />
17.1.7. EXISTS – schneller Vergleich mit e<strong>in</strong>er Liste<br />
Im Gegensatz zu den anderen Parametern der WHERE-Klausel arbeitet der<br />
EXISTS-Parameter nicht mit fest v<strong>org</strong>egebenen Werten, sondern nur mit dem<br />
Ergebnis e<strong>in</strong>er Abfrage, also e<strong>in</strong>er Unterabfrage. Das letzte Beispiel zum IN-<br />
Parameter kann auch so formuliert werden:<br />
SELECT * FROM Fahrzeug fz<br />
WHERE EXISTS<br />
( SELECT * FROM Fahrzeugtyp ft<br />
WHERE ft.Hersteller_ID = 1<br />
AND fz.Fahrzeugtyp_ID = ft.ID );<br />
Liste aller Fahrzeuge vom Typ 'Volkswagen'<br />
Zu jedem Datensatz aus der Tabelle Fahrzeug wird zu dieser Fahrzeugtyp_ID<br />
e<strong>in</strong>e Unterabfrage aus den Fahrzeugtypen erstellt: Wenn es dort e<strong>in</strong>en Datensatz<br />
mit passender ID und Hersteller-ID 1 (= Volkswagen) gibt, gehört der Fahrzeug-<br />
Datensatz zur Auswahl, andernfalls nicht.<br />
Da Unterabfragen zuerst ausgeführt werden, wird e<strong>in</strong>e EXISTS-Prüfung <strong>in</strong> aller<br />
Regel schneller erledigt als die entsprechende IN-Prüfung: Bei EXISTS handelt<br />
es sich um e<strong>in</strong>e Feststellung „ist überhaupt etwas vorhanden“; bei IN dagegen<br />
muss e<strong>in</strong> exakter Vergleich mit allen Werten e<strong>in</strong>er Liste durchgeführt werden. Bei<br />
unserer kle<strong>in</strong>en Beispieldatenbank spielt das natürlich ke<strong>in</strong>e Rolle, aber bei e<strong>in</strong>er<br />
„echten“ Datenbank mit Millionen von E<strong>in</strong>trägen schon.<br />
17.2. Mehrere Bed<strong>in</strong>gungen verknüpfen<br />
Bei der WHERE-Klausel geht es darum festzustellen, ob e<strong>in</strong> Datensatz Teil des<br />
Abfrageergebnisses ist oder nicht; bei der handelt sich also<br />
um e<strong>in</strong>en booleschen Ausdruck, d. h. e<strong>in</strong>en Ausdruck, der e<strong>in</strong>en der booleschen<br />
Werte WAHR oder FALSCH − TRUE bzw. FALSE − als Ergebnis hat. Nur bei e<strong>in</strong>fa-<br />
160
Mehrere Bed<strong>in</strong>gungen verknüpfen<br />
chen Abfragen genügt dazu e<strong>in</strong>e e<strong>in</strong>zelne Bed<strong>in</strong>gung; meistens müssen mehrere<br />
Bed<strong>in</strong>gungen verknüpft werden (wie beim letzten Beispiel unter IS NULL).<br />
Dazu gibt es die booleschen Operatoren NOT, AND, OR.<br />
NOT als Negation<br />
Dies liefert die Umkehrung: aus TRUE wird FALSE, aus FALSE wird TRUE.<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE NOT (Fuehrersche<strong>in</strong> >= ’01.01.2007’);<br />
AND als Konjunktion<br />
Siehe oben: ke<strong>in</strong>e Führersche<strong>in</strong>-Neul<strong>in</strong>ge<br />
E<strong>in</strong>e Bed<strong>in</strong>gung, die durch e<strong>in</strong>e AND-Verknüpfung gebildet wird, ist genau dann<br />
TRUE, wenn beide (bzw. alle) Bestandteile TRUE s<strong>in</strong>d.<br />
SELECT ID, Name, Vorname, PLZ, Ort<br />
FROM Versicherungsnehmer<br />
WHERE PLZ BETWEEN ’45000’ AND ’45999’<br />
AND Name < ’K’;<br />
Die nach Alphabet erste Hälfte der Versicherungsnehmer e<strong>in</strong>es PLZ-Bereichs<br />
OR als Adjunktion<br />
E<strong>in</strong>e Bed<strong>in</strong>gung, die durch e<strong>in</strong>e OR-Verknüpfung gebildet wird, ist genau dann<br />
TRUE, wenn m<strong>in</strong>destens e<strong>in</strong> Bestandteil TRUE ist; dabei ist es gleichgültig, ob<br />
die anderen Bestandteile TRUE oder FALSE s<strong>in</strong>d.<br />
SELECT ID, Name, Vorname, PLZ, Ort<br />
FROM Versicherungsnehmer<br />
WHERE PLZ BETWEEN ’45000’ AND ’45999’<br />
OR Name < ’K’;<br />
Die nach Alphabet erste Hälfte der Versicherungsnehmer und alle e<strong>in</strong>es PLZ-Bereichs<br />
Bitte beachten Sie, dass der normale Sprachgebrauch „alle . . . und alle . . . “ sagt.<br />
Geme<strong>in</strong>t ist nach logischen Begriffen aber, dass erfüllt se<strong>in</strong> muss<br />
ODER ODER BEIDE.<br />
XOR als Kontravalenz<br />
E<strong>in</strong>e Bed<strong>in</strong>gung, die durch e<strong>in</strong>e XOR-Verknüpfung gebildet wird, ist genau dann<br />
TRUE, wenn e<strong>in</strong> Bestandteil TRUE ist, aber der andere Bestandteil FALSE ist −<br />
„ausschließendes oder“ bzw. „entweder − oder“. Diese Verknüpfung gibt es selten,<br />
z. B. bei My<strong>SQL</strong>; hier wird es der Vollständigkeit halber erwähnt.<br />
161
WHERE-Klausel im Detail<br />
My<strong>SQL</strong>-Quelltext<br />
SELECT ID, Name, Vorname, PLZ, Ort<br />
FROM Versicherungsnehmer<br />
WHERE PLZ BETWEEN ’45000’ AND ’45999’<br />
XOR Name < ’K’;<br />
Die nach Alphabet erste Hälfte der Versicherungsnehmer oder alle e<strong>in</strong>es PLZ-Bereichs<br />
Bitte beachten Sie, dass hier der normale Sprachgebrauch „oder“ sagt und „entweder<br />
− oder“ geme<strong>in</strong>t ist.<br />
Anstelle von XOR kann immer e<strong>in</strong>e Komb<strong>in</strong>ation verwendet werden:<br />
( AND ( NOT ) )<br />
OR ( AND ( NOT ) )<br />
Klammern benutzen oder weglassen?<br />
Bereits im Kapitel „Ausführliche SELECT-Struktur“ steht die Hierarchie:<br />
• NOT ist die engste Verb<strong>in</strong>dung und wird vorrangig ausgewertet.<br />
• AND ist die nächststärkere Verb<strong>in</strong>dung und wird danach ausgewertet.<br />
• OR ist die schwächste Verb<strong>in</strong>dung und wird zuletzt ausgewertet.<br />
• Was <strong>in</strong> Klammern steht, wird vor allem anderen ausgewertet.<br />
Bitte setzen Sie im folgenden Beispiel Klammern an anderen Stellen oder streichen<br />
Sie Klammern, und vergleichen Sie die Ergebnisse.<br />
SELECT ID, Name, Vorname, PLZ, Ort FROM Versicherungsnehmer<br />
WHERE NOT ( PLZ BETWEEN ’45000’ AND ’45999’<br />
AND ( Name LIKE ’B%’<br />
OR Name LIKE ’K%’<br />
OR NOT Name CONTAINING ’ei’<br />
)<br />
)<br />
ORDER BY PLZ, Name;<br />
Es werden ziemlich unterschiedliche Ergebnisse kommen. Es empfiehlt sich deshalb,<br />
an allen s<strong>in</strong>nvollen Stellen Klammern zu setzen − auch dort, wo sie nicht<br />
nötig s<strong>in</strong>d − und das, was zusammengehört, durch E<strong>in</strong>rückungen zu gliedern.<br />
17.3. Zusammenfassung<br />
In diesem Kapitel lernten wir neben dem Vergleich von Werten viele Möglichkeiten<br />
kennen, mit denen Bed<strong>in</strong>gungen für Abfragen festgelegt werden können:<br />
162
• Mit BETWEEN . . . AND werden Werte <strong>in</strong>nerhalb e<strong>in</strong>es Bereichs geprüft.<br />
Übungen<br />
• Mit LIKE und CONTAINS werden Werte gesucht, die mit v<strong>org</strong>egebenen Werten<br />
teilweise übere<strong>in</strong>stimmen.<br />
• Mit IS NULL werden null-Werte gesucht.<br />
• Mit IN und EXISTS werden Spaltenwerte mit e<strong>in</strong>er Liste verglichen.<br />
Mit AND, OR, NOT werden Bed<strong>in</strong>gungen zusammengefasst.<br />
17.4. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 164.<br />
Bei den folgenden Aufgaben kommt es nur auf die WHERE-Klausel an; Sie dürfen<br />
e<strong>in</strong> SELECT „mit allen Spalten“ benutzen.<br />
Übung 1 – Auswahl nach Zeichenketten<br />
Suchen Sie alle Versicherungsnehmer, die folgenden Bed<strong>in</strong>gungen entsprechen:<br />
• Der erste Buchstabe des Nachnamens ist nicht bekannt, der zweite ist e<strong>in</strong> 'r'.<br />
• Der Vorname enthält e<strong>in</strong> 'a'.<br />
• Die Postleitzahl gehört zum Bereich Essen (PLZ 45...).<br />
Übung 2 – Auswahl nach Datumsbereich<br />
Suchen Sie alle Versicherungsnehmer, die <strong>in</strong> den Jahren 1967 bis 1970 ihren<br />
18. Geburtstag hatten.<br />
Übung 3 – Auswahl nach Ähnlichkeit<br />
Zeigen Sie alle Schadensfälle an, bei denen <strong>in</strong> der Beschreibung auf e<strong>in</strong>e prozentuale<br />
Angabe h<strong>in</strong>gewiesen wird.<br />
Übung 4 – Auswahl für unbekannte Werte<br />
Suchen Sie die Dienstwagen, die ke<strong>in</strong>em Mitarbeiter persönlich zugeordnet s<strong>in</strong>d.<br />
H<strong>in</strong>weis: Die Prüfung „Mitarbeiter ohne Dienstwagen“ ist komplizierter; das dafür<br />
erforderliche OUTER JOIN wird erst später behandelt.<br />
163
WHERE-Klausel im Detail<br />
Übung 5 – Bed<strong>in</strong>gungen verknüpfen<br />
Zeigen Sie alle Mitarbeiter der Abteilungen „Vertrieb“ (= ’Vert’) und „Ausbildung“<br />
(= ’Ausb’) an.<br />
H<strong>in</strong>weis: Bestimmen Sie zunächst die IDs der gesuchten Abteilungen und benutzen<br />
Sie das Ergebnis für die eigentliche Abfrage.<br />
Übung 6 – Bed<strong>in</strong>gungen verknüpfen<br />
Gesucht werden die Versicherungsverträge für Haftpflicht (= 'HP') und Teilkasko<br />
(= 'TK'), die m<strong>in</strong>destens seit dem Ende des Jahres 1980 bestehen und aktuell<br />
nicht mit dem m<strong>in</strong>imalen Prämiensatz berechnet werden.<br />
H<strong>in</strong>weis: Tragen Sie ausnahmsweise nur die notwendigen Klammern e<strong>in</strong>, nicht<br />
alle s<strong>in</strong>nvollen.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 163.<br />
Lösung zu Übung 1 – Auswahl nach Zeichenketten<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE Name LIKE ’_r%’ AND Vorname LIKE ’%a%’<br />
AND PLZ STARTING WITH ’45’ /* oder: */<br />
PLZ LIKE ’45%’;<br />
Lösung zu Übung 2 – Auswahl nach Datumsbereich<br />
SELECT * FROM Versicherungsnehmer<br />
WHERE DATEADD(YEAR, 18, Geburtsdatum) BETWEEN ’01.01.1967’ AND ’31.12.1970’;<br />
Lösung zu Übung 3 – Auswahl nach Ähnlichkeit<br />
SELECT * FROM Schadensfall<br />
WHERE Beschreibung LIKE ’%\%%’ ESCAPE ’\’;<br />
Lösung zu Übung 4 – Auswahl für unbekannte Werte<br />
164<br />
SELECT * FROM Dienstwagen<br />
WHERE Mitarbeiter_ID IS NULL;
Lösung zu Übung 5 – Bed<strong>in</strong>gungen verknüpfen<br />
SELECT * FROM Mitarbeiter<br />
WHERE Abteilung_ID IN (<br />
SELECT ID FROM Abteilung<br />
WHERE Kuerzel IN (’Vert’, ’Ausb’) );<br />
Lösung zu Übung 6 – Bed<strong>in</strong>gungen verknüpfen<br />
SELECT * FROM Versicherungsvertrag<br />
WHERE (Art = ’HP’ or Art = ’TK’)<br />
AND Abschlussdatum 30;<br />
17.5. Siehe auch<br />
Dieses Kapitel verweist auf die folgenden Kapitel:<br />
• Funktionen 1<br />
• Unterabfragen 2<br />
• Ausführliche SELECT-Struktur 3<br />
• Relationale Datenbanken 4<br />
• OUTER JOIN 5<br />
Bei Wikipedia gibt es weitere fachliche H<strong>in</strong>weise:<br />
• Wildcards 6<br />
• ESCAPE-Zeichen 7<br />
• Boolesche Operatoren 8<br />
Negation 9 – Konjunktion 10 – Adjunktion 11 – Kontravalenz 12<br />
1 Kapitel 14 auf Seite 109<br />
2 Kapitel 26 auf Seite 251<br />
3 Kapitel 15 auf Seite 129<br />
4 Kapitel 4 auf Seite 17<br />
5 Kapitel 21 auf Seite 191<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/Wildcard%20(Informatik)<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Escape-Sequenz<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Boolescher%20Operator<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Negation%23Logik<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/Konjunktion%20(Logik)<br />
11 http://de.wikipedia.<strong>org</strong>/wiki/Disjunktion<br />
12 http://de.wikipedia.<strong>org</strong>/wiki/Disjunktion<br />
Siehe auch<br />
165
18. Mehrere Tabellen<br />
18.1. Schreibweisen bei mehreren Tabellen . . . . . . . . . . . . . . . 167<br />
18.2. Mit Hilfe von WHERE – der traditionelle Weg . . . . . . . . . . 168<br />
18.3. JOINs – der moderne Weg . . . . . . . . . . . . . . . . . . . . . . . 168<br />
18.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 169<br />
18.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169<br />
E<strong>in</strong> besonderes Merkmal von relationalen Datenbanken und damit von <strong>SQL</strong><br />
ist, dass die Informationen fast immer über mehrere Tabellen verteilt s<strong>in</strong>d und<br />
bei Abfragen <strong>in</strong> e<strong>in</strong>er geme<strong>in</strong>samen Ergebnismenge zusammengeführt werden<br />
müssen. Dieses Kapitel gibt e<strong>in</strong>en Überblick über die Möglichkeiten dazu; E<strong>in</strong>zelheiten<br />
stehen <strong>in</strong> den folgenden Kapiteln.<br />
18.1. Schreibweisen bei mehreren Tabellen<br />
Bitte beachten Sie bei allen Befehlen, die mehrere Tabellen verwenden (das s<strong>in</strong>d<br />
zwangsläufig nur SELECT-Befehle):<br />
• Wenn e<strong>in</strong> Spaltenname <strong>in</strong> Bezug auf den gesamten <strong>SQL</strong>-Befehl e<strong>in</strong>deutig ist,<br />
genügt dieser Name.<br />
• Tritt e<strong>in</strong> Spaltenname mehrfach auf (wie ID), dann muss der Tabellenname<br />
vorangesetzt werden; der Spaltenname wird nach e<strong>in</strong>em Punkt angefügt.<br />
SELECT Personalnummer AS MitNr, Name, Vorname,<br />
Dienstwagen.ID, Kennzeichen, Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter, Dienstwagen;<br />
• Wegen der Übersichtlichkeit wird die Tabelle meistens auch dann bei jeder<br />
Spalte angegeben, wenn es wegen der ersten Regel nicht erforderlich wäre.<br />
SELECT Mitarbeiter.Personalnummer AS MitNr,<br />
Mitarbeiter.Name, Mitarbeiter.Vorname,<br />
Dienstwagen.ID AS DIW, Dienstwagen.Kennzeichen,<br />
Dienstwagen.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter, Dienstwagen;<br />
167
Mehrere Tabellen<br />
• Anstelle des Namens e<strong>in</strong>er Tabelle kann überall auch e<strong>in</strong> Tabellen-Alias benutzt<br />
werden; dieser muss e<strong>in</strong>mal h<strong>in</strong>ter ihrem Namen (<strong>in</strong> der FROM- oder <strong>in</strong><br />
der JOIN-Klausel) angegeben werden.<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi, Dienstwagen dw;<br />
Alle diese Befehle für „Liste der Mitarbeiter mit Dienstwagen“ s<strong>in</strong>d gleichwertig.<br />
Zu empfehlen ist die vollständige Schreibweise des vorigen Beispiels mit Alias.<br />
E<strong>in</strong> ähnlicher Befehl unter Verwendung der JOIN-Klausel sieht dann so aus:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
JOIN Dienstwagen dw ON mi.ID = dw.Mitarbeiter_ID;<br />
Der Alias ist nur für den betreffenden <strong>SQL</strong>-Befehl gültig. E<strong>in</strong> und dieselbe Tabelle<br />
kann mal als 'a', dann als 'mi' oder auch als 'xyz' bezeichnet werden. Wegen des<br />
leichteren Verständnisses s<strong>in</strong>d aussagefähige Kürzel s<strong>in</strong>nvoll; auch deshalb s<strong>in</strong>d<br />
sie im Kapitel Tabellenstruktur der Beispieldatenbank 1 angegeben.<br />
18.2. Mit Hilfe von WHERE – der traditionelle Weg<br />
Beim e<strong>in</strong>fachsten Verfahren, mehrere Tabellen gleichzeitig abzufragen, stehen<br />
alle Tabellen <strong>in</strong> der FROM-Klausel; die WHERE-Klausel enthält neben den Auswahlbed<strong>in</strong>gungen<br />
auch Bed<strong>in</strong>gungen zur Verknüpfung der Tabellen.<br />
E<strong>in</strong>zelheiten werden <strong>in</strong> E<strong>in</strong>fache Tabellenverknüpfung 2 behandelt.<br />
18.3. JOINs – der moderne Weg<br />
Beim „modernen“ Weg, mehrere Tabellen <strong>in</strong> e<strong>in</strong>er geme<strong>in</strong>samen Abfrage zu verknüpfen,<br />
wird jede Tabelle <strong>in</strong> e<strong>in</strong>er JOIN-Klausel aufgeführt; der ON-Parameter<br />
enthält die Verknüpfungsbed<strong>in</strong>gung. Die WHERE-Klausel enthält nur die Auswahlbed<strong>in</strong>gungen.<br />
1 Anhang A auf Seite 419<br />
2 Kapitel 19 auf Seite 173<br />
168
Die <strong>E<strong>in</strong>führung</strong> dazu wird <strong>in</strong> Arbeiten mit JOIN 3 besprochen.<br />
Zusammenfassung<br />
Bei Abfragen mit e<strong>in</strong>em „e<strong>in</strong>fachen“ JOIN werden jedoch nicht alle Datensätze<br />
aufgeführt. Wenn es <strong>in</strong> der e<strong>in</strong>en oder anderen Tabelle ke<strong>in</strong>e Verknüpfung<br />
gibt, führt das zu e<strong>in</strong>em NULL-Wert; solche Zeilen fehlen im Ergebnis. Mit e<strong>in</strong>em<br />
OUTER JOIN können auch solche „fehlenden“ Zeilen aufgeführt werden.<br />
E<strong>in</strong>zelheiten dazu werden <strong>in</strong> OUTER JOIN 4 behandelt.<br />
JOIN bietet darüber h<strong>in</strong>aus weitere Möglichkeiten.<br />
• Als SELF JOIN wird e<strong>in</strong>e Tabelle mit sich selbst verknüpft.<br />
• Oft kommt es vor, dass man die Daten aus e<strong>in</strong>er Tabelle erst bearbeiten möchte,<br />
bevor man sie mit e<strong>in</strong>er anderen Tabelle verknüpft. Dazu kann e<strong>in</strong>e „Inl<strong>in</strong>e-<br />
View“ verwendet werden.<br />
Diese Ergänzungen werden <strong>in</strong> Mehr zu JOIN 5 besprochen.<br />
18.4. Zusammenfassung<br />
In diesem Kapitel erhielten wir H<strong>in</strong>weise darauf, wie mehrere Tabellen veknüpft<br />
werden können:<br />
• e<strong>in</strong>fach über die FROM-Klausel und passende WHERE-Bed<strong>in</strong>gungen<br />
• übersichtlich über die JOIN-Klausel mit verschiedenen Varianten<br />
18.5. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 170.<br />
Übung 1 – Was ist an diesem SELECT-Befehl falsch?<br />
Zeigen Sie zu bestimmten Versicherungsverträgen die Daten der Fahrzeuge an.<br />
SELECT ID, Abschlussdatum, Art,<br />
vv.Kennzeichen, Farbe<br />
FROM Versicherungsvertrag vv, Fahrzeug<br />
WHERE vv.Fahrzeug_ID = Fahrzeug.ID<br />
AND Kennzeichen LIKE ’BO%’;<br />
3 Kapitel 20 auf Seite 181<br />
4 Kapitel 21 auf Seite 191<br />
5 Kapitel 22 auf Seite 203<br />
169
Mehrere Tabellen<br />
Übung 2 – Was ist an diesem SELECT-Befehl falsch?<br />
Zeigen Sie zu e<strong>in</strong>em Versicherungsvertrag die Daten des Versicherungsnehmers<br />
und des Sachbearbeiters an.<br />
SELECT ID, Vorname + ’ ’ + Name AS Kunde, Ort<br />
Name AS Sachbearbeiter, Telefon<br />
FROM Versicherungsvertrag, Versicherungsnehmer, Mitarbeiter<br />
WHERE ID = 27<br />
AND Versicherungsvertrag.Versicherungsnehmer_ID = Versicherungsnehmer.ID<br />
AND Versicherungsvertrag.Mitarbeiter_ID = Mitarbeiter.ID;<br />
Übung 3 – Berichtigen Sie den folgenden SELECT-Befehl.<br />
Zeigen Sie zu jedem Mitarbeiter die Daten se<strong>in</strong>es Dienstwagens (Kennzeichen,<br />
Typ, Hersteller) an.<br />
SELECT ID, Name, Vorname,<br />
Kennzeichen, Bezeichnung, Name<br />
FROM Mitarbeiter mi, Dienstwagen dw,<br />
Fahrzeugtyp ft, Fahrzeughersteller fh<br />
WHERE ID = dw.Mitarbeiter_ID<br />
AND ID = dw.Fahrzeugtyp_ID<br />
AND ID = ft.Hersteller_ID<br />
ORDER BY Name, Vorname;<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 169.<br />
Lösung zu Übung 1 – Was ist an diesem SELECT-Befehl falsch?<br />
1. Die ID muss mit Tabellennamen oder Alias versehen se<strong>in</strong>, weil sie <strong>in</strong> beiden<br />
Tabellen enthalten ist.<br />
2. Die Spalte Kennzeichen gehört zur Tabelle Fahrzeug, also ist der Alias vv<br />
falsch.<br />
Lösung zu Übung 2 – Was ist an diesem SELECT-Befehl falsch?<br />
170<br />
1. Die ID muss sowohl <strong>in</strong> der Spaltenliste als auch <strong>in</strong> der WHERE-Klausel mit<br />
Tabellennamen oder Alias versehen se<strong>in</strong>, weil sie <strong>in</strong> allen Tabellen enthalten<br />
ist.<br />
2. Gleiches gilt für Name und Vorname, weil diese Angaben <strong>in</strong> mehreren Tabellen<br />
enthalten s<strong>in</strong>d.
Übungen<br />
Wenn (wie <strong>in</strong> den Anmerkungen zur Beispieldatenbank 6 erwähnt) auch für die<br />
Kunden Kontaktdaten gespeichert wären, müsste das auch bei der Spalte Telefon<br />
beachtet werden. Für die Spalte Ort gilt das nicht, weil diese nicht zur Tabelle<br />
Mitarbeiter gehört, sondern zur Tabelle Abteilung, die hier nicht benutzt wird.<br />
Lösung zu Übung 3 – Berichtigen Sie den folgenden SELECT-Befehl.<br />
SELECT mi.ID, mi.Name, mi.Vorname,<br />
dw.Kennzeichen, ft.Bezeichnung, fh.Name<br />
FROM Mitarbeiter mi, Dienstwagen dw,<br />
Fahrzeugtyp ft, Fahrzeughersteller fh<br />
WHERE mi.ID = dw.Mitarbeiter_ID<br />
AND ft.ID = dw.Fahrzeugtyp_ID<br />
AND fh.ID = ft.Hersteller_ID<br />
ORDER BY mi.Name, mi.Vorname;<br />
6 Kapitel 6 auf Seite 39<br />
171
19. E<strong>in</strong>fache Tabellenverknüpfung<br />
19.1. Alle Komb<strong>in</strong>ationen aller Datensätze . . . . . . . . . . . . . . . 173<br />
19.2. Tabellen e<strong>in</strong>fach verb<strong>in</strong>den . . . . . . . . . . . . . . . . . . . . . 174<br />
19.3. Verknüpfungs- und Auswahlbed<strong>in</strong>gungen . . . . . . . . . . . . 176<br />
19.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 176<br />
19.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176<br />
19.6. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179<br />
Dieses Kapitel behandelt den „traditionellen“ Weg, mehrere Tabellen gleichzeitig<br />
abzufragen. Dazu werden <strong>in</strong> der FROM-Klausel alle Tabellen aufgeführt; die<br />
WHERE-Klausel enthält neben den Auswahlbed<strong>in</strong>gungen auch Verknüpfungsbed<strong>in</strong>gungen,<br />
wie die Tabellen zue<strong>in</strong>ander gehören.<br />
19.1. Alle Komb<strong>in</strong>ationen aller Datensätze<br />
Der e<strong>in</strong>fachste Weg, Tabellen zu verknüpfen, ist e<strong>in</strong> Befehl, <strong>in</strong> dem Spalten aus<br />
zwei Tabellen zusammengefasst werden – mit e<strong>in</strong>em seltsamen Ergebnis.<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi, Dienstwagen dw;<br />
MITNR NAME VORNAME DIW KENNZEICHEN Typ<br />
10001 Müller Kurt 1 DO-WB 421 14<br />
10002 Schneider Daniela 1 DO-WB 421 14<br />
20001 Meyer Walter 1 DO-WB 421 14<br />
20002 Schmitz Michael 1 DO-WB 421 14<br />
30001 Wagner Gaby 1 DO-WB 421 14<br />
30002 Feyerabend Werner 1 DO-WB 421 14<br />
40001 Langmann Matthias 1 DO-WB 421 14<br />
/* usw. */<br />
10001 Müller Kurt 2 DO-WB 422 14<br />
10002 Schneider Daniela 2 DO-WB 422 14<br />
20001 Meyer Walter 2 DO-WB 422 14<br />
20002 Schmitz Michael 2 DO-WB 422 14<br />
/* usw. */<br />
173
E<strong>in</strong>fache Tabellenverknüpfung<br />
Tatsächlich erzeugt dieser Befehl das „kartesische Produkt“ der beiden Tabellen:<br />
Jeder Datensatz der e<strong>in</strong>en Tabelle wird (mit den gewünschten Spalten) mit jedem<br />
Datensatz der anderen Tabelle verbunden. Das sieht also so aus, als wenn alle<br />
Dienstwagen zu jedem Mitarbeiter gehören würden, was natürlich Quatsch ist.<br />
Diese Variante ist also <strong>in</strong> aller Regel s<strong>in</strong>nlos (wenn auch syntaktisch korrekt).<br />
Nützlich ist sie nur dann, wenn auf e<strong>in</strong>fachem Wege große Mengen von Testdaten<br />
erzeugt werden sollen, wie es im Kapitel Testdaten erzeugen 1 benutzt wird.<br />
19.2. Tabellen e<strong>in</strong>fach verb<strong>in</strong>den<br />
S<strong>in</strong>nvoll wird die vorstehende Abfrage durch e<strong>in</strong>e kle<strong>in</strong>e Ergänzung. Was will<br />
man denn eigentlich wissen?<br />
Gib mir (e<strong>in</strong>ige) Spalten der Tabelle Mitarbeiter zusammen mit (e<strong>in</strong>igen)<br />
Spalten der Tabelle Dienstwagen, und zwar bei jedem Mitarbeiter<br />
denjenigen Dienstwagen, der zu diesem Mitarbeiter gehört.<br />
Woran erkennt man, zu welchem Mitarbeiter e<strong>in</strong> Dienstwagen gehört? Nun, <strong>in</strong><br />
der Tabelle Dienstwagen ist e<strong>in</strong>e Spalte Mitarbeiter_ID enthalten; dieser Wert ist<br />
identisch mit der ID e<strong>in</strong>es E<strong>in</strong>trags <strong>in</strong> der Tabelle Mitarbeiter. Wenn man diese<br />
Anfrage und Information <strong>in</strong> „Pseudocode“ übersetzt, kommt so etwas heraus:<br />
Hole Spalten der Tabelle Mitarbeiter<br />
sowie Spalten der Tabelle Dienstwagen<br />
wobei die Mitarbeiter_ID e<strong>in</strong>es Dienstwagens gleich ist der ID e<strong>in</strong>es Mitarbeiters<br />
Das können wir <strong>in</strong> e<strong>in</strong>e vollständige <strong>SQL</strong>-Abfrage übersetzen; die obige Abfrage<br />
muss nur m<strong>in</strong>imal erweitert werden:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi, Dienstwagen dw<br />
WHERE dw.Mitarbeiter_ID = mi.ID<br />
ORDER BY MitNr;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
100001 Grosser Horst 10 DO-WB 4210 14<br />
10001 Müller Kurt 1 DO-WB 421 14<br />
110001 Eggert Louis 11 DO-WB 4211 14<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14<br />
20001 Meyer Walter 2 DO-WB 422 14<br />
1 Kapitel 36 auf Seite 407<br />
174
30001 Wagner Gaby 3 DO-WB 423 14<br />
40001 Langmann Matthias 4 DO-WB 424 14<br />
50001 Pohl Helmut 5 DO-WB 425 14<br />
50002 Braun Christian 14 DO-WB 352 2<br />
50003 Polovic Frantisek 15 DO-WB 353 3<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4<br />
/* usw. */<br />
Tabellen e<strong>in</strong>fach verb<strong>in</strong>den<br />
Wir bekommen also tatsächlich genau diejenigen Mitarbeiter, die über e<strong>in</strong>en<br />
(persönlichen) Dienstwagen verfügen.<br />
H<strong>in</strong>weis: Wundern Sie sich nicht über die seltsame Reihenfolge. Die Personalnummer<br />
wurde als VARCHAR def<strong>in</strong>iert; also kommt das Ergebnis <strong>in</strong> alphabetischer<br />
und nicht <strong>in</strong> numerischer Reihenfolge.<br />
In der gleichen Weise können auch mehr als zwei Tabellen verknüpft werden. Im<br />
Kapitel Gruppierungen 2 steht e<strong>in</strong> Beispiel ähnlich wie dieses:<br />
Aufgabe: Gesucht wird für jeden Fahrzeughersteller (mit Angabe von ID und<br />
Name) und jedes Jahr die Summe der Schadenshöhe aus der Tabelle Schadensfall.<br />
SELECT fh.ID AS Hersteller_ID,<br />
fh.Name AS Hersteller_Name,<br />
EXTRACT(YEAR FROM sf.Datum) AS Jahr,<br />
SUM(sf.Schadenshoehe) AS Schadenssumme<br />
FROM Schadensfall sf, Zuordnung_SF_FZ zu,<br />
Fahrzeug fz, Fahrzeugtyp ft, Fahrzeughersteller fh<br />
WHERE sf.ID = zu.Schadensfall_ID<br />
AND fz.ID = zu.Fahrzeug_ID<br />
AND ft.ID = fz.Fahrzeugtyp_ID<br />
AND fh.ID = ft.Hersteller_ID<br />
GROUP BY Hersteller_ID, Hersteller_Name, Jahr<br />
ORDER BY Jahr, Hersteller_ID;<br />
Wichtig ist, dass es immer e<strong>in</strong>e e<strong>in</strong>e<strong>in</strong>deutige Zuordnung zwischen je e<strong>in</strong>er Spalte<br />
e<strong>in</strong>er Tabelle und e<strong>in</strong>er Spalte e<strong>in</strong>er anderen Tabelle gibt. Bitte beachten Sie:<br />
• Statt e<strong>in</strong>er e<strong>in</strong>zigen Spalte kann auch e<strong>in</strong>e Gruppe von Spalten verknüpft werden<br />
(z. B. Name + Vorname). Dies macht aber alles umständlicher, unübersichtlicher<br />
und unsicherer. Deshalb sollte vorzugsweise über e<strong>in</strong>deutige IDs<br />
o. ä. verknüpft werden.<br />
• Wenn es zwischen den Tabellen ke<strong>in</strong>e „geme<strong>in</strong>samen“ Spalten gibt, kommt<br />
wieder das kartesische Produkt heraus; das Ergebnis ist dann eher s<strong>in</strong>nlos.<br />
2 Kapitel 25 auf Seite 241<br />
175
E<strong>in</strong>fache Tabellenverknüpfung<br />
19.3. Verknüpfungs- und Auswahlbed<strong>in</strong>gungen<br />
Je mehr Komb<strong>in</strong>ationen benötigt werden, desto unübersichtlicher wird diese<br />
Konstruktion. Dabei enthält die WHERE-Klausel bisher nur die Verknüpfungen<br />
zwischen den Tabellen, aber noch ke<strong>in</strong>e Suchbed<strong>in</strong>gungen wie hier:<br />
SELECT ... FROM ... WHERE ...<br />
AND Jahr IN [2006, 2007, 2008]<br />
AND fhe.Land <strong>in</strong> [’Schweden’, ’Norwegen’, ’F<strong>in</strong>nland’]<br />
ORDER BY Jahr, Hersteller_ID;<br />
Das führt außerdem dazu, dass die WHERE-Klausel sachlich gewünschte Suchbed<strong>in</strong>gungen<br />
und logisch benötigte Verknüpfungsbed<strong>in</strong>gungen vermischt. Wer<br />
soll da noch durchblicken? Besser ist das <strong>in</strong> den nächsten Kapiteln ausführlich<br />
behandelte Verfahren mit JOIN.<br />
19.4. Zusammenfassung<br />
Dieses Kapitel erläutert, wie mehrere Tabellen e<strong>in</strong>fach durch die FROM-Klausel<br />
und passende WHERE-Bed<strong>in</strong>gungen verknüpft werden können:<br />
• In der Spaltenliste sollte immer der jeweilige Tabellenname angegeben werden;<br />
es kann auch e<strong>in</strong> Kürzel als Tabellen-Alias verwendet werden.<br />
• In der FROM-Klausel werden alle Tabellen aufgelistet und <strong>in</strong> der WHERE-<br />
Klausel durch geeignete Bed<strong>in</strong>gungen aufe<strong>in</strong>ander bezogen.<br />
• Durch die Vermischung zwischen Verknüpfungs- und Auswahlbed<strong>in</strong>gungen<br />
wird dieses Verfahren schnell unübersichtlich.<br />
19.5. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 177.<br />
Bei den folgenden Abfragen beziehen wir uns auf die Beispieldatenbank im „Anfangszustand“:<br />
die Tabellen Versicherungsvertrag, Fahrzeug, Mitarbeiter mit jeweils<br />
etwa 28 E<strong>in</strong>trägen und Versicherungsnehmer mit etwa 26 E<strong>in</strong>trägen.<br />
Übung 1 – E<strong>in</strong>e e<strong>in</strong>fache Abfrage<br />
Erstellen Sie e<strong>in</strong>e Abfrage zur Tabelle Versicherungsvertrag, die nur die wichtigsten<br />
Informationen (e<strong>in</strong>schließlich der IDs auf andere Tabellen) enthält. Wie viele<br />
E<strong>in</strong>träge zeigt die Ergebnismenge an?<br />
176
Übung 2 – Das kartesische Produkt<br />
Übungen<br />
Erweitern Sie die Abfrage von Aufgabe 1, sodass anstelle der Versicherungsnehmer_ID<br />
dessen Name und Vorname angezeigt werden, und verzichten Sie auf<br />
e<strong>in</strong>e WHERE-Klausel. Wie viele E<strong>in</strong>träge zeigt die Ergebnismenge an?<br />
Übung 3 – Das kartesische Produkt<br />
Erweitern Sie die Abfrage von Aufgabe 2, sodass anstelle der Fahrzeug_ID das<br />
Kennzeichen und anstelle der Mitarbeiter_ID dessen Name und Vorname angezeigt<br />
werden, und verzichten Sie auf e<strong>in</strong>e WHERE-Klausel. Wie viele E<strong>in</strong>träge<br />
zeigt die Ergebnismenge an?<br />
Übung 4 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Erweitern Sie die Abfrage von Aufgabe 2, sodass Name und Vorname des Versicherungsnehmers<br />
genau zu e<strong>in</strong>em jeden Vertrag passen. Wie viele E<strong>in</strong>träge zeigt<br />
die Ergebnismenge an?<br />
Übung 5 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Erweitern Sie die Abfrage von Aufgabe 3, sodass Name und Vorname des Mitarbeiters<br />
sowie das Fahrzeug-Kennzeichen genau zu e<strong>in</strong>em jeden Vertrag passen.<br />
Wie viele E<strong>in</strong>träge zeigt die Ergebnismenge an?<br />
Übung 6 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Erweitern Sie die Abfrage von Aufgabe 5, sodass die ausgewählten Zeilen den<br />
folgenden Bed<strong>in</strong>gungen entsprechen:<br />
• Es geht ausschließlich um Eigene Kunden.<br />
• Vollkasko-Verträge sollen immer angezeigt werden, ebenso Fahrzeuge aus dem<br />
Kreis Reckl<strong>in</strong>ghausen 'RE'.<br />
• Teilkasko-Verträge sollen angezeigt werden, wenn sie nach 1990 abgeschlossen<br />
wurden.<br />
• Haftpflicht-Verträge sollen angezeigt werden, wenn sie nach 1985 abgeschlossen<br />
wurden.<br />
Wie viele E<strong>in</strong>träge zeigt die Ergebnismenge an?<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 176.<br />
177
E<strong>in</strong>fache Tabellenverknüpfung<br />
Lösung zu Übung 1 – E<strong>in</strong>e e<strong>in</strong>fache Abfrage<br />
Es werden 28 Zeilen angezeigt.<br />
SELECT Vertragsnummer, Abschlussdatum, Art,<br />
Versicherungsnehmer_ID, Fahrzeug_ID, Mitarbeiter_ID<br />
FROM Versicherungsvertrag;<br />
Lösung zu Übung 2 – Das kartesische Produkt<br />
Es werden etwa 728 Zeilen angezeigt.<br />
SELECT vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
vn.Name, vn.Vorname,<br />
Fahrzeug_ID,<br />
Mitarbeiter_ID<br />
FROM Versicherungsvertrag vv, Versicherungsnehmer vn;<br />
Lösung zu Übung 3 – Das kartesische Produkt<br />
Es werden etwa 570 752 Zeilen angezeigt.<br />
SELECT vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
vn.Name, vn.Vorname,<br />
fz.Kennzeichen,<br />
mi.Name, mi.Vorname<br />
FROM Versicherungsvertrag vv, Versicherungsnehmer vn,<br />
Fahrzeug fz, Mitarbeiter mi;<br />
Lösung zu Übung 4 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Es werden etwa 28 Zeilen angezeigt.<br />
SELECT vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
vn.Name, vn.Vorname,<br />
Fahrzeug_ID,<br />
Mitarbeiter_ID<br />
FROM Versicherungsvertrag vv, Versicherungsnehmer vn<br />
WHERE vn.ID = vv.Versicherungsnehmer_ID;<br />
Lösung zu Übung 5 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Es werden etwa 28 Zeilen angezeigt.<br />
178<br />
SELECT vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
vn.Name, vn.Vorname,
fz.Kennzeichen,<br />
mi.Name, mi.Vorname<br />
FROM Versicherungsvertrag vv, Versicherungsnehmer vn,<br />
Fahrzeug fz, Mitarbeiter mi<br />
WHERE vn.ID = vv.Versicherungsnehmer_ID<br />
AND fz.ID = vv.Fahrzeug_ID<br />
AND mi.ID = vv.Mitarbeiter_ID;<br />
Lösung zu Übung 6 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Siehe auch<br />
Es werden etwa 19 Zeilen angezeigt. Die OR-Verknüpfungen könnten teilweise<br />
auch mit CASE geschrieben werden.<br />
SELECT vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
vn.Name, vn.Vorname,<br />
fz.Kennzeichen,<br />
mi.Name, mi.Vorname<br />
FROM Versicherungsvertrag vv, Versicherungsnehmer vn,<br />
Fahrzeug fz, Mitarbeiter mi<br />
WHERE vn.ID = vv.Versicherungsnehmer_ID<br />
AND fz.ID = vv.Fahrzeug_ID<br />
AND mi.ID = vv.Mitarbeiter_ID<br />
AND vn.Eigener_kunde = ’J’<br />
AND ( ( vv.Art = ’HP’ AND vv.Abschlussdatum > ’31.12.1985’ )<br />
OR ( vv.Art = ’TK’ AND vv.Abschlussdatum > ’31.12.1990’ )<br />
OR ( vv.Art = ’VK’ )<br />
OR ( fz.Kennzeichen STARTING WITH ’RE-’ ) );<br />
19.6. Siehe auch<br />
Bei Wikipedia stehen weitere H<strong>in</strong>weise:<br />
• Kartesisches Produkt 3<br />
• Pseudocode 4<br />
3 http://de.wikipedia.<strong>org</strong>/wiki/Kartesisches%20Produkt<br />
4 http://de.wikipedia.<strong>org</strong>/wiki/Pseudocode<br />
179
20. Arbeiten mit JOIN<br />
20.1. Die Syntax von JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . 181<br />
20.2. INNER JOIN von zwei Tabellen . . . . . . . . . . . . . . . . . . . 182<br />
20.3. WHERE-Klausel bei JOINs . . . . . . . . . . . . . . . . . . . . . . 183<br />
20.4. INNER JOIN mehrerer Tabellen . . . . . . . . . . . . . . . . . . . 185<br />
20.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 185<br />
20.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186<br />
20.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189<br />
Dieses Kapitel enthält die <strong>E<strong>in</strong>führung</strong> IN den „modernen“ Weg, mehrere Tabellen<br />
gleichzeitig abzufragen. Dazu wird jede verknüpfte Tabelle IN e<strong>in</strong>er JOIN-<br />
Klausel aufgeführt; der ON-Parameter enthält die Verknüpfungsbed<strong>in</strong>gung. Die<br />
WHERE-Klausel enthält „nur“ die Auswahlbed<strong>in</strong>gungen.<br />
20.1. Die Syntax von JOIN<br />
Um Tabellen s<strong>in</strong>nvoll mite<strong>in</strong>ander zu verknüpfen (= verb<strong>in</strong>den, engl. JOIN), wurde<br />
die JOIN-Klausel für den SELECT-Befehl mit folgender Syntax e<strong>in</strong>geführt.<br />
SELECT <br />
FROM <br />
[] JOIN ON <br />
Als stehen zur Verfügung:<br />
• [INNER] JOIN, auch Equi-Jo<strong>in</strong> genannt, ist e<strong>in</strong>e Verknüpfung „<strong>in</strong>nerhalb“<br />
zweier Tabellen, d. h. e<strong>in</strong> Teil des kartesischen Produkts, bei dem e<strong>in</strong> Wert IN<br />
beiden Tabellen vorhanden ist. INNER JOIN ist der Inhalt dieses Kapitels.<br />
• E<strong>in</strong> JOIN ohne nähere Angabe ist immer e<strong>in</strong> INNER JOIN.<br />
• OUTER JOIN bezeichnet Verknüpfungen, bei denen auch Datensätze geliefert<br />
werden, für die e<strong>in</strong>e Auswahlbed<strong>in</strong>gung nicht erfüllt ist.<br />
• LEFT JOIN, RIGHT JOIN, FULL JOIN bezeichnen Spezialfälle von OUTER<br />
JOIN, je nachdem IN welcher Tabelle e<strong>in</strong> gesuchter Wert fehlt.<br />
OUTER JOIN wird im nächsten Kapitel 1 behandelt.<br />
1 Kapitel 21 auf Seite 191<br />
181
Arbeiten mit JOIN<br />
E<strong>in</strong>ige Sonderfälle und Ergänzungen zu JOIN werden im Kapitel Mehr zu JOIN 2<br />
behandelt.<br />
Als wird normalerweise nur e<strong>in</strong>e Übere<strong>in</strong>stimmung (also e<strong>in</strong>e<br />
Gleichheit) zwischen zwei Tabellen geprüft, auch wenn jede Komb<strong>in</strong>ation von<br />
Bed<strong>in</strong>gungen erlaubt ist. Genauer: es geht um die Gleichheit von Werten je e<strong>in</strong>er<br />
Spalte IN zwei Tabellen. (Zwei Beispiele für andere Übere<strong>in</strong>stimmungen lernen<br />
Sie IN „Mehr zu JOIN“ kennen.)<br />
Auch mehrere Verknüpfungen s<strong>in</strong>d möglich, entweder direkt h<strong>in</strong>tere<strong>in</strong>ander:<br />
SELECT <br />
FROM <br />
[] JOIN ON <br />
[] JOIN ON <br />
[] JOIN ON <br />
oder durch Klammern gegliedert:<br />
SELECT <br />
FROM <br />
[] JOIN<br />
( <br />
[] JOIN<br />
( <br />
[] JOIN ON <br />
) ON <br />
) ON <br />
Bitte beachten Sie dabei genau, wo und wie die Klammern und die dazugehörigen<br />
ON-Bed<strong>in</strong>gungen gesetzt werden. Beide Varianten können unterschiedliche<br />
Ergebnisse liefern − abhängig vom JOIN-Typ und dem Zusammenhang zwischen<br />
den Tabellen.<br />
All dies wird IN den nächsten Abschnitten und Kapiteln genauer erläutert.<br />
20.2. INNER JOIN von zwei Tabellen<br />
Aufgabe: Das Beispiel „alle Mitarbeiter mit den zugehörigen Dienstwagen“<br />
aus dem vorigen Kapitel benötigt nur ger<strong>in</strong>ge Änderungen. (Das Ergebnis stimmt<br />
mit der Liste dort übere<strong>in</strong>; wir verzichten deshalb auf die erneute Ausgabe.)<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
2 Kapitel 22 auf Seite 203<br />
182
dw.ID, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
JOIN Dienstwagen dw ON dw.Mitarbeiter_ID = mi.ID<br />
ORDER BY MitNr;<br />
WHERE-Klausel bei JOINs<br />
Die zweite Tabelle wird IN die JOIN-Klausel verschoben, die Verknüpfungsbed<strong>in</strong>gung<br />
IN den ON-Parameter − fertig.<br />
20.3. WHERE-Klausel bei JOINs<br />
E<strong>in</strong>e solche Abfrage kann wie üblich durch e<strong>in</strong>e WHERE-Klausel e<strong>in</strong>geschränkt<br />
werden. E<strong>in</strong>e Suchbed<strong>in</strong>gung auf die verknüpfte Tabelle Dienstwagen kann<br />
wahlweise IN der WHERE-Klausel oder IN der JOIN-Klausel stehen. In den beiden<br />
folgenden Beispielen geht es nur um die Dienstwagen von Mercedes. Die<br />
Information, welche Typen zu Mercedes gehören, kommt über e<strong>in</strong>e Unterabfrage,<br />
die ebenfalls e<strong>in</strong>en JOIN verwendet und die IN Klammern gesetzt ist.<br />
Aufgabe: Suche die Dienstwagen vom Typ Mercedes.<br />
<strong>SQL</strong>-Quelltext Falsch<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
JOIN Dienstwagen dw<br />
ON mi.ID = dw.Mitarbeiter_ID<br />
AND dw.Fahrzeugtyp_ID IN ( SELECT ft.ID<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh<br />
ON ft.Hersteller_ID = fh.ID<br />
AND fh.Name = ’Mercedes-Benz’ );<br />
Zulässig, aber nicht so schön, weil Verknüpfungs- und Auswahlbed<strong>in</strong>gung vermischt werden<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
JOIN Dienstwagen dw<br />
ON mi.ID = dw.Mitarbeiter_ID<br />
WHERE dw.Fahrzeugtyp_ID IN ( SELECT ft.ID<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh<br />
ON ft.Hersteller_ID = fh.ID<br />
WHERE fh.Name = ’Mercedes-Benz’);<br />
Besseres V<strong>org</strong>ehen, weil die Auswahlbed<strong>in</strong>gungen als solche direkt zu erkennen s<strong>in</strong>d<br />
Natürlich s<strong>in</strong>d E<strong>in</strong>schränkungen auf beide Tabellen möglich:<br />
183
Arbeiten mit JOIN<br />
Aufgabe: Gesucht werden Mitarbeiter mit 'M' und Mercedes als Dienstwagen.<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
JOIN Dienstwagen dw<br />
ON mi.ID = dw.Mitarbeiter_ID<br />
WHERE dw.Fahrzeugtyp_ID IN ( SELECT ft.ID<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh<br />
ON ft.Hersteller_ID = fh.ID<br />
WHERE fh.Name = ’Mercedes-Benz’)<br />
AND mi.Name like ’M%’;<br />
MITNR NAME VORNAME ID KENNZEICHEN TYP<br />
10001 Müller Kurt 1 DO-WB 421 14<br />
20001 Meyer Walter 2 DO-WB 422 14<br />
Hier wird sofort deutlich, welche Bed<strong>in</strong>gungen die Verknüpfung und welche die<br />
Auswahl bezeichnen. Auf diese Übersichtlichkeit sollten Sie immer achten.<br />
Übrigens gibt es ke<strong>in</strong>e allgeme<strong>in</strong>e Regel, was als Haupttabelle und was als verknüpfte<br />
Tabelle zu verwenden ist. In den bisherigen Beispielen können die beiden<br />
Tabellen ohne Weiteres vertauscht werden:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Dienstwagen dw<br />
JOIN Mitarbeiter mi<br />
ON mi.ID = dw.Mitarbeiter_ID<br />
WHERE dw.Fahrzeugtyp_ID IN ( SELECT ft.ID<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh<br />
ON ft.Hersteller_ID = fh.ID<br />
WHERE fh.Name = ’Mercedes-Benz’)<br />
AND mi.Name like ’M%’;<br />
Mitarbeiter mit 'M' und Mercedes als Dienstwagen<br />
Die Haupttabelle kann nach folgenden Überlegungen gewählt werden:<br />
• Es sollte die Tabelle se<strong>in</strong>, die die „wichtigste“ bei der Abfrage ist.<br />
• Es sollte diejenige mit den größten E<strong>in</strong>schränkungen se<strong>in</strong>; das beschleunigt<br />
die Abfrage besonders stark.<br />
184
20.4. INNER JOIN mehrerer Tabellen<br />
INNER JOIN mehrerer Tabellen<br />
Dazu nehmen wir wiederum das komplexe Beispiel aus dem vorigen Kapitel,<br />
das bei den Gruppierungen genauer besprochen wird. In diesem Fall spielt die<br />
Reihenfolge der JOIN-Klauseln eher ke<strong>in</strong>e Rolle, weil es sich sowieso um direkte<br />
Übere<strong>in</strong>stimmungen handelt und nur solche Datensätze benutzt werden, die es<br />
zu den betreffenden Werten tatsächlich gibt.<br />
Aufgabe: Gesucht wird für jeden Fahrzeughersteller (mit Angabe von ID und<br />
Name) und jedes Jahr die Summe der Schadenshöhe aus der Tabelle Schadensfall.<br />
SELECT fh.ID AS Hersteller_ID,<br />
fh.Name AS Hersteller_Name,<br />
EXTRACT(YEAR FROM sf.Datum) AS Jahr,<br />
SUM(sf.Schadenshoehe) AS Schadenssumme<br />
FROM Schadensfall sf<br />
JOIN Zuordnung_SF_FZ zu ON sf.ID = zu.Schadensfall_ID<br />
JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
JOIN Fahrzeugtyp ft ON ft.ID = fz.Fahrzeugtyp_ID<br />
JOIN Fahrzeughersteller fh ON fh.ID = ft.Hersteller_ID<br />
GROUP BY Hersteller_ID, Hersteller_Name, Jahr<br />
ORDER BY Jahr, Hersteller_ID;<br />
Übrigens dürfen der „traditionelle“ Weg (mehrere Tabellen <strong>in</strong> der FROM-<br />
Klausel) und der „moderne“ Weg über JOIN gemischt werden. Wenn Sie wirklich<br />
ausnahmsweise e<strong>in</strong>mal so vorfahren wollen, sollten Sie erst recht die Übersichtlichkeit<br />
und den Zusammenhang der Bed<strong>in</strong>gungen beachten. Wir können uns<br />
ke<strong>in</strong>e passende Situation vorstellen, aber vielleicht ist es doch e<strong>in</strong>mal s<strong>in</strong>nvoll.<br />
20.5. Zusammenfassung<br />
Dieses Kapitel erklärte die Verknüpfung von Tabellen über die JOIN-Klausel.<br />
• Mit e<strong>in</strong>em INNER JOIN werden Datensätze abgefragt, bei denen e<strong>in</strong> Wert <strong>in</strong> je<br />
e<strong>in</strong>er Spalte beider Tabellen vorhanden ist.<br />
• In der ON-Klausel steht diese Verknüpfungsbed<strong>in</strong>gung.<br />
• In der WHERE-Klausel stehen die „normalen“ Auswahlbed<strong>in</strong>gungen.<br />
Genauso können mehrere Tabellen verknüpft werden.<br />
185
Arbeiten mit JOIN<br />
20.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 187.<br />
Übung 1 – Def<strong>in</strong>ition von JOINs<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche falsch, welche s<strong>in</strong>nvoll?<br />
1. Der INNER JOIN liefert das kartesische Produkt zwischen den Tabellen.<br />
2. LEFT JOIN ist e<strong>in</strong> Spezialfall von OUTER JOIN.<br />
3. Für e<strong>in</strong>en JOIN ist dies e<strong>in</strong>e zulässige Verknüpfungsbed<strong>in</strong>gung:<br />
ON Fahrzeug.ID >= Versicherungsvertrag.Fahrzeug_ID<br />
4. E<strong>in</strong>e E<strong>in</strong>schränkung auf die mit JOIN verknüpfte Tabelle gehört <strong>in</strong> die ON-<br />
Klausel:<br />
... FROM Zuordnung_SF_FZ zu<br />
JOIN Schadensfall sf<br />
ON sf.ID = zu.Schadensfall_ID AND EXTRACT(YEAR FROM sf.Datum) = 2008;<br />
Übung 2 – Def<strong>in</strong>ition von JOINs<br />
Erläutern Sie, was am folgenden Befehl falsch oder äußerst ungünstig ist. Es handelt<br />
sich um diese Abfrage:<br />
Aufgabe: Gesucht s<strong>in</strong>d die Schadensfälle des Jahres 2008. Zu jedem Schadensfall<br />
s<strong>in</strong>d die beteiligten Fahrzeuge, der Schadensanteil sowie die Versicherungsdaten<br />
des Fahrzeugs (e<strong>in</strong>schließlich Name des Halters) anzugeben.<br />
1. SELECT Datum, SUBSTRING(Ort FROM 1 FOR 30) AS Ort, Schadenshoehe,<br />
2. zu.Schadenshoehe,<br />
3. fz.Kennzeichen,<br />
4. Vertragsnummer AS Vertrag, Abschlussdatum, Art,<br />
5. vn.Name AS VN-Name, vn.Vorname AS VN-Vorname<br />
6. FROM Schadensfall sf<br />
7. JOIN Zuordnung_SF_FZ zu ON ID = zu.Schadensfall_ID<br />
8. JOIN Fahrzeug fz ON ID = zu.Fahrzeug_ID<br />
9. JOIN Versicherungsnehmer vn ON ID = vv.Versicherungsnehmer_ID<br />
10. JOIN Versicherungsvertrag vv ON vv.Fahrzeug_ID = zu.Fahrzeug_ID<br />
11. WHERE EXTRACT(YEAR FROM Datum) = 2008<br />
12. ORDER BY Schadensfall_ID, Fahrzeug_ID;<br />
Die folgenden Aufgaben entsprechen teilweise Aufgaben aus dem Kapitel „E<strong>in</strong>fache<br />
Tabellenverknüpfung“. Sie sollen jetzt an den passenden Stellen JOINs verwenden,<br />
anstatt die Tabellen e<strong>in</strong>fach aufzulisten.<br />
186
Übung 3 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Übungen<br />
Erstellen Sie e<strong>in</strong>e Abfrage zur Tabelle Versicherungsvertrag mit den wichtigsten<br />
Informationen (e<strong>in</strong>schließlich der IDs auf andere Tabellen). Beim Versicherungsnehmer<br />
sollen dessen Name und Vorname angezeigt werden. Es werden nur Verträge<br />
ab 1990 gesucht.<br />
Übung 4 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Erweitern Sie die Abfrage von Aufgabe 3, sodass Name und Vorname des Mitarbeiters<br />
sowie das Fahrzeug-Kennzeichen e<strong>in</strong>es jeden Vertrags angezeigt werden.<br />
Übung 5 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
Ändern Sie die Abfrage von Aufgabe 4 so, dass die ausgewählten Zeilen den folgenden<br />
Bed<strong>in</strong>gungen entsprechen:<br />
• Es geht ausschließlich um Eigene Kunden.<br />
• Vollkasko-Verträge sollen immer angezeigt werden, ebenso Fahrzeuge aus dem<br />
Kreis Reckl<strong>in</strong>ghausen 'RE'.<br />
• Teilkasko-Verträge sollen angezeigt werden, wenn sie nach 1990 abgeschlossen<br />
wurden.<br />
• Haftpflicht-Verträge sollen angezeigt werden, wenn sie nach 1985 abgeschlossen<br />
wurden.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 186.<br />
Lösung zu Übung 1 – Def<strong>in</strong>ition von JOINs<br />
1. Falsch; es liefert e<strong>in</strong>en Teil des kartesischen Produkts, der durch die ON-<br />
Bed<strong>in</strong>gung bestimmt wird.<br />
2. Richtig.<br />
3. Diese Bed<strong>in</strong>gung ist zulässig, aber nicht s<strong>in</strong>nvoll. JOIN-ON passt <strong>in</strong> der Regel<br />
nur für Gleichheiten.<br />
4. Diese Bed<strong>in</strong>gung ist zulässig. Besser ist es aber, e<strong>in</strong>e E<strong>in</strong>schränkung der<br />
Auswahl <strong>in</strong> die WHERE-Klausel zu setzen.<br />
187
Arbeiten mit JOIN<br />
Lösung zu Übung 2 – Def<strong>in</strong>ition von JOINs<br />
Richtig ist beispielsweise die folgende Version. Als Haupttabelle wurde wegen<br />
der WHERE-Klausel die Tabelle Schadensfall gewählt; wegen der Reihenfolge der<br />
Verknüpfungen wäre auch Zuordnung_SF_FZ als Haupttabelle geeignet.<br />
SELECT sf.Datum, SUBSTRING(sf.Ort FROM 1 FOR 30) AS Ort, sf.Schadenshoehe,<br />
zu.Schadenshoehe AS Teilschaden,<br />
fz.Kennzeichen,<br />
vv.Vertragsnummer AS Vertrag, vv.Abschlussdatum, vv.Art,<br />
vn.Name AS VN_Name, vn.Vorname AS VN_Vorname<br />
FROM Schadensfall sf<br />
JOIN Zuordnung_SF_FZ zu ON sf.ID = zu.Schadensfall_ID<br />
JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
JOIN Versicherungsvertrag vv ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
WHERE EXTRACT(YEAR FROM sf.Datum) = 2008<br />
ORDER BY zu.Schadensfall_ID, zu.Fahrzeug_ID;<br />
Die Variante aus der Aufgabenstellung enthält folgende Problemstellen:<br />
• Zeile 1: Der Tabellen-Alias sf fehlt bei Schadenshoehe und bei Ort. Bei Datum<br />
fehlt er auch, aber das ist möglich, weil es sie nur bei dieser Tabelle gibt.<br />
• Zeile 2: Diese Spalte sollte e<strong>in</strong>en Spalten-Alias bekommen wegen der abweichenden<br />
Bedeutung zu sf.Schadenshoehe.<br />
• Zeile 4: Es ist schöner, auch hier mit e<strong>in</strong>em Tabellen-Alias zu arbeiten.<br />
• Zeile 5: Der B<strong>in</strong>destrich <strong>in</strong> der Bezeichnung des Spalten-Alias wird nicht bei<br />
allen DBMS akzeptiert.<br />
• Zeile 7, 8, 9: Zur Spalte ID ist jeweils die Tabelle anzugeben, ggf. mit dem Alias.<br />
Die JOIN-ON-Bed<strong>in</strong>gung bezieht sich nicht automatisch auf diese Spalte und<br />
diese Tabelle.<br />
• Zeile 9, 10: In Zeile 9 ist die Tabelle Versicherungsvertrag vv noch nicht bekannt.<br />
Wegen der Verknüpfungen ist zuerst Zeile 10 zu verwenden, danach Zeile 9.<br />
Die Verknüpfung über vv.Fahrzeug_ID = zu.Fahrzeug_ID ist nicht glücklich<br />
(wenn auch korrekt); besser ist der Bezug auf die direkt zugeordnete Tabelle<br />
Fahrzeug und deren PrimaryKey, nämlich ID.<br />
• Zeile 11: Es ist klarer, auch hier den Tabellen-Alias sf zu verwenden.<br />
• Zeile 12: Der Tabellen-Alias zu fehlt bei beiden Spalten. Bei Fahrzeug_ID ist er<br />
erforderlich (doppelte Verwendung bei vv), bei Schadensfall_ID s<strong>in</strong>nvoll.<br />
Lösung zu Übung 3 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
188<br />
SELECT Vertragsnummer, Abschlussdatum, Art,<br />
Name, Vorname,<br />
Fahrzeug_ID,
Mitarbeiter_ID<br />
FROM Versicherungsvertrag vv<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
WHERE vv.Abschlussdatum >= ’01.01.1990’;<br />
Lösung zu Übung 4 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
SELECT vv.Vertragsnummer AS Vertrag, vv.Abschlussdatum, vv.Art,<br />
vn.Name AS VN_Name, vn.Vorname AS VN_Vorname,<br />
fz.Kennzeichen,<br />
mi.Name AS MI_Name, mi.Vorname AS MI_Vorname<br />
FROM Versicherungsvertrag vv<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Mitarbeiter mi ON mi.ID = vv.Mitarbeiter_ID<br />
WHERE vv.Abschlussdatum >= ’01.01.1990’;<br />
Lösung zu Übung 5 – S<strong>in</strong>nvolle Verknüpfung von Tabellen<br />
SELECT vv.Vertragsnummer AS Vertrag, vv.Abschlussdatum, vv.Art,<br />
vn.Name AS VN_Name, vn.Vorname AS VN_Vorname,<br />
fz.Kennzeichen,<br />
mi.Name AS MI_Name, mi.Vorname AS MI_Vorname<br />
FROM Versicherungsvertrag vv<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Mitarbeiter mi ON mi.ID = vv.Mitarbeiter_ID<br />
WHERE vn.Eigener_kunde = ’J’<br />
AND ( ( vv.Art = ’HP’ AND vv.Abschlussdatum > ’31.12.1985’ )<br />
OR ( vv.Art = ’TK’ AND vv.Abschlussdatum > ’31.12.1990’ )<br />
OR ( vv.Art = ’VK’ )<br />
OR ( fz.Kennzeichen STARTING WITH ’RE-’ ) );<br />
20.7. Siehe auch<br />
Siehe auch<br />
In diesem Kapitel werden Sachverhalte der folgenden Themen angesprochen:<br />
• E<strong>in</strong>fache Tabellenverknüpfung 3<br />
• Unterabfragen 4<br />
• Gruppierungen 5<br />
3 Kapitel 19 auf Seite 173<br />
4 Kapitel 26 auf Seite 251<br />
5 Kapitel 25 auf Seite 241<br />
189
21. OUTER JOIN<br />
21.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise . . . . . . . . . . . . . . . . . . . . . . . . . 191<br />
21.2. LEFT OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . 192<br />
21.3. RIGHT OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . 193<br />
21.4. FULL OUTER JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . 194<br />
21.5. Verknüpfung mehrerer Tabellen . . . . . . . . . . . . . . . . . . 195<br />
21.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 198<br />
21.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199<br />
Bei den Abfragen im vorigen Kapitel nach „alle Mitarbeiter und ihre Dienstwagen“<br />
werden nicht alle Mitarbeiter aufgeführt, weil <strong>in</strong> der Datenbank nicht für<br />
alle Mitarbeiter e<strong>in</strong> Dienstwagen registriert ist. Ebenso gibt es e<strong>in</strong>en Dienstwagen,<br />
der ke<strong>in</strong>em bestimmten Mitarbeiter zugeordnet ist.<br />
Mit e<strong>in</strong>em OUTER JOIN werden auch Mitarbeiter ohne Dienstwagen oder<br />
Dienstwagen ohne Mitarbeiter aufgeführt.<br />
21.1. Allgeme<strong>in</strong>e H<strong>in</strong>weise<br />
Die Syntax entspricht derjenigen von JOIN allgeme<strong>in</strong>. Wegen der speziellen Bedeutung<br />
s<strong>in</strong>d die Tabellen nicht gleichberechtigt, sondern werden begrifflich unterschieden:<br />
SELECT <br />
FROM <br />
[] JOIN ON <br />
Als Spezialfälle des OUTER JOIN gibt es die JOIN-Typen LEFT JOIN, RIGHT<br />
JOIN, FULL JOIN.<br />
Anstelle von und wird bei OUTER JOIN von<br />
und gesprochen, um die unterschiedliche Bedeutung<br />
zu verdeutlichen.<br />
Das Wort OUTER kann entfallen und wird meistens nicht benutzt, weil durch die<br />
Begriffe LEFT, RIGHT, FULL bereits e<strong>in</strong> OUTER JOIN gekennzeichnet wird.<br />
191
OUTER JOIN<br />
Die Begriffe und beziehen sich auf die beiden<br />
Tabellen bezüglich der normalen Lesefolge: Wir lesen von l<strong>in</strong>ks nach rechts,<br />
also ist die unter FROM genannte Tabelle die (bisher <br />
genannt) und die unter JOIN genannte Tabelle die <br />
(bisher genannt). Bei Verknüpfungen mit mehreren Tabellen ist<br />
ebenfalls die unter JOIN genannte Tabelle die ; die unmittelbar<br />
vorhergehende Tabelle ist die .<br />
Auch wenn die Beispiele so aussehen, als wenn die Datensätze s<strong>in</strong>nvoll sortiert<br />
wären, ist das Zufall; bitte denken Sie daran, dass <strong>SQL</strong> ungeordnete Datenmengen<br />
liefert. E<strong>in</strong>e bestimmte Sortierung erhalten Sie erst durch ORDER BY.<br />
Die Anzeige ist <strong>in</strong> der Regel nur e<strong>in</strong> Auszug des vollständigen Ergebnisses.<br />
21.2. LEFT OUTER JOIN<br />
Dieser JOIN liefert alle Datensätze der l<strong>in</strong>ken Tabelle, ggf. unter Berücksichtigung<br />
der WHERE-Klausel. Aus der rechten Tabelle werden nur diejenigen Datensätze<br />
übernommen, die nach der Verknüpfungsbed<strong>in</strong>gung passen.<br />
SELECT <br />
FROM <br />
LEFT [OUTER] JOIN ON ;<br />
Für unser Beispiel sieht das dann so aus:<br />
Aufgabe: Hole alle Mitarbeiter und (sofern vorhanden) die Angaben zum<br />
Dienstwagen.<br />
192<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
LEFT JOIN Dienstwagen dw ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
30001 Wagner Gaby 3 DO-WB 423 14<br />
30002 Feyerabend Werner<br />
40001 Langmann Matthias 4 DO-WB 424 14<br />
40002 Peters Michael<br />
50001 Pohl Helmut 5 DO-WB 425 14<br />
50002 Braun Christian 14 DO-WB 352 2<br />
50003 Polovic Frantisek 15 DO-WB 353 3<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4<br />
60001 Aagenau Karol<strong>in</strong> 6 DO-WB 426 14<br />
60002 P<strong>in</strong>kart Petra
Und wenn wir jetzt die beiden Tabellen vertauschen?<br />
RIGHT OUTER JOIN<br />
Dann erhalten wir alle Dienstwagen und dazu die passenden Mitarbeiter.<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14<br />
90001 Janssen Bernhard 9 DO-WB 429 14<br />
100001 Grosser Horst 10 DO-WB 4210 14<br />
110001 Eggert Louis 11 DO-WB 4211 14<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14<br />
13 DO-WB 111 16<br />
50002 Braun Christian 14 DO-WB 352 2<br />
50003 Polovic Frantisek 15 DO-WB 353 3<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4<br />
Bitte überlegen Sie selbst, wie sich WHERE-Klauseln auf das Ergebnis e<strong>in</strong>er Abfrage<br />
auswirken.<br />
21.3. RIGHT OUTER JOIN<br />
Dieser JOIN liefert alle Datensätze der rechten Tabelle, ggf. unter Berücksichtigung<br />
der WHERE-Klausel. Aus der l<strong>in</strong>ken Tabelle werden nur diejenigen Datensätze<br />
übernommen, die nach der Verknüpfungsbed<strong>in</strong>gung passen.<br />
SELECT <br />
FROM <br />
RIGHT [OUTER] JOIN ON ;<br />
Für unser Beispiel „Mitarbeiter und Dienstwagen“ sieht das dann so aus:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
RIGHT JOIN Dienstwagen dw ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14<br />
90001 Janssen Bernhard 9 DO-WB 429 14<br />
100001 Grosser Horst 10 DO-WB 4210 14<br />
193
OUTER JOIN<br />
110001 Eggert Louis 11 DO-WB 4211 14<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14<br />
13 DO-WB 111 16<br />
50002 Braun Christian 14 DO-WB 352 2<br />
50003 Polovic Frantisek 15 DO-WB 353 3<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4<br />
Nanu, dieses Ergebnis hatten wir doch gerade? Bei genauerem Überlegen wird<br />
klar: Beim LEFT JOIN gibt es alle Datensätze der l<strong>in</strong>ken Tabelle mit Informationen<br />
der rechten Tabelle; nun haben wir die beiden Tabellen vertauscht. Beim<br />
RIGHT JOIN werden alle Datensätze der rechten Tabelle mit Daten der l<strong>in</strong>ken<br />
Tabelle verknüpft; das entspricht diesem Beispiel.<br />
Ob wir also die beiden Tabellen vertauschen oder LEFT gegen RIGHT, bleibt sich<br />
zwangsläufig gleich. Kurz und „knackig“ formuliert kann man sich also merken:<br />
i Merksatz<br />
"A LEFT JOIN B" liefert dasselbe Ergebnis wie "B RIGHT JOIN A".<br />
Bitte überlegen Sie, welches Ergebnis die Vertauschung der beiden Tabellen<br />
beim RIGHT JOIN liefert und welche Auswirkung WHERE-Klauseln haben.<br />
21.4. FULL OUTER JOIN<br />
Dieser JOIN liefert alle Datensätze beider Tabellen, ggf. unter Berücksichtigung<br />
der WHERE-Klausel. Wenn Datensätze nach der Verknüpfungsbed<strong>in</strong>gung passen,<br />
stehen sie <strong>in</strong> e<strong>in</strong>er Zeile; ohne „Partner“ wird e<strong>in</strong> NULL-Wert angezeigt.<br />
SELECT <br />
FROM <br />
FULL [OUTER] JOIN ON ;<br />
Für unser Beispiel sieht das dann so aus:<br />
194<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
FULL JOIN Dienstwagen dw ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
100001 Grosser Horst 10 DO-WB 4210 14<br />
110001 Eggert Louis 11 DO-WB 4211 14
120001 Carlsen Zacharias 12 DO-WB 4212 14<br />
13 DO-WB 111 16<br />
50002 Braun Christian 14 DO-WB 352 2<br />
50003 Polovic Frantisek 15 DO-WB 353 3<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4<br />
80002 Aliman Zafer 17 DO-WB 382 2<br />
80003 Langer Norbert 18 DO-WB 383 3<br />
80004 Kolic Ivana 19 DO-WB 384 4<br />
10002 Schneider Daniela<br />
20002 Schmitz Michael<br />
30002 Feyerabend Werner<br />
40002 Peters Michael<br />
Auch hier wollen wir wieder die beiden Tabellen vertauschen:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Dienstwagen dw<br />
FULL JOIN Mitarbeiter mi ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYP<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14<br />
80002 Aliman Zafer 17 DO-WB 382 2<br />
80003 Langer Norbert 18 DO-WB 383 3<br />
80004 Kolic Ivana 19 DO-WB 384 4<br />
90001 Janssen Bernhard 9 DO-WB 429 14<br />
90002 H<strong>in</strong>kel Mart<strong>in</strong>a<br />
100001 Grosser Horst 10 DO-WB 4210 14<br />
100002 Friedrichsen Angel<strong>in</strong>a<br />
110001 Eggert Louis 11 DO-WB 4211 14<br />
110002 Deiters Gisela<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14<br />
120002 Baber Yvonne<br />
13 DO-WB 111 16<br />
Verknüpfung mehrerer Tabellen<br />
Bei detailliertem Vergleich des vollständigen Ergebnisses ergibt sich: Es ist gleich,<br />
nur <strong>in</strong> anderer Sortierung. Das sollte nicht mehr verwundern.<br />
21.5. Verknüpfung mehrerer Tabellen<br />
Alle bisherigen Beispiele kranken daran, dass als Typ des Dienstwagens nur<br />
die ID angegeben ist. Selbstverständlich möchte man die Typbezeichnung und<br />
den Hersteller lesen. Dazu müssen die beiden Tabellen Fahrzeugtyp und Fahrzeughersteller<br />
e<strong>in</strong>gebunden werden. Beim INNER JOIN war das ke<strong>in</strong> Problem;<br />
probieren wir aus, wie es beim OUTER JOIN aussehen könnte.<br />
195
OUTER JOIN<br />
21.5.1. Mehrere Tabellen parallel<br />
Aufgabe: Erweitern wir dazu die Aufstellung „alle Dienstwagen zusammen mit<br />
den zugeordneten Mitarbeitern“ um die Angabe zu den Fahrzeugen.<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS TypID,<br />
ft.Bezeichnung AS Typ, ft.Hersteller_ID AS FheID<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON dw.Mitarbeiter_ID = mi.ID<br />
JOIN Fahrzeugtyp ft ON dw.Fahrzeugtyp_ID = ft.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYPID TYP FHEID<br />
100001 Grosser Horst 10 DO-WB 4210 14 A160 6<br />
110001 Eggert Louis 11 DO-WB 4211 14 A160 6<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14 A160 6<br />
13 DO-WB 111 16 W211 (E-Klasse) 6<br />
50002 Braun Christian 14 DO-WB 352 2 Golf 1<br />
50003 Polovic Frantisek 15 DO-WB 353 3 Passat 1<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4 Kadett 2<br />
Der zweite JOIN wurde nicht genauer bezeichnet, ist also e<strong>in</strong> INNER JOIN. Das<br />
gleiche Ergebnis erhalten wir, wenn wir die Tabelle Fahrzeugtyp ausdrücklich als<br />
LEFT JOIN verknüpfen (bitte selbst ausprobieren!). Anders sieht es beim Versuch<br />
mit RIGHT JOIN oder FULL JOIN aus:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS TypID,<br />
ft.Bezeichnung AS Typ, ft.Hersteller_ID AS FheID<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON dw.Mitarbeiter_ID = mi.ID<br />
RIGHT | FULL JOIN Fahrzeugtyp ft ON dw.Fahrzeugtyp_ID = ft.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYPID TYP FHEID<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14 A160 6<br />
90001 Janssen Bernhard 9 DO-WB 429 14 A160 6<br />
100001 Grosser Horst 10 DO-WB 4210 14 A160 6<br />
110001 Eggert Louis 11 DO-WB 4211 14 A160 6<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14 A160 6<br />
W204 (C-Klasse) 6<br />
13 DO-WB 111 16 W211 (E-Klasse) 6<br />
Saab 9-3 8<br />
S40 9<br />
C30 9<br />
Die beiden JOINs stehen sozusagen auf der gleichen Ebene; jede JOIN-Klausel<br />
wird für sich mit Dienstwagen verknüpft. An der Verknüpfung zwischen Dienstwagen<br />
und Mitarbeiter ändert sich nichts. Aber für die Fahrzeugtypen gilt:<br />
196
Verknüpfung mehrerer Tabellen<br />
• Das erste Beispiel benutzt e<strong>in</strong>en INNER JOIN, nimmt also für jeden vorhandenen<br />
Dienstwagen genau „se<strong>in</strong>en“ Typ.<br />
• Wenn man stattdessen e<strong>in</strong>en LEFT JOIN verwendet, erhält man alle vorhandenen<br />
Dienstwagen, zusammen mit den passenden Typen. Das ist faktisch identisch<br />
mit dem Ergebnis des INNER JOIN.<br />
• Das zweite Beispiel benutzt e<strong>in</strong>en RIGHT JOIN, das liefert alle registrierten<br />
Fahrzeugtypen und (soweit vorhanden) die passenden Dienstwagen.<br />
• Wenn man stattdessen e<strong>in</strong>en FULL JOIN verwendet, erhält man alle Komb<strong>in</strong>ationen<br />
von Dienstwagen und Mitarbeitern, zusammen mit allen registrierten<br />
Fahrzeugtypen. Das ist faktisch identisch mit dem Ergebnis des RIGHT JOIN.<br />
Sie sehen: Es kommt genau auf die gewünschten und die tatsächlich vorhandenen<br />
Verknüpfungen an.<br />
21.5.2. Gliederung durch Klammern<br />
Für Verknüpfungen, die durch Klammern gegliedert werden, nehmen wir e<strong>in</strong> anderes<br />
Beispiel, nämlich „Mitarbeiter RIGHT JOIN Dienstwagen“, denn die Fahrzeugtypen<br />
s<strong>in</strong>d e<strong>in</strong>e Ergänzung zu den Dienstwagen, nicht zu den Mitarbeitern<br />
(auch wenn den Abteilungsleitern e<strong>in</strong> Mercedes zugestanden wird, aber das ist<br />
e<strong>in</strong> anderes Thema und hat nichts mit <strong>SQL</strong> zu tun).<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS TypID,<br />
ft.Bezeichnung AS Typ, ft.Hersteller_ID AS FheID<br />
FROM Mitarbeiter mi<br />
RIGHT JOIN ( Dienstwagen dw<br />
JOIN Fahrzeugtyp ft ON ft.ID = dw.Fahrzeugtyp_id )<br />
ON dw.Mitarbeiter_ID = mi.ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYPID TYP FHEID<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14 A160 6<br />
90001 Janssen Bernhard 9 DO-WB 429 14 A160 6<br />
100001 Grosser Horst 10 DO-WB 4210 14 A160 6<br />
110001 Eggert Louis 11 DO-WB 4211 14 A160 6<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14 A160 6<br />
13 DO-WB 111 16 W211 (E-Klasse) 6<br />
50002 Braun Christian 14 DO-WB 352 2 Golf 1<br />
50003 Polovic Frantisek 15 DO-WB 353 3 Passat 1<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4 Kadett 2<br />
Auch hier erhalten wir e<strong>in</strong> vergleichbares Ergebnis. Prüfen wir zunächst die Abfrage<br />
<strong>in</strong> der Klammer:<br />
197
OUTER JOIN<br />
• LEFT JOIN und INNER JOIN haben als Grundlage „alle Dienstwagen“, es wird<br />
also e<strong>in</strong>e Datenmenge „alle Dienstwagen“ (mit Zusatz<strong>in</strong>formationen über die<br />
Fahrzeugtypen) erstellt.<br />
• RIGHT JOIN und FULL JOIN gehen aus von „alle Fahrzeugtypen“, es wird<br />
also e<strong>in</strong>e Datenmenge „alle Fahrzeugtypen“ (mit Zusatz<strong>in</strong>formationen über<br />
die Dienstwagen) erstellt.<br />
Da der Ausdruck <strong>in</strong>nerhalb der Klammern zuerst ausgewertet wird, wird diese<br />
Datenmenge anschließend mit den Mitarbeitern verknüpft, soweit es der Verknüpfungsbed<strong>in</strong>gung<br />
auf der Basis von dw.Mitarbeiter_ID entspricht.<br />
Damit können wir nun auch den Hersteller mit se<strong>in</strong>em Namen anzeigen; benutzen<br />
wir wegen der bisherigen Erkenntnisse das erste Beispiel:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS TypID,<br />
ft.Bezeichnung AS Typ, fh.Name AS Hersteller<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.ID = dw.Mitarbeiter_ID<br />
INNER JOIN Fahrzeugtyp ft ON ft.ID = dw.Fahrzeugtyp_ID<br />
INNER JOIN Fahrzeughersteller fh ON fh.ID = ft.Hersteller_ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYPID TYP HERSTELLER<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14 A160 Mercedes-Benz<br />
90001 Janssen Bernhard 9 DO-WB 429 14 A160 Mercedes-Benz<br />
100001 Grosser Horst 10 DO-WB 4210 14 A160 Mercedes-Benz<br />
110001 Eggert Louis 11 DO-WB 4211 14 A160 Mercedes-Benz<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14 A160 Mercedes-Benz<br />
13 DO-WB 111 16 W211 (E-Klasse) Mercedes-Benz<br />
50002 Braun Christian 14 DO-WB 352 2 Golf Volkswagen<br />
50003 Polovic Frantisek 15 DO-WB 353 3 Passat Volkswagen<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4 Kadett Opel<br />
80002 Aliman Zafer 17 DO-WB 382 2 Golf Volkswagen<br />
80003 Langer Norbert 18 DO-WB 383 3 Passat Volkswagen<br />
80004 Kolic Ivana 19 DO-WB 384 4 Kadett Opel<br />
21.6. Zusammenfassung<br />
In diesem Kapitel lernten Sie die Verwendung von OUTER JOIN kennen:<br />
• Mit dieser Verknüpfung werden auch Datensätze abgefragt und angezeigt, bei<br />
denen es <strong>in</strong> e<strong>in</strong>er der Tabellen ke<strong>in</strong>en zugeordneten Datensatz gibt.<br />
• Mit e<strong>in</strong>em LEFT JOIN erhält man alle Datensätze der l<strong>in</strong>ken Tabelle, ergänzt<br />
durch passende Angaben aus der rechten Tabelle.<br />
198
Übungen<br />
• Mit e<strong>in</strong>em RIGHT JOIN erhält man alle Datensätze der rechten Tabelle, ergänzt<br />
durch passende Angaben aus der l<strong>in</strong>ken Tabelle.<br />
• Mit e<strong>in</strong>em FULL JOIN erhält man alle Datensätze beider Tabellen, wenn möglich<br />
ergänzt durch passende Angaben aus der jeweils anderen Tabelle.<br />
Bei der Verknüpfung mehrerer Tabellen ist genau auf den JOIN-Typ und ggf. auf<br />
Klammerung zu achten.<br />
21.7. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 201.<br />
Übung 1 – Allgeme<strong>in</strong>es<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. Um alle Mitarbeiter mit Dienstwagen aufzulisten, benötigt man e<strong>in</strong>en<br />
LEFT OUTER JOIN.<br />
2. LEFT JOIN ist nur e<strong>in</strong>e Kurzschreibweise für LEFT OUTER JOIN und hat<br />
ke<strong>in</strong>e zusätzliche <strong>in</strong>haltliche Bedeutung.<br />
3. E<strong>in</strong> LEFT JOIN von zwei Tabellen enthält alle Zeilen, die nach Auswahlbed<strong>in</strong>gung<br />
<strong>in</strong> der l<strong>in</strong>ken Tabelle enthalten s<strong>in</strong>d.<br />
4. E<strong>in</strong> RIGHT JOIN von zwei Tabellen enthält nur noch diejenigen Zeilen, die<br />
nach der Verknüpfungsbed<strong>in</strong>gung <strong>in</strong> der l<strong>in</strong>ken Tabelle enthalten s<strong>in</strong>d.<br />
5. Wenn wir bei e<strong>in</strong>er LEFT JOIN-Abfrage mit zwei Tabellen die beiden Tabellen<br />
vertauschen und stattdessen e<strong>in</strong>en RIGHT JOIN verwenden, erhalten<br />
wir dieselben Zeilen <strong>in</strong> der Ergebnismenge.<br />
6. Wir erhalten dabei nicht nur dieselben Zeilen, sondern auch dieselbe Reihenfolge.<br />
Übung 2 – Allgeme<strong>in</strong>es<br />
Was ist am folgenden SELECT-Befehl falsch und warum?<br />
Aufgabe: Gesucht werden Komb<strong>in</strong>ationen von Fahrzeug-Kennzeichen und<br />
Fahrzeugtypen, wobei alle Typen aufgeführt werden sollen; es werden nur die<br />
ersten 20 Fahrzeuge nach ID benötigt.<br />
SELECT Kennzeichen, Bezeichnung<br />
FROM Fahrzeug fz<br />
LEFT JOIN Fahrzeugtyp ft ON fz.Fahrzeugtyp_ID = ft.ID<br />
WHERE fz.ID
OUTER JOIN<br />
Übung 3 – S<strong>in</strong>nvollen SELECT-Befehl erstellen<br />
Gesucht werden alle registrierten Versicherungsgesellschaften und (soweit vorhanden)<br />
deren Kunden mit Name, Vorname.<br />
Übung 4 – S<strong>in</strong>nvollen SELECT-Befehl erstellen<br />
Gesucht werden die Dienstwagen, deren Fahrzeugtypen sowie die Hersteller. Die<br />
Liste der Typen soll vollständig se<strong>in</strong>.<br />
Übung 5 – S<strong>in</strong>nvollen SELECT-Befehl erstellen<br />
Gesucht werden Komb<strong>in</strong>ationen von Mitarbeitern und ihren Dienstwagen (e<strong>in</strong>schließlich<br />
Typ). Es geht um die Abteilungen 1 bis 5; auch nicht-persönliche<br />
Dienstwagen sollen aufgeführt werden.<br />
Übung 6 – S<strong>in</strong>nvollen SELECT-Befehl erstellen<br />
Gesucht werden alle registrierten Versicherungsgesellschaften sowie alle Kunden<br />
mit Name, Vorname, soweit der Nachname mit 'S' beg<strong>in</strong>nt.<br />
Übung 7 – RIGHT oder LEFT<br />
Vertauschen Sie <strong>in</strong> der Lösung von Übung 5 die beiden Tabellen Mitarbeiter und<br />
Dienstwagen und erläutern Sie:<br />
1. Warum werden jetzt mehr Mitarbeiter angezeigt, und zwar auch solche<br />
ohne Dienstwagen?<br />
2. Warum fehlt jetzt der „nicht-persönliche“ Dienstwagen?<br />
Übung 8 – SELECT-Befehl mit mehreren Tabellen<br />
Gesucht werden Angaben zu den Mitarbeitern und den Dienstwagen. Beim Mitarbeiter<br />
sollen Name und Vorname angegeben werden, bei den Dienstwagen Bezeichnung<br />
und Name des Herstellers. Die Liste aller Fahrzeugtypen soll vollständig<br />
se<strong>in</strong>.<br />
Übung 9 – SELECT-Befehl mit mehreren Tabellen<br />
Ergänzen Sie die Lösung zu Übung 8 <strong>in</strong>sofern, dass nur Mitarbeiter der Abteilungen<br />
1 bis 5 angezeigt werden; die Zeilen ohne Mitarbeiter sollen unverändert<br />
ausgegeben werden.<br />
200
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 199.<br />
Lösung zu Übung 1 – Allgeme<strong>in</strong>es<br />
Die Aussagen 2, 3, 5 s<strong>in</strong>d richtig, die Aussagen 1, 4, 6 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – Allgeme<strong>in</strong>es<br />
Richtig ist folgender Befehl:<br />
SELECT Kennzeichen, Bezeichnung<br />
FROM Fahrzeug fz<br />
RIGHT JOIN Fahrzeugtyp ft ON fz.Fahrzeugtyp_ID = ft.ID<br />
WHERE fz.ID
OUTER JOIN<br />
Die IS NULL-Prüfung wird wegen der „nicht-persönlichen“ Dienstwagen benötigt.<br />
Lösung zu Übung 6 – S<strong>in</strong>nvollen SELECT-Befehl erstellen<br />
SELECT Bezeichnung, Name, Vorname<br />
FROM Versicherungsgesellschaft vg<br />
full JOIN Versicherungsnehmer vn ON vg.id = vn.Versicherungsgesellschaft_id<br />
WHERE vn.Name LIKE ’S%’ OR vn.id IS NULL<br />
ORDER BY Bezeichnung, Name, Vorname;<br />
Lösung zu Übung 7 – RIGHT oder LEFT<br />
1. Bei e<strong>in</strong>em RIGHT JOIN werden alle E<strong>in</strong>träge der rechten Tabelle angezeigt.<br />
„Rechts“ stehen jetzt die Mitarbeiter, also werden alle Mitarbeiter der betreffenden<br />
Abteilungen angezeigt.<br />
2. Bei e<strong>in</strong>em RIGHT JOIN werden die E<strong>in</strong>träge der l<strong>in</strong>ken Tabelle nur dann angezeigt,<br />
wenn sie zu e<strong>in</strong>em E<strong>in</strong>trag der rechten Tabelle gehören. Der „nichtpersönliche“<br />
Dienstwagen aus der l<strong>in</strong>ken Tabelle gehört aber zu ke<strong>in</strong>em<br />
der Mitarbeiter.<br />
Lösung zu Übung 8 – SELECT-Befehl mit mehreren Tabellen<br />
SELECT mi.Name, mi.Vorname,<br />
dw.Kennzeichen, ft.Bezeichnung, fh.Name AS HST<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.id = dw.Mitarbeiter_id<br />
RIGHT JOIN Fahrzeugtyp ft ON ft.Id = dw.Fahrzeugtyp_id<br />
INNER JOIN Fahrzeughersteller fh ON fh.Id = ft.Hersteller_id;<br />
H<strong>in</strong>weise: Die Reihenfolge der JOINs ist nicht e<strong>in</strong>deutig; der LEFT JOIN kann<br />
auch später kommen. Wichtig ist, dass die Verb<strong>in</strong>dung Dienstwagen → Fahrzeugtyp<br />
e<strong>in</strong> RIGHT JOIN ist (oder bei Vertauschung der Tabellen e<strong>in</strong> LEFT JOIN).<br />
Lösung zu Übung 9 – SELECT-Befehl mit mehreren Tabellen<br />
202<br />
SELECT mi.Name, mi.Vorname,<br />
dw.Kennzeichen, ft.Bezeichnung, fh.Name AS HST<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.id = dw.Mitarbeiter_id<br />
RIGHT JOIN Fahrzeugtyp ft ON ft.Id = dw.Fahrzeugtyp_id<br />
INNER JOIN Fahrzeughersteller fh ON fh.Id = ft.Hersteller_id<br />
WHERE (mi.Abteilung_id
22. Mehr zu JOIN<br />
22.1. Welcher JOIN-Typ passt wann? . . . . . . . . . . . . . . . . . . . 203<br />
22.2. SELF JOIN – Verknüpfung mit sich selbst . . . . . . . . . . . . . 204<br />
22.3. CROSS JOIN – das kartesische Produkt . . . . . . . . . . . . . . 209<br />
22.4. WITH – Inl<strong>in</strong>e-View . . . . . . . . . . . . . . . . . . . . . . . . . . 210<br />
22.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 211<br />
22.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211<br />
22.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212<br />
Die folgenden Ergänzungen zu JOIN s<strong>in</strong>d <strong>in</strong> besonderen Situationen hilfreich.<br />
22.1. Welcher JOIN-Typ passt wann?<br />
Diese Frage stellt sich vor allem Anfängern sehr oft. Neben den (theoretischen)<br />
Überlegungen der vorigen Kapitel helfen oft besondere Beispiele wie dieses.<br />
Eltern und ihre K<strong>in</strong>der<br />
Wir haben zwei Tabellen: Paare (also Eltern) und K<strong>in</strong>der. Es gibt k<strong>in</strong>derlose Paare,<br />
Paare mit K<strong>in</strong>dern und Waisenk<strong>in</strong>der. Wir wollen die Eltern und K<strong>in</strong>der <strong>in</strong> Abfragen<br />
verknüpfen. Die Kreise entsprechen den Tabellen; also steht der l<strong>in</strong>ke Kreis<br />
für die Tabelle Paare und der rechte Kreis für die Tabelle K<strong>in</strong>der.<br />
LEFT JOIN Alle Paare und<br />
(falls es K<strong>in</strong>der<br />
gibt) auch diese<br />
INNER<br />
JOIN<br />
RIGHT<br />
JOIN<br />
LEFT JOIN<br />
IS NULL<br />
Nur Paare, die<br />
K<strong>in</strong>der haben<br />
Alle K<strong>in</strong>der und<br />
(falls es Eltern<br />
gibt) auch diese<br />
Nur Paare, die<br />
ke<strong>in</strong>e K<strong>in</strong>der<br />
haben<br />
SELECT * FROM Paare<br />
LEFT JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
SELECT * FROM Paare<br />
INNER JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
SELECT * FROM Paare<br />
RIGHT JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
SELECT * FROM Paare<br />
LEFT JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
WHERE K<strong>in</strong>der.Key IS NULL<br />
203
Mehr zu JOIN<br />
RIGHT<br />
JOIN<br />
IS NULL<br />
Nur Waisenk<strong>in</strong>der<br />
FULL JOIN Alle Paare und<br />
alle K<strong>in</strong>der<br />
FULL JOIN<br />
IS NULL<br />
Alle k<strong>in</strong>derlosen<br />
Paare und alle<br />
Waisenk<strong>in</strong>der<br />
SELECT * FROM Paare<br />
RIGHT JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
WHERE Paare.Key IS NULL<br />
SELECT * FROM Paare<br />
FULL JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
SELECT * FROM Paare<br />
FULL JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
WHERE K<strong>in</strong>der.Key IS NULL<br />
OR Paare.Key IS NULL<br />
Die zweite Variante – INNER JOIN – kann man auch so ausdrücken:<br />
LEFT JOIN<br />
IS NOT<br />
NULL<br />
Alle Paare und<br />
(falls es K<strong>in</strong>der<br />
gibt) auch<br />
diese, wobei es<br />
e<strong>in</strong> K<strong>in</strong>d geben<br />
muss<br />
SELECT * FROM Paare<br />
LEFT JOIN K<strong>in</strong>der<br />
ON Paare.Key = K<strong>in</strong>der.Key<br />
WHERE K<strong>in</strong>der.Key<br />
IS NOT NULL<br />
Tatsächlich s<strong>in</strong>d oft mehrere Wege möglich, wie bereits im letzten Kapitel gesagt<br />
wurde: Das Ergebnis für "A LEFT JOIN B" gleicht dem von "B RIGHT JOIN A".<br />
22.2. SELF JOIN – Verknüpfung mit sich selbst<br />
Solche Verknüpfungen s<strong>in</strong>d immer dann nötig, wenn Werte e<strong>in</strong>er e<strong>in</strong>zigen Spalte<br />
aus verschiedenen Datensätzen verbunden werden. Der JOIN dafür benutzt auf<br />
beiden Seiten dieselbe Tabelle ; diese beiden „Instanzen“ müssen durch<br />
e<strong>in</strong>en Alias unterschieden werden.<br />
SELECT <br />
FROM t1<br />
JOIN t2 ON <br />
WHERE <br />
H<strong>in</strong>weis: Es funktioniert nicht, wenn e<strong>in</strong>e der beiden Instanzen mit Alias und die<br />
andere ohne Alias benutzt wird. Dann kommt das DBMS erst recht durche<strong>in</strong>ander.<br />
In e<strong>in</strong>em Forum stand e<strong>in</strong>mal e<strong>in</strong> solches Problem mit e<strong>in</strong>er sehr vertrackten<br />
Ausgabe, bei dem diese Ursache erst nach längerer Diskussion klar wurde.<br />
Dies soll zunächst an zwei Beispielen umgesetzt werden.<br />
204
Beispiel 1<br />
SELF JOIN – Verknüpfung mit sich selbst<br />
Aufgabe: Zeige zu jedem Fahrzeug andere Fahrzeuge aus dem gleichen Kreis.<br />
Wir beschränken uns darauf, dass <strong>in</strong> e<strong>in</strong>er Zeile jeweils zwei von allen möglichen<br />
Komb<strong>in</strong>ationen angezeigt werden, auch wenn viele Angaben wiederholt werden.<br />
SELECT a.Kennzeichen, b.Kennzeichen<br />
FROM Fahrzeug a<br />
JOIN Fahrzeug b<br />
ON SUBSTRING(a.Kennzeichen FROM 1 FOR 3)<br />
= SUBSTRING(b.Kennzeichen FROM 1 FOR 3)<br />
WHERE a.Kennzeichen < b.Kennzeichen<br />
ORDER BY a.Kennzeichen;<br />
KENNZEICHEN KENNZEICHEN1<br />
BO-GH 102 BO-KL 678<br />
BOR-NO 234 BOR-PQ 567<br />
BOR-NO 234 BOR-RS 890<br />
BOR-PQ 567 BOR-RS 890<br />
GE-AB 123 GE-AC 246<br />
GE-AB 123 GE-EG 892<br />
GE-AC 246 GE-EG 892<br />
RE-CD 456 RE-LM 901<br />
RE-CD 456 RE-LM 902<br />
RE-CD 456 RE-LM 903<br />
Gesucht werden Komb<strong>in</strong>ationen e<strong>in</strong>es Fahrzeugs mit jeweils e<strong>in</strong>em anderen<br />
Fahrzeug, wobei die Bed<strong>in</strong>gung „gleicher Kreis“ erfüllt se<strong>in</strong> soll. Wir brauchen<br />
also <strong>in</strong> e<strong>in</strong>em SELECT-Befehl zwei Zugriffe auf die Tabelle Fahrzeug mit e<strong>in</strong>er<br />
passenden Auswahlbed<strong>in</strong>gung. (Diese haben wir ungenau formuliert, nämlich<br />
als Vergleich der ersten drei Zeichen, damit es nicht zu unübersichtlich wird.)<br />
Dies ist gleichzeitig e<strong>in</strong> Beispiel dafür, dass beliebige Bed<strong>in</strong>gungen (nicht nur<br />
die Gleichheit) möglich s<strong>in</strong>d. Überlegen Sie bitte auch, warum unter WHERE die<br />
„kle<strong>in</strong>er als“-Bed<strong>in</strong>gung benutzt wird.<br />
Beispiel 2<br />
Aufgabe: Zeige zu jedem Fahrzeug mit mehreren Schadensfällen den zeitlichen<br />
Abstand von e<strong>in</strong>em Vorfall zum nächsten an.<br />
Lösungsweg 1<br />
Wir benötigen für jedes Fahrzeug aus der Tabelle Schadensfall zwei E<strong>in</strong>träge mit<br />
Datum sowie die Anzahl der Tage als Differenz zweier Datumsangaben. Die Fahrzeuge<br />
s<strong>in</strong>d freilich erst über die Tabelle Zuordnung_SF_FZ zu f<strong>in</strong>den und müssen<br />
205
Mehr zu JOIN<br />
zusätzlich verbunden werden. Außerdem s<strong>in</strong>d die Fahrzeuge und die Reihenfolge<br />
der Datumsangaben zu kontrollieren.<br />
Datumsvergleich als Teil der Auswahlbed<strong>in</strong>gung<br />
SELECT fz.ID, fz.Kennzeichen,<br />
sf1.Datum AS Datum1, sf2.Datum AS Datum2, sf2.Datum - sf1.Datum AS Abstand<br />
FROM Zuordnung_SF_FZ zu1<br />
JOIN Zuordnung_SF_FZ zu2 ON zu1.Fahrzeug_ID = zu2.Fahrzeug_ID<br />
JOIN Schadensfall sf1 ON zu1.Schadensfall_ID = sf1.ID<br />
JOIN Schadensfall sf2 ON zu2.Schadensfall_ID = sf2.Id<br />
JOIN Fahrzeug fz ON zu1.Fahrzeug_ID = fz.ID<br />
WHERE sf1.Datum < sf2.Datum<br />
AND sf2.Datum = ( SELECT MIN(sf3.Datum)<br />
FROM Schadensfall sf3<br />
JOIN Zuordnung_SF_FZ zu3<br />
ON zu3.Schadensfall_ID = sf3.id<br />
WHERE sf1.Datum < sf3.Datum<br />
AND zu3.Fahrzeug_ID = zu1.Fahrzeug_ID )<br />
ORDER BY fz.ID, Datum1;<br />
ID KENNZEICHEN DATUM1 DATUM2 ABSTAND<br />
4 GE-AB 123 03.02.2007 05.10.2008 610<br />
6 HER-EF 789 19.12.2007 21.06.2009 550<br />
7 BO-GH 102 11.07.2007 13.03.2009 611<br />
7 BO-GH 102 13.03.2009 01.08.2009 141<br />
Lösungswege 2 und 3<br />
Alternativen bieten die folgenden Lösungen:<br />
206<br />
Datumsvergleich als Teil der Verknüpfungsbed<strong>in</strong>gungen<br />
SELECT fz.ID, fz.Kennzeichen,<br />
sf1.Datum AS Datum1, sf2.Datum AS Datum2, sf2.Datum - sf1.Datum AS Abstand<br />
FROM Zuordnung_SF_FZ zu1<br />
JOIN Zuordnung_SF_FZ zu2 ON zu1.Fahrzeug_ID = zu2.Fahrzeug_ID<br />
JOIN Schadensfall sf1 ON zu1.Schadensfall_ID = sf1.ID<br />
JOIN Schadensfall sf2<br />
ON zu2.Schadensfall_ID = sf2.Id<br />
AND sf1.Datum < sf2.Datum<br />
AND sf2.Datum = ( SELECT MIN(sf3.Datum)<br />
FROM Schadensfall sf3<br />
JOIN Zuordnung_SF_FZ zu3<br />
ON zu3.Schadensfall_ID = sf3.ID<br />
WHERE sf1.Datum < sf3.Datum<br />
AND zu3.Fahrzeug_ID = zu1.Fahrzeug_ID<br />
)<br />
JOIN Fahrzeug fz ON zu1.Fahrzeug_ID = fz.ID<br />
ORDER BY fz.ID, Datum1;
SELF JOIN – Verknüpfung mit sich selbst<br />
Datumsvergleich als Funktion bei den SELECT-Spalten<br />
SELECT fz.ID, fz.Kennzeichen,<br />
sf1.Datum AS Datum1, MIN(sf2.Datum) AS Datum2,<br />
MIN(sf2.Datum - sf1.Datum) AS Abstand<br />
FROM Zuordnung_SF_FZ zu1<br />
JOIN Zuordnung_SF_FZ zu2 ON zu1.Fahrzeug_ID = zu2.Fahrzeug_ID<br />
JOIN Schadensfall sf1 ON zu1.Schadensfall_ID = sf1.ID<br />
JOIN Schadensfall sf2 ON zu2.Schadensfall_ID = sf2.Id<br />
AND sf1.Datum < sf2.Datum<br />
JOIN Fahrzeug fz ON zu1.Fahrzeug_ID = fz.ID<br />
GROUP BY fz.ID, fz.Kennzeichen, sf1.Datum<br />
ORDER BY fz.ID, Datum1;<br />
Erläuterungen<br />
In dieser Aufgabe stecken mehrere Probleme:<br />
• Die Angaben aus der Spalte Datum der Tabelle Schadensfall müssen zweimal<br />
geholt werden.<br />
• Zu jedem Schadensfall wird der E<strong>in</strong>trag der Tabelle Zuordnung_SF_FZ benötigt,<br />
weil die Schadensfälle für jedes Fahrzeug gesucht werden.<br />
• Das Datum, das zu sf1 gehört, muss immer „kle<strong>in</strong>er“ se<strong>in</strong>, also früher liegen als<br />
das Datum, das zu sf2 gehört.<br />
• Außerdem benötigen wir „irgendwo“ die E<strong>in</strong>schränkung, dass zum Vergleich<br />
nur der jeweils folgende Schadensfall genommen werden darf, also das<br />
MINimum der späteren E<strong>in</strong>träge:<br />
• Die erste Lösung verwendet dafür e<strong>in</strong>e Unterabfrage für e<strong>in</strong>e Auswahlbed<strong>in</strong>gung.<br />
• Die zweite Lösung arbeitet mit e<strong>in</strong>er Unterabfrage bei der Verknüpfungsbed<strong>in</strong>gung.<br />
• Die dritte Lösung benutzt das MINimum direkt als Spaltenfunktion und verlangt<br />
„zum Ausgleich“ e<strong>in</strong>e GROUP BY-Klausel.<br />
Die Lösung benötigt deshalb mehrfach verknüpfte Tabellen:<br />
• Als Grundlage wird die Tabelle der Zuordnungen zwischen Schadensfällen und<br />
Fahrzeugen zu1 verwendet.<br />
• Hauptverknüpfung ist der Self-Jo<strong>in</strong> zu2 auf dieselbe Tabelle, weil nur solche<br />
E<strong>in</strong>träge verknüpft werden sollen, die sich auf dasselbe Fahrzeug beziehen.<br />
• Zu jedem Schadensfall aus zu1 gehören die detaillierten Angaben aus sf1.<br />
• Zu jedem Schadensfall aus zu2 gehören die detaillierten Angaben aus sf2.<br />
• Ergänzend benötigen wir das Kennzeichen des betreffenden Fahrzeugs, also<br />
e<strong>in</strong>en JOIN auf Fahrzeug.<br />
• Vor allem s<strong>in</strong>d die Auswahlbed<strong>in</strong>gungen für die Datumsangaben e<strong>in</strong>zubauen.<br />
207
Mehr zu JOIN<br />
Welche Lösung die Datenbank am wenigsten belastet, kann nicht generell gesagt<br />
werden, weil es von zu vielen Umständen abhängt.<br />
Erweiterung durch e<strong>in</strong>en OUTER JOIN<br />
Bei diesen Lösungen stehen nicht alle Schadensfälle im Ergebnis, weil es nur um<br />
den zeitlichen Abstand g<strong>in</strong>g. Wenn beispielsweise auch die Schadenshöhe gewünscht<br />
wird, müssen wir dafür s<strong>org</strong>en, dass von sf1 oder sf2 alle E<strong>in</strong>träge angezeigt<br />
werden; wir brauchen also e<strong>in</strong>en OUTER JOIN wie zum Beispiel (auf der<br />
Grundlage der letzten Version) so:<br />
SELECT fz.ID, fz.Kennzeichen,<br />
sf1.Datum AS Datum1, MIN(sf2.Datum) AS Datum2,<br />
MIN(sf2.Datum - sf1.Datum) AS Abstand,<br />
sf1.Schadenshoehe<br />
FROM Zuordnung_SF_FZ zu1<br />
LEFT JOIN Zuordnung_SF_FZ zu2 ON zu1.Fahrzeug_ID = zu2.Fahrzeug_ID<br />
LEFT JOIN Schadensfall sf1 ON zu1.Schadensfall_ID = sf1.ID<br />
LEFT JOIN Schadensfall sf2 ON zu2.Schadensfall_ID = sf2.Id<br />
AND sf1.Datum < sf2.Datum<br />
LEFT JOIN Fahrzeug fz ON zu1.Fahrzeug_ID = fz.ID<br />
GROUP BY fz.ID, fz.Kennzeichen, sf1.Datum, sf1.Schadenshoehe<br />
ORDER BY fz.ID, Datum1;<br />
ID KENNZEICHEN DATUM1 DATUM2 ABSTAND SCHADENSHOEHE<br />
3 RE-LM 903 27.05.2008 1.438,75<br />
4 GE-AB 123 03.02.2007 05.10.2008 610 1.234,50<br />
4 GE-AB 123 05.10.2008 1.983,00<br />
5 RE-CD 456 11.07.2007 2.066,00<br />
6 HER-EF 789 19.12.2007 21.06.2009 550 3.715,60<br />
6 HER-EF 789 21.06.2009 865,00<br />
7 BO-GH 102 11.07.2007 13.03.2009 611 2.066,00<br />
7 BO-GH 102 13.03.2009 01.08.2009 141 4.092,15<br />
7 BO-GH 102 01.08.2009 2.471,50<br />
Wir nehmen es h<strong>in</strong>, dass dann alle Schadensfälle aufgeführt werden, auch für<br />
die Fahrzeuge, die nur e<strong>in</strong>mal „aufgefallen“ s<strong>in</strong>d. Dies ist e<strong>in</strong>e Folge davon, dass<br />
Grundlage aller Verknüpfungen die Tabelle der Zuordnungen se<strong>in</strong> musste.<br />
Bei allen solchen Situationen müssen Sie genau überlegen, wie die verschiedenen<br />
Instanzen mite<strong>in</strong>ander verknüpft werden und wie die übrigen Bed<strong>in</strong>gungen<br />
e<strong>in</strong>zub<strong>in</strong>den s<strong>in</strong>d. Oft führen erst mehrere Versuche zum Ziel. Hilfreich s<strong>in</strong>d<br />
auch die Ausführungspläne, die e<strong>in</strong> DBMS anbieten kann.<br />
Weitere Situationen<br />
Zum Schluss sollen noch e<strong>in</strong> paar andere Beispiele erwähnt werden, bei denen<br />
e<strong>in</strong> Self-Jo<strong>in</strong> hilft.<br />
208
CROSS JOIN – das kartesische Produkt<br />
• Wenn bei den Dienstwagen die privat gefahrenen Strecken abgerechnet werden<br />
sollen, können der km-Stand beim Fahrtantritt und beim Fahrtende <strong>in</strong><br />
derselben Spalte, aber <strong>in</strong> getrennten Datensätzen gespeichert werden.<br />
• Doppelte Adressen <strong>in</strong>nerhalb e<strong>in</strong>er Adressendatei können aufgespürt werden<br />
(siehe Übung 3).<br />
• Wenn <strong>in</strong> der Tabelle Mitarbeiter zu e<strong>in</strong>em Mitarbeiter der Leiter der Abteilung<br />
gesucht wird, benötigen wir wegen des doppelten Zugriffs auf dieselbe Tabelle<br />
ebenfalls e<strong>in</strong>en Self-Jo<strong>in</strong>.<br />
22.3. CROSS JOIN – das kartesische Produkt<br />
Mit dieser speziellen Formulierung kann man deutlich machen, dass man wirklich<br />
e<strong>in</strong> kartesisches Produkt herstellen will und nicht etwa nur die Verknüpfungsbed<strong>in</strong>gung<br />
vergessen hat:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
CROSS JOIN Dienstwagen dw;<br />
Als Ergebnis wird tatsächlich jede Komb<strong>in</strong>ation e<strong>in</strong>es Mitarbeiters mit e<strong>in</strong>em<br />
Dienstwagen ausgegeben, also n mal m Sätze − wie beim allerersten Versuch im<br />
Kapitel E<strong>in</strong>fache Tabellenverknüpfung 1 . Man kann die Ergebnismenge auch e<strong>in</strong>schränken<br />
durch e<strong>in</strong>e WHERE-Klausel:<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
CROSS JOIN Dienstwagen dw<br />
WHERE mi.Name LIKE ’S%’ AND CHAR_LENGTH(dw.Kennzeichen) = 10;<br />
H<strong>in</strong>weis: Die DBMS verhalten sich bei e<strong>in</strong>em CROSS JOIN unterschiedlich; teilweise<br />
ist e<strong>in</strong> CROSS JOIN mit WHERE-Klausel nichts anderes als e<strong>in</strong> INNER<br />
JOIN.<br />
Der Nutzen des CROSS JOIN wird bei unserer sparsamen Beispieldatenbank<br />
nicht klar. Unter Oracle wäre folgendes Verfahren möglich und hilfreich:<br />
Aufgabe: Kontrollieren Sie mit der Tabelle Fahrzeugbuchung, welche Fahrzeuge<br />
am 2.12.2009 im Fuhrpark zur Verfügung stehen.<br />
1 Kapitel 19 auf Seite 173<br />
209
Mehr zu JOIN<br />
Oracle-Quelltext<br />
SELECT mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS Typ<br />
FROM Mitarbeiter mi<br />
CROSS JOIN Dienstwagen dw<br />
INNER JOIN Fahrzeugbuchung fb<br />
ON dw.Kennzeichen = fb.Kennzeichen<br />
WHERE fb.Datum = to_date(’02.12.2009’,’dd.mm.yyyy’)<br />
AND fb.Status = ’noch nicht gebucht’;<br />
22.4. WITH – Inl<strong>in</strong>e-View<br />
Oft kommt es vor, dass man die Daten aus e<strong>in</strong>er Tabelle erst bearbeiten möchte,<br />
bevor man sie mit e<strong>in</strong>er anderen Tabelle verknüpft. Beispiel:<br />
SELECT Kuerzel, Bezeichnung, Anzahl_Mitarbeiter FROM Abteilung<br />
INNER JOIN ( SELECT Abteilung_ID, COUNT(*) AS Anzahl_Mitarbeiter<br />
FROM Mitarbeiter<br />
GROUP BY Abteilung_ID<br />
) MA_Anzahl<br />
ON Abteilung.ID = MA_Anzahl.Abteilung_ID ;<br />
Dabei wird zunächst nach der Tabelle Mitarbeiter die Anzahl der Mitarbeiter für<br />
jede Abteilung bestimmt. Das Ergebnis wird wie e<strong>in</strong>e Tabelle MA_Anzahl behandelt<br />
und über Abteilung_ID mit der Tabelle Abteilung verknüpft.<br />
Diese Syntax ist ziemlich verschachtelt. Man kann sie auch so schreiben:<br />
WITH MA_Anzahl AS<br />
( SELECT Abteilung_ID, COUNT(*) AS Anzahl_Mitarbeiter<br />
FROM Mitarbeiter<br />
GROUP BY Abteilung_ID<br />
)<br />
SELECT Kuerzel, Bezeichnung, Anzahl_Mitarbeiter<br />
FROM Abteilung<br />
INNER JOIN MA_Anzahl<br />
ON Abteilung.ID = MA_Anzahl.Abteilung_ID ;<br />
MA_Anzahl wird benutzt wie e<strong>in</strong>e VIEW, die allerd<strong>in</strong>gs nicht permanent angelegt<br />
wird, sondern die nur für die Ausführung dieses e<strong>in</strong>en <strong>SQL</strong>-Befehls gültig ist.<br />
Der Unterschied liegt „nur“ dar<strong>in</strong>, dass die Unterabfrage herausgelöst wird und<br />
durch WITH als temporäre abgeleitete Tabelle e<strong>in</strong>gebunden wird.<br />
Welche Variante man besser f<strong>in</strong>det, ist sicher Geschmackssache und hat auch<br />
damit zu tun, welche Formulierung man gewöhnt ist. In vielen Fällen wären Formulierungen<br />
ohne WITH viel länger und komplizierter.<br />
210
22.5. Zusammenfassung<br />
Zusammenfassung<br />
In diesem Kapitel lernten Sie e<strong>in</strong>ige weitere Möglichkeiten im Zusammenhang<br />
mit JOINs kennen.<br />
• Für bestimmte Anforderungen s<strong>in</strong>d Verknüpfungen e<strong>in</strong>er Tabelle mit sich<br />
selbst s<strong>in</strong>nvoll oder notwendig.<br />
• In diesen Fällen s<strong>in</strong>d die Auswahl- und Verknüpfungsbed<strong>in</strong>gungen besonders<br />
s<strong>org</strong>fältig zu bestimmen.<br />
• Durch WITH können Verknüpfungen über JOINs übersichtlicher werden.<br />
22.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 212.<br />
Übung 1 – Fragen zum Verständnis<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche falsch?<br />
1. E<strong>in</strong>e Tabelle kann mit sich selbst verknüpft werden.<br />
2. SELF JOIN ist nur e<strong>in</strong> <strong>in</strong>haltlicher Begriff, aber ke<strong>in</strong> <strong>SQL</strong>-Schlüsselwort.<br />
3. Bei e<strong>in</strong>em SELF JOIN s<strong>in</strong>d nur INNER JOINs erlaubt.<br />
4. E<strong>in</strong>e bestimmte Tabelle darf <strong>in</strong> e<strong>in</strong>em SELF JOIN nur zweimal verwendet<br />
werden.<br />
5. Für e<strong>in</strong>en SELF JOIN können Tabellen-Aliase benutzt werden, aber sie s<strong>in</strong>d<br />
nicht überall erforderlich.<br />
6. E<strong>in</strong> CROSS JOIN ist e<strong>in</strong>e Verknüpfung zweier Tabellen ohne Verknüpfungsbed<strong>in</strong>gung.<br />
7. Bei e<strong>in</strong>em CROSS JOIN darf sich die WHERE-Klausel nicht auf die (rechte)<br />
Tabelle des JOINs beziehen.<br />
8. Die Schreibweise mit WITH ist ke<strong>in</strong> Sonderfall e<strong>in</strong>es JOINs, sondern e<strong>in</strong>e<br />
übersichtlichere Schreibweise, wenn mehrere Tabellen verknüpft werden.<br />
Übung 2 – Verknüpfung e<strong>in</strong>er Tabelle mit sich selbst<br />
Suchen Sie zu jedem Mitarbeiter den Namen und Vornamen des Leiters der Abteilung.<br />
Die Abteilungsleiter <strong>in</strong> unserer e<strong>in</strong>fachen Firmenhierarchie haben ke<strong>in</strong>en<br />
V<strong>org</strong>esetzten; sie sollen <strong>in</strong> der Liste deshalb nicht aufgeführt werden.<br />
211
Mehr zu JOIN<br />
Übung 3 – Doppelte Adressen suchen<br />
Suchen Sie E<strong>in</strong>träge <strong>in</strong> der Tabelle Versicherungsnehmer, bei denen Name, Vorname,<br />
PLZ, Strasse übere<strong>in</strong>stimmen. Jeweils zwei dieser Adressen sollen mit ihrer<br />
ID und den übere<strong>in</strong>stimmenden Angaben aufgeführt werden.<br />
H<strong>in</strong>weis: Benutzen Sie e<strong>in</strong>en JOIN, der sich nicht auf übere<strong>in</strong>stimmende IDs bezieht.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 211.<br />
Lösung zu Übung 1 – Fragen zum Verständnis<br />
Die Aussagen 1, 2, 6, 8 s<strong>in</strong>d wahr, die Aussagen 3, 4, 5, 7 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – Verknüpfung e<strong>in</strong>er Tabelle mit sich selbst<br />
SELECT mi1.Abteilung_ID AS Abt, mi1.Name, mi1.Vorname,<br />
mi2.Name AS LtrName, mi2.Vorname AS LtrVorn<br />
FROM Mitarbeiter mi1<br />
JOIN Abteilung ab ON mi1.Abteilung_ID = ab.ID<br />
JOIN Mitarbeiter mi2 ON mi2.Abteilung_ID = ab.ID<br />
WHERE mi2.Ist_Leiter = ’J’<br />
AND mi1.Ist_Leiter = ’N’;<br />
Lösung zu Übung 3 – Doppelte Adressen suchen<br />
SELECT a.Name, a.Vorname, a.PLZ, a.Strasse, a.ID, b.ID<br />
FROM Versicherungsnehmer a<br />
JOIN Versicherungsnehmer b<br />
ON a.Name = b.Name AND a.Vorname = b.Vorname<br />
AND a.PLZ = b.PLZ AND a.Strasse = b.Strasse<br />
WHERE a.ID < b.ID;<br />
22.7. Siehe auch<br />
Bei Wikipedia f<strong>in</strong>den Sie weitere Erläuterungen:<br />
• Auswertungsplan 2 , auch „Ausführungsplan“ genannt<br />
2 http://de.wikipedia.<strong>org</strong>/wiki/Auswertungsplan<br />
212
23. Nützliche Erweiterungen<br />
23.1. Beschränkung auf e<strong>in</strong>e Anzahl Zeilen . . . . . . . . . . . . . . . 213<br />
23.2. Mehrere Abfragen zusammenfassen . . . . . . . . . . . . . . . . 220<br />
23.3. CASE WHEN – Fallunterscheidungen . . . . . . . . . . . . . . . 223<br />
23.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 228<br />
23.5. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229<br />
Dieses Kapitel behandelt verschiedene Erweiterungen des SELECT-Befehls.<br />
Die Beispiele beziehen sich auch hier auf den Anfangsbestand der Beispieldatenbank;<br />
auf die Ausgabe der selektierten Datensätze wird wiederum weitgehend<br />
verzichtet. Bitte probieren Sie alle Beispiele aus und nehmen Sie verschiedene<br />
Änderungen vor, um die Auswirkungen zu erkennen.<br />
23.1. Beschränkung auf e<strong>in</strong>e Anzahl Zeilen<br />
Häufig will man nicht sofort das gesamte Ergebnis sehen, sondern nur e<strong>in</strong>en Teil<br />
der Zeilen.<br />
Vor allem im Netzwerk kostet es se<strong>in</strong>e Zeit, e<strong>in</strong>e größere Menge von Datensätzen<br />
zu übertragen. Es ist deshalb oft praktisch, zunächst e<strong>in</strong>en zu holen und anzuzeigen.<br />
Während der Anwender sich mit diesem Teilergebnis beschäftigt, wird<br />
„im H<strong>in</strong>tergrund“ der nächste Abschnitt geholt usw.<br />
Im <strong>SQL</strong>-Standard gibt es dafür (noch) ke<strong>in</strong> Verfahren. Abweichend vom üblichen<br />
V<strong>org</strong>ehen <strong>in</strong> diesem Buch erhalten Sie Lösungen für verschiedene DBMS.<br />
Anstelle konstanter Werte (ohne Klammern) kann <strong>in</strong> allen folgenden Fällen auch<br />
e<strong>in</strong> <strong>SQL</strong>-Ausdruck (<strong>in</strong> Klammern) angegeben werden.<br />
23.1.1. Firebird: FIRST SKIP oder ROWS<br />
Firebird bietet gleich zwei Lösungen an, die erste mit FIRST / SKIP:<br />
213
Nützliche Erweiterungen<br />
SELECT [DISTINCT]<br />
[ FIRST ]<br />
[ SKIP ]<br />
FROM ... /* usw. */<br />
Der FIRST-Parameter gibt an, wie viele Zeilen am Anfang anzuzeigen s<strong>in</strong>d; der<br />
SKIP-Parameter legt fest, wie viele Zeilen davor übersprungen werden sollen.<br />
Beide Parameter werden e<strong>in</strong>zeln oder zusammen benutzt; sie folgen direkt als<br />
erste Klausel nach DISTINCT, noch vor der Spaltenliste. E<strong>in</strong>ige Beispiele:<br />
Nur FIRST zeigt die ersten Zeilen.<br />
Firebird-Quelltext<br />
SELECT FIRST 10<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
Nur SKIP überspr<strong>in</strong>gt die ersten Zeilen.<br />
Firebird Quelltext<br />
SELECT SKIP 10<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
FIRST zeigt die ersten 10 Zeilen an, aber wegen SKIP werden vorher 5 Zeilen<br />
übersprungen.<br />
Firebird-Quelltext<br />
SELECT FIRST 10 SKIP 5<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
Mit e<strong>in</strong>em Ausdruck kann dafür ges<strong>org</strong>t werden, dass etwa das erste Viertel der<br />
Datensätze abgerufen wird:<br />
214<br />
Firebird-Quelltext<br />
SELECT FIRST ( SELECT COUNT(*) FROM Mitarbeiter) / 4 )<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
Der Wert für FIRST wird aus der Anzahl der Datensätze berechnet.
Beschränkung auf e<strong>in</strong>e Anzahl Zeilen<br />
Die zweite Firebird-Variante benutzt mit ROWS direkt Zeilennummern:<br />
SELECT ... FROM ... WHERE ...<br />
ORDER BY ...<br />
ROWS [ TO ]<br />
Die ROWS-Parameter legen fest, dass (nur) e<strong>in</strong>e bestimmte Anzahl Zeilen angezeigt<br />
werden sollen, die durch die Zeilennummern gekennzeichnet s<strong>in</strong>d.<br />
• Wenn nur ROWS benutzt wird, bezeichnet die Gesamtzahl der angezeigten<br />
Zeilen.<br />
• Wenn ROWS zusammen mit TO benutzt wird, ist die erste Zeilennummer<br />
und die letzte Zeilennummer.<br />
E<strong>in</strong>ige Beispiele:<br />
Ausgabe der Zeilen 10 bis 20 (also <strong>in</strong>sgesamt 11 Zeilen)<br />
Firebird Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name<br />
ROWS 10 TO 20;<br />
Der erste Datensatz gemäß Sortierung<br />
Firebird Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name<br />
ROWS 1;<br />
Der letzte Datensatz gemäß Sortierung<br />
Firebird Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name DESC<br />
ROWS 1;<br />
Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird,<br />
ist oft sehr nützlich.<br />
Bei e<strong>in</strong>er Interbase-Datenbank s<strong>in</strong>d auch prozentuale Angaben möglich; das ist<br />
bei Firebird entfallen.<br />
215
Nützliche Erweiterungen<br />
23.1.2. Microsoft <strong>SQL</strong>: TOP<br />
Der MS-<strong>SQL</strong> Server benutzt diese Syntax:<br />
SELECT [DISTINCT]<br />
TOP ( ) [PERCENT] [WITH TIES]<br />
FROM ... /* usw. */<br />
Die TOP-Parameter legen fest, dass (nur) e<strong>in</strong>e bestimmte Anzahl Zeilen angezeigt<br />
werden soll, die durch die Zeilennummern gekennzeichnet s<strong>in</strong>d. Diese Parameter<br />
folgen als erste Klausel nach DISTINCT, noch vor der Spaltenliste.<br />
• bezeichnet die Anzahl aller angezeigten Zeilen. Es wird empfohlen, die<br />
Klammern immer zu setzen.<br />
• Wenn TOP zusammen mit PERCENT benutzt wird, handelt es sich dabei um<br />
die prozentuale Angabe der Zeilenzahl.<br />
• WITH TIES muss zusammen mit ORDER BY benutzt werden und liefert dann<br />
zusätzliche doppelte Zeilen, wenn der letzte Wert nach der Sortierung gleich<br />
ist den Werten <strong>in</strong> danach folgenden Zeilen.<br />
E<strong>in</strong>ige Beispiele:<br />
Nur TOP zeigt die ersten Zeilen.<br />
MS-<strong>SQL</strong>-Quelltext<br />
SELECT TOP ( 10 )<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
TOP + PERCENT zeigt z. B. das erste Viertel an.<br />
MS-<strong>SQL</strong>-Quelltext<br />
SELECT TOP ( 25 ) PERCENT<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name;<br />
Der letzte Datensatz gemäß Sortierung<br />
216<br />
MS-<strong>SQL</strong>-Quelltext<br />
SELECT TOP ( 1 )<br />
ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name DESC;
Beschränkung auf e<strong>in</strong>e Anzahl Zeilen<br />
Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird,<br />
ist oft sehr nützlich.<br />
23.1.3. My<strong>SQL</strong> und Postgre<strong>SQL</strong>: LIMIT<br />
Diese DBMS benutzen den LIMIT-Parameter. Dieser Parameter folgt nach<br />
ORDER BY, wobei die Sortierung nicht angegeben werden muss.<br />
SELECT ... FROM ... WHERE ...<br />
ORDER BY ...<br />
LIMIT OFFSET <br />
Dabei wird mit angegeben, wie viele Zeilen am Anfang angezeigt werden<br />
sollen. Mit kann nach dem Begriff OFFSET außerdem angegeben<br />
werden, wie viele Zeilen davor übersprungen werden sollen.<br />
Es werden die ersten 10 Zeilen angezeigt.<br />
My<strong>SQL</strong>-Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name<br />
LIMIT 10;<br />
Zuerst werden 5 Zeilen übersprungen; die ersten 10 Zeilen, die danach folgen,<br />
werden angezeigt.<br />
My<strong>SQL</strong>-Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name<br />
LIMIT 10 OFFSET 5;<br />
Der letzte Datensatz gemäß Sortierung:<br />
My<strong>SQL</strong>-Quelltext<br />
SELECT ID, Name, Vorname, Abteilung_ID AS Abt<br />
FROM Mitarbeiter<br />
ORDER BY Name DESC<br />
LIMIT 1;<br />
Vor allem das letzte Beispiel, bei dem mit DESC die Sortierung umgekehrt wird,<br />
ist oft sehr nützlich.<br />
E<strong>in</strong>e andere Schreibweise für diesen Parameter verzichtet auf das Wort OFFSET:<br />
217
Nützliche Erweiterungen<br />
SELECT ... FROM ... WHERE<br />
ORDER BY ...<br />
LIMIT [ , ] <br />
Bei dieser Variante wird ebenso mit angegeben, wie viele Zeilen angezeigt<br />
werden sollen. Mit kann außerdem angegeben werden, wie viele<br />
Zeilen davor übersprungen werden sollen; dieser Wert wird jedoch zuerst angegeben<br />
und durch e<strong>in</strong> Komma von der gewünschten Zeilenzahl getrennt.<br />
Die Bedeutung beider Varianten ist identisch, sodass Beispiele nicht nötig s<strong>in</strong>d.<br />
Es ist wohl Geschmackssache, welche Version „e<strong>in</strong>gängiger“ ist.<br />
23.1.4. Oracle: ROWNUM<br />
Bei dem DBMS Oracle gibt es bei jedem SELECT-Ergebnis e<strong>in</strong>e implizite Spalte<br />
Rownum. Man kann diese Spalte mit ausgeben lassen. Solange man ke<strong>in</strong> ORDER<br />
BY angibt, ist die Reihenfolge der ausgegebenen Sätze nicht festgelegt. Dieselbe<br />
Abfrage kann an e<strong>in</strong>em anderen Tag durchaus e<strong>in</strong>e andere Nummerierung<br />
der Sätze hervorbr<strong>in</strong>gen. Das liegt z. B. daran, dass jemand die Datensätze <strong>in</strong> der<br />
Zwischenzeit re<strong>org</strong>anisiert hat.<br />
Oracle-Quelltext<br />
SELECT Name, Rownum<br />
FROM Mitarbeiter;<br />
NAME ROWNUM<br />
Müller 1<br />
Schneider 2<br />
Meyer 3<br />
Schmitz 4 /* usw. */<br />
Rownum als implizite Spalte der Ergebnismenge<br />
Wenn man diese Spalte Rownum nicht angibt, dann wird sie auch nicht ausgegeben.<br />
Ihr Vorteil ist, dass man sie auch bei WHERE verwenden kann:<br />
218<br />
Oracle-Quelltext<br />
SELECT Name<br />
FROM Mitarbeiter<br />
WHERE Rownum
NAME ROWNUM<br />
Müller 1<br />
Schneider 2<br />
Beschränkung auf e<strong>in</strong>e Anzahl Zeilen<br />
Folgende Formulierung funktioniert allerd<strong>in</strong>gs nicht, wenn man nur den 10. Satz<br />
ausgeben will:<br />
Oracle-Quelltext Falsch<br />
SELECT Name<br />
FROM Mitarbeiter;<br />
WHERE Rownum = 10;<br />
Nur den 10. Satz anzeigen − so geht es nicht.<br />
Das liegt daran, dass der Zähler Rownum nur die Zeilen zählt, die auch wirklich<br />
ausgegeben werden. Wenn die Tabelle 500 Sätze hat, dann wird bei jedem Satz<br />
geprüft, ob Rownum bereits den Wert 10 erreicht hat. Das ist jedoch nie der Fall,<br />
da der Zähler immer den Wert 0 behält.<br />
Da muss man schon das DBMS zw<strong>in</strong>gen, die Ergebnismenge mit der Rownum<br />
zwischenzuspeichern. Dann geht es:<br />
Oracle-Quelltext<br />
SELECT Name<br />
FROM ( SELECT Name, Rownum R<br />
FROM Mitarbeiter )<br />
WHERE R = 10;<br />
Nur den 10. Satz anzeigen − so klappt es.<br />
Welcher Satz dabei ausgegeben wird, ist jedoch dem Zufall überlassen.<br />
Wenn man die Ergebnismenge sortiert ausgeben und dabei auf die ersten 6 Sätze<br />
beschränken will, dann funktioniert die folgende Formulierung nicht:<br />
Oracle-Quelltext Falsch<br />
SELECT Name<br />
FROM Mitarbeiter;<br />
WHERE Rownum
Nützliche Erweiterungen<br />
Für die richtige Lösung muss die Datenbank schon etwas mehr tun. Sie muss<br />
zuerst alle vorhandenen Sätze sortieren und dann die ersten 6 Sätze ausgeben:<br />
Oracle-Quelltext<br />
SELECT Name<br />
FROM ( SELECT Name<br />
FROM Mitarbeiter<br />
ORDER BY Name )<br />
WHERE Rownum
SELECT <br />
FROM <br />
WHERE <br />
UNION [ DISTINCT | ALL ]<br />
SELECT <br />
FROM <br />
WHERE <br />
Mehrere Abfragen zusammenfassen<br />
Bei den e<strong>in</strong>zelnen Abfragen können grundsätzlich alle Klauseln benutzt werden.<br />
Bitte beachten Sie folgende Bed<strong>in</strong>gungen:<br />
• Alle E<strong>in</strong>zelabfragen müssen <strong>in</strong> Anzahl und Reihenfolge der Ergebnisspalten<br />
übere<strong>in</strong>stimmen. Die Datentypen müssen je nach DBMS genau gleich se<strong>in</strong><br />
oder zum<strong>in</strong>dest so ähnlich, dass sie automatisch konvertiert werden können.<br />
• Die Spaltennamen werden aus der ersten Spaltenliste übernommen, ggf. unter<br />
Berücksichtigung e<strong>in</strong>es Alias.<br />
• Doppelte Zeilen aus den E<strong>in</strong>zelabfragen werden unterdrückt, d. h. DISTINCT<br />
ist Standard und kann weggelassen werden. Zur Anzeige doppelter Zeilen ist<br />
ALL anzugeben.<br />
• Die Benutzung von Klammern sowie die Sortierung der gesamten Ergebnismenge<br />
durch ORDER BY wird je nach DBMS unterschiedlich geregelt.<br />
Aufgabe: Das folgende Beispiel ist e<strong>in</strong>e e<strong>in</strong>fache Zusammenfassung aller Fahrzeuge<br />
aus den Tabellen Fahrzeuge und Dienstwagen:<br />
SELECT ID, Kennzeichen, Farbe<br />
FROM Dienstwagen<br />
UNION<br />
SELECT ID, Kennzeichen, Farbe<br />
FROM Fahrzeug;<br />
Im folgenden Beispiel werden als Spalte Var Konstanten e<strong>in</strong>getragen, die die Herkunft<br />
der Daten angeben, und es werden verschiedene Auswahlbed<strong>in</strong>gungen benutzt.<br />
SELECT ’D’ AS Var, ID, Kennzeichen, Farbe<br />
FROM Dienstwagen<br />
WHERE Fahrzeugtyp_ID
Nützliche Erweiterungen<br />
My<strong>SQL</strong>-Quelltext<br />
( SELECT ’D’ AS Var, ID, Kennzeichen, Farbe<br />
FROM Dienstwagen<br />
WHERE Fahrzeugtyp_ID
CASE WHEN – Fallunterscheidungen<br />
SELECT FROM WHERE <br />
INTERSECT<br />
SELECT FROM WHERE <br />
Die Ergebnismenge besteht aus allen Zeilen, die sowohl zum ersten SELECT-<br />
Befehl als auch zum zweiten SELECT-Befehl gehören.<br />
EXCEPT (MS-<strong>SQL</strong>) oder MINUS (Oracle) liefern die Differenz der Teilabfragen.<br />
SELECT FROM WHERE <br />
EXCEPT | MINUS<br />
SELECT FROM WHERE <br />
Die Ergebnismenge besteht aus allen Zeilen, die zum ersten SELECT-Befehl gehören,<br />
aber beim zweiten SELECT-Befehl nicht enthalten s<strong>in</strong>d.<br />
Wie bei UNION können auch solche Verknüpfungen durch sehr komplizierte<br />
JOINs und WHERE-Klauseln nachgebildet werden.<br />
23.3. CASE WHEN – Fallunterscheidungen<br />
Oft möchte man bei e<strong>in</strong>em <strong>SQL</strong>-Befehl je nach Situation andere Werte erhalten.<br />
Das e<strong>in</strong>fachste Beispiel s<strong>in</strong>d die Mitarbeiter: Anstelle des Feld<strong>in</strong>halts Ist_Leiter<br />
mit 'J' oder 'N' kann 'Leiter' oder e<strong>in</strong>e leere Zeichenkette angezeigt werden. Dafür<br />
ist der CASE-Ausdruck v<strong>org</strong>esehen, den es <strong>in</strong> zwei Varianten gibt.<br />
Als können wahlweise konstante Werte oder komplexe Ausdrücke<br />
verwendet werden. Der CASE-Ausdruck ist nicht nur für SELECT, sondern auch<br />
für Speichern-Befehle geeignet und tritt nicht nur (wie <strong>in</strong> den meisten Beispielen)<br />
als Teil der Spaltenliste auf, sondern auch <strong>in</strong> der WHERE-Klausel oder an<br />
anderen Stellen, an denen entsprechende Werte benötigt werden.<br />
Der Datentyp des CASE-Ausdrucks ergibt sich aus den Ergebniswerten.<br />
23.3.1. Simple Case – die e<strong>in</strong>fache Fallunterscheidung<br />
Die e<strong>in</strong>fache Variante hat folgende Syntax:<br />
CASE <br />
WHEN THEN <br />
[ WHEN THEN ] /* usw. */<br />
[ ELSE ]<br />
END<br />
223
Nützliche Erweiterungen<br />
Bei dieser Version wird der Wert, der sich durch den ersten Ausdruck beim CASE<br />
ergibt, nache<strong>in</strong>ander mit den Ausdrücken nach WHEN verglichen. Sobald e<strong>in</strong>e<br />
Gleichheit festgestellt wird, wird der Wert des -Ausdrucks als Wert für<br />
den CASE-Ausdruck übernommen. Wenn es ke<strong>in</strong>e Übere<strong>in</strong>stimmung gibt, wird<br />
der -Ausdruck als V<strong>org</strong>abewert übernommen; wenn ELSE nicht vorhanden<br />
ist, wird NULL als Wert genommen. Beispiele:<br />
Aufgabe: E<strong>in</strong> vorhandener Feld<strong>in</strong>halt, nämlich 'J' oder 'N', wird für die Ausgabe<br />
durch andere Texte ersetzt:<br />
SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb,<br />
CASE Ist_Leiter<br />
WHEN ’J’ THEN ’Leiter’<br />
ELSE ”<br />
END AS Leiter,<br />
Mobil<br />
FROM Mitarbeiter;<br />
PERS NAME VORNAME GEB LEITER MOBIL<br />
40001 Langmann Matthias 28.03.1976 Leiter<br />
40002 Peters Michael 15.11.1973<br />
50001 Pohl Helmut 27.10.1980 Leiter (0171) 4123456<br />
50002 Braun Christian 05.09.1966 (0170) 8351647<br />
Dieselbe Lösung ohne ELSE-Zweig ändert nur die Anzeige:<br />
SELECT Personalnummer AS Pers, Name, Vorname, Geburtsdatum AS Geb,<br />
CASE Ist_Leiter<br />
WHEN ’J’ THEN ’Leiter’<br />
END AS Leiter,<br />
Mobil<br />
FROM Mitarbeiter;<br />
PERS NAME VORNAME GEB LEITER MOBIL<br />
40001 Langmann Matthias 28.03.1976 Leiter<br />
40002 Peters Michael 15.11.1973 <br />
50001 Pohl Helmut 27.10.1980 Leiter (0171) 4123456<br />
50002 Braun Christian 05.09.1966 (0170) 8351647<br />
Aufgabe: Bei der Art der Versicherungsverträge möchten wir die Varianten im<br />
„Klartext“ lesen:<br />
224<br />
SELECT Vertragsnummer AS Vertrag, Abschlussdatum AS Datum,<br />
CASE Art<br />
WHEN ’VK’ THEN ’Vollkasko’<br />
WHEN ’TK’ THEN ’Teilkasko’<br />
WHEN ’HP’ THEN ’Haftpflicht’<br />
END<br />
FROM Versicherungsvertrag;
VERTRAG DATUM CASE<br />
DB-04 25.01.2008 Haftpflicht<br />
RH-01 11.12.1976 Vollkasko<br />
RD-02 29.01.1988 Haftpflicht<br />
RM-03 13.01.1996 Haftpflicht<br />
RD-04 23.11.2006 Haftpflicht<br />
RR-05 29.06.1990 Teilkasko<br />
CASE WHEN – Fallunterscheidungen<br />
23.3.2. Searched Case – die komplexe Fallunterscheidung<br />
Die komplexe Variante hat ke<strong>in</strong>en Ausdruck h<strong>in</strong>ter dem CASE; sie arbeitet nach<br />
folgender Syntax:<br />
CASE<br />
WHEN THEN <br />
[ WHEN THEN ] /* usw. */<br />
[ ELSE ]<br />
END<br />
Bei dieser Variante werden nache<strong>in</strong>ander Bed<strong>in</strong>gungen geprüft. Sobald e<strong>in</strong>e Bed<strong>in</strong>gung<br />
als WAHR festgestellt wird, wird der -Wert h<strong>in</strong>ter THEN als Wert<br />
für den CASE-Ausdruck übernommen. Wenn es ke<strong>in</strong>e Übere<strong>in</strong>stimmung gibt,<br />
wird der -Ausdruck als V<strong>org</strong>abewert übernommen; wenn ELSE nicht<br />
vorhanden ist, wird NULL als Wert genommen. Beispiele:<br />
Aufgabe: In e<strong>in</strong>er e<strong>in</strong>zigen Zeile wird angegeben, wie viele Versicherungsverträge<br />
<strong>in</strong>nerhalb e<strong>in</strong>es Jahrzehnts abgeschlossen wurden.<br />
SELECT<br />
SUM( CASE WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1970 AND 1979<br />
THEN 1 ELSE 0<br />
END ) AS S_197_,<br />
SUM( CASE WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1980 AND 1989<br />
THEN 1 ELSE 0<br />
END ) AS S_198_,<br />
SUM( CASE WHEN EXTRACT(YEAR FROM Abschlussdatum) BETWEEN 1990 AND 1999<br />
THEN 1 ELSE 0<br />
END ) AS S_199_,<br />
SUM( CASE WHEN EXTRACT(YEAR FROM Abschlussdatum) >= 2000<br />
THEN 1 ELSE 0<br />
END ) AS S_200_<br />
FROM Versicherungsvertrag;<br />
S_197_ S_198_ S_199_ S_200_<br />
6 6 6 5<br />
225
Nützliche Erweiterungen<br />
Dazu wird für jedes Jahrzehnt e<strong>in</strong>e Spalte v<strong>org</strong>esehen. Jede Spalte enthält e<strong>in</strong>en<br />
CASE-Ausdruck mit e<strong>in</strong>er WHEN-Bed<strong>in</strong>gung. Wenn für e<strong>in</strong>e Zeile diese Bed<strong>in</strong>gung<br />
TRUE ergibt, wird 1 zur Summe dieses Jahrzehnts addiert, andernfalls 0.<br />
Aufgabe: Der CASE-Ausdruck soll <strong>in</strong>nerhalb von ORDER BY e<strong>in</strong>e unterschiedliche<br />
Sortierung, abhängig von der Art der Versicherung, erreichen:<br />
SELECT ID, Art,<br />
Abschlussdatum AS Datum, Vertragsnummer AS Vertr,<br />
Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG<br />
FROM Versicherungsvertrag<br />
ORDER BY Art,<br />
CASE WHEN Art = ’TK’ THEN ID<br />
WHEN Art = ’VK’ THEN Mitarbeiter_ID<br />
WHEN Art = ’HP’ THEN Fahrzeug_ID<br />
END;<br />
ID ART DATUM VERTR MIT FZG<br />
14 HP 15.03.1998 KG-03 9 16<br />
18 HP 17.05.2000 HG-03 9 17<br />
19 HP 21.09.2004 HB-04 9 19<br />
10 HP 23.11.2006 RD-04 9 20<br />
6 HP 25.01.2008 DB-04 9 21<br />
15 HP 27.03.1988 KV-04 10 22<br />
11 TK 29.06.1990 RR-05 9 23<br />
12 TK 14.02.1978 KB-01 10 6<br />
21 VK 20.06.1982 XH-02 9 8<br />
23 VK 19.07.2002 XO-04 9 18<br />
7 VK 11.12.1976 RH-01 10 5<br />
22 VK 05.06.1992 XW-03 10 13<br />
Hier wurden alle Abschnitte numerisch sortiert. Auch die Teilsortierung nach<br />
Datum funktioniert, muss aber unter Umständen überarbeitet werden:<br />
Firebird-Quelltext Falsch<br />
SELECT ID, Art,<br />
Abschlussdatum AS Datum, Vertragsnummer AS Vertr,<br />
Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG<br />
FROM Versicherungsvertrag<br />
ORDER BY Art,<br />
CASE WHEN Art = ’TK’ THEN ID<br />
WHEN Art = ’VK’ THEN Mitarbeiter_ID<br />
WHEN Art = ’HP’ THEN Abschlussdatum<br />
END;<br />
Invalid token. <strong>SQL</strong> error code = -104.<br />
Datatypes are not comparable <strong>in</strong> expression CASE.<br />
Auch wenn Firebird (wie im ersten Beispiel) Namen von Spalten akzeptiert, werden<br />
die Datentypen der Spalten verglichen. Diese s<strong>in</strong>d offensichtlich nicht kom-<br />
226
CASE WHEN – Fallunterscheidungen<br />
patibel; also bricht Firebird diese Abfrage ab. Aber <strong>in</strong> der ORDER BY-Klausel können<br />
auch die Nummern der Spalten angegeben werden; dann klappt es:<br />
Firebird-Quelltext<br />
SELECT ID, Art,<br />
Abschlussdatum AS Datum, Vertragsnummer AS Vertr,<br />
Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG<br />
FROM Versicherungsvertrag<br />
ORDER BY Art,<br />
CASE WHEN Art = ’TK’ THEN ID<br />
WHEN Art = ’VK’ THEN Mitarbeiter_ID<br />
WHEN Art = ’HP’ THEN 2<br />
END;<br />
Mit Nummern der Spalten anstelle der Namen.<br />
Bitte wundern Sie sich nicht über das identische Ergebnis wie oben bei der Sortierung<br />
nach Fahrzeug_ID: Die Fahrzeuge wurden <strong>in</strong> der gleichen Reihenfolge<br />
erfasst wie die Verträge; also s<strong>in</strong>d <strong>in</strong> der ersten Zeit beide Sortierungen gleich.<br />
Die Sortierung kann nur e<strong>in</strong>heitlich festgelegt werden, aber nicht mal so, mal so:<br />
Firebird-Quelltext Falsch<br />
SELECT ID, Art,<br />
Abschlussdatum AS Datum, Vertragsnummer AS Vertr,<br />
Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG<br />
FROM Versicherungsvertrag<br />
ORDER BY Art,<br />
CASE<br />
WHEN Art = ’TK’ THEN ID ASC<br />
WHEN Art = ’VK’ THEN Mitarbeiter_ID ASC<br />
WHEN Art = ’HP’ THEN 2 DESC<br />
END;<br />
Invalid token. Dynamic <strong>SQL</strong> Error. <strong>SQL</strong> error code = -104.<br />
Token unknown - l<strong>in</strong>e 7, column 39. ASC.<br />
SELECT ID, Art,<br />
Abschlussdatum AS Datum, Vertragsnummer AS Vertr,<br />
Mitarbeiter_ID AS Mit, Fahrzeug_ID AS FZG<br />
FROM Versicherungsvertrag<br />
ORDER BY Art,<br />
CASE<br />
WHEN Art = ’TK’ THEN ID<br />
WHEN Art = ’VK’ THEN Mitarbeiter_ID<br />
WHEN Art = ’HP’ THEN 2<br />
END DESC;<br />
E<strong>in</strong>heitliche Sortierung DESC sollte möglich se<strong>in</strong><br />
227
Nützliche Erweiterungen<br />
Zum<strong>in</strong>dest unter Firebird gel<strong>in</strong>gt dennoch ke<strong>in</strong>e korrekte Sortierung: nur der Abschnitt<br />
'TK' wird absteigend sortiert, die beiden anderen nicht. Sie müssen aber<br />
sowieso selbst ausprobieren, was <strong>in</strong> welchem DBMS möglich ist und was nicht.<br />
23.3.3. CASE-Ausdruck beim Speichern<br />
Wie beim SELECT-Befehl kann e<strong>in</strong> CASE-Ausdruck auch beim Speichern benutzt<br />
werden, vor allem bei den Zuweisungen der Werte für INSERT und UPDATE sowie<br />
bei der WHERE-Klausel für UPDATE und DELETE. Da sich diese Kapitel vor<br />
allem mit Abfragen beschäftigen, beschränken wir uns auf e<strong>in</strong> Beispiel.<br />
Aufgabe: Die Zuständigkeit der Mitarbeiter für die Versicherungsverträge wird<br />
neu geregelt:<br />
• Vollkasko-Verträge gehören zum Aufgabenbereich des Abteilungsleiters<br />
(Mitarbeiter-ID 9).<br />
• Teilkasko-Verträge gehören zum Aufgabenbereich des Mitarbeiters mit ID 12.<br />
• Die Haftpflicht-Verträge werden <strong>in</strong> Abhängigkeit von der ID aufgeteilt auf die<br />
Mitarbeiter 10 und 11.<br />
UPDATE Versicherungsvertrag<br />
SET Mitarbeiter_id =<br />
CASE WHEN Art = ’VK’ THEN 9<br />
WHEN Art = ’TK’ THEN 12<br />
WHEN Art = ’HP’ THEN 10 + MOD(ID, 2)<br />
END;<br />
Es wird also e<strong>in</strong>e normale SET-Anweisung geschrieben. Die CASE-Anweisung<br />
liefert die benötigten Werte, wobei für den Fall „Haftpflicht“ für gerade IDs der<br />
Wert (10+0) und für ungerade IDs der Wert (10+1) gesetzt wird. E<strong>in</strong>e WHERE-<br />
Klausel ist nicht erforderlich, weil alle Verträge neu zugeordnet werden sollen.<br />
23.4. Zusammenfassung<br />
In diesem Kapitel lernten Sie e<strong>in</strong>ige nützliche Erweiterungen vor allem für Abfragen<br />
kennen:<br />
• Je nach DBMS wird unterschiedlich geregelt, wenn nur e<strong>in</strong>e gewisse Anzahl<br />
von Zeilen gewünscht wird.<br />
• Mit UNION wird das Ergebnis von zwei oder mehr Abfragen <strong>in</strong> e<strong>in</strong>er geme<strong>in</strong>samen<br />
Ergebnistabelle zusammengefasst.<br />
• Mit CASE WHEN s<strong>in</strong>d Fallunterscheidungen möglich; dies ist auch beim Speichern<br />
sowohl für die Werte als auch für die WHERE-Klausel hilfreich.<br />
228
23.5. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 231.<br />
Übung 1 – Richtig oder falsch?<br />
Welche der folgenden Aussagen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
Übungen<br />
1. Es ist äußerst selten erforderlich, das Ergebnis e<strong>in</strong>er Abfrage nur <strong>in</strong> Teilen<br />
zu holen.<br />
2. Für die Anzeige oder Verwendung e<strong>in</strong>er ROW_NUMBER hat der <strong>SQL</strong>-<br />
Standard (2003) e<strong>in</strong> Verfahren v<strong>org</strong>eschrieben.<br />
3. E<strong>in</strong> solches Verfahren wird von den meisten DBMS verwendet, ist aber <strong>in</strong><br />
unterschiedlicher Weise verwirklicht.<br />
4. Bei UNION, INTERSECT usw. müssen die ausgewählten Spalten von der<br />
Anzahl her übere<strong>in</strong>stimmen.<br />
5. Bei UNION, INTERSECT usw. müssen die ausgewählten Spalten vom Datentyp<br />
her genau übere<strong>in</strong>stimmen.<br />
Übung 2 – UNION, INTERSECT usw.<br />
Was ist an den folgenden Befehlen falsch oder fragwürdig? Wie kann man das<br />
DBMS dazu br<strong>in</strong>gen, den „fragwürdigen“ Befehl auf jeden Fall auszuführen?<br />
-- Befehl 1<br />
SELECT Name, Vorname FROM Mitarbeiter<br />
UNION<br />
SELECT Name, Vorname, Geburtsdatum FROM Versicherungsnehmer<br />
-- Befehl 2<br />
SELECT Name, Vorname, Abteilung_ID FROM Mitarbeiter<br />
UNION<br />
SELECT Name, Vorname, Geburtsdatum FROM Versicherungsnehmer<br />
Übung 3 – UNION, INTERSECT usw.<br />
Erstellen Sie e<strong>in</strong>e Abfrage für die Tabellen Versicherungsvertrag, Fahrzeug,<br />
Zuordnung_SF-FZ, <strong>in</strong> der folgende Bed<strong>in</strong>gungen berücksichtigt werden:<br />
• Es sollen Vertragsnummer, Abschlussdatum, Kennzeichen sowie ggf. anteilige<br />
Schadenshöhe angezeigt werden.<br />
• Fahrzeuge mit e<strong>in</strong>em Schaden sollen vollständig angezeigt werden.<br />
• Fahrzeuge ohne Schaden sollen nur angezeigt werden, wenn der Vertrag vor<br />
1990 abgeschlossen wurde.<br />
• Das Ergebnis soll nach Schadenshöhe und Kennzeichen sortiert werden.<br />
229
Nützliche Erweiterungen<br />
Benutzen Sie UNION zur Verknüpfung der Verträge mit und ohne Schaden.<br />
Übung 4 – Fallunterscheidung für Nachschlagewerte<br />
Schreiben Sie e<strong>in</strong>en SELECT-Befehl, bei dem die korrekte Briefanrede für die Mitarbeiter<br />
erstellt wird.<br />
H<strong>in</strong>weis: Benutzen Sie CONCAT zum Verknüpfen mehrerer Zeichenketten.<br />
Übung 5 – Fallunterscheidung für Bereiche<br />
Zur Tabelle Fahrzeug soll aus dem Kennzeichen die regionale Herkunft abgeleitet<br />
und angezeigt werden. Schreiben Sie e<strong>in</strong>e Abfrage für diese Spalten Kennzeichen<br />
und Herkunft.<br />
H<strong>in</strong>weis: Benutzen Sie POSITION zur Feststellung des B<strong>in</strong>destrichs sowie SUB-<br />
STRING.<br />
Zusatzfrage: Wie müsste e<strong>in</strong> solches Problem s<strong>in</strong>nvollerweise gelöst werden, falls<br />
e<strong>in</strong>e solche Zuordnung häufiger und allgeme<strong>in</strong>er benötigt wird?<br />
Übung 6 – Fallunterscheidung für mehrere Varianten<br />
Aus der Tabelle Versicherungsnehmer sollen Name, Vorname und Anschrift<br />
angezeigt werden. Außerdem soll jede Adresse e<strong>in</strong>e Markierung bekommen:<br />
F = (eigene) Firmenkunden, P = (eigene) Privatkunden, X = eXterne Verträge (d. h.<br />
Kunden fremder Versicherungsgesellschaften). Begründen Sie die Reihenfolge<br />
der WHEN-ELSE-Bed<strong>in</strong>gungen.<br />
Übung 7 – Fallunterscheidung beim Speichern<br />
Schreiben Sie e<strong>in</strong>en UPDATE-Befehl, der nach Ablauf e<strong>in</strong>es Versicherungsjahres<br />
den Prämiensatz für das nächste Jahr ändert. Berücksichtigen Sie dabei folgende<br />
Bed<strong>in</strong>gungen:<br />
• Neue Verträge, für die noch ke<strong>in</strong> Prämiensatz gilt, werden auf 200 [Prozent]<br />
gesetzt.<br />
• Verträge mit e<strong>in</strong>em Prämiensatz von m<strong>in</strong>destens 100 werden um 20 reduziert.<br />
• Verträge mit e<strong>in</strong>em Prämiensatz von weniger als 100 werden um 10 reduziert.<br />
• Der M<strong>in</strong>destsatz von 30 darf nicht unterschritten werden.<br />
Ignorieren Sie dabei zunächst, dass dies nur im Fall von Schadensfreiheit gelten<br />
darf und <strong>in</strong>nerhalb e<strong>in</strong>es Jahres nur e<strong>in</strong>mal neu berechnet werden darf.<br />
230
Übung 8 – Fallunterscheidung beim Speichern<br />
Bei genauerer Untersuchung von Übung 7 s<strong>in</strong>d weitere Bed<strong>in</strong>gungen nötig.<br />
Übungen<br />
1. Wie wäre die Schadensfreiheit zu berücksichtigen? Abhängig von der Schadenshöhe<br />
soll sich der Prämiensatz gar nicht, wenig oder mehr erhöhen.<br />
2. Wie kann unter Verwendung des Datums Prämienänderung gesichert werden,<br />
dass die Neuberechnung nur e<strong>in</strong>mal jährlich stattf<strong>in</strong>den darf?<br />
Versuchen Sie, dies im selben UPDATE-Befehl zu berücksichtigen. Sie sollen ke<strong>in</strong>en<br />
Befehl schreiben, sondern die nötigen Klauseln erwähnen und erläutern.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 229.<br />
Lösung zu Übung 1 – Richtig oder falsch?<br />
Die Aussagen 3, 4 s<strong>in</strong>d richtig. Die Aussagen 1, 2, 5 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – UNION, INTERSECT usw.<br />
Bei Befehl 1 werden unterschiedlich viele Spalten ausgewählt, das ist unzulässig.<br />
Bei Befehl 2 unterscheiden sich die Datentypen der dritten Spalte; es ist unsicher,<br />
ob das DBMS die unterschiedlichen Spalten e<strong>in</strong>heitlich als Zeichenkette<br />
(zulässig) oder nicht-kompatibel (unzulässig) <strong>in</strong>terpretieren will.<br />
E<strong>in</strong> CAST beider Spalten auf VARCHAR macht die Datentypen kompatibel.<br />
Lösung zu Übung 3 – UNION, INTERSECT usw.<br />
E<strong>in</strong>e mögliche Variante lautet so (Firebird-Version für ORDER BY):<br />
SELECT * FROM (<br />
SELECT Vertragsnummer, Abschlussdatum, Kennzeichen, Schadenshoehe<br />
FROM Versicherungsvertrag vv<br />
INNER JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
RIGHT JOIN Zuordnung_SF_FZ zu ON fz.ID = zu.Fahrzeug_ID<br />
UNION<br />
SELECT Vertragsnummer, Abschlussdatum, Kennzeichen, 0<br />
FROM Versicherungsvertrag vv<br />
INNER JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
WHERE Abschlussdatum < ’01.01.1990’ AND NOT<br />
EXISTS ( SELECT ID FROM Zuordnung_SF_FZ zu WHERE zu.Fahrzeug_ID = fz.ID )<br />
) ORDER BY Schadenshoehe, Kennzeichen;<br />
Der zweite SELECT kann auch so geschrieben werden:<br />
231
Nützliche Erweiterungen<br />
SELECT Vertragsnummer, Abschlussdatum, Kennzeichen, 0<br />
FROM Versicherungsvertrag vv<br />
INNER JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
LEFT JOIN Zuordnung_SF_FZ zu ON fz.ID = zu.Fahrzeug_ID<br />
WHERE Abschlussdatum < ’01.01.1990’ AND zu.ID IS NULL<br />
Lösung zu Übung 4 – Fallunterscheidung für Nachschlagewerte<br />
SELECT CONCAT( ’Sehr geehrte’,<br />
CASE Geschlecht<br />
WHEN ’M’ THEN ’r Herr ’<br />
WHEN ’W’ THEN ’ Frau ’<br />
ELSE ’/r Frau/Herr ’<br />
END,<br />
Name ) AS Anrede<br />
FROM Mitarbeiter;<br />
H<strong>in</strong>weise: CONCAT oder die Str<strong>in</strong>g-Verknüpfung erzeugen u. U. e<strong>in</strong>e falsche<br />
Länge des ersten Teils. Das kann mit TRIM „repariert“ werden; dann ist aber das<br />
abschließende Leerzeichen h<strong>in</strong>zuzufügen. Der ELSE-Zweig ist überflüssig, weil<br />
Geschlecht nicht NULL se<strong>in</strong> kann; er wird nur der Vollständigkeit halber genannt.<br />
Lösung zu Übung 5 – Fallunterscheidung für Bereiche<br />
SELECT Kennzeichen,<br />
CASE SUBSTRING(Kennzeichen FROM 1 FOR POSITION(’-’, Kennzeichen)-1 )<br />
WHEN ’RE’ THEN ’Kreis Reckl<strong>in</strong>ghausen’<br />
WHEN ’GE’ THEN ’Stadt Gelsenkirchen’<br />
WHEN ’E’ THEN ’Stadt Essen’<br />
WHEN ’BO’ THEN ’Stadt Bochum’<br />
WHEN ’HER’ THEN ’Stadt Herne’<br />
ELSE ’unbekannt’<br />
END AS Herkunft<br />
FROM Fahrzeug<br />
ORDER BY 2, 1;<br />
Zur Zusatzfrage: E<strong>in</strong>e Tabelle Region o. ä. würde mit LEFT JOIN über den o. g.<br />
SUBSTRING verknüpft. Auch e<strong>in</strong> INNER JOIN wäre möglich, dann würden aber<br />
Fahrzeuge fehlen, deren Kennzeichen <strong>in</strong> der Tabelle Region fehlen.<br />
Lösung zu Übung 6 – Fallunterscheidung für mehrere Varianten<br />
232<br />
SELECT Name, Vorname, PLZ, Strasse, Hausnummer,<br />
CASE<br />
WHEN Eigener_Kunde = ’N’ THEN ’X’
WHEN Vorname IS NULL OR Fuehrersche<strong>in</strong> IS NULL THEN ’F’<br />
ELSE ’P’<br />
END AS Markierung<br />
FROM Versicherungsnehmer;<br />
Übungen<br />
Zur Reihenfolge: Bei den Fremdverträgen wird nicht zwischen Firmen- und Privatkunden<br />
unterschieden; dies muss also zuerst geprüft werden. Bei Firmenkunden<br />
s<strong>in</strong>d weder Vorname noch Führersche<strong>in</strong>-Datum registriert, das wird als<br />
nächstes geprüft (e<strong>in</strong>es der WHEN-Kriterien würde genügen). Alle übrigen Fälle<br />
s<strong>in</strong>d Privatkunden.<br />
Lösung zu Übung 7 – Fallunterscheidung beim Speichern<br />
UPDATE Versicherungsvertrag<br />
SET Praemiensatz = CASE<br />
WHEN Praemiensatz IS NULL THEN 200<br />
WHEN Praemiensatz >= 100 THEN Praemiensatz - 20<br />
WHEN Praemiensatz >= 40 THEN Praemiensatz - 10<br />
ELSE 30<br />
END<br />
WHERE -- passende Bed<strong>in</strong>gungen<br />
Lösung zu Übung 8 – Fallunterscheidung beim Speichern<br />
1. Es wird e<strong>in</strong>e geschachtelte CASE-Anweisung verwendet. Zunächst wird unterschieden,<br />
ob überhaupt Schadensfälle aufgetreten s<strong>in</strong>d. Im ersten Zweig<br />
„ke<strong>in</strong> Schaden“ wird die Lösung von Übung 7 benutzt. Im zweiten Zweig<br />
„mit Schaden“ wird e<strong>in</strong>e ähnliche CASE-Anweisung e<strong>in</strong>gefügt, die abhängig<br />
von der Schadenshöhe den Prämiensatz erhöht.<br />
2. In der WHERE-Klausel wird geprüft, ob die letzte Prämienänderung überschritten<br />
ist. Gleichzeitig mit der Neuberechnung wird dieses Datum um<br />
e<strong>in</strong> Jahr weitergesetzt.<br />
H<strong>in</strong>weis: Dieses Verfahren wird ausführlich im Kapitel Prozeduren: Automatisches<br />
UPDATE 1 behandelt. Bei Interesse können Sie im dortigen Code den abschließenden<br />
UPDATE-Befehl vergleichen.<br />
1 Abschnitt 32.2.3 auf Seite 363<br />
233
24. Berechnete Spalten<br />
24.1. Ergebnis von Berechnungen . . . . . . . . . . . . . . . . . . . . . 236<br />
24.2. Zeichenketten verb<strong>in</strong>den und bearbeiten . . . . . . . . . . . . 236<br />
24.3. Ergebnis von Funktionen . . . . . . . . . . . . . . . . . . . . . . . 237<br />
24.4. Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
24.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
24.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238<br />
Mit Abfragen kann man nicht nur Spalten auswählen, sondern auch völlig neue<br />
Spalten aus anderen Spalten oder mit Funktionen erstellen.<br />
E<strong>in</strong>e neue Spalte wird als Teil e<strong>in</strong>er Abfrage wie folgt erstellt:<br />
[AS] <br />
Ausdruck ist allgeme<strong>in</strong> der H<strong>in</strong>weis auf etwas, das an der betreffenden Stelle verwendet<br />
wird: e<strong>in</strong> konstanter Wert, der Inhalt e<strong>in</strong>er oder mehrerer Spalten, e<strong>in</strong>e<br />
Berechnung mit diesen Spalten, das Ergebnis e<strong>in</strong>er Funktion oder das Ergebnis<br />
e<strong>in</strong>er Unterabfrage. Die Spalte, die das Ergebnis aufnimmt, erhält mit AS e<strong>in</strong>en<br />
eigenen Namen als Alias; das AS kann auch entfallen.<br />
Bei manchen DBMS ist die Angabe e<strong>in</strong>es Alias Pflicht, manche erzeugen bei Bedarf<br />
e<strong>in</strong>en zufälligen Namen, und andere meckern erst, wenn man versucht, e<strong>in</strong>e<br />
physische Tabelle mit e<strong>in</strong>er namenlosen Spalte zu füllen.<br />
Bei e<strong>in</strong>er Unterabfrage als berechneter Spalte muss sie unbed<strong>in</strong>gt − wie bei e<strong>in</strong>er<br />
Funktion, Berechnung oder Verknüpfung − für jede Zeile der Hauptabfrage<br />
genau e<strong>in</strong>en Wert ergeben. In e<strong>in</strong>em der Beispiele erreicht dies e<strong>in</strong>e passende<br />
WHERE-Bed<strong>in</strong>gung.<br />
H<strong>in</strong>weis: Fast alles, was hier über Ausdrücke, Funktionen und Konvertierungen<br />
gesagt wird, ist auch bei der Manipulation oder Verarbeitung von Daten wichtig<br />
– siehe auch die Kapitel Funktionen 1 und Funktionen (2) 2 .<br />
1 Kapitel 14 auf Seite 109<br />
2 Kapitel 16 auf Seite 141<br />
235
Berechnete Spalten<br />
24.1. Ergebnis von Berechnungen<br />
Alle Werte e<strong>in</strong>er Spalte werden verrechnet, vorzugsweise mit den Grundrechenarten.<br />
Hier wird <strong>in</strong> der Tabelle Schadensfall aus dem Feld Schadenshoehe der Nettobetrag<br />
(ohne MWSt) errechnet und mit allen weiteren Feldern verbunden:<br />
SELECT Schadenshoehe / 1.19 AS Netto,<br />
*<br />
FROM Schadensfall;<br />
Dieses Ergebnis kann auch für weitere Berechnungen verwendet werden:<br />
Der Alias-Name der neuen Spalte Netto kann nicht nochmals verwendet werden.<br />
Quelltext Falsch<br />
SELECT Schadenshoehe AS Brutto,<br />
Schadenshoehe / 1.19 AS Netto,<br />
Schadenshoehe - Netto AS MWSt<br />
FROM Schadensfall;<br />
Aber die Berechnung kann erneut zugewiesen werden.<br />
SELECT Schadenshoehe AS Brutto,<br />
Schadenshoehe / 1.19 AS Netto,<br />
Schadenshoehe - (Schadenshoehe / 1.19) AS MWSt<br />
FROM Schadensfall;<br />
Man kann davon ausgehen, dass das DBMS die Abfrage soweit optimiert, dass<br />
die Berechnung tatsächlich nur e<strong>in</strong>mal ausgeführt werden muss.<br />
24.2. Zeichenketten verb<strong>in</strong>den und bearbeiten<br />
Ähnlich wie für Zahlen werden Zeichenketten verarbeitet. Das e<strong>in</strong>fachste ist die<br />
Verknüpfung (Verkettung) von Str<strong>in</strong>gs durch den Operator || bzw. +:<br />
SELECT Name || ’, ’ || Vorname AS Gesamtname<br />
FROM Mitarbeiter;<br />
Gesamtname<br />
Müller, Kurt<br />
Schneider, Daniela<br />
In diesem Beispiel werden zwischen die Spalten noch das Komma und e<strong>in</strong> Leerzeichen<br />
als konstanter Text gesetzt.<br />
236
Ergebnis von Funktionen<br />
Bei Textspalten mit fester Feldlänge, die wir <strong>in</strong> der Beispieldatenbank nicht haben,<br />
werden überzählige Leerzeichen mit der Funktion TRIM abgeschnitten:<br />
SELECT TRIM(Name) || ’, ’ || TRIM(Vorname) AS Gesamtname<br />
FROM Mitarbeiter;<br />
Auf diese Weise kann man auch Teile von Texten übernehmen und den Rest abschneiden:<br />
SELECT Name || ’, ’ || TRIM( SUBSTRING(Vorname FROM 1 FOR 1) ) || ’.’ AS<br />
Name_kurz<br />
FROM Mitarbeiter;<br />
Name_kurz<br />
Müller, K.<br />
Schneider, D.<br />
H<strong>in</strong>weis: Wie schon bei den Funktionen 3 erwähnt, endet e<strong>in</strong> Text gemäß <strong>SQL</strong>-<br />
Standard nach SUBSTRING mit überzähligen Leerzeichen, die mit TRIM zu entfernen<br />
s<strong>in</strong>d. Vielleicht ist Ihr DBMS „pfiffiger“ und macht das automatisch.<br />
24.3. Ergebnis von Funktionen<br />
In den bisherigen Beispielen werden Feld<strong>in</strong>halte direkt modifiziert und ausgegeben.<br />
In den beiden Kapiteln über „Funktionen“ werden neue Informationen<br />
berechnet und unabhängig von vorhandenen Zeilen und Spalten ausgegeben:<br />
SELECT COUNT(Farbe) AS Anzahl_Farbe<br />
FROM Fahrzeug;<br />
Bei diesem Beispiel mit e<strong>in</strong>er Aggregatfunktion besteht das Ergebnis aus e<strong>in</strong>er<br />
e<strong>in</strong>zelnen Zahl <strong>in</strong> e<strong>in</strong>er e<strong>in</strong>zigen Zeile. Dies muss aber nicht so se<strong>in</strong>; vor allem<br />
mit Gruppierungen 4 gibt es viele Varianten.<br />
Weitere Funktionen ermöglichen oder erleichtern besondere Abfragen. Dieses<br />
EXTRACT liefert die Liste aller Geburtstage, sortiert nach Monat:<br />
SELECT Name, Vorname,<br />
EXTRACT(MONTH FROM Geburtsdatum) AS Monat,<br />
Geburtsdatum<br />
FROM Mitarbeiter<br />
ORDER BY Monat;<br />
3 Abschnitt 14.3.4 auf Seite 114<br />
4 Kapitel 25 auf Seite 241<br />
237
Berechnete Spalten<br />
NAME VORNAME MONAT GEBURTSDATUM<br />
Aagenau Karol<strong>in</strong> 1 02.01.1950<br />
Langer Norbert 1 13.01.1968<br />
Wagner Gaby 1 18.01.1970<br />
Müller Kurt 1 05.01.1977<br />
Kolic Ivana 2 14.02.1971<br />
Schneider Daniela 2 16.02.1980 // usw.<br />
24.4. Unterabfragen<br />
Wir beschränken uns hier auf e<strong>in</strong> Beispiel; mehr steht im Kapitel Unterabfragen 5 .<br />
Aufgabe: Gesucht s<strong>in</strong>d alle Abteilungen mit der Anzahl ihrer Mitarbeiter:<br />
SELECT ab.ID,<br />
ab.Kuerzel,<br />
ab.Ort,<br />
( SELECT COUNT(*)<br />
FROM Mitarbeiter mi<br />
WHERE mi.Abteilung_ID = ab.ID<br />
) AS Mitarbeiterzahl<br />
FROM Abteilung ab ;<br />
In den Klammern steht zu jeder Abteilung ab.ID die Anzahl ihrer Mitarbeiter; dies<br />
wird mit den anderen Spalten zusammengefasst. Weil es ID <strong>in</strong> beiden Tabellen<br />
gibt, muss die gewünschte Tabelle ausdrücklich erwähnt werden.<br />
24.5. Zusammenfassung<br />
In diesem Kapitel bekamen wir verschiedene Erläuterungen dafür, wie aus Berechnungen,<br />
Verkettung von Zeichenketten oder als Ergebnis von Skalar- oder<br />
Spaltenfunktionen neue Spalten für die Ergebnistabelle entstehen.<br />
24.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 239.<br />
Übung 1 – Zusatzspalten durch Berechnung<br />
Zur Tabelle Versicherungsvertrag sollen Versicherungsnummer, Basisprämie und<br />
Prämiensatz angegeben sowie die aktuelle Prämie berechnet werden.<br />
5 Kapitel 26 auf Seite 251<br />
238
Übung 2 – Zusatzspalten durch Berechnung<br />
Übungen<br />
Geben Sie (unter Verwendung der Lösung von Übung 1) die Gesamtzahl der Versicherungsverträge<br />
sowie den Gesamtbetrag aller aktuellen Prämien an.<br />
Übung 3 – Zusatzspalten durch Str<strong>in</strong>g-Verknüpfung<br />
Erstellen Sie zur Tabelle Versicherungsnehmer per Abfrage die Druckanschrift.<br />
Benutzen Sie CASE, CAST, RPAD und CONCAT; auf TRIM können Sie verzichten.<br />
• Zeile 1 mit Anrede (22 Zeichen, basierend auf der Spalte Geschlecht mit den<br />
Inhalten 'W' bzw. 'M') und der ID am rechten Rand (8 Zeichen rechtsbündig)<br />
• Zeile 2 mit Vorname und Name<br />
• Zeile 3 mit Straße und Hausnummer<br />
• Zeile 4 mit PLZ und Ort<br />
Übung 4 – Neue Spalten durch Spaltenfunktion<br />
Bestimmen Sie, wie viele Fahrzeuge <strong>in</strong> Bochum ('BO') und wie viele <strong>in</strong> Gelsenkirchen<br />
('GE') angemeldet s<strong>in</strong>d. Gruppierungen werden erst im nächsten Kapitel<br />
behandelt; verwenden Sie stattdessen UNION.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 238.<br />
Lösung zu Übung 1 – Zusatzspalten durch Berechnung<br />
SELECT Vertragsnummer,<br />
Basispraemie,<br />
Praemiensatz,<br />
Basispraemie * Praemiensatz / 100 AS Aktuell<br />
FROM Versicherungsvertrag;<br />
Lösung zu Übung 2 – Zusatzspalten durch Berechnung<br />
SELECT COUNT(*) AS Gesamtzahl,<br />
SUM(Basispraemie * Praemiensatz / 100) AS Praemiensumme<br />
FROM Versicherungsvertrag;<br />
239
Berechnete Spalten<br />
Lösung zu Übung 3 – Zusatzspalten durch Str<strong>in</strong>g-Verknüpfung<br />
SELECT CONCAT( CAST( ( CASE Geschlecht<br />
WHEN ’M’ THEN ’Herrn’<br />
WHEN ’W’ THEN ’Frau’<br />
ELSE ”<br />
END ) AS CHAR(22) ),<br />
RPAD( CAST( ID AS VARCHAR(8) ), 8 )<br />
) AS Zeile1,<br />
CASE<br />
WHEN Vorname IS NULL THEN Name<br />
ELSE CONCAT(Vorname, ’ ’, Name)<br />
END AS Zeile2,<br />
CONCAT(Strasse, ’ ’, Hausnummer) AS Zeile3,<br />
CONCAT(PLZ, ’ ’, Ort) AS Zeile4<br />
FROM Versicherungsnehmer vn;<br />
Lösung zu Übung 4 – Neue Spalten durch Spaltenfunktion<br />
SELECT COUNT(*) AS Anzahl, ’BO’ AS Kreis<br />
FROM Fahrzeug<br />
WHERE Kennzeichen STARTING WITH ’BO-’<br />
UNION<br />
SELECT COUNT(*), ’GE’<br />
FROM Fahrzeug<br />
WHERE Kennzeichen STARTING WITH ’GE-’;<br />
Für STARTING WITH gibt es Alternativen; wir haben verschiedentlich SUB-<br />
STRING mit POSITION o. a. verwendet.<br />
240
25. Gruppierungen<br />
25.1. Syntax von GROUP BY . . . . . . . . . . . . . . . . . . . . . . . . . 241<br />
25.2. Gruppierung bei e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . . 242<br />
25.3. Gruppierung über mehrere Tabellen . . . . . . . . . . . . . . . . 243<br />
25.4. Voraussetzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244<br />
25.5. Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245<br />
25.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 247<br />
25.7. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247<br />
25.8. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250<br />
Abfragen werden sehr häufig gruppiert, weil nicht nur e<strong>in</strong>zelne Informationen,<br />
sondern auch Zusammenfassungen gewünscht werden. Durch die GROUP BY-<br />
Klausel im SELECT-Befehl werden alle Zeilen, die <strong>in</strong> e<strong>in</strong>er oder mehreren Spalten<br />
den gleichen Wert enthalten, <strong>in</strong> jeweils e<strong>in</strong>er Gruppe zusammengefasst.<br />
Dies macht <strong>in</strong> der Regel nur S<strong>in</strong>n, wenn <strong>in</strong> der Spaltenliste des SELECT-Befehls<br />
e<strong>in</strong>e gruppenweise Auswertung, also e<strong>in</strong>e der Spaltenfunktionen enthalten ist.<br />
25.1. Syntax von GROUP BY<br />
Die GROUP BY-Klausel hat folgenden allgeme<strong>in</strong>en Aufbau:<br />
GROUP BY <br />
Die Spaltenliste enthält, durch Komma getrennt, die Namen von e<strong>in</strong>er oder mehreren<br />
Spalten. Für jede Spalte kann e<strong>in</strong>e eigene Sortierung angegeben werden:<br />
<br />
-- oder<br />
COLLATE <br />
Die Spalten <strong>in</strong> der Spaltenliste können meistens wahlweise mit dem Spaltennamen<br />
der Tabelle, mit dem Alias-Namen aus der Select-Liste oder mit Spaltennummer<br />
gemäß der Select-Liste (ab 1 gezählt) angegeben werden.<br />
In der Regel enthält die Abfrage e<strong>in</strong>e der Aggregatfunktionen und wird durch<br />
ORDER BY nach den gleichen Spalten sortiert.<br />
241
Gruppierungen<br />
25.2. Gruppierung bei e<strong>in</strong>er Tabelle<br />
Im e<strong>in</strong>fachsten Fall werden Daten nach e<strong>in</strong>er Spalte gruppiert und gezählt.<br />
Aufgabe: Im folgenden Beispiel wird die Anzahl der Abteilungen für jeden Ort<br />
aufgeführt.<br />
SELECT Ort, COUNT(*) AS Anzahl<br />
FROM Abteilung<br />
GROUP BY Ort<br />
ORDER BY Ort;<br />
ORT ANZAHL<br />
Bochum 3<br />
Dortmund 4<br />
Essen 4<br />
Herne 1<br />
Aufgabe: Die folgende Abfrage listet auf, wie viele Mitarbeiter es <strong>in</strong> den Abteilungen<br />
und Raumnummern gibt:<br />
SELECT Abteilung_ID AS Abt,<br />
Raum,<br />
COUNT(*) AS Anzahl<br />
FROM Mitarbeiter<br />
GROUP BY Abt, Raum<br />
ORDER BY Abt, Raum;<br />
ABT RAUM ANZAHL<br />
1 112 1<br />
1 113 1<br />
2 212 2<br />
3 312 1<br />
3 316 1<br />
4 412 2 // usw.<br />
Am folgenden Beispiel wird die Gruppierung besonders deutlich.<br />
Aufgabe: Berechne die mittlere Schadenshöhe für die Schadensfälle mit und<br />
ohne Personenschäden.<br />
242<br />
SELECT Verletzte,<br />
AVG(Schadenshoehe) AS Mittlere_Schadenshoehe<br />
FROM Schadensfall<br />
GROUP BY Verletzte;<br />
VERLETZTE MITTLERE_SCHADENSHOEHE<br />
J 3.903,87<br />
N 1.517,45
Gruppierung über mehrere Tabellen<br />
Die Spalte Verletzte enthält entweder 'J' oder 'N' und ist verpflichtend, kann also<br />
ke<strong>in</strong>e NULL-Werte enthalten. Deshalb werden durch die GROUP BY-Anweisung<br />
e<strong>in</strong>e oder zwei Gruppen gebildet. Für jede Gruppe wird der Mittelwert gesondert<br />
berechnet aus den Werten, die <strong>in</strong> der Gruppe vorkommen. In diesem Fall liefert<br />
die Funktion AVG also e<strong>in</strong> oder zwei Ergebnisse, abhängig davon, welche Werte<br />
<strong>in</strong> der Spalte Verletzte überhaupt vorkommen.<br />
Zeilen, bei denen e<strong>in</strong>er der Werte zur Gruppierung fehlt, oder Zeilen mit NULL-<br />
Werten werden als eigene Gruppe gezählt.<br />
25.3. Gruppierung über mehrere Tabellen<br />
E<strong>in</strong>e Gruppierung kann auch Felder aus verschiedenen Tabellen auswerten. Dafür<br />
s<strong>in</strong>d zunächst die Voraussetzungen für die Verknüpfung mehrerer Tabellen<br />
zu beachten. Bitte beachten Sie das folgende Beispiel.<br />
Aufgabe: Gesucht wird für jeden Fahrzeughersteller (mit Angabe von ID und<br />
Name) und Jahr die Summe der Schadenshöhe aus der Tabelle Schadensfall.<br />
SELECT fh.ID AS Hersteller_ID,<br />
fh.Name AS Hersteller_Name,<br />
EXTRACT(YEAR FROM sf.Datum) AS Jahr,<br />
SUM(sf.Schadenshoehe) AS Schadenssumme<br />
FROM Schadensfall sf<br />
JOIN Zuordnung_SF_FZ zu ON sf.ID = zu.Schadensfall_ID<br />
JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
JOIN Fahrzeugtyp ft ON ft.ID = fz.Fahrzeugtyp_ID<br />
JOIN Fahrzeughersteller fh ON fh.ID = ft.Hersteller_ID<br />
GROUP BY Hersteller_ID, Hersteller_Name, Jahr<br />
ORDER BY Jahr, Hersteller_ID;<br />
HERSTELLER_ID HERSTELLER_NAME JAHR SCHADENSSUMME<br />
9 Volvo 2007 2.066,00<br />
10 Renault 2007 5.781,60<br />
11 Seat 2007 1.234,50<br />
2 Opel 2008 1.438,75<br />
11 Seat 2008 1.983,00<br />
9 Volvo 2009 4.092,15<br />
10 Renault 2009 865,00<br />
Ausgangspunkt ist die Tabelle Schadensfall, aus deren E<strong>in</strong>trägen die Summe gebildet<br />
werden soll. Durch JOIN werden die anderen Tabellen herangezogen, und<br />
zwar mit der ID auf die Verknüpfung: Schadensfall → Zuordnung → Fahrzeug<br />
→ Fahrzeugtyp → Hersteller. Dann stehen ID und Name aus der Tabelle Fahrzeughersteller<br />
zur Verfügung, die für die Gruppierung gewünscht werden.<br />
243
Gruppierungen<br />
Zur Gruppierung genügt eigentlich die Verwendung von Hersteller_ID. Aber man<br />
möchte sich natürlich den Herstellernamen anzeigen lassen. Allerd<strong>in</strong>gs gibt es<br />
e<strong>in</strong>en Fehler, wenn man den Namen nur <strong>in</strong> der SELECT-Liste benutzt und <strong>in</strong> der<br />
GROUP BY-Liste streicht (siehe dazu die Erläuterungen im nächsten Abschnitt):<br />
Quelltext Falsch<br />
SELECT fh.ID AS Hersteller_ID,<br />
fh.Name AS Hersteller_Name,<br />
EXTRACT(YEAR FROM sf.Datum) AS Jahr,<br />
SUM(sf.Schadenshoehe) AS Schadenssumme<br />
FROM Schadensfall sf<br />
JOIN ... (wie oben)<br />
GROUP BY Hersteller_ID, Jahr<br />
ORDER BY Jahr, Hersteller_ID<br />
Ungültiger Ausdruck <strong>in</strong> der Select-Liste<br />
(fehlt entweder <strong>in</strong> e<strong>in</strong>er Aggregatfunktion oder <strong>in</strong> der GROUP BY-Klausel).<br />
25.4. Voraussetzungen<br />
Wie das letzte Beispiel zeigt, muss die GROUP BY-Klausel gewisse Bed<strong>in</strong>gungen<br />
erfüllen. Auch dafür gilt: Jedes DBMS weicht teilweise vom Standard ab.<br />
• Jeder Spaltenname der SELECT-Auswahl, der nicht zu e<strong>in</strong>er Aggregatfunktion<br />
gehört, muss auch <strong>in</strong> der GROUP BY-Klausel benutzt werden.<br />
Diese Bed<strong>in</strong>gung wird im letzten Beispiel verletzt: Hersteller_Name steht <strong>in</strong> der<br />
SELECT-Liste, aber nicht <strong>in</strong> der GROUP BY-Klausel. Hier ist e<strong>in</strong>e Änderung<br />
e<strong>in</strong>fach, weil ID und Name des Herstellers gleichwertig s<strong>in</strong>d. Übrigens erlaubt<br />
My<strong>SQL</strong> auch die Auswahl von Feldern, die <strong>in</strong> der GROUP BY-Klausel fehlen.<br />
Umgekehrt ist es <strong>in</strong> der Regel möglich, e<strong>in</strong>e Spalte per GROUP BY zu gruppieren,<br />
ohne die Spalte selbst <strong>in</strong> der SELECT-Liste zu verwenden.<br />
• GROUP BY kann Spalten der Tabellen, abgeleiteter Tabellen oder VIEWs <strong>in</strong> der<br />
FROM-Klausel oder der JOIN-Klausel enthalten. Sie kann ke<strong>in</strong>er Spalte entsprechen,<br />
die das Ergebnis e<strong>in</strong>er Funktion (genauer: e<strong>in</strong>er Berechnung, e<strong>in</strong>er<br />
Aggregatfunktion oder e<strong>in</strong>er benutzerdef<strong>in</strong>ierten Funktion) s<strong>in</strong>d.<br />
Dies entspricht der gleichen E<strong>in</strong>schränkung, die bei den unter „Ergebnis von Berechnungen“<br />
im Kapitel Berechnete Spalten 1 genannt ist.<br />
Mit der Beispieldatenbank s<strong>in</strong>d ke<strong>in</strong>e passenden Beispiele möglich; wir müssen<br />
uns deshalb auf fiktive Tabellen und Spalten beschränken:<br />
1 Abschnitt 24.1 auf Seite 236<br />
244
Die folgenden Abfragen s<strong>in</strong>d nicht zulässig.<br />
Erweiterungen<br />
Quelltext Falsch<br />
SELECT Spalte1, Spalte2 FROM T GROUP BY Spalte1 + Spalte2;<br />
SELECT Spalte1 + constant + Spalte2 FROM T GROUP BY Spalte1 + Spalte2;<br />
Die folgenden Abfragen s<strong>in</strong>d zulässig.<br />
SELECT Spalte1, Spalte2 FROM T GROUP BY Spalte1, Spalte2;<br />
SELECT Spalte1 + Spalte2 FROM T GROUP BY Spalte1, Spalte2;<br />
SELECT Spalte1 + Spalte2 FROM T GROUP BY Spalte1+ Spalte2;<br />
SELECT Spalte1 + Spalte2 + constant FROM T GROUP BY Spalte1, Spalte2;<br />
• GROUP BY kann nicht benutzt werden <strong>in</strong> e<strong>in</strong>em SELECT-Befehl mit folgenden<br />
Bed<strong>in</strong>gungen:<br />
• Der Befehl enthält e<strong>in</strong>e INTO-Klausel (d. h. er wird benutzt, um e<strong>in</strong>zelne Zeilen<br />
zu verarbeiten − dies wird <strong>in</strong> dieser <strong>E<strong>in</strong>führung</strong> nicht erläutert).<br />
• Der Befehl enthält e<strong>in</strong>e Unterabfrage oder e<strong>in</strong>e VIEW, die selbst mit e<strong>in</strong>er<br />
GROUP BY- oder HAVING-Klausel arbeitet.<br />
• Jeder SELECT-Befehl darf e<strong>in</strong>schließlich aller Unterabfragen höchstens e<strong>in</strong>e<br />
GROUP BY-Klausel enthalten.<br />
Zu dieser Bed<strong>in</strong>gung ist den Autoren ke<strong>in</strong> vernünftiges Beispiel e<strong>in</strong>gefallen. E<strong>in</strong>es,<br />
das dafür konstruiert wurde, hat Firebird trotz klarer Verletzung ausgeführt,<br />
wahrsche<strong>in</strong>lich weil es sich wegen der anderen Bed<strong>in</strong>gungen sowieso nur um jeweils<br />
e<strong>in</strong>e Zeile handelte und ke<strong>in</strong>e Gruppierung erforderlich war.<br />
25.5. Erweiterungen<br />
25.5.1. Zwischensummen mit CUBE<br />
Diese Erweiterung steht nur <strong>in</strong> manchen DBMS zur Verfügung. Es soll deshalb<br />
e<strong>in</strong> kurzer H<strong>in</strong>weis genügen.<br />
GROUP BY CUBE ()<br />
Mit diesem Befehl werden zusätzlich zu den normalerweise von GROUP BY erstellten<br />
Zeilen auch Zwischensummen <strong>in</strong> das Ergebnis aufgenommen. Für jede<br />
mögliche Komb<strong>in</strong>ation von Spalten <strong>in</strong> der wird e<strong>in</strong>e eigene<br />
GROUP BY-Summenzeile zurückgegeben.<br />
Erläuterungen und Beispiele s<strong>in</strong>d zu f<strong>in</strong>den z. B. unter GROUP BY (Microsoft) 2<br />
und weiteren L<strong>in</strong>ks.<br />
2 http://msdn.microsoft.com/de-de/library/ms177673%28<strong>SQL</strong>.90%29.aspx<br />
245
Gruppierungen<br />
25.5.2. Gesamtsummen mit ROLLUP<br />
Auch diese Erweiterung steht nur <strong>in</strong> manchen DBMS zur Verfügung. Es soll wiederum<br />
e<strong>in</strong> kurzer H<strong>in</strong>weis genügen.<br />
GROUP BY WITH ROLLUP<br />
Mit diesem Befehl werden zusätzlich zu den von GROUP BY erstellten Zeilen<br />
auch Gesamtsummen <strong>in</strong> das Ergebnis aufgenommen.<br />
Erläuterungen und Beispiele s<strong>in</strong>d zu f<strong>in</strong>den z. B. unter GROUP BY (My<strong>SQL</strong>) 3 .<br />
25.5.3. Ergebnis mit HAVING e<strong>in</strong>schränken<br />
Diese Erweiterung ist e<strong>in</strong>e selbständige Klausel des SELECT-Befehls und hat eigentlich<br />
nichts mit GROUP BY zu tun. In der Praxis wird sie aber überwiegend<br />
als Ergänzung zur Gruppierung verwendet und folgt ggf. direkt danach.<br />
GROUP BY <br />
HAVING <br />
Dieser Befehl dient dazu, nicht alle Gruppierungen <strong>in</strong> die Ausgabe zu übernehmen,<br />
sondern nur diejenigen, die den zusätzlichen Bed<strong>in</strong>gungen entsprechen.<br />
Im folgenden Beispiel (vergleiche oben unter „Gruppierung bei e<strong>in</strong>er Tabelle“)<br />
wird festgestellt, an welchen Orten sich genau e<strong>in</strong>e Abteilung bef<strong>in</strong>det.<br />
SELECT Ort, COUNT(*) AS Anzahl<br />
FROM Abteilung<br />
GROUP BY Ort<br />
HAVING COUNT(*) = 1<br />
ORDER BY Ort;<br />
Herne 1<br />
Bitte beachten Sie, dass der Alias-Name nicht verwendet werden kann, sondern<br />
die Aggregatfunktion erneut aufgeführt werden muss.<br />
H<strong>in</strong>weis: Die HAVING-Klausel wird als letzter Teil des SELECT-Befehls ausgeführt.<br />
Es ist deshalb zu empfehlen, alle E<strong>in</strong>schränkungen vorher zu regeln, z. B.<br />
als Teil von WHERE-Bed<strong>in</strong>gungen. Nur wenn − wie bei Aggregatfunktionen −<br />
diese E<strong>in</strong>schränkung erst am Schluss geprüft werden kann, ist HAVING zu benutzen.<br />
3 http://dev.mysql.com/doc/refman/5.1/de/GROUP-BY-modifiers.html<br />
246
Möglich, aber nicht günstig.<br />
Zusammenfassung<br />
<strong>SQL</strong>-Quelltext Falsch<br />
SELECT Abteilung_ID,<br />
COUNT(*)<br />
FROM MITARBEITER<br />
GROUP BY Abteilung_ID<br />
HAVING Abteilung_ID < 3;<br />
Besseres Verfahren, weil weniger Datensätze geprüft werden müssen.<br />
<strong>SQL</strong>-Quelltext Richtig<br />
SELECT Abteilung_ID,<br />
COUNT(*)<br />
FROM MITARBEITER<br />
GROUP BY Abteilung_ID<br />
WHERE Abteilung_ID < 3;<br />
25.6. Zusammenfassung<br />
In diesem Kapitel lernten wir E<strong>in</strong>zelheiten über die Gruppierung bei Abfragen.<br />
• Dies wird meistens gleichzeitig mit ORDER BY und <strong>in</strong> Verb<strong>in</strong>dung mit Aggregatfunktionen<br />
verwendet.<br />
• Die Gruppierung ist auch über mehrere Tabellen h<strong>in</strong>weg möglich.<br />
• E<strong>in</strong>ige wichtige E<strong>in</strong>schränkungen s<strong>in</strong>d zu beachten; vor allem s<strong>in</strong>d die Felder<br />
aus der Spaltenliste <strong>in</strong> der Regel auch unter GROUP BY aufzuführen.<br />
Die HAVING-Klausel kann das Abfrageergebnis e<strong>in</strong>schränken, sollte aber zurückhaltend<br />
benutzt werden.<br />
25.7. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 248.<br />
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Feststellungen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. GROUP BY kann nur zusammen mit (m<strong>in</strong>destens) e<strong>in</strong>er Spaltenfunktion<br />
benutzt werden.<br />
2. GROUP BY kann nur auf „echte“ Spalten angewendet werden, nicht auf berechnete<br />
Spalten.<br />
247
Gruppierungen<br />
3. In der GROUP BY-Klausel kann e<strong>in</strong> Spaltenname ebenso angegeben werden<br />
wie e<strong>in</strong> Spalten-Alias.<br />
4. Die WHERE-Klausel kommt vor der GROUP BY-Klausel.<br />
5. Folgende Gruppierung nach den ersten zwei Ziffern der PLZ ist zulässig.<br />
SELECT PLZ, COUNT(*)<br />
FROM Versicherungsnehmer vn<br />
GROUP BY SUBSTRING(vn.PLZ FROM 1 for 2)<br />
ORDER BY 1;<br />
6. HAVING darf nur zusammen mit e<strong>in</strong>er Gruppierung verwendet werden.<br />
Übung 2 – Gruppierung für 1 Tabelle<br />
Bestimmen Sie die Anzahl der Kunden (Versicherungsnehmer) <strong>in</strong> jedem Briefzentrum<br />
(d. h. die Ziffern 1 und 2 der PLZ).<br />
Übung 3 – Gruppierung für 1 Tabelle<br />
Bestimmen Sie, wie viele Fahrzeuge <strong>in</strong> jedem Kreis angemeldet s<strong>in</strong>d.<br />
Übung 4 – Gruppierung für mehrere Tabellen<br />
Bestimmen Sie, wie viele Fahrzeugtypen pro Hersteller registriert s<strong>in</strong>d, und nennen<br />
Sie Namen und Land der Hersteller. – H<strong>in</strong>weis: Erstellen Sie e<strong>in</strong>e Abfrage<br />
für Anzahl plus Hersteller-ID und verknüpfen Sie das Ergebnis mit der Tabelle<br />
Hersteller.<br />
Übung 5 – Gruppierung für mehrere Tabellen<br />
Bestimmen Sie – gruppiert nach Jahr des Schadensfalls und Kreis des Fahrzeugs<br />
– die Anzahl der Schadensfälle. Es sollen bei den Fahrzeugen nur Schadensfälle<br />
mit e<strong>in</strong>em Schuldanteil von mehr als 50 [Prozent] berücksichtigt werden.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 247.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Richtig s<strong>in</strong>d die Aussagen 3, 4. Falsch s<strong>in</strong>d die Aussagen 1, 2, 5, 6.<br />
248
Lösung zu Übung 2 – Gruppierung für 1 Tabelle<br />
SELECT SUBSTRING(vn.PLZ FROM 1 for 2), COUNT(*)<br />
FROM Versicherungsnehmer vn<br />
GROUP BY 1<br />
ORDER BY 1;<br />
Übungen<br />
H<strong>in</strong>weis: Hierbei handelt es sich um die korrekte Version zur Frage 5 aus<br />
Übung 1.<br />
Lösung zu Übung 3 – Gruppierung für 1 Tabelle<br />
SELECT SUBSTRING(Kennzeichen FROM 1 for POSITION(’-’, Kennzeichen)-1 ) AS Kreis,<br />
COUNT(*) AS Anzahl<br />
FROM Fahrzeug fz<br />
GROUP BY 1<br />
ORDER BY 1;<br />
H<strong>in</strong>weis: Hierbei handelt es sich um die vollständige Version der letzten Übung<br />
zu „berechneten Spalten“.<br />
Lösung zu Übung 4 – Gruppierung für mehrere Tabellen<br />
SELECT Name, Land, Anzahl<br />
FROM (<br />
SELECT ft.Hersteller_ID AS ID, Count(ft.Hersteller_ID) AS Anzahl<br />
FROM Fahrzeugtyp ft<br />
GROUP BY ft.Hersteller_ID<br />
) temp<br />
JOIN Fahrzeughersteller fh<br />
ON fh.ID = temp.ID<br />
ORDER BY Name;<br />
Lösung zu Übung 5 – Gruppierung für mehrere Tabellen<br />
SELECT EXTRACT(YEAR FROM sf.Datum) AS Jahr,<br />
SUBSTRING(Kennzeichen FROM 1 for POSITION(’-’, Kennzeichen)-1 ) AS Kreis,<br />
COUNT(*)<br />
FROM Zuordnung_SF_FZ zu<br />
RIGHT JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
INNER JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
WHERE zu.Schuldanteil > 50<br />
GROUP BY 1, 2<br />
ORDER BY 1, 2;<br />
249
Gruppierungen<br />
Erläuterungen: Die Tabelle der Zuordnungen ist kle<strong>in</strong>er als die diejenige der<br />
Fahrzeuge, und darauf bezieht sich die WHERE-Bed<strong>in</strong>gung; deshalb ist sie als<br />
Haupttabelle am s<strong>in</strong>nvollsten. Wegen der Kennzeichen benötigen wir e<strong>in</strong>en JOIN<br />
auf die Tabelle Fahrzeug. Wegen des Datums des Schadensfalls für die Gruppierung<br />
nach Jahr benötigen wir e<strong>in</strong>en JOIN auf die Tabelle Schadensfall.<br />
25.8. Siehe auch<br />
Ergänzende Informationen s<strong>in</strong>d <strong>in</strong> diesen Kapiteln zu f<strong>in</strong>den:<br />
• Datentypen 4 <strong>in</strong>formiert auch über besondere Sortierungen e<strong>in</strong>er e<strong>in</strong>zelnen<br />
Spalte.<br />
• Mehrere Tabellen 5 verknüpfen<br />
4 Kapitel 13 auf Seite 97<br />
5 Kapitel 18 auf Seite 167<br />
250
26. Unterabfragen<br />
26.1. Ergebnis als e<strong>in</strong>zelner Wert . . . . . . . . . . . . . . . . . . . . . 251<br />
26.2. Ergebnis als Liste mehrerer Werte . . . . . . . . . . . . . . . . . 254<br />
26.3. Ergebnis <strong>in</strong> Form e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . 256<br />
26.4. Verwendung bei Befehlen zum Speichern . . . . . . . . . . . . 258<br />
26.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 264<br />
26.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264<br />
Immer wieder werden zur Durchführung e<strong>in</strong>er Abfrage oder e<strong>in</strong>es anderen Befehls<br />
Informationen benötigt, die zuerst durch e<strong>in</strong>e eigene Abfrage geholt werden<br />
müssen. Solche „Unterabfragen“ werden <strong>in</strong> diesem Kapitel behandelt.<br />
• Wenn e<strong>in</strong>e Abfrage als Ergebnis e<strong>in</strong>en e<strong>in</strong>zelnen Wert liefert, kann sie anstelle<br />
e<strong>in</strong>es Wertes benutzt werden.<br />
• Wenn e<strong>in</strong>e Abfrage als Ergebnis e<strong>in</strong>e Liste von Werten liefert, kann sie anstelle<br />
e<strong>in</strong>er solchen Liste benutzt werden.<br />
• Wenn e<strong>in</strong>e Abfrage e<strong>in</strong>e Ergebnismenge, also etwas <strong>in</strong> Form e<strong>in</strong>er Tabelle liefert,<br />
kann sie anstelle e<strong>in</strong>er Tabelle benutzt werden.<br />
Bitte beachten Sie, dass die Unterabfrage immer <strong>in</strong> Klammern gesetzt wird. Auch<br />
wenn e<strong>in</strong> DBMS das nicht verlangen sollte, ist es wegen der Übersichtlichkeit<br />
dr<strong>in</strong>gend zu empfehlen.<br />
Allgeme<strong>in</strong>er H<strong>in</strong>weis: Unterabfragen arbeiten häufig langsamer als andere Verfahren.<br />
Wenn irgend möglich sollten Sie e<strong>in</strong>e der JOIN-Varianten vorziehen.<br />
26.1. Ergebnis als e<strong>in</strong>zelner Wert<br />
26.1.1. Ergebnisse e<strong>in</strong>facher Abfragen<br />
Immer wieder kennt der Anwender den Namen e<strong>in</strong>es Objekts, benötigt aber für<br />
Abfragen die ID. Diese holt er sich mit e<strong>in</strong>er Unterabfrage und übergibt das Ergebnis<br />
an die eigentliche Abfrage.<br />
Aufgabe: Nenne alle Mitarbeiter der Abteilung „Schadensabwicklung“.<br />
251
Unterabfragen<br />
• Lösung Teil 1: Hole die ID dieser Abteilung anhand des Namens.<br />
• Lösung Teil 2: Hole die Mitarbeiter dieser Abteilung unter Benutzung der gefundenen<br />
ID.<br />
SELECT Personalnummer, Name, Vorname<br />
FROM Mitarbeiter<br />
WHERE Abteilung_ID =<br />
( SELECT ID FROM Abteilung<br />
WHERE Kuerzel = ’ScAb’ );<br />
PERSONALNUMMER NAME VORNAME<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a<br />
80002 Aliman Zafer<br />
80003 Langer Norbert<br />
80004 Kolic Ivana<br />
Teil 1 der Lösung ist der SELECT-Befehl <strong>in</strong>nerhalb der Klammern. Das Ergebnis<br />
ist e<strong>in</strong>e e<strong>in</strong>zelne ID. Diese kann anstelle e<strong>in</strong>er konkreten Zahl <strong>in</strong> die WHERE-<br />
Klausel der eigentlichen Abfrage übernommen werden. Das Wörtchen „Diese“<br />
hat <strong>in</strong> diesem Fall sprachlich e<strong>in</strong>e doppelte Bedeutung: zum e<strong>in</strong>en steht es für die<br />
Unterabfrage, zum anderen für die ID als Ergebnis.<br />
H<strong>in</strong>weis: Dies funktioniert nur deshalb auf e<strong>in</strong>fache Weise, weil die Kurzbezeichnung<br />
faktisch e<strong>in</strong>deutig ist und deshalb genau e<strong>in</strong>e ID geliefert wird. Wenn wir<br />
uns darauf nicht verlassen wollen oder können oder wenn das DBMS „empf<strong>in</strong>dlich“<br />
ist und die E<strong>in</strong>deutigkeit des Ergebnisses nicht erkennt, können wir daraus<br />
bewusst e<strong>in</strong>en e<strong>in</strong>zelnen Wert machen:<br />
SELECT Personalnummer, Name, Vorname<br />
FROM Mitarbeiter<br />
WHERE Abteilung_ID =<br />
( SELECT MAX(ID) FROM Abteilung<br />
WHERE Kuerzel = ’ScAb’ );<br />
E<strong>in</strong>e solche Aufgabe kann auch zweimal dieselbe Tabelle benutzen.<br />
Aufgabe: Nenne alle anderen Mitarbeiter der Abteilung, deren Leiter<strong>in</strong> Christ<strong>in</strong>a<br />
Schatz<strong>in</strong>g ist.<br />
• Lösung Teil 1: Hole die Abteilung_ID, die bei Christ<strong>in</strong>a Schatz<strong>in</strong>g registriert ist.<br />
• Lösung Teil 2: Hole die Mitarbeiter dieser Abteilung unter Benutzung der gefundenen<br />
Abteilung_ID.<br />
Zur Sicherheit prüfen wir auch die Eigenschaft Ist_Leiter.<br />
252<br />
SELECT Personalnummer, Name, Vorname<br />
FROM Mitarbeiter<br />
WHERE ( Abteilung_ID =<br />
( SELECT Abteilung_ID
FROM Mitarbeiter<br />
WHERE ( Name = ’Sch<strong>in</strong>dler’ )<br />
AND ( Vorname = ’Christ<strong>in</strong>a’ )<br />
AND ( Ist_Leiter = ’J’ )<br />
)<br />
) AND ( Ist_Leiter = ’N’ );<br />
PERSONALNUMMER NAME VORNAME<br />
80002 Aliman Zafer<br />
80003 Langer Norbert<br />
80004 Kolic Ivana<br />
26.1.2. Ergebnisse von Spaltenfunktionen<br />
Ergebnis als e<strong>in</strong>zelner Wert<br />
Oft werden Ergebnisse von Aggregatfunktionen für die WHERE-Klausel benötigt.<br />
Aufgabe: Hole die Schadensfälle mit unterdurchschnittlicher Schadenshöhe.<br />
• Lösung Teil 1: Berechne die durchschnittliche Höhe aller Schadensfälle.<br />
• Lösung Teil 2: Übernimm das Ergebnis als Vergleich <strong>in</strong> die eigentliche Abfrage.<br />
SELECT ID, Datum, Ort, Schadenshoehe<br />
FROM Schadensfall<br />
WHERE Schadenshoehe <<br />
( SELECT AVG(Schadenshoehe) FROM Schadensfall );<br />
ID DATUM ORT SCHADENSHOEHE<br />
1 03.02.2007 Reckl<strong>in</strong>ghausen, Bergknappenstr. 144 1.234,50<br />
2 11.07.2007 Haltern, Hauptstr. 46 2.066,00<br />
4 27.05.2008 Reckl<strong>in</strong>ghausen, Südgrabenstr. 23 1.438,75<br />
5 05.10.2008 Dorsten, Oberhausener Str. 18 1.983,00<br />
7 21.06.2009 Reckl<strong>in</strong>ghausen, Bergknappenstr. 144 865,00<br />
Aufgabe: Bestimme alle Schadensfälle, die von der durchschnittlichen Schadenshöhe<br />
e<strong>in</strong>es Jahres maximal 300 =C abweichen.<br />
• Lösung Teil 1: Bestimme den Durchschnitt aller Schadensfälle pro Jahr.<br />
• Lösung Teil 2: Hole alle Schadensfälle, deren Schadenshöhe im betreffenden<br />
Jahr <strong>in</strong>nerhalb des Bereichs „Durchschnitt plus/m<strong>in</strong>us 300“ liegen.<br />
SELECT sf.ID, sf.Datum, sf.Schadenshoehe, EXTRACT(YEAR FROM sf.Datum) AS Jahr<br />
FROM Schadensfall sf<br />
WHERE ABS(Schadenshoehe - ( SELECT AVG(sf2.Schadenshoehe)<br />
FROM Schadensfall sf2<br />
WHERE EXTRACT(YEAR FROM sf2.Datum)<br />
= EXTRACT(YEAR FROM sf.Datum)<br />
)<br />
)
Unterabfragen<br />
ID DATUM SCHADENSHOEHE JAHR<br />
2 11.07.2007 2.066,00 2007<br />
4 27.05.2008 1.438,75 2008<br />
5 05.10.2008 1.983,00 2008<br />
8 01.08.2009 2.471,50 2009<br />
Zuerst muss für jeden e<strong>in</strong>zelnen Schadensfall aus sf das Jahr bestimmt werden.<br />
In der Unterabfrage, die <strong>in</strong> der <strong>in</strong>neren Klammer steht, wird für alle Schadensfälle<br />
des betreffenden Jahres die durchschnittliche Schadenshöhe bestimmt. Dieser<br />
Wert wird mit der aktuellen Schadenshöhe verglichen; dazu wird die ABS-<br />
Funktion benutzt, also der absolute Betrag der Differenz der beiden Werte.<br />
i Information<br />
Dies ist e<strong>in</strong> Paradebeispiel dafür, wie Unterabfragen nicht benutzt<br />
werden sollen.<br />
Für jeden e<strong>in</strong>zelnen Datensatz muss <strong>in</strong> der WHERE-Bed<strong>in</strong>gung e<strong>in</strong>e neue Unterabfrage<br />
gestartet werden − mit eigener WHERE-Klausel und Durchschnittsberechnung.<br />
Viel besser ist e<strong>in</strong>e der JOIN-Varianten oder e<strong>in</strong>e der Lösungen im<br />
Abschnitt „Ergebnis <strong>in</strong> Form e<strong>in</strong>er Tabelle“ (siehe unten).<br />
26.2. Ergebnis als Liste mehrerer Werte<br />
Das Ergebnis e<strong>in</strong>er Abfrage kann als Filter für die eigentliche Abfrage benutzt<br />
werden.<br />
Aufgabe: Bestimme alle Fahrzeuge e<strong>in</strong>es bestimmten Herstellers.<br />
• Lösung Teil 1: Hole die ID des gewünschten Herstellers.<br />
• Lösung Teil 2: Hole alle IDs der Tabelle Fahrzeugtyp zu dieser Hersteller-ID.<br />
• Lösung Teil 3: Hole alle Fahrzeuge, die zu dieser Liste von Fahrzeugtypen-IDs<br />
passen.<br />
254<br />
SELECT ID, Kennzeichen, Fahrzeugtyp_ID AS TypID<br />
FROM Fahrzeug<br />
WHERE Fahrzeugtyp_ID <strong>in</strong><br />
( SELECT ID<br />
FROM Fahrzeugtyp<br />
WHERE Hersteller_ID =<br />
( SELECT ID<br />
FROM Fahrzeughersteller<br />
WHERE Name = ’Volkswagen’ ) );
ID KENNZEICHEN TYPID<br />
22 BOR-PQ 567 3<br />
23 BOR-RS 890 2<br />
Ergebnis als Liste mehrerer Werte<br />
Teil 1 der Lösung ist die „<strong>in</strong>nere“ Klammer; dies ist das gleiche Verfahren wie<br />
im Abschnitt „Ergebnisse e<strong>in</strong>facher Abfragen“. Teil 2 der Lösung ist die „äußere“<br />
Klammer; Ergebnis ist e<strong>in</strong>e Liste von IDs der Tabelle Fahrzeugtyp, die als Werte<br />
für den Vergleich der WHERE-IN-Klausel verwendet werden.<br />
Wenn im Ergebnis der Fahrzeugtyp als Text angezeigt werden soll, muss die Abfrage<br />
erweitert werden, weil die Bezeichnung <strong>in</strong> der Tabelle Fahrzeugtyp zu f<strong>in</strong>den<br />
ist. Dafür kann diese Tabelle e<strong>in</strong> zweites Mal benutzt werden, wie es im Kapitel<br />
Mehrere Tabellen 1 erläutert wird; es ist auch e<strong>in</strong> Verfahren möglich, wie es<br />
unten im Abschnitt „Ergebnis <strong>in</strong> Form e<strong>in</strong>er Tabelle“ erläutert wird.<br />
Das Beispiel mit der durchschnittlichen Schadenshöhe geht auch so:<br />
Aufgabe: Gib alle Informationen zu den Schadensfällen des Jahres 2008, die<br />
von der durchschnittlichen Schadenshöhe 2008 maximal 300 =C abweichen.<br />
• Lösung Teil 1: Bestimme den Durchschnitt aller Schadensfälle <strong>in</strong>nerhalb von<br />
2008.<br />
• Lösung Teil 2: Hole alle IDs von Schadensfällen, deren Schadenshöhe <strong>in</strong>nerhalb<br />
des Bereichs „Durchschnitt plus/m<strong>in</strong>us 300“ liegen.<br />
• Lösung Teil 3: Hole alle anderen Informationen zu diesen IDs.<br />
SELECT *<br />
FROM Schadensfall<br />
WHERE ID <strong>in</strong> ( SELECT ID<br />
FROM Schadensfall<br />
WHERE ( ABS(Schadenshoehe<br />
- ( SELECT AVG(sf2.Schadenshoehe)<br />
FROM Schadensfall sf2<br />
WHERE EXTRACT(YEAR FROM sf2.Datum) = 2008<br />
)<br />
)
Unterabfragen<br />
Diese Situation wird dadurch e<strong>in</strong>facher, dass das Jahr 2008 fest v<strong>org</strong>egeben ist.<br />
Die <strong>in</strong>nerste Klammer bestimmt als Teil 1 der Lösung die durchschnittliche Schadenshöhe<br />
dieses Jahres. Die nächste Klammer vergleicht diesen Wert (absolut<br />
gesehen) mit der Schadenshöhe e<strong>in</strong>es jeden e<strong>in</strong>zelnen Schadensfalls im Jahr<br />
2008; alle „passenden“ IDs werden <strong>in</strong> der äußersten Klammer als Teil 2 der Lösung<br />
<strong>in</strong> e<strong>in</strong>er weiteren Unterabfrage zusammengestellt. Diese Liste liefert die<br />
Werte für die eigentliche Abfrage.<br />
Ganz offensichtlich ist dieses Beispiel konstruiert: Weil immer dieselbe Tabelle<br />
verwendet wird, kann die WHERE-Klausel der Unterabfrage <strong>in</strong> der äußersten<br />
Klammer auch als WHERE-Klausel der Hauptabfrage verwendet werden (die<br />
E<strong>in</strong>rückungen wurden zum besseren Vergleich nicht geändert):<br />
SELECT *<br />
FROM Schadensfall<br />
WHERE ( ABS(Schadenshoehe<br />
- ( SELECT AVG(sf2.Schadenshoehe)<br />
FROM Schadensfall sf2<br />
WHERE EXTRACT(YEAR FROM sf2.Datum) = 2008<br />
)<br />
)
Ergebnis <strong>in</strong> Form e<strong>in</strong>er Tabelle<br />
• In der Spaltenliste sollte jeweils e<strong>in</strong> Name als Spalten-Alias vor allem dann<br />
v<strong>org</strong>esehen werden, wenn mehrere Tabellen verknüpft werden; andernfalls erzeugt<br />
<strong>SQL</strong> selbständig Namen wie ID, ID1 usw., die man nicht ohne Weiteres<br />
versteht und zuordnen kann.<br />
• ORDER BY kann nicht s<strong>in</strong>nvoll genutzt werden, weil das Ergebnis der Unterabfrage<br />
als Tabelle behandelt wird und mit der Haupttabelle oder e<strong>in</strong>er anderen<br />
Tabelle verknüpft wird, wodurch e<strong>in</strong>e Sortierung sowieso verloreng<strong>in</strong>ge.<br />
Wie gesagt: E<strong>in</strong>e solche Unterabfrage kann überall stehen, wo e<strong>in</strong>e Tabelle v<strong>org</strong>esehen<br />
ist. In der vorstehenden Syntax steht sie nur beispielhaft <strong>in</strong>nerhalb der<br />
FROM-Klausel.<br />
Überarbeiten wir jetzt, wie oben angekündigt, e<strong>in</strong>ige Beispiele. Dabei wird die<br />
Unterabfrage, die bisher zur WHERE-Klausel gehörte, als Tabelle <strong>in</strong> die FROM-<br />
Klausel e<strong>in</strong>gebaut.<br />
Aufgabe: Bestimme alle Schadensfälle, die von der durchschnittlichen Schadenshöhe<br />
e<strong>in</strong>es Jahres maximal 300 =C abweichen.<br />
• Lösung Teil 1: Stelle alle Jahre zusammen und bestimme den Durchschnitt aller<br />
Schadensfälle <strong>in</strong>nerhalb e<strong>in</strong>es Jahres.<br />
• Lösung Teil 2: Hole alle Schadensfälle, deren Schadenshöhe im jeweiligen Jahr<br />
<strong>in</strong>nerhalb des Bereichs „Durchschnitt plus/m<strong>in</strong>us 300“ liegen.<br />
SELECT sf.ID, sf.Datum, sf.Schadenshoehe, temp.Jahr, temp.Durchschnitt<br />
FROM Schadensfall sf,<br />
( SELECT AVG(sf2.Schadenshoehe) AS Durchschnitt,<br />
EXTRACT(YEAR FROM sf2.Datum) AS Jahr<br />
FROM Schadensfall sf2<br />
GROUP BY EXTRACT(YEAR FROM sf2.Datum)<br />
) temp<br />
WHERE temp.Jahr = EXTRACT(YEAR FROM sf.Datum)<br />
AND ABS(Schadenshoehe - temp.Durchschnitt)
Unterabfragen<br />
• Lösung Teil 2: Hole alle IDs und Bezeichnungen der Tabelle Fahrzeugtyp, die<br />
zu dieser Hersteller-ID gehören.<br />
• Lösung Teil 3: Hole alle Fahrzeuge, die zu dieser Liste von Fahrzeugtyp-IDs gehören.<br />
SELECT Fahrzeug.ID, Kennzeichen, Typen.ID AS TYP, Typen.Bezeichnung<br />
FROM Fahrzeug,<br />
( SELECT ID, Bezeichnung<br />
FROM Fahrzeugtyp<br />
WHERE Hersteller_ID =<br />
( SELECT ID<br />
FROM Fahrzeughersteller<br />
WHERE Name = ’Volkswagen’ )<br />
) Typen<br />
WHERE Fahrzeugtyp_ID = Typen.ID;<br />
ID KENNZEICHEN TYP BEZEICHNUNG<br />
23 BOR-RS 890 2 Golf<br />
22 BOR-PQ 567 3 Passat<br />
Teil 1 der Lösung ist die „<strong>in</strong>nere“ Klammer; dies entspricht dem obigen Verfahren.<br />
Teil 2 der Lösung ist die „äußere“ Klammer; Ergebnis ist e<strong>in</strong>e Tabelle von IDs<br />
und Bezeichnungen, also e<strong>in</strong> Teil der Tabelle Fahrzeugtyp, deren Werte für den<br />
Vergleich der WHERE-Klausel und außerdem für die Ausgabe verwendet werden.<br />
H<strong>in</strong>weis: Mit den Möglichkeiten des nächsten Kapitels Erstellen von Views 2 ergeben<br />
sich wesentliche Verbesserungen: Mit e<strong>in</strong>er VIEW lassen sich Unterabfragen,<br />
die − wie die Liste von Typen und Herstellern − immer wieder benötigt werden,<br />
dauerhaft bereitstellen. Und mit e<strong>in</strong>er Inl<strong>in</strong>e-View werden verschachtelte Abfragen<br />
deutlich übersichtlicher.<br />
26.4. Verwendung bei Befehlen zum Speichern<br />
Bisher hatten wir Abfragen als Teil von anderen Abfragen benutzt; deshalb wird<br />
das Ganze auch als „Unterabfrage“ bezeichnet. Man kann das Ergebnis e<strong>in</strong>er Abfrage<br />
aber auch zum Speichern verwenden: sowohl für e<strong>in</strong>en e<strong>in</strong>zelnen Wert als<br />
auch als vollständigen Datensatz.<br />
26.4.1. Verwendung bei INSERT INTO ... SELECT<br />
E<strong>in</strong>e Abfrage <strong>in</strong> e<strong>in</strong>en INSERT-Befehl e<strong>in</strong>zub<strong>in</strong>den, ist e<strong>in</strong>es der Standardverfahren<br />
für INSERT:<br />
2 Kapitel 27 auf Seite 271<br />
258
INSERT INTO <br />
( )<br />
SELECT <br />
FROM <br />
[ ]<br />
[ WHERE ]<br />
Verwendung bei Befehlen zum Speichern<br />
Der SELECT-Befehl kann dabei beliebig aufgebaut se<strong>in</strong>: Daten aus e<strong>in</strong>er oder<br />
mehreren Tabellen holen, mit oder ohne E<strong>in</strong>schränkungen, mit oder ohne weitere<br />
Festlegungen. Lediglich drei Punkte s<strong>in</strong>d zu beachten:<br />
• Die Spaltenliste <strong>in</strong> der Zieltabelle muss mit den Spalten <strong>in</strong> der SELECT-<br />
Auflistung genau übere<strong>in</strong>stimmen; genauer: Die Datentypen müssen zue<strong>in</strong>ander<br />
passen, also gleich se<strong>in</strong> oder automatisch konvertiert werden können.<br />
• Es ist möglich, Daten aus der Zieltabelle zu holen und somit zu verdoppeln.<br />
Dann muss die ID automatisch vergeben werden können; und es ist unbed<strong>in</strong>gt<br />
mit WHERE zu arbeiten, weil es andernfalls zu e<strong>in</strong>er Endlosschleife kommen<br />
kann (siehe das erste nachfolgende Beispiel).<br />
• ORDER BY kann nicht s<strong>in</strong>nvoll genutzt werden, weil das Ergebnis <strong>in</strong> die Zieltabelle<br />
e<strong>in</strong>gefügt wird, wodurch e<strong>in</strong>e Sortierung sowieso verloreng<strong>in</strong>ge.<br />
Um weitere Testdaten zu erhalten, könnte so verfahren werden:<br />
Aufgabe: Kopiere die vorhandenen Schadensfälle.<br />
INSERT <strong>in</strong>to Schadensfall<br />
( Datum, Ort, Beschreibung, Schadenshoehe, Verletzte, Mitarbeiter_ID )<br />
SELECT Datum, Ort, Beschreibung, Schadenshoehe, Verletzte, Mitarbeiter_ID<br />
FROM Schadensfall<br />
WHERE ID < 10000;<br />
Auf die doppelte Angabe der Spalten kann nicht verzichtet werden, weil ID nicht<br />
benutzt werden darf (sie soll automatisch neu vergeben werden) und das DBMS<br />
wissen muss, welche Werte wie zugeordnet werden sollen. Auf diese Weise kann<br />
man ganz leicht 100 oder 1000 Testdatensätze erzeugen. Für den produktiven<br />
Betrieb wird man diese Syntax wohl eher seltener brauchen.<br />
In e<strong>in</strong>em ersten Versuch fehlte die WHERE-Bed<strong>in</strong>gung; erwartet wurde,<br />
dass nur die vor dem Befehl vorhandenen Datensätze bearbeitet<br />
würden. Tatsächlich hatte Firebird endlos kopiert, bis mit Strg + Alt<br />
+ Entf der „Stecker gezogen“ wurde. Danach war die Datenbank von<br />
3 MB auf 740 MB aufgebläht worden und (natürlich) beschädigt, sodass<br />
auf diese Tabelle nicht mehr richtig zugegriffen werden konnte.<br />
Weitere Versuche mit WHERE-Bed<strong>in</strong>gung arbeiteten wie v<strong>org</strong>esehen:<br />
nur die vorhandenen Datensätze wurden e<strong>in</strong>malig kopiert.<br />
259
Unterabfragen<br />
Die „neuen“ Daten können auch aus e<strong>in</strong>er anderen Tabelle geholt und mit konstanten<br />
Werten gemischt werden, wie es <strong>in</strong> der Beispieldatenbank geschieht:<br />
Aufgabe: Jeder Abteilungsleiter erhält e<strong>in</strong>en persönlichen Dienstwagen.<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID )<br />
SELECT ’DO-WB 42’ || Abteilung_ID,<br />
’elfenbe<strong>in</strong>’, 14, ID<br />
FROM Mitarbeiter<br />
WHERE Ist_Leiter = ’J’;<br />
Die Spaltenliste der Zieltabelle Dienstwagen enthält alle Spalten mit Ausnahme<br />
der ID; diese wird automatisch vergeben. Diesen Spalten werden die Spalten der<br />
SELECT-Abfrage zugewiesen:<br />
• Die Mitarbeiter_ID ist das e<strong>in</strong>zige Feld aus der Quelltabelle Mitarbeiter, das<br />
unbed<strong>in</strong>gt benötigt wird und übernommen werden muss.<br />
• Die Farbe wird als Konstante e<strong>in</strong>getragen: Alle Abteilungsleiter bekommen den<br />
gleichen Wagentyp.<br />
• Der Fahrzeugtyp wird ebenso als Konstante e<strong>in</strong>getragen. Auch e<strong>in</strong>e Unterabfrage<br />
wie oben als Ergebnis e<strong>in</strong>er e<strong>in</strong>fachen Abfrage wäre möglich.<br />
• Für das Kennzeichen ist e<strong>in</strong>e Str<strong>in</strong>g-Verknüpfung v<strong>org</strong>esehen, bei der zusätzlich<br />
die Abteilung_ID übernommen wird. Bitte benutzen Sie die für Ihr DBMS<br />
richtige Art der Str<strong>in</strong>g-Verknüpfung.<br />
Die WHERE-Bed<strong>in</strong>gung beschränkt alles auf Abteilungsleiter.<br />
26.4.2. Verwendung bei INSERT INTO ... VALUES<br />
Die andere Version des INSERT-Befehls nutzt direkt e<strong>in</strong>e Liste von Werten:<br />
INSERT INTO <br />
[ ( ) ]<br />
VALUES ( )<br />
E<strong>in</strong> e<strong>in</strong>zelner Befehl sieht dabei wie folgt aus:<br />
Aufgabe: Der Mitarbeiter 2 bekommt e<strong>in</strong>en gelben Dienstwagen Typ 2.<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Farbe, Fahrzeugtyp_ID, Mitarbeiter_ID )<br />
VALUES ( ’DO-WB 202’, ’gelb’, 2, 2 );<br />
Versuchen wir, diesen Befehl variabel zu gestalten, sodass Mitarbeiter_ID und<br />
Kennzeichen (wie oben) aus e<strong>in</strong>er Abfrage kommen können. Dazu setzen wir<br />
260
Verwendung bei Befehlen zum Speichern<br />
diese beiden Spalten h<strong>in</strong>tere<strong>in</strong>ander und ordnen diesen das „Ergebnis als Liste<br />
mehrerer Werte“ zu unter Verwendung der LPAD-Funktion (siehe das Kapitel<br />
Funktionen (2) 3 ).<br />
Quelltext Falsch<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Mitarbeiter_ID, Farbe, Fahrzeugtyp_ID )<br />
VALUES ( ( SELECT ’DO-WB 2’ || LPAD(ID, 2, ’0’), ID<br />
FROM Mitarbeiter WHERE ID = 2 ),<br />
’gelb’, 2 );<br />
<strong>SQL</strong> error code = -804.<br />
Count of read-write columns does not equal count of values.<br />
Aha, der SELECT-Befehl wird nur als e<strong>in</strong> Wert <strong>in</strong>terpretiert, obwohl er zwei passende<br />
Werte liefert. Können wir die Abfrage an beiden Stellen verwenden?<br />
INSERT INTO Dienstwagen<br />
( Kennzeichen, Mitarbeiter_ID, Farbe, Fahrzeugtyp_ID )<br />
VALUES ( ( SELECT ’DO-WB 2’ || LPAD(ID, 2, ’0’) FROM Mitarbeiter WHERE ID = 2 ),<br />
( SELECT ID FROM Mitarbeiter WHERE ID = 2 ),<br />
’gelb’, 2 );<br />
So funktioniert es, aber es ist natürlich nicht schön, wenn e<strong>in</strong>e fast identische<br />
Abfrage doppelt auftauchen muss (auch wenn davon auszugehen ist, dass e<strong>in</strong><br />
DBMS e<strong>in</strong>en vernünftigen Ausführungsplan erstellt). Dann ist die obige Version<br />
mit INSERT INTO ... SELECT mit e<strong>in</strong>er Mischung aus Konstanten und Tabellenspalten<br />
die bessere Lösung.<br />
26.4.3. Verwendung bei UPDATE<br />
Schauen wir uns die grundsätzliche Struktur e<strong>in</strong>es UPDATE-Befehls an:<br />
UPDATE <br />
SET = [ ,<br />
= ]<br />
WHERE ;<br />
Daraus ergibt sich, dass Abfragen benutzt werden können, um e<strong>in</strong>en oder mehrere<br />
Werte zu speichern oder um Vergleichswerte für die Auswahlbed<strong>in</strong>gungen<br />
zu liefern (ebenso wie <strong>in</strong> verschiedenen früheren Beispielen).<br />
3 Abschnitt 16.2 auf Seite 144<br />
261
Unterabfragen<br />
Aufgabe: Die Mitarbeiter am Dienstort „Bochum“ erhalten e<strong>in</strong>e neue Telefonnummer,<br />
die neben der Zentrale die Abteilung und die Personalnummer enthält.<br />
• Lösung 1: Die WHERE-Bed<strong>in</strong>gung muss die betreffenden Datensätze (genauer:<br />
die IDs) der Tabelle Abteilung prüfen und vergleichen.<br />
• Lösung 2: Der zugeordnete neue Wert muss passend gestaltet werden. (LPAD<br />
und SUBSTRING werden nur verwendet, damit mit festen Längen gearbeitet<br />
werden kann.)<br />
UPDATE Mitarbeiter<br />
SET Telefon = ’0234/66’ || LPAD(Abteilung_ID, 3, ’0’)<br />
|| SUBSTRING(LPAD(Personalnummer, 6, ’0’) FROM 4 FOR 3)<br />
WHERE Abteilung_ID IN ( SELECT ID<br />
FROM Abteilung<br />
WHERE Ort = ’Bochum’ );<br />
Diese Lösung enthält nichts Neues: Die Abfrage wird mit der IN-Abfrage <strong>in</strong> die<br />
WHERE-Klausel e<strong>in</strong>gebunden; die neuen Werte werden aus den vorhandenen<br />
(Abteilung und Personalnummer) gebildet.<br />
Aufgabe: Die Abteilung „Ausbildung“ soll dorth<strong>in</strong> umziehen, wo die Abteilung<br />
„Personalverwaltung“ sitzt.<br />
• Lösung: Der gewünschte Ort wird aus e<strong>in</strong>er Abfrage geholt.<br />
UPDATE Abteilung<br />
SET Ort = ( SELECT Ort<br />
FROM Abteilung<br />
WHERE Kuerzel = ’Pers’ )<br />
WHERE Kuerzel = ’Ausb’;<br />
Bitte wundern Sie sich nicht über e<strong>in</strong> solch konstruiertes Beispiel; der neue<br />
Ort könnte natürlich im Klartext angegeben werden. Bei der v<strong>org</strong>egebenen Datenstruktur<br />
ist es manchmal schwer, s<strong>in</strong>nvolle Beispiele zu entwickeln. Wichtig<br />
ist, dass Sie die möglichen Zusammenhänge zwischen e<strong>in</strong>er Abfrage und e<strong>in</strong>em<br />
Speichern-Befehl erkennen.<br />
Man kann <strong>in</strong> der Unterabfrage auch Spalten derjenigen Tabelle, die geändert<br />
werden soll, verwenden. Beispiel (nur teilweiser Bezug auf die Beispieldatenbank):<br />
262<br />
Quelltext Falsch<br />
UPDATE Abteilung<br />
SET Ort = ( SELECT Ort<br />
FROM Adressbuch<br />
WHERE Abteilung.PLZ = Adressbuch.PLZ )<br />
;
Verwendung bei Befehlen zum Speichern<br />
Diese Abfrage führt zu folgendem Problem: Sie funktioniert nur dann, wenn es<br />
im Adressbuch exakt e<strong>in</strong>en e<strong>in</strong>zigen E<strong>in</strong>trag zu e<strong>in</strong>er bestimmten PLZ gibt. Sobald<br />
man zu e<strong>in</strong>er PLZ mehrere Adressen notiert hat, f<strong>in</strong>det die Unterabfrage<br />
mehrere Sätze; das ist bei dieser Unterabfrage nicht zulässig. Damit der Update<br />
auch <strong>in</strong> solchen Fällen funktioniert, muss e<strong>in</strong> DISTINCT e<strong>in</strong>gefügt werden, oder<br />
man verwendet die MAX- oder die MIN-Funktion:<br />
Quelltext Richtig<br />
UPDATE Abteilung<br />
SET Ort = ( SELECT MAX(Ort)<br />
FROM Adressbuch<br />
WHERE Abteilung.PLZ = Adressbuch.PLZ ) ;<br />
Bei solchen Unterabfragen mit e<strong>in</strong>em Bezug zu dem Satz, der verändert werden<br />
soll, kann es vorkommen, dass die Ausführung dieser Anweisung ziemlich lange<br />
dauert. Das liegt daran, dass alle Sätze aus der Tabelle Abteilung verändert<br />
werden sollen. Für jeden Satz muss die Unterabfrage erneut ausgeführt werden.<br />
Wenn e<strong>in</strong>e e<strong>in</strong>zelne Ausführung dieser Unterabfrage e<strong>in</strong>e Sekunde dauert, und<br />
wir haben z. B. 1000 Sätze <strong>in</strong> der Tabelle Abteilung, dann dauert die Ausführung<br />
des gesamten Statements 1000 Sekunden, also ca. 16 M<strong>in</strong>uten.<br />
Man kann auch mehrere Spalten aus Unterabfragen befüllen. Beispiel:<br />
UPDATE Abteilung<br />
SET Ort = ( SELECT MAX(Ort)<br />
FROM Telefonbuch<br />
WHERE PLZ = ’12345’ ),<br />
Leiter = ( SELECT Manager<br />
FROM Mitarbeiter<br />
WHERE Kuerzel = ’A073’ )<br />
WHERE Kuerzel = ’Ausb’;<br />
Wenn man mehrere Werte aus derselben Unterabfrage übernehmen will, dann<br />
könnte man dieselbe Unterabfrage mehrfach angeben. Aber oft kann das Datenbanksystem<br />
nicht erkennen, dass es sich immer wieder um dieselbe Unterabfrage<br />
handelt, und müsste sie mehrfach ausführen. E<strong>in</strong>facher, übersichtlicher und<br />
dann auch schneller ist die folgende Variante, die aber nicht jedes DBMS kennt.<br />
Aufgabe: In der Tabelle Abteilung werden die Spalten Ort, Leiter, Telefon geme<strong>in</strong>sam<br />
geändert.<br />
UPDATE Abteilung<br />
SET ( Ort, Leiter, Telefon )<br />
= ( SELECT Ort, Name, Telefon<br />
FROM Adressbuch<br />
WHERE Adressbuch.Personalnummer = Abteilung.Chef_Personalnummer<br />
);<br />
263
Unterabfragen<br />
Hier werden alle Sätze <strong>in</strong> der Tabelle Abteilung aktualisiert unter Verwendung der<br />
Tabelle Adressbuch; die Personalnummer des Abteilungsleiters kann wie oben<br />
bestimmt werden.<br />
26.4.4. Verwendung bei DELETE<br />
Schauen wir uns noch die grundsätzliche Struktur e<strong>in</strong>es DELETE-Befehls an:<br />
DELETE FROM <br />
[ WHERE ];<br />
Daraus ergibt sich, dass Abfragen nur für Vergleichswerte der Auswahlbed<strong>in</strong>gungen<br />
s<strong>in</strong>nvoll s<strong>in</strong>d (ebenso wie <strong>in</strong> verschiedenen früheren Beispielen).<br />
Aufgabe: Die Abteilung „Forschung und Entwicklung“ wird ausgelagert; alle<br />
zugeordneten Mitarbeiter werden <strong>in</strong> der Datenbank gelöscht.<br />
DELETE FROM Mitarbeiter<br />
WHERE Abteilung_ID <strong>in</strong> ( SELECT ID<br />
FROM Abteilung<br />
WHERE Bezeichnung = ’Forschung und Entwicklung’ ) ;<br />
26.5. Zusammenfassung<br />
In diesem Kapitel benutzten wir Unterabfragen:<br />
• Sowohl e<strong>in</strong>zelne Werte als auch Listen als Ergebnis e<strong>in</strong>er Abfrage können als<br />
Vergleichswerte <strong>in</strong> der WHERE-Klausel verwendet werden.<br />
• E<strong>in</strong>e Tabelle als Ergebnis von Abfragen kann wie jede „echte“ Tabelle als Teil<br />
der FROM- oder JOIN-Klausel verwendet werden.<br />
Ähnlich können Ergebnisse von Abfragen beim Speichern genutzt werden:<br />
• Ganze Datensätze werden mit INSERT <strong>in</strong> e<strong>in</strong>e Tabelle e<strong>in</strong>gefügt werden.<br />
• E<strong>in</strong>zelne Werte kommen <strong>in</strong> die WHERE-Klausel oder SET-Anweisung.<br />
26.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 267.<br />
264
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Feststellungen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
Übungen<br />
1. Das Ergebnis e<strong>in</strong>er Unterabfrage kann verwendet werden, wenn es e<strong>in</strong> e<strong>in</strong>zelner<br />
Wert oder e<strong>in</strong>e Liste <strong>in</strong> Form e<strong>in</strong>er Tabelle ist. Andere Ergebnisse<br />
s<strong>in</strong>d nicht möglich.<br />
2. E<strong>in</strong> e<strong>in</strong>zelner Wert als Ergebnis kann durch e<strong>in</strong>e direkte Abfrage oder durch<br />
e<strong>in</strong>e Spaltenfunktion erhalten werden.<br />
3. Unterabfragen sollten nicht verwendet werden, wenn die WHERE-<br />
Bed<strong>in</strong>gung für jede Zeile der Hauptabfrage e<strong>in</strong>en anderen Wert erhält und<br />
deshalb die Unterabfrage neu ausgeführt werden muss.<br />
4. Mehrere Unterabfragen können verschachtelt werden.<br />
5. Für die Arbeitsgeschw<strong>in</strong>digkeit ist es gleichgültig, ob mehrere Unterabfragen<br />
oder JOINs verwendet werden.<br />
6. E<strong>in</strong>e Unterabfrage mit e<strong>in</strong>er Tabelle als Ergebnis kann GROUP BY nicht<br />
s<strong>in</strong>nvoll nutzen.<br />
7. E<strong>in</strong>e Unterabfrage mit e<strong>in</strong>er Tabelle als Ergebnis kann ORDER BY nicht<br />
s<strong>in</strong>nvoll nutzen.<br />
8. Bei e<strong>in</strong>er Unterabfrage mit e<strong>in</strong>er Tabelle als Ergebnis ist e<strong>in</strong> Alias-Name für<br />
die Tabelle s<strong>in</strong>nvoll, aber nicht notwendig.<br />
9. Bei e<strong>in</strong>er Unterabfrage mit e<strong>in</strong>er Tabelle als Ergebnis s<strong>in</strong>d Alias-Namen für<br />
die Spalten s<strong>in</strong>nvoll, aber nicht notwendig.<br />
Übung 2 – E<strong>in</strong> e<strong>in</strong>zelner Wert<br />
Welche Verträge (mit e<strong>in</strong>igen Angaben) hat der Mitarbeiter „Braun, Christian“<br />
abgeschlossen? Ignorieren Sie die Möglichkeit, dass es mehrere Mitarbeiter dieses<br />
Namens geben könnte.<br />
Übung 3 – E<strong>in</strong> e<strong>in</strong>zelner Wert<br />
Zeigen Sie alle Verträge, die zum Kunden „Heckel Obsthandel GmbH“ gehören.<br />
Ignorieren Sie die Möglichkeit, dass der Kunde mehrfach gespeichert se<strong>in</strong> könnte.<br />
Übung 4 – E<strong>in</strong>e Liste von Werten<br />
Ändern Sie die Lösung von Übung 3, sodass auch mehrere Kunden mit diesem<br />
Namen als Ergebnis denkbar s<strong>in</strong>d.<br />
265
Unterabfragen<br />
Übung 5 – E<strong>in</strong>e Liste von Werten<br />
Zeigen Sie alle Fahrzeuge, die im Jahr 2008 an e<strong>in</strong>em Schadensfall beteiligt<br />
waren.<br />
Übung 6 – E<strong>in</strong>e Liste von Werten<br />
Zeigen Sie alle Fahrzeugtypen (mit ID, Bezeichnung und Name des Herstellers),<br />
die im Jahr 2008 an e<strong>in</strong>em Schadensfall beteiligt waren.<br />
Übung 7 – E<strong>in</strong>e Tabelle als Ergebnis<br />
Bestimmen Sie alle Fahrzeuge e<strong>in</strong>es bestimmten Herstellers mit Angabe des<br />
Typs.<br />
H<strong>in</strong>weis: Es handelt sich um das letzte Beispiel aus dem Abschnitt „Ergebnis <strong>in</strong><br />
Form e<strong>in</strong>er Tabelle“. Benutzen Sie jetzt JOIN.<br />
Übung 8 – E<strong>in</strong>e Tabelle als Ergebnis<br />
Zeigen Sie zu jedem Mitarbeiter der Abteilung „Vertrieb“ den ersten Vertrag (mit<br />
e<strong>in</strong>igen Angaben) an, den er abgeschlossen hat. Der Mitarbeiter soll mit ID und<br />
Name/Vorname angezeigt werden.<br />
Übung 9 – Speichern mit Unterabfrage<br />
Von der Deutschen Post AG wird e<strong>in</strong>e Tabelle PLZ_Aenderung mit folgenden<br />
Inhalten geliefert:<br />
ID PLZalt Ortalt PLZneu Ortneu<br />
1 45658 Reckl<strong>in</strong>ghausen 45659 Reckl<strong>in</strong>ghausen<br />
2 45721 Hamm-Bossendorf 45721 Haltern OT Hamm<br />
3 45772 Marl 45770 Marl<br />
4 45701 Herten 45699 Herten<br />
Ändern Sie die Tabelle Versicherungsnehmer so, dass bei allen Adressen, bei<br />
denen PLZ/Ort mit PLZalt/Ortalt übere<strong>in</strong>stimmen, diese Angaben durch PLZneu/Ortneu<br />
geändert werden.<br />
H<strong>in</strong>weise: Beschränken Sie sich auf die Änderung mit der ID=3. (Die vollständige<br />
Lösung ist erst mit <strong>SQL</strong>-Programmierung 4 möglich.) Bei dieser Änderungsdatei<br />
handelt es sich nur um fiktive Daten, ke<strong>in</strong>e echten Änderungen.<br />
4 Kapitel 30 auf Seite 323<br />
266
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 264.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Richtig s<strong>in</strong>d 2, 3, 4, 7, 9; falsch s<strong>in</strong>d 1, 5, 6, 8.<br />
Lösung zu Übung 2 – E<strong>in</strong> e<strong>in</strong>zelner Wert<br />
SELECT ID, Vertragsnummer, Abschlussdatum, Art<br />
FROM Versicherungsvertrag<br />
WHERE Mitarbeiter_ID<br />
IN ( SELECT ID<br />
FROM Mitarbeiter<br />
WHERE Name = ’Braun’<br />
AND Vorname = ’Christian’ );<br />
Lösung zu Übung 3 – E<strong>in</strong> e<strong>in</strong>zelner Wert<br />
SELECT ID, Vertragsnummer, Abschlussdatum, Art<br />
FROM Versicherungsvertrag<br />
WHERE Versicherungsnehmer_ID<br />
= ( SELECT ID FROM Versicherungsnehmer<br />
WHERE Name =’Heckel Obsthandel GmbH’ );<br />
Lösung zu Übung 4 – E<strong>in</strong>e Liste von Werten<br />
SELECT ID, Vertragsnummer, Abschlussdatum, Art<br />
FROM Versicherungsvertrag<br />
WHERE Versicherungsnehmer_ID<br />
IN ( SELECT ID FROM Versicherungsnehmer<br />
WHERE Name =’Heckel Obsthandel GmbH’ );<br />
Lösung zu Übung 5 – E<strong>in</strong>e Liste von Werten<br />
SELECT ID, Kennzeichen, Fahrzeugtyp_ID AS TypID<br />
FROM Fahrzeug fz<br />
WHERE ID IN ( SELECT Fahrzeug_ID<br />
FROM Zuordnung_sf_fz zu<br />
JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
WHERE EXTRACT(YEAR FROM sf.Datum) = 2008 );<br />
Übungen<br />
267
Unterabfragen<br />
Lösung zu Übung 6 – E<strong>in</strong>e Liste von Werten<br />
SELECT dist<strong>in</strong>ct ft.ID AS TypID, ft.Bezeichnung AS Typ, fh.Name AS Hersteller<br />
FROM Fahrzeugtyp ft<br />
INNER JOIN Fahrzeughersteller fh ON fh.ID = ft.Hersteller_ID<br />
RIGHT JOIN Fahrzeug fz ON ft.ID = fz.Fahrzeugtyp_ID<br />
WHERE fz.ID IN ( SELECT Fahrzeug_ID<br />
FROM Zuordnung_sf_fz zu<br />
JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
WHERE EXTRACT(YEAR FROM sf.Datum) = 2008 );<br />
Beachten Sie vor allem, dass die WHERE-Bed<strong>in</strong>gung übernommen werden<br />
konnte, aber die Tabellen anders zu verknüpfen s<strong>in</strong>d. Die Bed<strong>in</strong>gung könnte <strong>in</strong><br />
die ON-Klausel e<strong>in</strong>bezogen werden; da sie aber die Auswahl beschränken soll, ist<br />
die WHERE-Klausel vorzuziehen.<br />
Lösung zu Übung 7 – E<strong>in</strong>e Tabelle als Ergebnis<br />
SELECT fz.ID, fz.Kennzeichen, Typen.ID AS TYP, Typen.Bezeichnung<br />
FROM Fahrzeug fz<br />
JOIN ( SELECT ID, Bezeichnung<br />
FROM Fahrzeugtyp<br />
WHERE Hersteller_ID =<br />
( SELECT ID FROM Fahrzeughersteller<br />
WHERE Name = ’Volkswagen’ )<br />
) Typen ON fz.Fahrzeugtyp_ID = Typen.ID;<br />
Lösung zu Übung 8 – E<strong>in</strong>e Tabelle als Ergebnis<br />
SELECT vv.ID AS VV, vv.Vertragsnummer, vv.Abschlussdatum, vv.Art,<br />
mi.ID AS MI, mi.Name, mi.Vorname<br />
FROM Versicherungsvertrag vv<br />
RIGHT JOIN ( SELECT MIN(vv2.ID) AS ID, vv2.Mitarbeiter_ID<br />
FROM Versicherungsvertrag vv2<br />
GROUP BY vv2.Mitarbeiter_id ) Temp<br />
ON Temp.ID = vv.ID<br />
RIGHT JOIN Mitarbeiter mi ON mi.ID = vv.Mitarbeiter_ID<br />
WHERE mi.Abteilung_ID = ( SELECT ID FROM Abteilung<br />
WHERE Bezeichnung = ’Vertrieb’ );<br />
Erläuterungen: Wir benötigen e<strong>in</strong>e e<strong>in</strong>fache Unterabfrage, um die Liste der Mitarbeiter<br />
für „Vertrieb“ zu erhalten, und wir benötigen e<strong>in</strong>e Unterabfrage, die uns<br />
zur Mitarbeiter-ID die kle<strong>in</strong>ste Vertrags-ID liefert. Wegen der Aufgabenstellung<br />
„zu jedem Mitarbeiter“ sowie „mit e<strong>in</strong>igen Angaben“ muss es sich bei beiden<br />
Verknüpfungen um e<strong>in</strong>en RIGHT JOIN handeln.<br />
268
Lösung zu Übung 9 – Speichern mit Unterabfrage<br />
UPDATE Versicherungsnehmer<br />
SET PLZ, Ort<br />
= ( SELECT PLZneu, Ortneu<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 )<br />
WHERE PLZ = ( SELECT PLZalt<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 )<br />
AND Ort = ( SELECT Ortalt<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 );<br />
Übungen<br />
Vielleicht funktioniert diese Variante bei Ihrem DBMS nicht; dann ist die folgende<br />
Version nötig:<br />
UPDATE Versicherungsnehmer<br />
SET PLZ = ( SELECT PLZneu<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 ),<br />
Ort = ( SELECT Ortneu<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 )<br />
WHERE PLZ = ( SELECT PLZalt<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 )<br />
AND Ort = ( SELECT Ortalt<br />
FROM PLZ_Aenderg<br />
WHERE ID = 3 );<br />
269
27. Erstellen von Views<br />
27.1. E<strong>in</strong>e View anlegen und benutzen . . . . . . . . . . . . . . . . . . 272<br />
27.2. E<strong>in</strong>e View ändern oder löschen . . . . . . . . . . . . . . . . . . . 276<br />
27.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 277<br />
27.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277<br />
27.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282<br />
VIEWs s<strong>in</strong>d Abfragen, die <strong>in</strong> der Datenbank als Objekt fest gespeichert s<strong>in</strong>d.<br />
Sie können als virtuelle Tabellen verstanden werden, deren Inhalt und Struktur<br />
auf anderen Tabellen oder Views basieren, und können <strong>in</strong> (fast) jedem SELECT-<br />
Befehl anstelle e<strong>in</strong>er „echten“ Tabelle verwendet werden.<br />
Bei e<strong>in</strong>er View wird die Abfrage <strong>in</strong> der Datenbank gespeichert, aber nicht das Ergebnis.<br />
Bei jedem neuen Aufruf der View wird die dah<strong>in</strong>terliegende Abfrage neu<br />
ausgeführt, denn sie soll ja das Ergebnis anhand der aktuellen Daten bestimmen.<br />
Die Abfragen, auf denen Views basieren, können grundsätzlich alle Klauseln wie<br />
e<strong>in</strong>e normale Abfrage enthalten. Somit ist es möglich, bestimmte Daten <strong>in</strong> e<strong>in</strong>er<br />
View zu selektieren und zu gruppieren. Hierbei können die Daten aus mehreren<br />
Tabellen oder Views selektiert werden.<br />
Je nach DBMS und Situation kann e<strong>in</strong>e e<strong>in</strong>zelne Klausel der View unwirksam<br />
se<strong>in</strong> oder zu unklaren Ergebnissen führen.<br />
• E<strong>in</strong>e ORDER BY-Klausel der View wird ignoriert, wenn der SELECT-Befehl, der<br />
sie benutzt, selbst e<strong>in</strong>e Sortierung verwendet.<br />
• Bei e<strong>in</strong>er E<strong>in</strong>schränkung durch LIMIT o. ä. weiß das DBMS oft nicht, nach welchen<br />
Regeln diese E<strong>in</strong>schränkung verwirklicht werden soll.<br />
• WHERE-Bed<strong>in</strong>gungen können nur fest e<strong>in</strong>gebaut werden, aber nicht mit variablen<br />
Parametern.<br />
Mit Views wird die stark differenzierte Struktur e<strong>in</strong>es Auswahlbefehls vere<strong>in</strong>facht.<br />
Die View wird mit ihrer komplexen Abfrage e<strong>in</strong>mal angelegt, und die Nutzer<br />
können die Daten dieser View immer wieder abfragen.<br />
Weiterh<strong>in</strong> s<strong>in</strong>d Views geeignet, um den Zugriff auf bestimmte Daten e<strong>in</strong>zuschränken.<br />
Nutzer können Zugriff nur auf bestimmte Views bekommen. Somit<br />
lässt sich der Zugriff für e<strong>in</strong>zelne Nutzer auf bestimmte Daten (Spalten und Datensätze)<br />
beschränken.<br />
271
Erstellen von Views<br />
27.1. E<strong>in</strong>e View anlegen und benutzen<br />
Views werden mit dem Befehl CREATE VIEW mit folgender Syntax angelegt.<br />
CREATE VIEW <br />
[ ( ) ]<br />
AS ;<br />
Zu dieser Def<strong>in</strong>ition gehören folgende Bestandteile:<br />
• CREATE VIEW kennzeichnet den Befehl.<br />
• Unter ist e<strong>in</strong>e Bezeichnung anzugeben, unter der die View <strong>in</strong><br />
e<strong>in</strong>em SELECT-Befehl angesprochen wird. Dieser Name muss e<strong>in</strong>deutig se<strong>in</strong><br />
und darf auch ke<strong>in</strong> Name e<strong>in</strong>er „echten“ Tabelle se<strong>in</strong>.<br />
• Als wird e<strong>in</strong> (beliebiger) SELECT-Befehl e<strong>in</strong>getragen.<br />
• Es wird empfohlen, möglichst bei allen Spalten mit e<strong>in</strong>em Alias zu arbeiten.<br />
• Diese können wahlweise vor dem AS <strong>in</strong> Klammern angegeben werden oder<br />
(wie üblich) Teil des s se<strong>in</strong>.<br />
Die View wird dann wie jede Tabelle benutzt, z. B. e<strong>in</strong>fach:<br />
SELECT * FROM <br />
Oder auch als Teil e<strong>in</strong>er komplexen Abfrage:<br />
SELECT <br />
FROM <br />
JOIN ON /* usw. */<br />
27.1.1. E<strong>in</strong>e e<strong>in</strong>fache View<br />
Im e<strong>in</strong>fachsten Fall greifen wir auf e<strong>in</strong>e e<strong>in</strong>fache Verknüpfung zweier Tabellen zu<br />
und verb<strong>in</strong>den dies mit e<strong>in</strong>er festen Auswahlbed<strong>in</strong>gung.<br />
Aufgabe: Erstelle e<strong>in</strong>e View, die e<strong>in</strong>e Liste aller Fahrzeugtypen deutscher Hersteller<br />
anzeigt.<br />
272<br />
CREATE VIEW Deutscher_Fahrzeugtyp<br />
AS SELECT DISTINCT ft.Bezeichnung AS Fahrzeugtyp, fh.Name AS Hersteller<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh ON ft.Hersteller_ID = fh.ID<br />
WHERE fh.Land = ’Deutschland’;
E<strong>in</strong>e View anlegen und benutzen<br />
Die Abfrage basiert auf den beiden Tabellen Fahrzeugtyp und Fahrzeughersteller.<br />
Es werden nur die Spalten Bezeichnung und Name abgefragt; durch die WHERE-<br />
Klausel wird das Ergebnis auf Fahrzeuge deutscher Hersteller beschränkt. Für die<br />
Spalten werden Spalten-Aliase genutzt.<br />
Diese View wird dann wie e<strong>in</strong>e „normale“ Tabellen <strong>in</strong> Abfragen genutzt.<br />
SELECT * FROM Deutscher_Fahrzeugtyp ORDER BY Hersteller;<br />
FAHRZEUGTYP HERSTELLER<br />
A3 Audi<br />
A4 Audi<br />
325 BMW<br />
525 BMW<br />
Z3 BMW<br />
Fiesta Ford<br />
Focus Ford /* usw. */<br />
In diesem Fall kann die ORDER BY-Klausel ebensogut Teil der View se<strong>in</strong>; das untersuchen<br />
wir später noch.<br />
27.1.2. E<strong>in</strong>e View mit variabler Selektion<br />
Es klappt leider nicht, <strong>in</strong> die WHERE-Klausel e<strong>in</strong>en (variablen) Parameter e<strong>in</strong>zubauen,<br />
der erst beim Aufruf mit e<strong>in</strong>em konkreten Wert versehen wird.<br />
Aufgabe: Gesucht wird e<strong>in</strong>e Abfrage über die Mitarbeiter e<strong>in</strong>er Abteilung; am<br />
Anfang soll der Abteilungsleiter stehen, danach alphabetisch die betreffenden<br />
Mitarbeiter. Die Nummer der Abteilung soll nicht fest v<strong>org</strong>egeben werden, sondern<br />
variabel se<strong>in</strong>.<br />
Quelltext Falsch<br />
CREATE VIEW Mitarbeiter_<strong>in</strong>_Abteilung<br />
AS SELECT Personalnummer, Name, Vorname, Geburtsdatum<br />
FROM Mitarbeiter<br />
WHERE Abteilung_ID = ?<br />
ORDER BY Ist_Leiter, Name, Vorname;<br />
Commit nicht möglich<br />
Invalid token.<br />
Auch Alternativen für das Fragezeichen führen nicht zum Ziel. Es bleibt nur e<strong>in</strong><br />
kle<strong>in</strong>er Umweg, nämlich die Abteilung_ID <strong>in</strong> der View zu berücksichtigen und<br />
später für WHERE zu nutzen:<br />
273
Erstellen von Views<br />
CREATE VIEW Mitarbeiter_<strong>in</strong>_Abteilung<br />
( Pers, Name, Vorname, Geburtsdatum, Abt )<br />
AS SELECT Personalnummer, Name, Vorname, Geburtsdatum, Abteilung_ID<br />
FROM Mitarbeiter<br />
ORDER BY Ist_Leiter, Name, Vorname;<br />
Damit können alle Angaben e<strong>in</strong>er bestimmten Abteilung geholt werden; die<br />
Spalte Abt bleibt zur Verdeutlichung stehen:<br />
SELECT * FROM Mitarbeiter_<strong>in</strong>_Abteilung<br />
WHERE Abt = 5;<br />
PERS NAME VORNAME GEBURTSDATUM ABT<br />
50001 Pohl Helmut 27.10.1980 5<br />
50002 Braun Christian 05.09.1966 5<br />
50004 Kalman Ayd<strong>in</strong> 17.12.1976 5<br />
50003 Polovic Frantisek 26.11.1961 5<br />
Und siehe da: zuerst kommt der Abteilungsleiter, danach die anderen Mitarbeiter<br />
<strong>in</strong> alphabetischer Reihenfolge.<br />
H<strong>in</strong>weis: E<strong>in</strong>e Alternative zu e<strong>in</strong>er VIEW mit variabler WHERE-Bed<strong>in</strong>gung ist<br />
e<strong>in</strong>e „StoredProcedure“, die diese Abfrage enthält und e<strong>in</strong>en Wert als Parameter<br />
entgegennimmt; sie wird <strong>in</strong> e<strong>in</strong>em späteren Kapitel behandelt.<br />
27.1.3. Probleme mit der Sortierung<br />
Ändern wir die obige View deutscher Fahrzeuge dah<strong>in</strong>, dass die Sortierung nach<br />
Hersteller fest e<strong>in</strong>gebaut wird.<br />
• Bitte beachten Sie: Wenn Sie oben die View Deutscher_Fahrzeugtyp fest gespeichert<br />
haben, müssen Sie <strong>in</strong> diesem Abschnitt e<strong>in</strong>en anderen Namen verwenden<br />
oder stattdessen etwas wie CREATE OR ALTER (siehe die DBMS-<br />
Dokumentation) benutzen.<br />
CREATE VIEW Deutscher_Fahrzeugtyp ( Typ, Firma )<br />
AS SELECT DISTINCT ft.Bezeichnung, fh.Name AS Firma<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh ON ft.Hersteller_ID = fh.ID<br />
WHERE fh.Land = ’Deutschland’<br />
ORDER BY Firma;<br />
Bitte beachten Sie, dass <strong>in</strong> diesem Fall der Spalten-Alias Firma auch Teil des<br />
SELECT-Befehls se<strong>in</strong> muss, damit er <strong>in</strong> der ORDER BY-Klausel bekannt ist.<br />
Jetzt wird die Liste wahlweise mit oder ohne Sortierung abgerufen:<br />
274
E<strong>in</strong>e View anlegen und benutzen<br />
SELECT * FROM Deutscher_Fahrzeugtyp; -- automatisch sortiert nach Firma<br />
SELECT * FROM Deutscher_Fahrzeugtyp ORDER BY Typ; -- speziell sortiert nach Typ<br />
27.1.4. Views <strong>in</strong> Verb<strong>in</strong>dung mit JOIN<br />
Die obige Verknüpfung „Fahrzeugtyp plus Hersteller“ benötigen wir <strong>in</strong> der Praxis<br />
ständig, nicht nur <strong>in</strong> der konkreten Abfrage nach deutschen Herstellern. Bisher<br />
− zum Beispiel mit OUTER JOIN − haben wir beide Tabellen separat per JOIN<br />
e<strong>in</strong>gebunden, mussten aber immer auf die Art des JOINs aufpassen. Das kann<br />
man e<strong>in</strong>malig durch e<strong>in</strong>e abgeleitete Tabelle Fahrzeugart, also e<strong>in</strong>e VIEW mit<br />
den benötigten Informationen steuern.<br />
E<strong>in</strong>e solche VIEW erfüllt mehrere Wünsche:<br />
• Die eigentlichen Informationen werden getrennt gespeichert; es ist nicht nötig,<br />
bei jedem Fahrzeugtyp den Hersteller und se<strong>in</strong> Herkunftsland aufzuführen.<br />
Wie wir aus der Wirtschaftspolitik des Jahres 2009 wissen, kann sich e<strong>in</strong><br />
Herkunftsland durchaus ändern; nach den Regeln der Normalisierung ist die<br />
separate Tabelle der Hersteller nicht nur s<strong>in</strong>nvoll, sondern notwendig.<br />
• Bei jeder Abfrage des Fahrzeugtyps erhalten wir sofort auch den Hersteller.<br />
• Jede Abfrage damit wird e<strong>in</strong>facher, weil e<strong>in</strong>e Tabelle weniger benötigt wird.<br />
• Das DBMS kennt se<strong>in</strong>e VIEWs und hat sie „von Haus aus“ optimiert; also wird<br />
jede solche Abfrage auch schneller ausgeführt.<br />
Diese Aussage gilt nicht unbed<strong>in</strong>gt bei jeder Abfrage und jedem<br />
DBMS. Aber nach allen Erkenntnissen über <strong>in</strong>terne Datenbankstrukturen<br />
kann man davon ausgehen.<br />
Das obige „e<strong>in</strong>fache Beispiel“ der VIEW müssen wir nur wenig umschreiben:<br />
Aufgabe: Bereite e<strong>in</strong>e (fiktive) Tabelle Fahrzeugart vor mit allen relevanten Informationen<br />
aus den Tabellen Fahrzeugtyp und Fahrzeughersteller.<br />
CREATE VIEW Fahrzeugart<br />
( ID, Bezeichnung, Hersteller, Land )<br />
AS SELECT ft.ID, ft.Bezeichnung, fh.Name, fh.Land<br />
FROM Fahrzeugtyp ft<br />
JOIN Fahrzeughersteller fh ON ft.Hersteller_ID = fh.ID;<br />
Für den Anwender sieht es tatsächlich so aus, als hätten wir e<strong>in</strong>e e<strong>in</strong>fache Tabelle<br />
mit allen Angaben:<br />
SELECT * FROM Fahrzeugart<br />
ORDER BY Land, Hersteller, Bezeichnung;<br />
275
Erstellen von Views<br />
ID BEZEICHNUNG HERSTELLER LAND<br />
19 C30 Volvo<br />
18 S40 Volvo<br />
12 A3 Audi Deutschland<br />
13 A4 Audi Deutschland<br />
9 325 BMW Deutschland<br />
10 525 BMW Deutschland<br />
11 Z3 BMW Deutschland<br />
Damit kann das letzte der Beispiele zu OUTER JOIN vere<strong>in</strong>facht werden.<br />
Aufgabe: Hole alle Dienstwagen (ggf. mit den zugehörigen Mitarbeitern) und<br />
nenne dazu alle Fahrzeugdaten.<br />
SELECT<br />
mi.Personalnummer AS MitNr,<br />
mi.Name, mi.Vorname,<br />
dw.ID AS DIW, dw.Kennzeichen, dw.Fahrzeugtyp_ID AS TypID,<br />
fa.Bezeichnung AS Typ, fa.Hersteller<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.ID = dw.Mitarbeiter_ID<br />
INNER JOIN Fahrzeugart fa ON fa.ID = dw.Fahrzeugtyp_ID;<br />
MITNR NAME VORNAME DIW KENNZEICHEN TYPID TYP HERSTELLER<br />
80001 Sch<strong>in</strong>dler Christ<strong>in</strong>a 8 DO-WB 428 14 A160 Mercedes-Benz<br />
90001 Janssen Bernhard 9 DO-WB 429 14 A160 Mercedes-Benz<br />
100001 Grosser Horst 10 DO-WB 4210 14 A160 Mercedes-Benz<br />
110001 Eggert Louis 11 DO-WB 4211 14 A160 Mercedes-Benz<br />
120001 Carlsen Zacharias 12 DO-WB 4212 14 A160 Mercedes-Benz<br />
13 DO-WB 111 16 W211 (E-Klasse) Mercedes-Benz<br />
50002 Braun Christian 14 DO-WB 352 2 Golf Volkswagen<br />
50003 Polovic Frantisek 15 DO-WB 353 3 Passat Volkswagen<br />
50004 Kalman Ayd<strong>in</strong> 16 DO-WB 354 4 Kadett Opel<br />
80002 Aliman Zafer 17 DO-WB 382 2 Golf Volkswagen<br />
80003 Langer Norbert 18 DO-WB 383 3 Passat Volkswagen<br />
80004 Kolic Ivana 19 DO-WB 384 4 Kadett Opel<br />
E<strong>in</strong>ige kle<strong>in</strong>e Änderungen vere<strong>in</strong>fachen alles: Die Tabelle Fahrzeugtyp wird<br />
durch die View Fahrzeugart ersetzt; der JOIN auf Fahrzeughersteller entfällt ersatzlos.<br />
Lediglich zur Klarheit ändern wir Tabellen-Alias und Spaltennamen.<br />
27.2. E<strong>in</strong>e View ändern oder löschen<br />
Die Änderung e<strong>in</strong>er VIEW wird unterschiedlich gehandhabt.<br />
• Üblich ist das „normale“ ALTER VIEW.<br />
276
Zusammenfassung<br />
• Firebird behandelt e<strong>in</strong>e Änderung mit RECREATE als Löschung und anschließende<br />
Neuaufnahme.<br />
Die Löschung e<strong>in</strong>er View erfolgt mit dem üblichen Befehl DROP VIEW.<br />
DROP VIEW Deutscher_Fahrzeugtyp;<br />
Hierbei wird nur die View als Objekt <strong>in</strong> der Datenbank gelöscht. Die Tabellen<br />
und Daten <strong>in</strong> den Tabellen, auf denen die View basiert, werden davon nicht bee<strong>in</strong>flusst<br />
– sie werden nicht gelöscht.<br />
27.3. Zusammenfassung<br />
• Views s<strong>in</strong>d Abfragen, die <strong>in</strong> der Datenbank als Objekt gespeichert werden.<br />
• Views können die Komplexität für den Anwender reduzieren.<br />
• Views können für e<strong>in</strong>e detaillierte Zugriffskontrolle genutzt werden.<br />
• Views werden <strong>in</strong> Abfragen wie jede Tabelle benutzt.<br />
• Sie werden mit CREATE VIEW erstellt und mit DROP VIEW gelöscht.<br />
27.4. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 279.<br />
Die Formulierung „e<strong>in</strong>e View kontrollieren“ me<strong>in</strong>t: Mit e<strong>in</strong>er geeigneten Abfrage<br />
soll überprüft werden, ob die View richtig erstellt worden ist.<br />
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Feststellungen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
1. E<strong>in</strong>e View ist wie e<strong>in</strong>e „normale“ Abfrage, deren Bestandteile <strong>in</strong> der Datenbank<br />
fest gespeichert werden.<br />
2. Das Ergebnis dieser Abfrage wird gleichzeitig gespeichert und steht damit<br />
beim nächsten Aufruf der View sofort zur Verfügung.<br />
3. E<strong>in</strong>e ORDER BY-Klausel kann <strong>in</strong> e<strong>in</strong>er View immer benutzt werden.<br />
4. E<strong>in</strong>e ORDER BY-Klausel ist <strong>in</strong> e<strong>in</strong>er View nicht erforderlich.<br />
5. Wenn diese Klausel <strong>in</strong> e<strong>in</strong>er View benutzt wird, hat diese Sortierung Vorrang<br />
vor e<strong>in</strong>er ORDER BY-Klausel <strong>in</strong> dem SELECT-Befehl, der die View benutzt.<br />
6. Wenn e<strong>in</strong> SELECT-Befehl komplexe JOINs oder andere Klauseln benutzt<br />
und häufiger benutzt wird, ist es s<strong>in</strong>nvoll, ihn <strong>in</strong> e<strong>in</strong>er View zu kapseln.<br />
277
Erstellen von Views<br />
7. Wenn e<strong>in</strong> Anwender nicht alle Daten sehen darf, ist es notwendig, die<br />
Zugriffsrechte auf die Spalten zu beschränken; diese Beschränkung kann<br />
nicht über e<strong>in</strong>e View gesteuert werden.<br />
8. E<strong>in</strong>e View kann <strong>in</strong> e<strong>in</strong>em SELECT-Befehl <strong>in</strong> der FROM-Klausel anstatt e<strong>in</strong>er<br />
Tabelle aufgerufen werden.<br />
9. E<strong>in</strong>e View kann nicht <strong>in</strong> e<strong>in</strong>em JOIN benutzt werden.<br />
Übung 2 – E<strong>in</strong>e View benutzen<br />
Skizzieren Sie e<strong>in</strong>e Abfrage, durch die e<strong>in</strong>e beliebige View benutzt werden kann.<br />
Übung 3 – E<strong>in</strong>e e<strong>in</strong>fache View erstellen<br />
Bei der Suche nach Dienstwagen sollen mit der View Dienstwagen_Anzeige immer<br />
auch angezeigt werden:<br />
• Name und Vorname des Mitarbeiters<br />
• ID und Bezeichnung se<strong>in</strong>er Abteilung<br />
• der Fahrzeugtyp (nur als ID)<br />
Stellen Sie sicher, dass auch nicht-persönliche Dienstwagen immer angezeigt<br />
werden, und kontrollieren Sie das Ergebnis durch e<strong>in</strong>e Abfrage ähnlich diesem<br />
Muster:<br />
SELECT * FROM Dienstwagen_Anzeige<br />
WHERE ( Abt_ID BETWEEN 5 AND 8 ) or ( Mi_Name is null );<br />
Übung 4 – Mehrere Tabellen und Views verb<strong>in</strong>den<br />
Erweitern Sie die vorstehende View so, dass mit Hilfe der View Fahrzeugart auch<br />
Bezeichnung, Hersteller und Land angezeigt werden. Kontrollieren Sie das Ergebnis<br />
durch die o. g. Abfrage.<br />
Dies ist e<strong>in</strong> Beispiel dafür, dass e<strong>in</strong>e View bei Abfragen genauso wie e<strong>in</strong>e „echte“<br />
Tabelle benutzt werden kann.<br />
Übung 5 – E<strong>in</strong>e VIEW auf mehrere Tabellen<br />
Erstellen Sie e<strong>in</strong>e Sicht Vertrag_Anzeige, die zu jedem Vertrag anzeigt:<br />
• ID, Vertragsnummer, Abschlussdatum, Art (als Text)<br />
• Name, Vorname des Mitarbeiters<br />
• Name, Vorname des Versicherungsnehmers<br />
• Kennzeichen des Fahrzeugs<br />
278
Übung 6 – E<strong>in</strong>e VIEW auf mehrere Tabellen<br />
Übungen<br />
Erweitern Sie die vorstehende View so, dass mit Hilfe der View Fahrzeugart auch<br />
Bezeichnung, Hersteller und Land angezeigt werden.<br />
Übung 7 – E<strong>in</strong>e View abschnittsweise kontrollieren<br />
Erstellen Sie e<strong>in</strong>e Abfrage, sodass für e<strong>in</strong>en Teil der Verträge die vorstehende<br />
View kontrolliert wird.<br />
Übung 8 – E<strong>in</strong>e weitere VIEW auf mehrere Tabellen<br />
Erstellen Sie e<strong>in</strong>e Sicht Schaden_Anzeige, bei der zu jedem an e<strong>in</strong>em Schadensfall<br />
beteiligten Fahrzeug angezeigt werden:<br />
• ID, Datum, Gesamthöhe e<strong>in</strong>es Schadensfalls<br />
• Kennzeichen und Typ des beteiligten Fahrzeugs<br />
• Anteiliger Schaden<br />
• ID des Versicherungsvertrags<br />
Übung 9 – E<strong>in</strong>e weitere VIEW auf mehrere Tabellen<br />
Erweitern Sie die vorstehende View so, dass mit Hilfe der View Fahrzeugart auch<br />
Bezeichnung, Hersteller und Land sowie Vertragsnummer und ID des Versicherungsnehmers<br />
angezeigt werden.<br />
Übung 10 – E<strong>in</strong>e View zur Auswertung e<strong>in</strong>er View<br />
Erstellen Sie e<strong>in</strong>e weitere View so, dass die vorstehende View für alle Schadensfälle<br />
des aktuellen Jahres benutzt wird.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 277.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Richtig s<strong>in</strong>d die Aussagen 1, 3, 4, 6, 8. Falsch s<strong>in</strong>d die Aussagen 2, 5, 7, 9.<br />
Lösung zu Übung 2 – E<strong>in</strong>e View benutzen<br />
SELECT * FROM ;<br />
279
Erstellen von Views<br />
Lösung zu Übung 3 – E<strong>in</strong>e View erstellen<br />
CREATE VIEW Dienstwagen_Anzeige<br />
( Kennzeichen, TypId,<br />
Mi_Name, Mi_Vorname,<br />
Ab_ID, Ab_Name )<br />
AS SELECT dw.Kennzeichen, dw.Fahrzeugtyp_ID,<br />
mi.Name, mi.Vorname,<br />
mi.Abteilung_ID,<br />
ab.Bezeichnung<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.ID = dw.Mitarbeiter_ID<br />
LEFT JOIN Abteilung ab ON ab.ID = mi.Abteilung_ID;<br />
Erläuterung: LEFT JOIN <strong>in</strong> beiden Fällen wird benötigt, damit auch NULL-Werte,<br />
nämlich die nicht-persönlichen Dienstwagen angezeigt werden.<br />
Lösung zu Übung 4 – Mehrere Tabellen und Views verb<strong>in</strong>den<br />
ALTER VIEW Dienstwagen_Anzeige<br />
( Kennzeichen, TypId,<br />
Typ, Fz_Hersteller, Fz_Land,<br />
Mi_Name, Mi_Vorname,<br />
Ab_ID, Ab_Name )<br />
AS SELECT dw.Kennzeichen, dw.Fahrzeugtyp_ID,<br />
fa.Bezeichnung, fa.Hersteller, fa.Land,<br />
mi.Name, mi.Vorname,<br />
mi.Abteilung_ID,<br />
ab.Bezeichnung<br />
FROM Dienstwagen dw<br />
LEFT JOIN Mitarbeiter mi ON mi.ID = dw.Mitarbeiter_ID<br />
LEFT JOIN Abteilung ab ON ab.ID = mi.Abteilung_ID<br />
INNER JOIN Fahrzeugart fa ON fa.ID = dw.Fahrzeugtyp_ID;<br />
Lösung zu Übung 5 – E<strong>in</strong>e VIEW auf mehrere Tabellen<br />
280<br />
CREATE VIEW Vertrag_Anzeige<br />
( ID, Vertragsnummer, Abschlussdatum, Art,<br />
Mi_Name, Mi_Vorname,<br />
Vn_Name, Vn_Vorname,<br />
Kennzeichen )<br />
AS SELECT vv.ID, vv.Vertragsnummer, vv.Abschlussdatum,<br />
CASE vv.Art<br />
WHEN ’TK’ THEN ’Teilkasko’<br />
WHEN ’VK’ THEN ’Vollkasko’<br />
ELSE ’Haftpflicht’<br />
END,<br />
mi.Name, mi.Vorname,<br />
vn.Name, vn.Vorname,
fz.Kennzeichen<br />
FROM Versicherungsvertrag vv<br />
JOIN Mitarbeiter mi ON mi.ID = vv.Mitarbeiter_ID<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID;<br />
Übungen<br />
H<strong>in</strong>weis: Weil die Zusatzangaben Pflicht s<strong>in</strong>d, können wir e<strong>in</strong>heitlich mit INNER<br />
JOIN arbeiten.<br />
Lösung zu Übung 6 – E<strong>in</strong>e VIEW auf mehrere Tabellen<br />
ALTER VIEW Vertrag_Anzeige<br />
( ID, Vertragsnummer, Abschlussdatum, Art,<br />
Mi_Name, Mi_Vorname,<br />
Vn_Name, Vn_Vorname,<br />
Kennzeichen, Typ, Hersteller, Land )<br />
AS SELECT vv.ID, vv.Vertragsnummer, vv.Abschlussdatum,<br />
CASE vv.Art<br />
WHEN ’TK’ THEN ’Teilkasko’<br />
WHEN ’VK’ THEN ’Vollkasko’<br />
ELSE ’Haftpflicht’<br />
END,<br />
mi.Name, mi.Vorname,<br />
vn.Name, vn.Vorname,<br />
fz.Kennzeichen, fa.Bezeichnung, fa.Hersteller, fa.Land<br />
FROM Versicherungsvertrag vv<br />
JOIN Mitarbeiter mi ON mi.ID = vv.Mitarbeiter_ID<br />
JOIN Versicherungsnehmer vn ON vn.ID = vv.Versicherungsnehmer_ID<br />
JOIN Fahrzeug fz ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Fahrzeugart fa ON fa.ID = fz.Fahrzeugtyp_ID;<br />
Lösung zu Übung 7 – E<strong>in</strong>e View abschnittsweise kontrollieren<br />
SELECT * FROM Vertrag_Anzeige<br />
WHERE EXTRACT(YEAR FROM Abschlussdatum)
Erstellen von Views<br />
JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
JOIN Versicherungsvertrag vv ON fz.ID = vv.Fahrzeug_ID;<br />
Lösung zu Übung 9 – E<strong>in</strong>e weitere VIEW auf mehrere Tabellen<br />
ALTER VIEW Schaden_Anzeige<br />
( ID, Datum, Gesamtschaden,<br />
Kennzeichen, Typ, Hersteller, Land,<br />
Schadensanteil,<br />
VV_ID, Vertragsnummer, VN_ID )<br />
AS SELECT sf.ID, sf.Datum, sf.Schadenshoehe,<br />
fz.Kennzeichen, fa.Bezeichnung, fa.Hersteller, fa.Land,<br />
zu.Schadenshoehe,<br />
vv.ID, vv.Vertragsnummer, vv.Versicherungsnehmer_ID<br />
FROM Zuordnung_SF_FZ zu<br />
JOIN Schadensfall sf ON sf.ID = zu.Schadensfall_ID<br />
JOIN Fahrzeug fz ON fz.ID = zu.Fahrzeug_ID<br />
JOIN Versicherungsvertrag vv ON fz.ID = vv.Fahrzeug_ID<br />
JOIN Fahrzeugart fa ON fa.ID = fz.Fahrzeugtyp_ID;<br />
Lösung zu Übung 10 – E<strong>in</strong>e View zur Auswertung e<strong>in</strong>er View<br />
CREATE VIEW Schaden_Anzeige_Jahr<br />
AS SELECT *<br />
FROM Schaden_Anzeige<br />
WHERE EXTRACT(YEAR FROM Datum) = EXTRACT(YEAR FROM CURRENT_DATE);<br />
27.5. Siehe auch<br />
Ergänzende Informationen gibt es <strong>in</strong> den folgenden Kapiteln:<br />
• StoredProcedure 1<br />
• Verknüpfung mehrerer Tabellen bei OUTER JOIN 2<br />
1 Kapitel 32 auf Seite 357<br />
2 Abschnitt 21.5.1 auf Seite 196<br />
282
Teil IV.<br />
Erweiterungen<br />
283
28. DDL – E<strong>in</strong>zelheiten<br />
28.1. Def<strong>in</strong>ition e<strong>in</strong>er Tabelle . . . . . . . . . . . . . . . . . . . . . . . . 285<br />
28.2. Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte . . . . . . . . . . . . . . . . . . 287<br />
28.3. Tabelle ändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291<br />
28.4. CONSTRAINTs – E<strong>in</strong>schränkungen . . . . . . . . . . . . . . . . . 293<br />
28.5. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 301<br />
28.6. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301<br />
28.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304<br />
In diesem Kapitel werden e<strong>in</strong>ige Befehle der Data Def<strong>in</strong>ition Language (DDL)<br />
vertieft behandelt.<br />
Wegen des Umfangs mancher Befehle und Optionen werden die Abschnitte<br />
sachlich gegliedert, nicht nach e<strong>in</strong>em e<strong>in</strong>zelnen Befehl.<br />
28.1. Def<strong>in</strong>ition e<strong>in</strong>er Tabelle<br />
Um e<strong>in</strong>e Tabelle zu erzeugen, s<strong>in</strong>d sehr umfangreiche Angaben nötig.<br />
CREATE TABLE <br />
( <br />
[ , ]<br />
);<br />
Zum Erstellen e<strong>in</strong>er Tabelle gehören folgende Angaben:<br />
• der Name der Tabelle, mit dem die Daten über die DML-Befehle gespeichert<br />
und abgerufen werden<br />
Dazu kommen − <strong>in</strong> Klammern gesetzt − die weiteren Angaben:<br />
• die Liste der Spalten (Felder), und zwar vor allem mit ihren Datentypen.<br />
• Angaben wie der Primärschlüssel (PRIMARY KEY, PK) oder weitere Indizes<br />
Jede Spalte und E<strong>in</strong>schränkung wird mit e<strong>in</strong>em Komma abgeschlossen; dieses<br />
entfällt vor der schließenden Klammer. Die E<strong>in</strong>schränkungen − CONSTRAINTs<br />
285
DDL – E<strong>in</strong>zelheiten<br />
− werden häufig nicht sofort festgelegt, sondern durch anschließende ALTER<br />
TABLE-Befehle; sie werden deshalb getrennt besprochen.<br />
Notwendig s<strong>in</strong>d: der Name des Befehls, der Name der Tabelle, die runden Klammern,<br />
m<strong>in</strong>destens e<strong>in</strong>e Spalte mit Name und Typ. E<strong>in</strong>e solche „M<strong>in</strong>imalversion“<br />
gibt es aber nur für Code-Beispiele; <strong>in</strong> der Praxis gehören immer mehrere Spalten<br />
und der PK dazu.<br />
Beim Entwurf e<strong>in</strong>er Datenbank und ihrer Tabellen sollten Sie immer beachten:<br />
• Datentypen, Art und Umfang der Zusatzangaben hängen vom DBMS ab.<br />
• Primärschlüssel: Manche DBMS verlangen ausdrücklich e<strong>in</strong>en PK − meistens<br />
e<strong>in</strong>e ID o. ä., nur selten aus mehreren Spalten zusammengesetzt. Auch wenn<br />
es nicht verlangt wird, ist e<strong>in</strong> PK dr<strong>in</strong>gend zu empfehlen; e<strong>in</strong>e Tabelle ohne PK<br />
ist selten s<strong>in</strong>nvoll. Dessen Inhalte müssen e<strong>in</strong>deutig se<strong>in</strong> und dürfen sich nicht<br />
wiederholen.<br />
• Dafür gibt es meistens e<strong>in</strong>en automatischen Zähler mit AUTO_INCREMENT,<br />
was ohne Entwicklungsaufwand die Bed<strong>in</strong>gungen e<strong>in</strong>es Primärschlüssels erfüllt.<br />
i H<strong>in</strong>weis<br />
Sie müssen bei Art und Umfang aller Angaben immer die<br />
Besonderheiten Ihres DBMS beachten!<br />
Die Code-Auszüge stammen überwiegend aus den Skripten zur Beispieldatenbank.<br />
Die wichtigsten Bestandteile ersehen Sie aus dem folgenden Beispiel; weitere<br />
Bestandteile werden <strong>in</strong> den späteren Abschnitten behandelt.<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE TABLE Abteilung<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Kuerzel VARCHAR(10) NOT NULL,<br />
Bezeichnung VARCHAR(30) NOT NULL,<br />
Ort VARCHAR(30)<br />
);<br />
Die Tabelle Abteilung wird mit vier Spalten erzeugt:<br />
• ID ist e<strong>in</strong>e ganze Zahl, die nicht NULL se<strong>in</strong> darf, ihre Werte durch die automatische<br />
Zählung erhält und als PRIMARY KEY benutzt wird.<br />
• Kuerzel ist e<strong>in</strong>e Zeichenkette mit variabler Länge (höchstens 10 Zeichen), die<br />
nicht NULL se<strong>in</strong> darf.<br />
• Bezeichnung und Ort s<strong>in</strong>d Zeichenketten mit höchstens 30 Zeichen. Der Ort<br />
darf NULL se<strong>in</strong>, die Bezeichnung nicht.<br />
286
28.2. Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte<br />
Jede e<strong>in</strong>zelne Spalte wird wie folgt def<strong>in</strong>iert.<br />
[ ]<br />
Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte<br />
Jede der Optionen wird mit e<strong>in</strong>em Schlüsselwort und der erforderlichen Angabe<br />
h<strong>in</strong>zugefügt. Die Optionen können <strong>in</strong> der Regel komb<strong>in</strong>iert werden; die Reihenfolge<br />
muss beachtet werden. Verschiedene der E<strong>in</strong>schränkungen (siehe unten)<br />
können auch bei e<strong>in</strong>er e<strong>in</strong>zelnen Spalte angegeben werden.<br />
28.2.1. COLLATE – Sortierungsregel<br />
Jede Spalte kann e<strong>in</strong>e eigene Regel für die alphabetische Sortierung erhalten −<br />
abweichend von der Regel der Tabelle oder Datenbank bzw. von der Standardsortierung<br />
gemäß Zeichensatz.<br />
Aufgabe: In der obigen Def<strong>in</strong>ition der Tabelle Abteilung soll die Bezeichnung<br />
nicht nach den allgeme<strong>in</strong> für die Datenbank gültigen Regeln sortiert werden,<br />
sondern nach denen für kanadisches Französisch:<br />
Firebird-Quelltext<br />
CREATE /* usw. bis */<br />
Bezeichnung VARCHAR(30) NOT NULL COLLATION FR_CA,<br />
/* usw. */<br />
Achtung: So funktioniert der Befehl nicht. Der obige CREATE-Befehl stammt aus<br />
der My<strong>SQL</strong>-Version, diese COLLATION-Ergänzung aber aus Firebird.<br />
28.2.2. NULL-Werte zulässig oder nicht<br />
NULL bzw. NOT NULL legt ausdrücklich fest, ob NULL-Werte <strong>in</strong> der Spalte zulässig<br />
s<strong>in</strong>d. Der Standardwert ist „zulässig“, das NULL kann deshalb entfallen.<br />
Im obigen CREATE-Befehl gilt:<br />
• Die Spalten ID, Kuerzel, Bezeichnung dürfen ke<strong>in</strong>e NULL-Werte enthalten. Die<br />
Informationen <strong>in</strong> diesen Spalten s<strong>in</strong>d wesentlich für e<strong>in</strong>en Datensatz; e<strong>in</strong>e<br />
Speicherung ohne e<strong>in</strong>en dieser Werte wäre s<strong>in</strong>nlos.<br />
• Die Spalte Ort darf dagegen NULL-Werte enthalten. Diese Angabe ist nur e<strong>in</strong>e<br />
zusätzliche Information; die Abteilung steht auch dann e<strong>in</strong>deutig fest, wenn<br />
der Sitz nicht bekannt oder noch nicht festgelegt ist.<br />
287
DDL – E<strong>in</strong>zelheiten<br />
28.2.3. DEFAULT – V<strong>org</strong>abewert<br />
Mit DEFAULT wird e<strong>in</strong> Standardwert festgelegt (als konstanter Wert oder<br />
als Ergebnis e<strong>in</strong>er Funktion); dieser wird immer dann verwendet, wenn bei e<strong>in</strong>er<br />
Neuaufnahme für diese Spalte ke<strong>in</strong> Wert angegeben ist.<br />
Aufgabe: In der Tabelle Mitarbeiter erhält die Spalte Ist_Leiter den V<strong>org</strong>abewert<br />
'N'; denn e<strong>in</strong> Mitarbeiter ist normalerweise ke<strong>in</strong> Abteilungsleiter:<br />
CREATE TABLE Mitarbeiter<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
/* usw. bis */<br />
Ist_Leiter CHAR(1) DEFAULT ’N’,<br />
Abteilung_ID INTEGER NOT NULL<br />
);<br />
Weit verbreitet s<strong>in</strong>d dabei Standardwerte, mit denen Datum/Zeit e<strong>in</strong>er Änderung<br />
und die Bearbeiter<strong>in</strong> registriert werden:<br />
• CURRENT_TIMESTAMP als aktuelles Datum und Uhrzeit<br />
• CURRENT_USER<br />
28.2.4. AUTO_INCREMENT – automatischer Zähler<br />
AUTO_INCREMENT legt fest, dass die Werte <strong>in</strong> dieser Spalte automatisch vom<br />
DBMS hochgezählt werden − siehe das obige Beispiel im Abschnitt Def<strong>in</strong>ition<br />
e<strong>in</strong>er Tabelle.<br />
MS-<strong>SQL</strong> – IDENTITY<br />
ALTER TABLE dbo.doc_exe<br />
ADD column_b INT IDENTITY CONSTRAINT column_b_pk PRIMARY KEY<br />
My<strong>SQL</strong> – AUTO_INCREMENT<br />
CREATE TABLE Abteilung<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARYKEY, /* usw. */<br />
);<br />
Oracle – AUTO INCREMENT<br />
Diese Variante ist nur möglich bei der Lite-Version für mobile Computer;<br />
"AUTO INCREMENT" wird dabei mit Leerzeichen geschrieben.<br />
288
CREATE TABLE Fahrzeug<br />
( ID INTEGER NOT NULL AUTO INCREMENT, /* usw. */<br />
CONSTRAINT Fahrzeug_PK PRIMARY KEY (ID)<br />
) ;<br />
28.2.5. Zähler mit anderen Verfahren<br />
Firebird – SEQUENCE mit TRIGGER<br />
Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte<br />
Es wird e<strong>in</strong> Zähler def<strong>in</strong>iert (wahlweise je Tabelle oder pauschal). In e<strong>in</strong>em<br />
Before-Insert-Trigger wird der nächste Wert abgerufen und e<strong>in</strong>gefügt.<br />
/* Def<strong>in</strong>ition */<br />
CREATE SEQUENCE Fahrzeug_ID;<br />
/* Benutzung im Trigger */<br />
CREATE OR ALTER TRIGGER Fahrzeug_BI0 FOR Fahrzeug<br />
ACTIVE BEFORE INSERT POSITION 0<br />
AS<br />
BEGIN<br />
IF ((new.ID IS NULL) OR (new.ID = 0))<br />
THEN new.ID = NEXT VALUE FOR Fahrzeug_ID;<br />
END<br />
Firebird (veraltet) – GENERATOR mit TRIGGER<br />
Es wird e<strong>in</strong> Zähler def<strong>in</strong>iert (wahlweise je Tabelle oder pauschal). In e<strong>in</strong>em<br />
Before-Insert-Trigger wird der nächste Wert abgerufen und e<strong>in</strong>gefügt.<br />
E<strong>in</strong> GENERATOR sollte für neue Datenbanken nicht mehr benutzt werden.<br />
/* Def<strong>in</strong>ition */<br />
CREATE GENERATOR Fahrzeug_ID;<br />
SET GENERATOR Fahrzeug_ID TO 0;<br />
/* Benutzung im Trigger */<br />
CREATE OR ALTER TRIGGER Fahrzeug_BI0 FOR Fahrzeug<br />
ACTIVE BEFORE INSERT POSITION 0<br />
AS<br />
BEGIN<br />
IF ((new.ID IS NULL) OR (new.ID = 0))<br />
THEN new.ID = GEN_ID(Fahrzeug_ID, 1);<br />
END<br />
289
DDL – E<strong>in</strong>zelheiten<br />
Oracle – SEQUENCE mit TRIGGER<br />
Es wird e<strong>in</strong> Zähler def<strong>in</strong>iert (wahlweise je Tabelle oder pauschal). In e<strong>in</strong>em<br />
Before-Insert-Trigger wird der nächste Wert abgerufen und e<strong>in</strong>gefügt.<br />
/* Def<strong>in</strong>ition */<br />
CREATE SEQUENCE Fahrzeug_ID;<br />
/* Benutzung im Trigger */<br />
CREATE OR REPLACE TRIGGER Fahrzeug_BI<br />
BEFORE INSERT ON Fahrzeug<br />
REFERENCING NEW AS NEW OLD AS OLD<br />
FOR EACH ROW<br />
WHEN (:NEW.ID IS NULL)<br />
BEGIN<br />
/* <strong>in</strong> früheren Oracle-Versionen: */<br />
SELECT Fahrzeug_ID.NEXTVAL INTO :NEW.ID FROM DUAL;<br />
/* ab Version 11g direkte Zuweisung: */<br />
:NEW.ID := Fahrzeug_ID.NEXTVAL;<br />
END<br />
Oracle – SEQUENCE mit INSERT<br />
Es wird e<strong>in</strong> Zähler def<strong>in</strong>iert (wahlweise je Tabelle oder pauschal). Im INSERT-<br />
Befehl wird der nächste Wert abgerufen und übernommen.<br />
Oracle schreibt selbst ausdrücklich, dass die Version mit Trigger vorzuziehen ist.<br />
/* Def<strong>in</strong>ition */<br />
CREATE SEQUENCE Fahrzeug_ID;<br />
/* Benutzung im INSERT-Befehl */<br />
INSERT INTO Fahrzeug<br />
( ID, Kennzeichen /* usw. */ )<br />
VALUES<br />
( Fahrzeug_ID.NEXTVAL, ’BO-CR 123’ /* usw. */ )<br />
;<br />
H<strong>in</strong>weis zu Sequenzen: Bitte beachten Sie auch die allgeme<strong>in</strong>e Anmerkung<br />
im Kapitel DDL – Struktur der Datenbank 1 . Für die meisten Varianten gibt<br />
es Parameter zur genaueren Steuerung, nachzulesen <strong>in</strong> der jeweiligen DBMS-<br />
Dokumentation.<br />
1 Abschnitt 10.3.4 auf Seite 84<br />
290
28.2.6. COMMENT – Beschreibung verwenden<br />
Tabelle ändern<br />
Dies beschreibt den Inhalt der Spalte – nützlich für alle Spalten, sofern e<strong>in</strong> Bezeichner<br />
nicht ganz klar ist oder der Inhalt besondere Bed<strong>in</strong>gungen erfüllen soll.<br />
Damit erleichtern Sie sich und anderen die Arbeit.<br />
CREATE TABLE Schadensfall<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Datum DATE NOT NULL,<br />
Ort VARCHAR(200) NOT NULL<br />
COMMENT ’nicht nur Ortsnamen, sondern auch Straße und Umstände’<br />
, /* usw. */<br />
);<br />
28.3. Tabelle ändern<br />
Mit ALTER TABLE wird die Struktur e<strong>in</strong>er Tabelle geändert:<br />
ALTER TABLE ;<br />
Es können also mehrere Aufgaben mit e<strong>in</strong>em ALTER-Befehl ausgeführt werden.<br />
Die möglichen Aufgaben s<strong>in</strong>d <strong>in</strong> den e<strong>in</strong>zelnen Abschnitten beschrieben.<br />
Der Begriff COLUMN ist nicht immer Teil des Befehls: Bei manchen DBMS kann<br />
er weggelassen werden, bei manchen darf er nicht benutzt werden.<br />
28.3.1. Stop – Aufgabe kann nicht ausgeführt werden<br />
E<strong>in</strong>e Spalte kann oft nicht geändert oder gelöscht werden, wenn sie an anderer<br />
Stelle benutzt wird. Das gilt vor allem dann, wenn diese Spalte beim PRIMARY<br />
KEY, e<strong>in</strong>em INDEX, e<strong>in</strong>em FOREIGN KEY oder <strong>in</strong> e<strong>in</strong>er CHECK-E<strong>in</strong>schränkung<br />
für die Tabelle benutzt wird. In diesen Fällen muss zunächst die „abhängige“<br />
Konstruktion gelöscht, deaktiviert oder geändert werden. Erst danach kann die<br />
Änderung <strong>in</strong> der Tabelle ausgeführt werden.<br />
Der Datentyp kann durch ALTER COLUMN nur dann geändert werden, wenn die<br />
„alten“ Werte automatisch (implizit) <strong>in</strong> den neuen Datentyp konvertiert werden<br />
können.<br />
28.3.2. ADD COLUMN – Spalte h<strong>in</strong>zufügen<br />
Diese Aufgabe fügt der Tabelle e<strong>in</strong>e weitere Spalte h<strong>in</strong>zu.<br />
291
DDL – E<strong>in</strong>zelheiten<br />
Aufgabe: Die Tabelle Versicherungsvertrag wird um Spalten zur Berechnung<br />
und Anpassung der Versicherungsprämie erweitert.<br />
Firebird-Version h<strong>in</strong>sichtlich Schreibweise (ohne "COLUMN") und Reihenfolge der Optionen<br />
Firebird Quelltext<br />
ALTER TABLE Versicherungsvertrag<br />
ADD Basispraemie DECIMAL DEFAULT 500 NOT NULL,<br />
ADD Praemiensatz INTEGER DEFAULT 100 NOT NULL,<br />
ADD Praemienaenderung DATE;<br />
Die bisherigen Inhalte der Tabelle bleiben unverändert. In den neuen Spalten<br />
wird der DEFAULT-Wert e<strong>in</strong>getragen, andernfalls NULL.<br />
28.3.3. ALTER COLUMN – Spalte ändern<br />
Diese Aufgabe ändert e<strong>in</strong>e Spalte dieser Tabelle. Dies kann e<strong>in</strong>e Änderung des<br />
Datentyps, e<strong>in</strong> anderer DEFAULT-Wert oder e<strong>in</strong>e andere E<strong>in</strong>schränkung se<strong>in</strong>.<br />
Aufgabe: In der Tabelle Abteilung ist die Spalte Kuerzel mit VARCHAR(10) def<strong>in</strong>iert,<br />
die e<strong>in</strong>zelnen Werte s<strong>in</strong>d aber immer genau 4 Zeichen lang. Die Spaltendef<strong>in</strong>ition<br />
soll an die Realität angepasst werden.<br />
Firebird-Quelltext Falsch<br />
ALTER TABLE Abteilung<br />
ALTER COLUMN Kuerzel TYPE CHAR(4);<br />
Firebird-Fehlermeldung: This operation is not def<strong>in</strong>ed for system tables.<br />
unsuccessful metadata update.<br />
New size specified for column KUERZEL must be at least 40 characters.<br />
Die Option TYPE ändert den Datentyp. Vorhandene Werte werden möglichst implizit<br />
konvertiert. Beispielsweise My<strong>SQL</strong> hat ke<strong>in</strong>e Probleme damit, den Text entsprechend<br />
zu verkürzen. Firebird weigert sich aber, obwohl die tatsächlichen Inhalte<br />
passen, sondern br<strong>in</strong>gt e<strong>in</strong>e völlig verwirrende Fehlermeldung.<br />
E<strong>in</strong> Umweg dazu wird im Kapitel Änderung der Datenbankstruktur 2 benutzt:<br />
• Fügen Sie e<strong>in</strong>e neue, temporäre Spalte h<strong>in</strong>zu.<br />
• Kopieren Sie alle Inhalte durch e<strong>in</strong>en UPDATE-Befehl aus der „alten“ Spalte,<br />
die geändert werden soll, <strong>in</strong> die temporäre Spalte.<br />
• Löschen Sie die „alte“ Spalte.<br />
2 Abschnitt 35.1.2 auf Seite 398<br />
292
CONSTRAINTs – E<strong>in</strong>schränkungen<br />
• Erzeugen Sie e<strong>in</strong>e neue Spalte unter dem „alten“ Namen mit den „neuen“ Eigenschaften.<br />
• Kopieren Sie alle Inhalte durch e<strong>in</strong>en UPDATE-Befehl aus der temporären<br />
Spalte <strong>in</strong> die neue Spalte, wobei sie passend konvertiert werden müssen.<br />
• Löschen Sie die temporäre Spalte.<br />
28.3.4. DROP COLUMN – Spalte entfernen<br />
Diese Aufgabe entfernt e<strong>in</strong>e Spalte aus der Tabelle, z. B. die eben erwähnte temporäre<br />
Spalte.<br />
ALTER TABLE Abteilung<br />
DROP COLUMN Temp;<br />
Durch Löschen e<strong>in</strong>er Spalte wird nicht der Speicherplatz der Spalte freigegeben.<br />
Dafür muss (sofern erforderlich) e<strong>in</strong>e vollständige Datensicherung – Backup und<br />
Restore – ausgeführt werden. Aber das machen Sie ja sowieso regelmäßig.<br />
28.3.5. ADD CONSTRAINT – E<strong>in</strong>schränkung h<strong>in</strong>zufügen<br />
Diese Aufgabe erweitert die Bed<strong>in</strong>gungen für die Daten der Tabelle.<br />
ALTER TABLE <br />
ADD [ CONSTRAINT ] ;<br />
E<strong>in</strong>zelheiten zum folgen unter „CONSTRAINTs − E<strong>in</strong>schränkungen“.<br />
28.3.6. DROP CONSTRAINT – E<strong>in</strong>schränkung entfernen<br />
Diese Aufgabe löscht e<strong>in</strong>e Bed<strong>in</strong>gung, die für die Daten der Tabelle gültig war.<br />
ALTER TABLE <br />
DROP CONSTRAINT ;<br />
28.4. CONSTRAINTs – E<strong>in</strong>schränkungen<br />
Dabei handelt es sich um Bed<strong>in</strong>gungen, denen e<strong>in</strong> Datensatz entsprechen muss.<br />
Wenn e<strong>in</strong>e der aktuell gültigen Bed<strong>in</strong>gungen verletzt wird, wird der betreffende<br />
Datensatz nicht gespeichert. Die Bed<strong>in</strong>gungen können Folgendes betreffen:<br />
293
DDL – E<strong>in</strong>zelheiten<br />
• die Schlüssel <strong>in</strong>nerhalb der Tabelle: PRIMARY KEY, INDEX<br />
• die Beziehungen zu anderen Tabellen: FOREIGN KEY<br />
• die Werte <strong>in</strong>nerhalb der Spalte: UNIQUE, CHECK<br />
E<strong>in</strong> CONSTRAINT kann mit oder ohne eigenen Namen festgelegt werden. Wir<br />
empfehlen die Benutzung e<strong>in</strong>es Namens, weil dies die Arbeit übersichtlicher<br />
macht: Bei Verletzung e<strong>in</strong>er Regel wird dieser Name meistens angegeben; anhand<br />
des Namens ist das Löschen direkt möglich. Aber das ist Geschmackssache und<br />
hängt wohl auch vom DBMS ab. Das Schlüsselwort CONSTRAINT selbst ist nur<br />
erforderlich bei Verwendung des Namens; ansonsten würden die Schlüsselwörter<br />
der e<strong>in</strong>zelnen Bed<strong>in</strong>gungen ausreichen.<br />
E<strong>in</strong> CONSTRAINT wird auf e<strong>in</strong>e der folgenden Arten festgelegt:<br />
• im CREATE TABLE-Befehl bei e<strong>in</strong>er e<strong>in</strong>zelnen Spalte als Bed<strong>in</strong>gung für diese<br />
Spalte<br />
• im CREATE TABLE-Befehl als Bed<strong>in</strong>gung für die Tabelle, also <strong>in</strong> der Liste der<br />
<br />
• im ALTER TABLE-Befehl durch ADD CONSTRAINT<br />
E<strong>in</strong> CONSTRAINT wird wie folgt gelöscht:<br />
ALTER TABLE <br />
DROP CONSTRAINT ;<br />
E<strong>in</strong> CONSTRAINT kann nicht geändert werden. Es ist nur Löschen und erneute<br />
Festlegung möglich.<br />
Welche Wörter der Schlüsselbegriffe optional s<strong>in</strong>d (z. B. KEY), hängt von der konkreten<br />
Situation ab.<br />
28.4.1. PRIMARY KEY – Primärschlüssel der Tabelle<br />
Der Primärschlüssel − PRIMARY KEY mit PK als gängiger Abkürzung − ist das<br />
wichtigste Mittel, mit dem die Datenbank alle E<strong>in</strong>träge verwaltet. Ohne PK s<strong>in</strong>d<br />
weder Änderungen noch Löschungen e<strong>in</strong>zelner Datensätze möglich, ohne alle<br />
Spalten anzugeben. Im praktischen E<strong>in</strong>satz haben Tabellen ohne Primärschlüssel<br />
ke<strong>in</strong>en S<strong>in</strong>n. Fremdschlüssel (FOREIGN KEYs, FK) wären ohne Primärschlüssel<br />
nicht möglich.<br />
Als Primärschlüssel geeignet s<strong>in</strong>d folgende Arten von Spalten:<br />
• der Datentyp GUID<br />
• e<strong>in</strong>e Spalte mit e<strong>in</strong>em INTEGER-Datentyp, der als AUTO_INCREMENT verwendet<br />
wird oder ersatzweise durch e<strong>in</strong>e SEQUENCE bestimmt wird<br />
294
CONSTRAINTs – E<strong>in</strong>schränkungen<br />
Die Beispieldatenbank benutzt ausnahmslos e<strong>in</strong>e solche Spalte namens<br />
ID.<br />
• e<strong>in</strong>e Spalte mit e<strong>in</strong>em INTEGER-Datentyp, sofern die Werte nach Lage der D<strong>in</strong>ge<br />
e<strong>in</strong>deutig s<strong>in</strong>d und während der „Lebenszeit“ der Datenbank nicht mehr<br />
geändert werden.<br />
Die Beispieldatenbank enthält <strong>in</strong> der Tabelle Mitarbeiter die Spalte<br />
Personalnummer. Diese ist eigentlich e<strong>in</strong>deutig und dürfte deshalb<br />
als PK verwendet werden. Da die Firma aber ihre <strong>in</strong>terne Struktur<br />
ändern und die Personalnummern anpassen könnte, scheidet diese<br />
Spalte als PK aus.<br />
• e<strong>in</strong>e Komb<strong>in</strong>ation aus zwei Spalten, von denen jede dem PK jeweils e<strong>in</strong>er anderen<br />
Tabelle entspricht, wenn die „neue“ Tabelle nur die Verknüpfungen zwischen<br />
den beiden anderen Tabellen darstellt.<br />
Die Tabelle Zuordnung_SF_FZ der Beispieldatenbank enthält die Zuordnungen<br />
Fahrzeuge/Schadensfälle; anstelle e<strong>in</strong>er eigenen ID wäre<br />
auch e<strong>in</strong> Primärschlüssel aus Fahrzeug_ID plus Schadensfall_ID<br />
möglich und s<strong>in</strong>nvoll.<br />
Als Primärschlüssel ungeeignet oder unmöglich s<strong>in</strong>d diese Arten von Spalten:<br />
• Unmöglich s<strong>in</strong>d sämtliche Spalten (wie e<strong>in</strong>e PLZ), bei denen mehrere Datensätze<br />
mit dem gleichen Wert vorkommen können.<br />
• Unmöglich ist e<strong>in</strong>e Komb<strong>in</strong>ation von Name/Vorname bei allen Tabellen mit<br />
Namen, weil über kurz oder lang e<strong>in</strong> „Müller, Lucas“ doppelt vorkommt.<br />
• Auch e<strong>in</strong>e Komb<strong>in</strong>ation von Name/Vorname/Geburtstag scheidet aus dem<br />
gleichen Grund aus.<br />
• E<strong>in</strong>e Komb<strong>in</strong>ation von Name/Geburtstag/Region/lfd. Nr. (ähnlich wie bei der<br />
Versicherungsnummer der Deutschen Rentenversicherung) ist zwar e<strong>in</strong>deutig,<br />
aber als Komb<strong>in</strong>ation von vier Spalten äußerst unpraktisch.<br />
• E<strong>in</strong>e Spalte, deren Werte sich ändern können, ist zwar möglich, aber nicht geeignet.<br />
Das gilt z. B. für das Kfz-Kennzeichen, aber auch (wie schon gesagt) für<br />
etwas wie die Personalnummer.<br />
Der Primärschlüssel kann wie folgt festgelegt werden:<br />
• im CREATE TABLE-Befehl als Zuordnung für e<strong>in</strong>e e<strong>in</strong>zelne Spalte<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE TABLE Abteilung<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Kuerzel VARCHAR(10) NOT NULL,<br />
Bezeichnung VARCHAR(30) NOT NULL,<br />
Ort VARCHAR(30)<br />
);<br />
295
DDL – E<strong>in</strong>zelheiten<br />
Die Spalte ID wird direkt als (e<strong>in</strong>zige) Spalte des PK def<strong>in</strong>iert. Dies ist implizit<br />
e<strong>in</strong> CONSTRAINT, bekommt aber ke<strong>in</strong>en eigenen Namen.<br />
• im CREATE TABLE-Befehl <strong>in</strong> der Liste der <br />
Firebird-Quelltext<br />
CREATE TABLE Abteilung<br />
( ID INTEGER,<br />
Kuerzel VARCHAR(10) NOT NULL,<br />
Bezeichnung VARCHAR(30) NOT NULL,<br />
Ort VARCHAR(30)<br />
CONSTRAINT Abteilung_PK PRIMARY KEY (ID)<br />
);<br />
Der PK bekommt als CONSTRAINT e<strong>in</strong>en eigenen Namen, der Vermerk <strong>in</strong> Klammern<br />
führt die Spalten auf, die als PK verwendet werden (hier wie meistens handelt<br />
es sich um e<strong>in</strong>e e<strong>in</strong>zelne Spalte).<br />
• im ALTER TABLE-Befehl durch ADD CONSTRAINT<br />
My<strong>SQL</strong> Quelltext<br />
My<strong>SQL</strong>-Version mit zwei aufe<strong>in</strong>anderfolgenden Befehlen<br />
CREATE TABLE Abteilung<br />
( ID INTEGER NOT NULL AUTO_INCREMENT,<br />
Kuerzel VARCHAR(10) NOT NULL,<br />
Bezeichnung VARCHAR(30) NOT NULL,<br />
Ort VARCHAR(30)<br />
);<br />
ALTER TABLE Abteilung<br />
ADD CONSTRAINT Abteilung_PK PRIMARY KEY (ID);<br />
Die Tabelle erhält zunächst noch ke<strong>in</strong>en PK, auch wenn das durch AUTO_-<br />
INCREMENT suggeriert und vorbereitet wird. Vielmehr wird der PK anschließend<br />
(mit eigenem Namen) def<strong>in</strong>iert; der Vermerk <strong>in</strong> Klammern führt die Spalten<br />
auf, die als PK verwendet werden.<br />
28.4.2. UNIQUE – E<strong>in</strong>deutigkeit<br />
E<strong>in</strong> UNIQUE KEY s<strong>org</strong>t dafür, dass <strong>in</strong>nerhalb e<strong>in</strong>er Spalte bzw. e<strong>in</strong>er Komb<strong>in</strong>ation<br />
von SpaltenKomb<strong>in</strong>ation!Spalten ke<strong>in</strong> Wert doppelt auftreten kann. Beispiele<br />
s<strong>in</strong>d <strong>in</strong> der Tabelle Mitarbeiter die Spalte Personalnummer und <strong>in</strong> der Tabelle<br />
Fahrzeug die Spalte Kennzeichen.<br />
E<strong>in</strong>e solche E<strong>in</strong>deutigkeitsbed<strong>in</strong>gung kann wie folgt festgelegt werden:<br />
• im CREATE TABLE-Befehl bei e<strong>in</strong>er Spalte als E<strong>in</strong>schränkung für diese Spalte<br />
296
CONSTRAINTs – E<strong>in</strong>schränkungen<br />
CREATE TABLE Fahrzeug<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Kennzeichen VARCHAR(10) NOT NULL UNIQUE,<br />
Farbe VARCHAR(30),<br />
Fahrzeugtyp_ID INTEGER NOT NULL<br />
);<br />
• im CREATE TABLE-Befehl <strong>in</strong> der Liste der mit Bezug auf<br />
e<strong>in</strong>e oder mehrere Spalten<br />
CREATE TABLE Fahrzeug<br />
( ID INTEGER NOT NULL AUTO_INCREMENT,<br />
Kennzeichen VARCHAR(10) NOT NULL,<br />
Farbe VARCHAR(30),<br />
Fahrzeugtyp_ID INTEGER NOT NULL,<br />
CONSTRAINT Fahrzeug_PK PRIMARY KEY (ID),<br />
CONSTRAINT Fahrzeug_Kz UNIQUE (Kennzeichen)<br />
);<br />
• im ALTER TABLE-Befehl durch ADD CONSTRAINT<br />
ALTER TABLE Fahrzeug<br />
ADD [ CONSTRAINT Fahrzeug_Kz ] UNIQUE (Kennzeichen);<br />
28.4.3. INDEX – Suche beschleunigen<br />
E<strong>in</strong> INDEX ist e<strong>in</strong> Verfahren <strong>in</strong>nerhalb e<strong>in</strong>er Datenbank, mit dem schnell auf Datensätze<br />
zugegriffen werden kann. Vor allem der PK benutzt selbst e<strong>in</strong>en Index.<br />
(Intern arbeiten die DBMS unterschiedlich; für den Nutzer sieht es immer so aus,<br />
als wenn der PK e<strong>in</strong> Index ist.) Sämtliche Spalten bzw. Komb<strong>in</strong>ationen von Spalten,<br />
nach denen <strong>in</strong> SELECT-Befehlen häufiger gesucht oder sortiert wird, sollten<br />
mit e<strong>in</strong>em Index versehen werden. Beispiele:<br />
• Name/Vorname sowie PLZ und separat PLZ/Name (vielleicht auch PLZ/Straße)<br />
<strong>in</strong> Tabellen mit Adressen<br />
• solche Spalten, die für den Nutzer wie e<strong>in</strong> PK aussehen, es aber nicht s<strong>in</strong>d,<br />
z. B. <strong>in</strong> der Tabelle Mitarbeiter die Spalte Personalnummer und <strong>in</strong> der Tabelle<br />
Fahrzeug die Spalte Kennzeichen<br />
Die Angabe UNIQUE für e<strong>in</strong>e Spalte s<strong>org</strong>t bereits für e<strong>in</strong>en Index;<br />
e<strong>in</strong>e doppelte Festlegung ist nicht nötig.<br />
• das Datum <strong>in</strong> der Tabelle Schadensfall<br />
Wenn vorzugsweise die größeren Werte benötigt werden, ist mit DESC e<strong>in</strong> absteigender<br />
Index (wie im folgenden Beispiel) s<strong>in</strong>nvoll. In manchen Fällen s<strong>in</strong>d<br />
297
DDL – E<strong>in</strong>zelheiten<br />
durchaus zwei getrennte Indizes auf dieselbe Spalte angebracht − der e<strong>in</strong>e ASC,<br />
der andere DESC.<br />
E<strong>in</strong> Index ist nicht s<strong>in</strong>nvoll, wenn e<strong>in</strong>e Spalte nur wenige verschiedene Werte<br />
enthalten kann wie <strong>in</strong> der Tabelle Versicherungsvertrag die Spalte Art. Dann wäre<br />
der Aufwand für das DBMS, den Index ständig zu aktualisieren, größer als der<br />
Aufwand beim Selektieren und Sortieren.<br />
E<strong>in</strong> Index kann wie folgt festgelegt werden:<br />
• im CREATE TABLE-Befehl <strong>in</strong> der Liste der mit Bezug auf<br />
e<strong>in</strong>e oder mehrere Spalten<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE TABLE Schadensfall<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Datum DATE NOT NULL,<br />
Ort VARCHAR(200) NOT NULL,<br />
/* usw. */<br />
INDEX Schadensfall_Datum (Datum DESC)<br />
);<br />
In der Tabelle wird sofort e<strong>in</strong> Index (mit Namen) für die Spalte Datum angelegt,<br />
und zwar nach absteigenden Werten. (Das bedeutet, dass die Daten mit dem<br />
„größten“ Wert, also die aktuellsten Werte zuerst gefunden werden.) Der Standardwert,<br />
der nicht angegeben werden muss, ist ASC (= aufsteigend).<br />
• mit e<strong>in</strong>em zusätzlichen CREATE INDEX-Befehl <strong>in</strong> folgender Syntax:<br />
CREATE [ UNIQUE ] INDEX <br />
ON ( );<br />
„Irgendwo“ (unterschiedlich nach DBMS) kann außerdem ASC bzw. DESC festgelegt<br />
werden.<br />
Das vorige Beispiel sieht dann unter Firebird (DESC v<strong>org</strong>ezogen) so aus:<br />
Firebird-Quelltext<br />
CREATE DESC INDEX Schadensfall_Datum<br />
ON Schadensfall (Datum);<br />
Die E<strong>in</strong>deutigkeitsbed<strong>in</strong>gung UNIQUE benutzt <strong>in</strong>tern (vermutlich immer) ebenfalls<br />
e<strong>in</strong>en INDEX; dieser kann auch ausdrücklich angegeben werden:<br />
298<br />
ALTER TABLE Fahrzeug<br />
ADD CONSTRAINT Fahrzeug_Kennzeichen UNIQUE (Kennzeichen)<br />
USING INDEX Fahrzeug_Kennzeichen_UK;
28.4.4. FOREIGN KEY – Fremdschlüssel<br />
CONSTRAINTs – E<strong>in</strong>schränkungen<br />
E<strong>in</strong> FOREIGN KEY (FK) regelt die logischen Verknüpfungen zwischen zwei Tabellen:<br />
E<strong>in</strong> Datensatz <strong>in</strong> e<strong>in</strong>er Tabelle darf <strong>in</strong> e<strong>in</strong>er bestimmten Spalte nur solche<br />
Werte benutzen, die <strong>in</strong> e<strong>in</strong>er anderen Tabelle als PK registriert s<strong>in</strong>d. Beispiele:<br />
• In der Tabelle Mitarbeiter darf als Abteilung_ID nur e<strong>in</strong>e gültige ID der Tabelle<br />
Abteilung stehen.<br />
• In der Tabelle Fahrzeug darf als Fahrzeugtyp_ID nur e<strong>in</strong>e gültige ID der Tabelle<br />
Fahrzeugtyp stehen.<br />
• In der Tabelle Fahrzeugtyp darf als Hersteller_ID nur e<strong>in</strong>e gültige ID der Tabelle<br />
Fahrzeughersteller stehen.<br />
E<strong>in</strong> Fremdschlüssel kann wie folgt festgelegt werden:<br />
• im CREATE TABLE-Befehl bei e<strong>in</strong>er e<strong>in</strong>zelnen Spalte als Bed<strong>in</strong>gung für diese<br />
Spalte<br />
• im CREATE TABLE-Befehl als Bed<strong>in</strong>gung für die Tabelle, also <strong>in</strong> der Liste der<br />
<br />
• im ALTER TABLE-Befehl durch ADD CONSTRAINT<br />
E<strong>in</strong>zelheiten werden im Kapitel Fremdschlüssel-Beziehungen 3 behandelt.<br />
28.4.5. CHECK – Werteprüfungen<br />
E<strong>in</strong> CHECK ist e<strong>in</strong>e Prüfung, ob die Werte, die für e<strong>in</strong>en Datensatz gespeichert<br />
werden sollen, bestimmten Regeln entsprechen. Diese Prüfung wird sowohl bei<br />
INSERT als auch bei UPDATE v<strong>org</strong>enommen; sie kann für e<strong>in</strong>e e<strong>in</strong>zelne Spalte<br />
oder für die Tabelle festgelegt werden.<br />
Als Bed<strong>in</strong>gung <strong>in</strong> der CHECK-Klausel kann im Wesentlichen alles stehen, was für<br />
die WHERE-Klausel v<strong>org</strong>esehen ist.<br />
E<strong>in</strong>e solche Prüfung kann wie folgt festgelegt werden:<br />
• im CREATE TABLE-Befehl bei e<strong>in</strong>er e<strong>in</strong>zelnen Spalte als E<strong>in</strong>schränkung für<br />
diese Spalte<br />
Aufgabe: In der Tabelle Schadensfall s<strong>in</strong>d als Schadenshoehe natürlich ke<strong>in</strong>e<br />
negativen Zahlen zulässig. Die Spalte Verletzte ist als CHAR(1) def<strong>in</strong>iert; s<strong>in</strong>nvollerweise<br />
s<strong>in</strong>d nur die Werte 'J' und 'N' zulässig.<br />
3 Kapitel 29 auf Seite 305<br />
299
DDL – E<strong>in</strong>zelheiten<br />
CREATE TABLE Schadensfall<br />
( ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,<br />
Datum DATE NOT NULL,<br />
Ort VARCHAR(200) NOT NULL,<br />
Beschreibung VARCHAR(1000) NOT NULL,<br />
Schadenshoehe DECIMAL(16,2) CHECK(Schadenshoehe >= 0),<br />
Verletzte CHAR(1) NOT NULL<br />
CHECK(Verletzte = ’J’ OR Verletzte = ’N’),<br />
Mitarbeiter_ID INTEGER NOT NULL<br />
);<br />
• im CREATE TABLE-Befehl als Bed<strong>in</strong>gung für die Tabelle, also <strong>in</strong> der Liste der<br />
<br />
Aufgabe: Bei Personen als Versicherungsnehmer benötigt man Vorname, Geburtsdatum<br />
und Führersche<strong>in</strong> sowie e<strong>in</strong> M<strong>in</strong>destalter von 16 Jahren.<br />
CREATE TABLE Versicherungsnehmer<br />
( ID INTEGER NOT NULL AUTO_INCREMENT Primary Key,<br />
Name VARCHAR(30) NOT NULL ,<br />
Vorname VARCHAR(30) NOT NULL ,<br />
Geburtsdatum DATE ,<br />
Fuehrersche<strong>in</strong> DATE ,<br />
/* usw. für alle anderen Spalten, danach: */<br />
CONSTRAINT Versicherungsnehmer_CheckDatum<br />
CHECK( ( (Geburtsdatum IS NULL)<br />
AND (Fuehrersche<strong>in</strong> IS NULL)<br />
AND (Vorname IS NULL OR Vorname = ”) )<br />
OR (Fuehrersche<strong>in</strong> >= Geburtsdatum + 365*16) ));<br />
Die ersten Bed<strong>in</strong>gungen prüfen, ob es sich um e<strong>in</strong>e Person handelt; wenn nicht,<br />
s<strong>in</strong>d Führersche<strong>in</strong>prüfung und Geburtsdatum irrelevant, und der Datensatz<br />
kann gespeichert werden. Wenn es sich um e<strong>in</strong>e Person handelt, wird auch die<br />
letzte Bed<strong>in</strong>gung benötigt; diese wird „näherungsweise“ geprüft und berücksichtigt,<br />
dass das DBMS e<strong>in</strong> Datum <strong>in</strong>tern als ganze Zahl speichert. Alternativ<br />
könnten auch mit EXTRACT() Tag, Monat, Jahr getrennt verglichen werden. Dieses<br />
Verfahren wäre aber deutlich umständlicher; deshalb sollte es hier nicht stehen.<br />
• im ALTER TABLE-Befehl durch ADD CONSTRAINT<br />
Aufgabe: In der Tabelle Versicherungsvertrag s<strong>in</strong>d als Art nur bestimmte Werte<br />
zulässig: 'VK' (= Vollkasko), 'TK' (= Teilkasko <strong>in</strong>cl. Haftpflicht), 'HP' (= Haftpflicht).<br />
300<br />
ALTER TABLE Versicherungsvertrag<br />
ADD CONSTRAINT Vertrag_CheckArt<br />
CHECK (Art IN (’VK’, ’TK’, ’HP’) );
28.5. Zusammenfassung<br />
Zusammenfassung<br />
In diesem Kapitel lernten wir mehrere Verfahren kennen, mit denen e<strong>in</strong>zelne<br />
Spalten und ganze Tabellen genauer festgelegt werden:<br />
• Zu e<strong>in</strong>er Spalte gehören nicht nur der Datentyp, sondern auch die V<strong>org</strong>abe von<br />
Werten und Wertebereichen.<br />
• Für e<strong>in</strong>e Spalte können E<strong>in</strong>schränkungen wie „E<strong>in</strong>deutigkeit“ oder „Teil des<br />
Primärschlüssels“ oder „Teil e<strong>in</strong>es Index“ gelten.<br />
• Für e<strong>in</strong>e Tabelle können Wertebereiche über mehrere Spalten geprüft werden.<br />
• Eigentlich immer gehört zu e<strong>in</strong>er Tabelle e<strong>in</strong> Primärschlüssel.<br />
• Außerdem können Indizes und Fremdschlüssel festgelegt werden.<br />
28.6. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 303.<br />
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. Zur Def<strong>in</strong>ition e<strong>in</strong>er Tabelle gehört unbed<strong>in</strong>gt die Def<strong>in</strong>ition der Spalten.<br />
2. Zur Def<strong>in</strong>ition e<strong>in</strong>er Tabelle gehört unbed<strong>in</strong>gt die Def<strong>in</strong>ition des Primärschlüssels.<br />
3. Zur Def<strong>in</strong>ition e<strong>in</strong>er Tabelle gehören unbed<strong>in</strong>gt die Klammern.<br />
4. Die Def<strong>in</strong>ition von E<strong>in</strong>schränkungen ist während des CREATE-Befehls oder<br />
durch e<strong>in</strong>en ALTER-Befehl möglich.<br />
5. Als UNIQUE darf nur e<strong>in</strong>e Spalte festgelegt werden.<br />
6. Jede Spalte kann als NOT NULL festgelegt werden.<br />
7. Für jede Spalte können V<strong>org</strong>abewerte festgelegt werden.<br />
8. Es gibt Situationen, <strong>in</strong> denen die Def<strong>in</strong>ition e<strong>in</strong>er Spalte nicht geändert<br />
werden kann.<br />
9. Der Begriff CONSTRAINT gehört zur Def<strong>in</strong>ition e<strong>in</strong>er E<strong>in</strong>schränkung.<br />
10. E<strong>in</strong> Primärschlüssel kann über beliebig viele Spalten festgelegt wird.<br />
11. Es ist üblich, dass der Wert e<strong>in</strong>es Primärschlüssels immer wieder e<strong>in</strong>mal<br />
geändert wird.<br />
301
DDL – E<strong>in</strong>zelheiten<br />
Übung 2 – Tabellendef<strong>in</strong>ition<br />
Bitte geben Sie an, welche Bestandteile der folgenden Def<strong>in</strong>ition falsch s<strong>in</strong>d bzw.<br />
welche Angaben fehlen.<br />
CREATE TABLE Computer<br />
CONSTRAINT ComputerID PRIMARY KEY (Nummer)<br />
UNIQUE Name,<br />
Name NOT NULL VARCHAR COLLATION W<strong>in</strong>1252<br />
Nummer INTEGER PRIMARY KEY<br />
Hersteller VARCHAR(30)<br />
Herstellung DATE<br />
Festplatte LONG DEFAULT 320*1024*1024*1024<br />
Ram_Groesse LONG,<br />
;<br />
H<strong>in</strong>weis: Bei den folgenden Def<strong>in</strong>itionen verwenden Sie bitte für alle E<strong>in</strong>schränkungen<br />
geeignete Namen.<br />
Übung 3 – E<strong>in</strong>schränkungen allgeme<strong>in</strong> def<strong>in</strong>ieren<br />
Erstellen Sie die Def<strong>in</strong>ition für e<strong>in</strong>e Tabelle mit <strong>in</strong>ternationalen Postleitzahlen:<br />
laufende Nummer, Land, Code, Ortsname. Legen Sie für jede Spalte möglichst<br />
viele E<strong>in</strong>zelheiten fest; bei der Reihenfolge der E<strong>in</strong>zelheiten müssen Sie wegen<br />
der Unterschiede der DBMS nur auf die CONSTRAINTS achten.<br />
Übung 4 – Spalten mit E<strong>in</strong>schränkungen h<strong>in</strong>zufügen<br />
Ergänzen Sie die Tabelle Versicherungsvertrag um folgende Spalten:<br />
• Basisprämie für e<strong>in</strong>en Betrag, V<strong>org</strong>abewert 500, ke<strong>in</strong>e negativen Beträge<br />
• Prämiensatz für e<strong>in</strong>e Zahl, V<strong>org</strong>abewert 100, M<strong>in</strong>imalwert 10 [%]<br />
Übung 5 – E<strong>in</strong>schränkung und Index h<strong>in</strong>zufügen<br />
Ändern Sie die Tabelle Versicherungsvertrag so, dass die Spalte Vertragsnummer<br />
e<strong>in</strong>deutig ist und e<strong>in</strong>en (ausdrücklich angegebenen) Index benutzt.<br />
Übung 6 – E<strong>in</strong>schränkung h<strong>in</strong>zufügen<br />
Ändern Sie die Tabelle Versicherungsnehmer so, dass die Spalte Eigener_Kunde<br />
nur die Werte 'J' und 'N' annehmen darf.<br />
302
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 301.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Übungen<br />
Die Aussagen 1, 3, 4, 6, 7, 8, 10 s<strong>in</strong>d wahr, die Aussagen 2, 5, 9, 11 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – Tabellendef<strong>in</strong>ition<br />
• Es fehlen die Klammern um die gesamte Auflistung aller E<strong>in</strong>zelheiten, und<br />
es fehlen Kommata nach jedem e<strong>in</strong>zelnen Bestandteil. Dagegen ist das letzte<br />
Komma falsch.<br />
• Zeilen 2/3: Diese Zeilen gehören an das Ende: Zuerst müssen die Spalten festgelegt<br />
werden, dann kann darauf Bezug genommen werden.<br />
• Zeile 4: Es fehlt die Größenangabe für die Zeichenkette. Ob die Reihenfolge der<br />
Teile passt, hängt vom DBMS ab.<br />
• Zeile 2 und 5: Doppelte Festlegung des Primary Key. Es bezieht sich zwar <strong>in</strong> beiden<br />
Fällen auf dieselbe Spalte, es s<strong>in</strong>d aber wegen des CONSTRAINT-Namens<br />
unterschiedliche Def<strong>in</strong>itionen.<br />
Lösung zu Übung 3 – E<strong>in</strong>schränkungen allgeme<strong>in</strong> def<strong>in</strong>ieren<br />
CREATE TABLE PLZ_Codes<br />
( ID INTEGER NOT NULL AUTO_INCREMENT<br />
CONSTRAINT PLZ_PK PRIMARY KEY,<br />
Land CHAR ( 2) NOT NULL DEFAULT ’DE’,<br />
Code VARCHAR(10) NOT NULL, -- auch CHAR(10) ist denkbar<br />
Ort VARCHAR(30) NOT NULL,<br />
CONSTRAINT PLZ_UK UNIQUE (Land, Code)<br />
);<br />
Lösung zu Übung 4 – Spalten mit E<strong>in</strong>schränkungen h<strong>in</strong>zufügen<br />
ALTER TABLE Versicherungsvertrag<br />
ADD [COLUMN] Basispraemie DECIMAL<br />
DEFAULT 500 NOT NULL<br />
CONSTRAINT Vertrag_Basispraemie_Check CHECK(Basispraemie > 0),<br />
ADD [COLUMN] Praemiensatz INTEGER<br />
DEFAULT 100 NOT NULL<br />
CONSTRAINT Vertrag_Praemiensatz_Check CHECK(Praemiensatz >= 10);<br />
303
DDL – E<strong>in</strong>zelheiten<br />
Lösung zu Übung 5 – E<strong>in</strong>schränkung und Index h<strong>in</strong>zufügen<br />
ALTER TABLE Versicherungsvertrag<br />
ADD CONSTRAINT Versicherungsvertrag_Nummer UNIQUE (Vertragsnummer)<br />
USING INDEX Versicherungsvertrag_Nummer_UK;<br />
Lösung zu Übung 6 – E<strong>in</strong>schränkung h<strong>in</strong>zufügen<br />
ALTER TABLE Versicherungsnehmer<br />
ADD CONSTRAINT Versicherungsnehmer_Eigener_Kunde<br />
CHECK( Eigener_Kunde = ’J’ OR Eigener_Kunde = ’N’ );<br />
28.7. Siehe auch<br />
In den folgenden Kapiteln s<strong>in</strong>d E<strong>in</strong>zelheiten zu f<strong>in</strong>den:<br />
• Datentypen 4<br />
• Tabellenstruktur der Beispieldatenbank 5<br />
• WHERE-Klausel im Detail 6<br />
Bei Wikipedia gibt es grundlegende Erläuterungen:<br />
• Globally Unique Identifier 7 (GUID) als e<strong>in</strong>deutige Kennung<br />
• Versicherungsnummer 8 der deutschen Rentenversicherung<br />
4 Kapitel 13 auf Seite 97<br />
5 Anhang A auf Seite 419<br />
6 Kapitel 17 auf Seite 155<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Globally%20Unique%20Identifier<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Versicherungsnummer<br />
304
29. Fremdschlüssel-Beziehungen<br />
29.1. Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305<br />
29.2. Grundsätze der Lösung . . . . . . . . . . . . . . . . . . . . . . . . 306<br />
29.3. Syntax und Optionen . . . . . . . . . . . . . . . . . . . . . . . . . 307<br />
29.4. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311<br />
29.5. Komb<strong>in</strong>ation von Fremdschlüsseln . . . . . . . . . . . . . . . . 312<br />
29.6. Rekursive Fremdschlüssel . . . . . . . . . . . . . . . . . . . . . . 314<br />
29.7. Reihenfolge der Maßnahmen beachten . . . . . . . . . . . . . . 316<br />
29.8. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 317<br />
29.9. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318<br />
29.10. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321<br />
In diesem Kapitel werden wir die Verknüpfungen zwischen Tabellen über Fremdschlüssel<br />
behandeln. Bereits <strong>in</strong> e<strong>in</strong>leitenden Kapiteln wurde die „referentielle Integrität“<br />
betont, dass nämlich die Datensätze <strong>in</strong> verschiedenen Tabellen dauerhaft<br />
zue<strong>in</strong>ander passen müssen. Diesem Zweck dienen Fremdschlüssel.<br />
29.1. Problemstellung<br />
In der Beispieldatenbank gibt es viele Verweise von e<strong>in</strong>er Tabelle auf andere Tabellen:<br />
• Zu jedem E<strong>in</strong>trag der Tabelle Versicherungsvertrag gehört genau e<strong>in</strong> E<strong>in</strong>trag der<br />
Tabelle Fahrzeug: Verträge und Fahrzeuge gehören unmittelbar zusammen.<br />
• Zu jedem E<strong>in</strong>trag der Tabelle Versicherungsvertrag gehört e<strong>in</strong> E<strong>in</strong>trag der Tabelle<br />
Versicherungsnehmer: E<strong>in</strong> Vertrag ohne e<strong>in</strong>en Kunden ist s<strong>in</strong>nlos.<br />
• E<strong>in</strong>em E<strong>in</strong>trag der Tabelle Versicherungsnehmer s<strong>in</strong>d E<strong>in</strong>träge der Tabelle Versicherungsvertrag<br />
zugeordnet: E<strong>in</strong> Kunde kann e<strong>in</strong>en oder mehrere Verträge<br />
halten; zu ihm können auch null Verträge gehören, wenn er nämlich aktuell<br />
ke<strong>in</strong>en Vertrag hat, aber als potenzieller Kunde weiterh<strong>in</strong> registriert ist.<br />
• Zu jedem Vertrag gehört e<strong>in</strong> E<strong>in</strong>trag der Tabelle Mitarbeiter: Jeder Vertrag wird<br />
hauptsächlich von e<strong>in</strong>em bestimmten Mitarbeiter bearbeitet. Das ist zwar <strong>in</strong><br />
der Praxis nicht so eng zu sehen, wird aber <strong>in</strong> unserer theoretischen Firma so<br />
geregelt.<br />
305
Fremdschlüssel-Beziehungen<br />
• Umgekehrt gehört nicht zu jedem E<strong>in</strong>trag der Tabelle Mitarbeiter e<strong>in</strong> Vertrag –<br />
z. B. bei der Abteilung „Forschung und Entwicklung“.<br />
Es wäre viel Aufwand, wenn e<strong>in</strong> Anwender all dies selbst beachten müsste. In<br />
e<strong>in</strong>em Büro gibt es immer Störungen; und schon fehlt e<strong>in</strong>e unbed<strong>in</strong>gt nötige Information:<br />
E<strong>in</strong> neuer Vertrag wird gespeichert, aber der Kunde fehlt noch; dann<br />
kommt e<strong>in</strong> Telefonat dazwischen; dann macht die Mitarbeiter<strong>in</strong> mit dem nächsten<br />
Vertrag weiter. Und woh<strong>in</strong> soll die Rechnung zum vorigen Vertrag gehen?<br />
Viel s<strong>in</strong>nvoller ist es, wenn das DBMS diese Bed<strong>in</strong>gungen direkt berücksichtigen<br />
kann. Dies wird über die Fremdschlüssel – englisch: ForeignKeys (FK) – geregelt.<br />
29.2. Grundsätze der Lösung<br />
29.2.1. Beziehungen beschreiben<br />
Bei all diesen Beziehungen geht es um die referentielle Integrität, dass nämlich<br />
die Verb<strong>in</strong>dungen zwischen den Tabellen und Querverweise immer auf dem aktuellen<br />
Stand s<strong>in</strong>d und die Werte <strong>in</strong> allen Tabellen korrekt zusammenpassen.<br />
Dies wird auch als „<strong>in</strong>terne Datensicherheit“ bezeichnet: Die Daten sollen <strong>in</strong>nerhalb<br />
der Datenbank <strong>in</strong>sofern sicher se<strong>in</strong>, dass ke<strong>in</strong>e Widersprüche zwischen den<br />
Daten <strong>in</strong> verschiedenen Tabellen auftreten.<br />
Durch die Fremdschlüssel werden Beziehungen zwischen Datensätzen verschiedener<br />
Tabellen def<strong>in</strong>iert. Das DBMS s<strong>org</strong>t dann für die richtigen Verknüpfungen:<br />
• Ke<strong>in</strong> neuer Vertrag kann ohne Fahrzeug-ID, Versicherungsnehmer-ID und<br />
Mitarbeiter-ID e<strong>in</strong>getragen werden.<br />
• Die entsprechenden Datensätze <strong>in</strong> Tabellen, auf die verwiesen wird, müssen<br />
zuerst gespeichert werden, bevor e<strong>in</strong> Vertrag neu aufgenommen werden kann.<br />
• Ke<strong>in</strong> Versicherungsnehmer kann gelöscht werden, solange noch Verträge vorliegen.<br />
• Bei entsprechender Festlegung können zunächst alle Verträge e<strong>in</strong>es Kunden<br />
und sofort der Kundensatz selbst gelöscht werden.<br />
Ergänzend s<strong>in</strong>d weitere Bed<strong>in</strong>gungen denkbar:<br />
• Wenn Kundennummern geändert werden, müssen die abhängigen Verträge<br />
ebenso geändert werden (Update-Weitergabe); oder Änderungen an Kundennummern<br />
werden erst gar nicht zugelassen (Update-Restriktion).<br />
Die letzten Bed<strong>in</strong>gungen können vernachlässigt werden, da die Beziehungen<br />
über die IDs geregelt werden. Nach den Regeln der relationalen Datenbanken<br />
hat die ID ke<strong>in</strong>e andere Bedeutung als die Identifizierung der Datensätze; es gibt<br />
niemals die Notwendigkeit, sie zu ändern.<br />
306
29.2.2. Beziehungen def<strong>in</strong>ieren<br />
Syntax und Optionen<br />
In der Tabellenstruktur der Beispieldatenbank s<strong>in</strong>d bei den e<strong>in</strong>zelnen Tabellen<br />
<strong>in</strong> der Spalte Erläuterung die Verknüpfungen aufgeführt, beispielsweise:<br />
• Die Tabelle Versicherungsvertrag enthält die folgenden Verweise:<br />
• E<strong>in</strong> E<strong>in</strong>trag <strong>in</strong> der Spalte Versicherungsnehmer-ID verweist auf e<strong>in</strong>e ID der<br />
Tabelle Versicherungsnehmer.<br />
• Die Spalte Fahrzeug-ID verweist auf e<strong>in</strong>e ID der Tabelle Fahrzeug.<br />
• Die Spalte Mitarbeiter-ID verweist auf e<strong>in</strong>e ID der Tabelle Mitarbeiter.<br />
• Die Tabelle Zuordnung_SF_FZ enthält die folgenden Verweise:<br />
• Die Spalte Schadensfall-ID verweist auf e<strong>in</strong>e ID der Tabelle Schadensfall.<br />
• Die Spalte Fahrzeug-ID verweist auf e<strong>in</strong>e ID der Tabelle Fahrzeug.<br />
• Die Tabelle Versicherungsnehmer enthält den folgenden Verweis:<br />
• Die Spalte Versicherungsgesellschaft-ID verweist optional (nämlich nur bei<br />
Fremdkunden) auf e<strong>in</strong>e ID der Tabelle Versicherungsgesellschaft.<br />
In allen Fällen bedeuten die Verweise: In e<strong>in</strong>em Datensatz der jeweils ersten Tabelle<br />
stehen ke<strong>in</strong>e weiteren E<strong>in</strong>zelheiten (z. B. zum Versicherungsnehmer). Stattdessen<br />
steht dort e<strong>in</strong>e ID; die Angaben dazu s<strong>in</strong>d im Datensatz der „weiteren“<br />
Tabelle unter der genannten ID zu f<strong>in</strong>den. Bei e<strong>in</strong>em optionalen Verweis kann<br />
<strong>in</strong> der „ersten“ Tabelle auch der NULL-Wert stehen; ob das mit e<strong>in</strong>em Fremdschlüssel<br />
automatisiert gesteuert werden kann, hängt vom DBMS ab.<br />
29.3. Syntax und Optionen<br />
Es s<strong>in</strong>d also zwei Punkte sicherzustellen:<br />
• Als Fremdschlüssel <strong>in</strong> der e<strong>in</strong>en Tabelle (z. B. beim Versicherungsvertrag) dürfen<br />
nur solche Werte stehen, die als Primärschlüssel <strong>in</strong> der anderen Tabelle<br />
(z. B. beim Versicherungsnehmer) vorhanden s<strong>in</strong>d.<br />
• E<strong>in</strong> Primärschlüssel darf nur dann geändert oder gelöscht werden, wenn er<br />
nicht als Fremdschlüssel <strong>in</strong> e<strong>in</strong>er weiteren Tabelle verwendet wird.<br />
29.3.1. Begriffe<br />
Auf der Ebene von Tabellen werden die folgenden Begriffe verwendet; diese beziehen<br />
sich immer auf e<strong>in</strong>e bestimmte Fremdschlüsel-Beziehung.<br />
• Die Tabelle, auf deren Primärschlüssel verwiesen wird, heißt Primärtabelle;<br />
auch die Begriffe Master-Tabelle oder Parent-Tabelle s<strong>in</strong>d gebräuchlich –<br />
manchmal auch die deutsche Bezeichnung Eltern-Tabelle.<br />
307
Fremdschlüssel-Beziehungen<br />
Im ersten Beispiel der Auflistung s<strong>in</strong>d dies die Tabellen Versicherungsnehmer,<br />
Fahrzeug und Mitarbeiter.<br />
• Die Tabelle, die den bzw. die Fremdschlüssel enthält, bezeichnet man als Detailtabelle<br />
oder Child-Tabelle oder auch abhängige Tabelle – manchmal auch<br />
mit der deutschen Bezeichnung K<strong>in</strong>d-Tabelle.<br />
Im Beispiel ist das die Tabelle Versicherungsvertrag.<br />
Der Begriff Detailtabelle hat im Beispiel nichts damit zu tun, dass <strong>in</strong> der Tabelle<br />
E<strong>in</strong>zelheiten zum Fahrzeug oder zum Sachbearbeiter stünden (das ist gerade<br />
nicht der Fall). Mit dem Beispiel im Wikipedia-Artikel „Fremdschlüssel“ wird die<br />
Bedeutung von Master und Detail verständlich: Die Kunden s<strong>in</strong>d die maßgebende<br />
Tabelle; zu jedem Kunden gehören als Details „se<strong>in</strong>e“ Bestellungen.<br />
Wir sprechen deshalb von Detailtabelle mit Fremdschlüsseln: E<strong>in</strong>e Spalte <strong>in</strong> der<br />
Detailtabelle wird über den Fremdschlüssel verknüpft mit dem Primärschlüssel<br />
<strong>in</strong> der Primärtabelle.<br />
29.3.2. Die Syntax<br />
Die Fremdschlüssel gehören zur Tabellendef<strong>in</strong>ition. Wie bei anderen Befehlen<br />
der Data Def<strong>in</strong>ition Language (DDL) gibt es für FOREIGN KEY mehrere Wege:<br />
• Bei der Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte, die den Fremdschlüssel benutzt:<br />
<br />
-- ergänzt durch:<br />
REFERENCES ( )<br />
[ ]<br />
• Bei der Def<strong>in</strong>ition der Tabelle <strong>in</strong> der Liste der E<strong>in</strong>schränkungen (CON-<br />
STRAINTs):<br />
[ CONSTRAINT ]<br />
FOREIGN KEY ( )<br />
REFERENCES ( )<br />
[ ]<br />
• Als Änderung der Tabellendef<strong>in</strong>ition mit ALTER TABLE:<br />
308<br />
ALTER TABLE <br />
ADD [ CONSTRAINT ]<br />
FOREIGN KEY ( )<br />
REFERENCES ( )<br />
[ ]
Syntax und Optionen<br />
Zu dem Zeitpunkt, an dem e<strong>in</strong> FOREIGN KEY festgelegt wird, muss die Tabelle,<br />
auf die verwiesen wird, bereits def<strong>in</strong>iert se<strong>in</strong>. Der letzte Weg ist deshalb <strong>in</strong> der Regel<br />
am sichersten: Zuerst werden alle Tabellen bestimmt, danach alle FOREIGN<br />
KEYs als Verknüpfung.<br />
Zur eigentlichen Def<strong>in</strong>ition gehören die folgenden Bestandteile:<br />
• CONSTRAINT <br />
Dies legt den Namen dieser E<strong>in</strong>schränkung fest und kann auch entfallen.<br />
• FOREIGN KEY <br />
Dies ist der H<strong>in</strong>weis auf e<strong>in</strong>en Fremdschlüssel und bestimmt, zu welcher Spalte<br />
dieser gehört.<br />
Bei der Def<strong>in</strong>ition e<strong>in</strong>er e<strong>in</strong>zelnen Spalte (erste Variante) ist von vornhere<strong>in</strong> klar,<br />
um welche Spalte es sich handelt; dies muss deshalb nicht wiederholt werden<br />
und entfällt bei dieser Variante.<br />
• REFERENCES <br />
Dies bestimmt die Tabelle, auf die mit dem Fremdschlüssel verwiesen wird.<br />
• <strong>in</strong> Klammern gesetzt<br />
Damit wird festgelegt, welche Spalten <strong>in</strong> den beiden Tabellen mite<strong>in</strong>ander <strong>in</strong><br />
Beziehung gesetzt werden.<br />
Die Optionen werden im nächsten Abschnitt erläutert.<br />
Der Vollständigkeit halber sei darauf h<strong>in</strong>gewiesen: E<strong>in</strong>e solche Verknüpfung<br />
kann sich auch auf mehrere Spalten beziehen, nämlich sowohl beim Primärschlüssel<br />
als auch beim Fremdschlüssel. Da e<strong>in</strong> Primärschlüssel ke<strong>in</strong>e weitere<br />
Bedeutung haben soll, besteht er sowieso (fast) immer aus e<strong>in</strong>er e<strong>in</strong>zelnen Spalte;<br />
deshalb wird <strong>in</strong> der Übersicht nur e<strong>in</strong> erwähnt.<br />
29.3.3. Optionen<br />
Die Optionen e<strong>in</strong>es FOREIGN KEY bestimmen das Verhalten der Tabelle, die die<br />
Verweise (Fremdschlüssel) benutzt – also der Detailtabelle –, sobald <strong>in</strong> der Primärtabelle<br />
die Primärschlüssel geändert werden. Allgeme<strong>in</strong> steht folgendes Verhalten<br />
zur Auswahl:<br />
• NO ACTION − alle Änderungen werden verweigert<br />
• CASCADE − die Weitergabe der Änderung an die Detailtabelle<br />
• RESTRICT − die Verweigerung der Änderung auch <strong>in</strong> der Primärtabelle<br />
• SET NULL − die Änderung des Verweises <strong>in</strong> der Detailtabelle auf NULL<br />
• SET DEFAULT − die Änderung des Verweises <strong>in</strong> der Detailtabelle auf den V<strong>org</strong>abewert<br />
der Spalte<br />
309
Fremdschlüssel-Beziehungen<br />
Die verschiedenen Optionen werden nicht immer unterstützt. Die Option ON<br />
UPDATE CASCADE z. B. wird von den meisten DBMS nicht angeboten.<br />
Im E<strong>in</strong>zelnen wirken sich diese Optionen wie folgt aus.<br />
Bei Neuaufnahmen <strong>in</strong> der Primärtabelle s<strong>in</strong>d Datensätze <strong>in</strong> e<strong>in</strong>er Tabelle mit<br />
Verweisen darauf noch nicht vorhanden. Also kann es ke<strong>in</strong>e Probleme geben;<br />
deshalb muss dies bei den Optionen nicht beachtet werden.<br />
Die folgenden Optionen wirken sich bei Änderungen und Löschungen <strong>in</strong> der<br />
Primärtabelle <strong>in</strong> gleicher Weise aus:<br />
• ON UPDATE NO ACTION und ON DELETE NO ACTION<br />
Die „Inaktivität“ bedeutet: Wenn e<strong>in</strong> Primärschlüssel <strong>in</strong> der Primärtabelle geändert<br />
bzw. gelöscht werden soll und abhängige Sätze <strong>in</strong> der Detailtabelle existieren,<br />
dann wird die Änderung/Löschung mit e<strong>in</strong>em Fehler abgebrochen; es<br />
erfolgt e<strong>in</strong> ROLLBACK.<br />
• ON UPDATE RESTRICT und ON DELETE RESTRICT<br />
Die „Restriktion der Aktualisierung“ bedeutet: Wenn e<strong>in</strong> Primärschlüssel <strong>in</strong> der<br />
Primärtabelle geändert bzw. gelöscht werden soll und abhängige Sätze <strong>in</strong> der<br />
Detailtabelle existieren, dann wird die Änderung/Löschung verweigert.<br />
• ON UPDATE SET NULL und ON DELETE SET NULL<br />
Das „NULL-Setzen“ bedeutet: Wenn e<strong>in</strong> Primärschlüssel <strong>in</strong> der Primärtabelle<br />
geändert bzw. gelöscht wird, dann werden die Verweise <strong>in</strong> der Detailtabelle auf<br />
NULL gesetzt. Das ist nur möglich, wenn die betreffende Spalte NULL-Werte<br />
zulässt (also nicht mit NOT NULL def<strong>in</strong>iert wurde).<br />
• ON UPDATE SET DEFAULT und ON DELETE SET DEFAULT<br />
Das „DEFAULT-Setzen“ bedeutet: Wenn e<strong>in</strong> Primärschlüssel <strong>in</strong> der Primärtabelle<br />
geändert bzw. gelöscht wird, dann werden die Verweise <strong>in</strong> der Detailtabelle<br />
auf den V<strong>org</strong>abewert der betreffenden Spalte gesetzt. Das ist nur möglich,<br />
wenn für die betreffende Spalte e<strong>in</strong> V<strong>org</strong>abewert festgelegt ist.<br />
Die folgende Option unterscheidet sich bei Änderungen und Löschungen:<br />
• ON UPDATE CASCADE – also bei Änderungen<br />
Bei „Weitergabe der Aktualisierung“ werden die Fremdschlüssel <strong>in</strong> der Detailtabelle<br />
genauso geändert wie der Primärschlüssel <strong>in</strong> der Primärtabelle.<br />
• ON DELETE CASCADE – also bei Löschungen<br />
Die „Löschweitergabe“ bedeutet: Zusammen mit dem Datensatz <strong>in</strong> der Primärtabelle<br />
werden auch alle Datensätze <strong>in</strong> der Detailtabelle gelöscht, die sich<br />
auf diesen Schlüssel beziehen.<br />
Wenn der Primärschlüssel „richtig“ def<strong>in</strong>iert ist, nämlich für alle Zeiten unveränderlich<br />
ist, dann wären UPDATE-Optionen eigentlich überflüssig. Aber man<br />
sollte vorbereitet se<strong>in</strong>, falls man doch auf die Idee kommt, e<strong>in</strong>en Primary Key zu<br />
ändern.<br />
310
Auswirkungen<br />
Beispiele<br />
Änderungen <strong>in</strong> der Primärtabelle haben mit den Optionen nichts zu tun. Sie<br />
werden durch e<strong>in</strong>en direkten Befehl – INSERT, UPDATE, DELETE – ausgelöst.<br />
E<strong>in</strong> Fremdschlüssel steuert mit den Optionen nur, <strong>in</strong>wieweit e<strong>in</strong>e Speicherung<br />
Auswirkungen auf die Detailtabelle hat oder nicht. Allerd<strong>in</strong>gs kann es passieren,<br />
dass durch die Restriktion nicht nur die Änderung <strong>in</strong> der Detailtabelle, sondern<br />
auch die Änderung <strong>in</strong> der Primärtabelle verh<strong>in</strong>dert wird.<br />
Änderungen <strong>in</strong> der Detailtabelle werden durch e<strong>in</strong>en FK wie folgt e<strong>in</strong>geschränkt:<br />
Bei INSERT und UPDATE dürfen <strong>in</strong> den Spalten des Fremdschlüssels nur solche<br />
Werte e<strong>in</strong>gefügt werden, die <strong>in</strong> der Primärtabelle als Primärschlüssel vorhanden<br />
s<strong>in</strong>d. E<strong>in</strong>zige Ausnahme ist, wenn die Fremdschlüssel-Spalte als optional def<strong>in</strong>iert<br />
ist. Dann kann hier auch NULL e<strong>in</strong>gefügt werden, obwohl NULL niemals<br />
als Primärschlüssel <strong>in</strong> der Primärtabelle stehen wird.<br />
29.4. Beispiele<br />
29.4.1. Versicherungsvertrag und Kunden<br />
Beg<strong>in</strong>nen wir für die Tabelle Versicherungsvertrag mit dem Verweis von der Spalte<br />
Versicherungsnehmer_ID auf die Tabelle Versicherungsnehmer.<br />
ALTER TABLE Versicherungsvertrag<br />
ADD CONSTRAINT Versicherungsvertrag_VN<br />
FOREIGN KEY (Versicherungsnehmer_ID)<br />
REFERENCES Versicherungsnehmer (ID);<br />
Wie fast immer benutzen wir e<strong>in</strong>e E<strong>in</strong>schränkung mit Namen. Wie bei den anderen<br />
CONSTRAINTs hängen wir an den Namen der Tabelle „etwas“ an (Suffix),<br />
das für die Art der E<strong>in</strong>schränkung steht. Wenn e<strong>in</strong>e Tabelle nur e<strong>in</strong>en Fremdschlüssel<br />
bekommt, wäre FK als Suffix geeignet. Da zur Tabelle Versicherungsvertrag<br />
drei Fremdschlüssel gehören, verwenden wir stattdessen den jeweiligen<br />
Tabellen-Alias wie VN für die Tabelle Versicherungsnehmer.<br />
Mit diesem CONSTRAINT ist festgelegt: E<strong>in</strong> neuer Versicherungsvertrag kann<br />
nur dann registriert werden, wenn die v<strong>org</strong>esehene Versicherungsnehmer_ID <strong>in</strong><br />
der Tabelle Versicherungsnehmer als ID bereits registriert ist.<br />
Wie gesagt: E<strong>in</strong>e „richtige“ ID wird niemals mehr geändert. Vorsichtshalber legen<br />
wir aber auch die Optionen fest:<br />
ALTER TABLE Versicherungsvertrag<br />
ADD CONSTRAINT Versicherungsvertrag_VN<br />
FOREIGN KEY (Versicherungsnehmer_ID)<br />
311
Fremdschlüssel-Beziehungen<br />
REFERENCES Versicherungsnehmer (ID)<br />
ON UPDATE RESTRICT<br />
ON DELETE RESTRICT;<br />
Die Änderung der ID oder die Löschung e<strong>in</strong>es Versicherungsnehmers ist also<br />
nicht zulässig, wenn zu diesem Kunden e<strong>in</strong> Versicherungsvertrag registriert ist.<br />
29.4.2. Mitarbeiter und Abteilung<br />
Die Beziehung zwischen diesen Tabellen kann so festgelegt werden:<br />
ALTER TABLE Mitarbeiter<br />
ADD CONSTRAINT Mitarbeiter_FK<br />
FOREIGN KEY (Abteilung_ID)<br />
REFERENCES Abteilung (ID);<br />
ON UPDATE CASCADE<br />
ON DELETE RESTRICT;<br />
Das DBMS s<strong>org</strong>t damit für die <strong>in</strong>terne Datensicherheit:<br />
• E<strong>in</strong> neuer Mitarbeiter kann nicht e<strong>in</strong>getragen werden, ohne dass die<br />
Abteilung_ID <strong>in</strong> der Tabelle Abteilung als ID vorhanden ist.<br />
• Wenn die Nummer e<strong>in</strong>er Abteilung geändert wird, wird das automatisch bei<br />
allen ihren Mitarbeitern angepasst und ebenfalls geändert.<br />
• Wenn e<strong>in</strong>e Abteilung gelöscht (d. h. geschlossen) werden soll, tritt folgende<br />
Prüfung e<strong>in</strong>:<br />
• Durch ON DELETE RESTRICT kann sie nicht gelöscht werden, solange ihr<br />
Mitarbeiter zugeordnet s<strong>in</strong>d.<br />
• Aber mit ON DELETE CASCADE würden beim Löschen e<strong>in</strong>er Abteilung automatisch<br />
alle dort arbeitenden Mitarbeiter ebenfalls gestrichen.<br />
29.5. Komb<strong>in</strong>ation von Fremdschlüsseln<br />
Grundsätzlich kann e<strong>in</strong>e Detailtabelle gleichzeitig als Primärtabelle für e<strong>in</strong>e<br />
andere Tabelle def<strong>in</strong>iert werden. In unserer Beispieldatenbank betrifft das die<br />
mehrfachen Verknüpfungen:<br />
Versicherungsvertrag ⇔ Versicherungsnehmer ⇔ Versicherungsgesellschaft<br />
⇔ Fahrzeug ⇔ Fahrzeugtyp<br />
⇔ Fahrzeughersteller<br />
⇔ Mitarbeiter ⇔ Abteilung<br />
Fahrzeug ⇔ Zuordnung_SF_FZ ⇔ Schadensfall<br />
312
Komb<strong>in</strong>ation von Fremdschlüsseln<br />
Dann s<strong>in</strong>d aber nicht alle Komb<strong>in</strong>ationen der Optionen CASCADE (Weitergabe)<br />
und RESTRICT (Restriktion) zulässig − weder bei DELETE noch bei UPDATE. Das<br />
DBMS prüft beim Ausführen der DDL-Befehle, ob die gewünschte Regel zulässig<br />
ist oder nicht.<br />
Nicht zulässig s<strong>in</strong>d folgende Verknüpfungen von Optionen:<br />
Tabelle C Tabelle B Tabelle A<br />
(Details zu Tabelle B) (Details zu Tabelle A, (Master zu Tabelle B)<br />
(Master zu Tabelle C)<br />
Versicherungsvertrag ⇔ Versicherungsnehmer ⇔ Versicherungsgesellschaft<br />
ON DELETE RESTRICT ON DELETE CASCADE<br />
Erläuterung: Es ist nicht zulässig, e<strong>in</strong>en Versicherungsnehmer zu löschen, wenn<br />
zu ihm noch (m<strong>in</strong>destens) e<strong>in</strong> Vertrag registriert ist. Andererseits sollen mit e<strong>in</strong>er<br />
Versicherungsgesellschaft auch alle ihre Versicherungsnehmer automatisch<br />
gelöscht werden. Diese automatische Löschung stünde im Widerspruch zur Verh<strong>in</strong>derung<br />
der Löschung bei vorhandenen Verträgen.<br />
Zulässig s<strong>in</strong>d folgende Verknüpfungen von Optionen:<br />
Tabelle C Tabelle B Tabelle A<br />
(Details zu Tabelle B) (Details zu Tabelle A, (Master zu Tabelle B)<br />
(Master zu Tabelle C)<br />
Versicherungsvertrag ⇔ Versicherungsnehmer ⇔ Versicherungsgesellschaft<br />
ON DELETE CASCADE ON DELETE RESTRICT<br />
Erläuterung: Das Löschen der Versicherungsgesellschaft ist nicht zulässig, wenn<br />
noch Versicherungsnehmer registriert s<strong>in</strong>d. Damit wird die Weitergabe oder Restriktion<br />
der Löschung vom Versicherungsnehmer zum Versicherungsvertrag<br />
überhaupt nicht bee<strong>in</strong>flusst.<br />
Möglich s<strong>in</strong>d auch R<strong>in</strong>g-Verkettungen:<br />
Tabelle A ⇔ Tabelle B ⇔ Tabelle C ⇔ Tabelle A<br />
Ob Sie diese Verknüpfungen von rechts nach l<strong>in</strong>ks lesen oder umgekehrt, ist<br />
gleichgültig: E<strong>in</strong>e Tabelle hat Verweise auf e<strong>in</strong>e andere, diese auf e<strong>in</strong>e nächste<br />
und e<strong>in</strong>e weitere wieder auf die erste.<br />
313
Fremdschlüssel-Beziehungen<br />
29.6. Rekursive Fremdschlüssel<br />
Fremdschlüsselbeziehungen können auch rekursiv def<strong>in</strong>iert werden. Dabei verweist<br />
<strong>in</strong> e<strong>in</strong>er Tabelle e<strong>in</strong>e abhängige Spalte auf den eigenen Primärschlüssel.<br />
Als Beispiel verwenden wir e<strong>in</strong>e hierarchische Gliederung der Abteilungen:<br />
CREATE TABLE Abteilung<br />
( AbtNr INTEGER NOT NULL,<br />
UebergeordneteAbt INTEGER,<br />
AbtName VARCHAR(100),<br />
PRIMARY KEY (AbtNr),<br />
FOREIGN KEY (UebergeordneteAbt)<br />
REFERENCES Abteilung (AbtNr)<br />
ON DELETE CASCADE<br />
);<br />
Die gesamte Firma wird mit AbtNr = 1 gespeichert; zu ihr gehört ke<strong>in</strong>e UebergeordneteAbt.<br />
Bei jeder anderen Abteilung wird <strong>in</strong> dieser Spalte die AbtNr derjenigen<br />
Abteilung registriert, der sie direkt zugeordnet ist: E<strong>in</strong>e Hauptabteilung<br />
gehört direkt zur Firma, e<strong>in</strong>e beliebige Abteilung zu ihrer Hauptabteilung, e<strong>in</strong>e<br />
Arbeitsgruppe zu e<strong>in</strong>er bestimmten Abteilung usw.<br />
29.6.1. Praktische Probleme<br />
Rekursive Fremdschlüsselbeziehungen s<strong>in</strong>d etwas problematisch <strong>in</strong> der Handhabung.<br />
Die Neuaufnahme von Datensätzen muss <strong>in</strong> e<strong>in</strong>er bestimmten Reihenfolge geschehen:<br />
zuerst die oberste Ebene (Firma), dann die nächste Ebene (Hauptabteilungen)<br />
usw. Auf jeder Ebene ist e<strong>in</strong>e Neuaufnahme nur dann möglich, wenn<br />
der übergeordnete E<strong>in</strong>trag schon vorhanden ist.<br />
Beim Löschen von Datensätzen kann es zu verschiedenen Problemen kommen.<br />
Mit Lösch-Weitergabe – ON DELETE CASCADE – könnten wesentlich mehr Daten<br />
gelöscht werden, als <strong>in</strong> der WHERE-Bed<strong>in</strong>gung angegeben wird:<br />
DELETE FROM Abteilung<br />
WHERE AbtNr = 33;<br />
Bei diesem Beispiel werden auch alle Sätze gelöscht, die der Abteilung 33 untergeordnet<br />
s<strong>in</strong>d.<br />
Mit Lösch-Restriktion – ON DELETE RESTRICT – wird nach jeder e<strong>in</strong>zelnen Löschung<br />
geprüft, ob es ke<strong>in</strong>e Fremdschlüsselverletzung gibt. Selbst wenn alle Sätze<br />
aus der Tabelle entfernt werden sollen, könnte die Ausführung fehlschlagen.<br />
314
DELETE FROM Abteilung;<br />
Rekursive Fremdschlüssel<br />
Das liegt daran, dass bei der Ausführung des DELETE-Befehls die Sätze <strong>in</strong> e<strong>in</strong>er<br />
beliebigen Reihenfolge gelöscht werden, meistens <strong>in</strong> der Reihenfolge, <strong>in</strong> der sie<br />
„real“ <strong>in</strong> der Tabelle gespeichert s<strong>in</strong>d. Nur wenn die Sätze exakt <strong>in</strong> der richtigen<br />
Reihenfolge (von der untersten Abteilung beg<strong>in</strong>nend bis zur obersten Abteilung)<br />
gespeichert s<strong>in</strong>d, dann kann die Ausführung des DELETE-Befehls gel<strong>in</strong>gen.<br />
Dass der Erfolg e<strong>in</strong>es <strong>SQL</strong>-Befehls von der physischen Speicherreihenfolge der<br />
Sätze abhängig ist, darf <strong>in</strong> e<strong>in</strong>em DBMS nicht vorkommen. Daher bieten e<strong>in</strong>ige<br />
DBMS die Möglichkeit der verzögerten Prüfung bei der Löschweitergabe.<br />
Durch e<strong>in</strong>en e<strong>in</strong>zigen DELETE-Befehl können mehrere Sätze und u. U. auch alle<br />
Sätze e<strong>in</strong>er Tabelle gelöscht werden. Innerhalb e<strong>in</strong>er Transaktion können mehrere<br />
DELETE-Befehle ausgeführt werden. Standardmäßig erfolgt die Prüfung, ob<br />
e<strong>in</strong>e Löschung ausgeführt werden darf, nach jedem e<strong>in</strong>zelnen Satz, der gelöscht<br />
wurde. Das hat den Vorteil, dass bei e<strong>in</strong>er unzulässigen Löschung gleich abgebrochen<br />
werden kann und der ROLLBACK nicht unnötig viel zu tun hat.<br />
29.6.2. Maßnahmen<br />
Um die oben beschriebenen Lösch-Anomalien zu vermeiden, kann bei e<strong>in</strong>igen<br />
DBMS die Prüfung, ob die Löschung zulässig ist, als Gesamtprüfung stattf<strong>in</strong>den<br />
und nicht nach jedem e<strong>in</strong>zelnen Satz. Dies s<strong>in</strong>d e<strong>in</strong> paar Möglichkeiten dafür:<br />
• Nach der Löschung aller Sätze, die durch e<strong>in</strong>en DELETE-Befehl v<strong>org</strong>esehen<br />
s<strong>in</strong>d, wird der Zusammenhang der Daten überprüft.<br />
Diese Variante wird z. B. von DB2 angeboten durch die Option ON DELETE NO<br />
ACTION.<br />
• Erst zum Abschluss der Transaktion wird die Prüfung erledigt.<br />
Diese Variante gibt es z. B. bei Oracle durch die Option INITIALLY IMMEDIATE<br />
DEFERRABLE.<br />
• E<strong>in</strong>ige DBMS können Fremdschlüssel-Beziehungen deaktivieren und später<br />
wieder aktivieren. Bei der Aktivierung muss der gesamte Datenbestand der betroffenen<br />
Tabelle überprüft werden, und es müssen Anweisungen erteilt werden,<br />
wie mit fehlerhaften Sätzen umgegangen werden soll.<br />
• Mit Tools (z. B. Import, Load) kann man bei den meisten DBMS Sätze <strong>in</strong> e<strong>in</strong>e<br />
Tabelle laden, ohne dabei die Fremdschlüssel-Beziehungen zu prüfen.<br />
Bei DB2 z. B. ist die Tabelle danach gesperrt und muss durch das CHECK-Tool<br />
geprüft werden. Erst dann steht die Tabelle wieder für reguläre Zugriffe zur Verfügung.<br />
• Wieder andere DBMS lassen rekursive Fremdschlüsselbeziehungen überhaupt<br />
nicht zu.<br />
315
Fremdschlüssel-Beziehungen<br />
29.7. Reihenfolge der Maßnahmen beachten<br />
Wenn die Tabellen <strong>in</strong> e<strong>in</strong>er Datenbank mit Fremdschlüsseln verbunden s<strong>in</strong>d,<br />
muss beim Bearbeiten der Tabellen e<strong>in</strong>e bestimmte Reihenfolge e<strong>in</strong>gehalten<br />
werden, und zwar sowohl beim E<strong>in</strong>fügen als auch beim Löschen. Ob auch das<br />
Ändern mit Schwierigkeiten verbunden se<strong>in</strong> kann, hängt von der Situation ab.<br />
Alle hier genannten Probleme beziehen sich ausschließlich auf diejenigen Spalten,<br />
die mit Fremdschlüsseln an andere Tabellen gebunden s<strong>in</strong>d. Änderungen <strong>in</strong><br />
anderen Spalten können immer ohne Probleme ausgeführt werden.<br />
29.7.1. Bei INSERT<br />
Man muss mit den Tabellen beg<strong>in</strong>nen, die ke<strong>in</strong>e Fremdschlüssel haben. Danach<br />
können die Tabellen befüllt werden, die auf diese Tabellen verweisen, und so<br />
weiter. In der Beispieldatenbank gilt so für e<strong>in</strong>en neuen Versicherungsvertrag:<br />
• Registriere den Versicherungsnehmer.<br />
• Dazu ist vorher ggf. die Versicherungsgesellschaft zu speichern.<br />
• Registriere das Fahrzeug.<br />
• Dazu ist vorher ggf. der Fahrzeugtyp zu speichern<br />
• und noch e<strong>in</strong>en Schritt früher der Fahrzeughersteller.<br />
• Registriere, soweit notwendig, den Mitarbeiter.<br />
• Dazu ist vorher ggf. die Abteilung zu speichern.<br />
Erst jetzt s<strong>in</strong>d alle Voraussetzungen vorhanden, sodass der Vertrag gespeichert<br />
werden kann.<br />
Bei R<strong>in</strong>g-Verkettungen werden Tools zum <strong>in</strong>itialen Befüllen benötigt; oder es<br />
muss mit Sätzen begonnen werden, die NULL als Fremdschlüssel enthalten.<br />
29.7.2. Bei DELETE<br />
Zum Löschen von Datensätzen muss – sofern nicht mit e<strong>in</strong>er automatischen<br />
Lösch-Weitergabe gearbeitet wird – genau die umgekehrte Reihenfolge e<strong>in</strong>gehalten<br />
werden.<br />
29.7.3. Bei UPDATE<br />
Die Fremdschlüssel-Beziehungen verlangen, dass die Datensätze <strong>in</strong> der Primärtabelle,<br />
auf die aus der Detailtabelle verwiesen wird, vorhanden s<strong>in</strong>d. Ändert<br />
316
Zusammenfassung<br />
man e<strong>in</strong>en Fremdschlüssel <strong>in</strong> der Detailtabelle, muss der neue Wert ebenfalls<br />
<strong>in</strong> der Primärtabelle vorhanden se<strong>in</strong>. Wenn man e<strong>in</strong>en Schlüsselwert <strong>in</strong> der Primärtabelle<br />
ändern will, dann ist das nur möglich, wenn der alte Wert von ke<strong>in</strong>em<br />
Satz <strong>in</strong> der Detailtabelle verwendet wird. Sollte der Wert doch verwendet (referenziert)<br />
werden, dann ist der UPDATE nicht möglich.<br />
Theoretisch wäre auch e<strong>in</strong> UPDATE-CASCADE vorstellbar. Das würde bedeuten,<br />
dass die Änderung e<strong>in</strong>es Wertes <strong>in</strong> der Primärtabelle auch gleichzeitig alle referenzierten<br />
Werte <strong>in</strong> der Detailtabelle ändert. E<strong>in</strong>e solche Funktion wird jedoch<br />
von den meisten Datenbanken nicht angeboten.<br />
Wenn e<strong>in</strong> Schlüsselwert <strong>in</strong> der Primärtabelle zu ändern ist, der von mehreren<br />
Sätzen <strong>in</strong> der Detailtabelle verwendet wird, kann nur so v<strong>org</strong>egangen werden:<br />
• E<strong>in</strong>en Satz mit dem neuen Wert <strong>in</strong> der Primärtabelle e<strong>in</strong>fügen (INSERT)<br />
• Alle Sätze <strong>in</strong> der Detailtabelle ändern auf den neuen Wert (UPDATE)<br />
• Den Satz mit dem alten Wert <strong>in</strong> der Primärtabelle löschen (DELETE)<br />
29.7.4. Bestimme die „E<strong>in</strong>füge-Reihenfolge“<br />
Theoretisch handelt es sich hier um e<strong>in</strong> Problem der „topologischen Sortierung“.<br />
Bei großen Datenmodellen sollte man alle vorhandenen Tabellen <strong>in</strong> der Reihenfolge<br />
notieren, <strong>in</strong> der sie befüllt werden können. Dazu kann man die Struktur der<br />
Datenbank auslesen; Tabellen und Fremdschlüssel müssen also bereits def<strong>in</strong>iert<br />
se<strong>in</strong>. Beispiele dazu s<strong>in</strong>d im Kapitel Tipps und Tricks 1 zu f<strong>in</strong>den.<br />
29.8. Zusammenfassung<br />
In diesem Kapitel erfuhren wir e<strong>in</strong>iges darüber, wie mit Fremdschlüsseln<br />
(= FOREIGN KEYs) die Verb<strong>in</strong>dungen zwischen Tabellen sichergestellt werden:<br />
• Die Def<strong>in</strong>ition gehört zur DDL und erfolgt mit e<strong>in</strong>er entsprechenden E<strong>in</strong>schränkung<br />
(CONSTRAINT), meistens über ALTER TABLE.<br />
• Sowohl beim E<strong>in</strong>fügen von Datensätzen als auch beim Ändern und Löschen<br />
s<strong>in</strong>d diese Beziehungen zu beachten.<br />
• Daraus ergibt sich e<strong>in</strong>e bestimmte Reihenfolge, welche Daten zuerst gespeichert<br />
werden müssen.<br />
• Beim Ändern und Löschen können die Anpassungen durch e<strong>in</strong>e Klausel automatisiert<br />
werden.<br />
1 Abschnitt 34.2 auf Seite 391<br />
317
Fremdschlüssel-Beziehungen<br />
29.9. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 320.<br />
Bei allen <strong>SQL</strong>-Befehlen benutzen Sie bitte CONSTRAINTS mit Namen.<br />
Übung 1 – Def<strong>in</strong>itionen<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. E<strong>in</strong> Fremdschlüssel legt fest, dass <strong>in</strong> e<strong>in</strong>er bestimmten Spalte der e<strong>in</strong>en Tabelle<br />
nur solche Werte verwendet werden können, die als Primärschlüssel<br />
der anderen Tabelle vorhanden s<strong>in</strong>d.<br />
2. Die Tabelle, die den Primärschlüssel enthält, wird als Master-Tabelle bezeichnet,<br />
die Tabelle mit dem Fremdschlüssel als Detail-Tabelle.<br />
3. In unserer Beispieldatenbank ist bei der Verknüpfung Fahrzeugtyp/Fahrzeughersteller<br />
die Tabelle Fahrzeugtyp der „Master“, die Tabelle Fahrzeughersteller<br />
die Detailtabelle.<br />
4. Bei der Verknüpfung Fahrzeug/Fahrzeugtyp gilt die Tabelle Fahrzeug als<br />
Detailtabelle, die Tabelle Fahrzeugtyp als Primärtabelle.<br />
5. E<strong>in</strong> INSERT <strong>in</strong> der Detailtabelle ist immer möglich, ohne die Werte <strong>in</strong> der<br />
Primärtabelle zu beachten.<br />
6. E<strong>in</strong> UPDATE <strong>in</strong> der Primärtabelle ist immer möglich, ohne die Werte <strong>in</strong> der<br />
Detailtabelle zu beachten.<br />
7. E<strong>in</strong> DELETE <strong>in</strong> der Detailtabelle ist immer möglich, ohne die Werte <strong>in</strong> der<br />
Primärtabelle zu beachten.<br />
8. E<strong>in</strong> DELETE <strong>in</strong> der Primärtabelle ist immer möglich, ohne die Werte <strong>in</strong> der<br />
Detailtabelle zu beachten.<br />
9. E<strong>in</strong> FOREIGN KEY wird so def<strong>in</strong>iert: Er wird der Tabelle mit dem Fremdschlüssel<br />
und der betreffenden Spalte zugeordnet und verweist auf die Tabelle<br />
mit dem Primärschlüssel und der betreffenden Spalte.<br />
10. E<strong>in</strong> FOREIGN KEY kann nicht unmittelbar bei der Def<strong>in</strong>ition der<br />
Fremdschlüssel-Spalte angegeben werden.<br />
11. E<strong>in</strong>e Fremdschlüssel-Beziehung kann nur zu jeweils e<strong>in</strong>er Spalte der beiden<br />
Tabellen gehören.<br />
Übung 2 – Zusammenhänge<br />
Welche Fremdschlüssel s<strong>in</strong>d bei den Tabellen Fahrzeug und Mitarbeiter der Beispieldatenbank<br />
vorzusehen? Nennen Sie jeweils die betreffenden Spalten. S<strong>in</strong>d<br />
bei den Primärtabellen weitere Fremdschlüssel vorzusehen?<br />
318
Übung 3 – Fremdschlüssel festlegen<br />
Übungen<br />
Verknüpfen Sie die Tabelle Fahrzeugtyp mit der Tabelle Fahrzeughersteller als<br />
Fremdschlüssel auf die entsprechenden Spalten.<br />
Übung 4 – Fremdschlüssel festlegen<br />
Verknüpfen Sie die Tabelle Dienstwagen mit den Tabellen Mitarbeiter und Fahrzeugtyp<br />
als Fremdschlüssel auf die entsprechenden Spalten.<br />
Übung 5 – Optionen bei Neuaufnahmen<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. Für Neuaufnahmen gibt es ke<strong>in</strong>e Option ON INSERT zur Automatisierung.<br />
2. Bei e<strong>in</strong>er Neuaufnahme <strong>in</strong> der Primärtabelle müssen Verknüpfungen <strong>in</strong> der<br />
Detailtabelle nicht beachtet werden.<br />
3. Bei e<strong>in</strong>er Neuaufnahme <strong>in</strong> der Detailtabelle müssen Verknüpfungen <strong>in</strong> der<br />
Primärtabelle nicht beachtet werden.<br />
4. Bei e<strong>in</strong>er Neuaufnahme <strong>in</strong> der Primärtabelle kann der erforderliche Datensatz<br />
<strong>in</strong> der Detailtabelle automatisch aufgenommen werden.<br />
5. Bei e<strong>in</strong>er Neuaufnahme <strong>in</strong> der Detailtabelle kann der erforderliche Datensatz<br />
<strong>in</strong> der Primärtabelle automatisch aufgenommen werden.<br />
6. Bei Neuaufnahmen <strong>in</strong> beiden Tabellen ist die Reihenfolge zu beachten.<br />
Übung 6 – Optionen bei Änderungen<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. Bei e<strong>in</strong>er Änderung <strong>in</strong> der Primärtabelle müssen Verknüpfungen <strong>in</strong> der Detailtabelle<br />
nicht beachtet werden.<br />
2. Bei e<strong>in</strong>er Änderung <strong>in</strong> der Detailtabelle müssen Verknüpfungen <strong>in</strong> der Primärtabelle<br />
nicht beachtet werden.<br />
3. Bei e<strong>in</strong>er Änderung <strong>in</strong> der Primärtabelle wird der zugehörige Datensatz <strong>in</strong><br />
der Detailtabelle automatisch geändert, sofern ON UPDATE SET DEFAULT<br />
def<strong>in</strong>iert ist.<br />
4. Bei e<strong>in</strong>er Änderung <strong>in</strong> der Primärtabelle wird der zugehörige Datensatz<br />
<strong>in</strong> der Detailtabelle automatisch geändert, sofern ON UPDATE CASCADE<br />
festgelegt ist.<br />
5. E<strong>in</strong>e Änderung <strong>in</strong> der Detailtabelle ändert auch den zugehörigen Datensatz<br />
<strong>in</strong> der Primärtabelle, sofern ON UPDATE CASCADE gilt.<br />
6. Sofern ON UPDATE RESTRICT festgelegt ist, wird e<strong>in</strong>e Änderung <strong>in</strong> der Primärtabelle<br />
immer ausgeführt.<br />
319
Fremdschlüssel-Beziehungen<br />
Übung 7 – Die Reihenfolge<br />
Nennen Sie die Reihenfolge der INSERT-Befehle, wenn e<strong>in</strong> Schadensfall mit drei<br />
beteiligten Fahrzeugen aufzunehmen ist. Dabei soll e<strong>in</strong> Fahrzeug zu e<strong>in</strong>em „Eigenen<br />
Kunden“ und zwei Fahrzeuge zu „Fremdkunden“ gehören; die e<strong>in</strong>e „fremde“<br />
Versicherungsgesellschaft soll schon gespeichert se<strong>in</strong>, die andere nicht.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 318.<br />
Lösung zu Übung 1 – Def<strong>in</strong>itionen<br />
Die Aussagen 1, 2, 4, 5, 8, 10 s<strong>in</strong>d wahr. Die Aussagen 3, 6, 7, 9, 11, 12 s<strong>in</strong>d falsch.<br />
H<strong>in</strong>weis zu Aussage 6: Die Ergänzung „immer“ macht die Aussage falsch. Wenn<br />
die ID ausgenommen würde, wäre die Aussage wahr.<br />
Lösung zu Übung 2 – Zusammenhänge<br />
Fahrzeug: Fahrzeugtyp_ID verweist auf ID der Tabelle Fahrzeugtyp.<br />
Fahrzeugtyp: Hersteller_ID verweist auf ID der Tabelle Fahrzeughersteller.<br />
Mitarbeiter: Abteilung_ID verweist auf ID der Tabelle Abteilung.<br />
Lösung zu Übung 3 – Fremdschlüssel festlegen<br />
ALTER TABLE Fahrzeugtyp<br />
ADD CONSTRAINT Fahrzeugtyp_FK<br />
FOREIGN KEY (Hersteller_ID)<br />
REFERENCES Fahrzeughersteller (ID);<br />
Lösung zu Übung 4 – Fremdschlüssel festlegen<br />
ALTER TABLE Dienstwagen<br />
ADD CONSTRAINT Dienstwagen_FZ<br />
FOREIGN KEY (Fahrzeugtyp_ID) REFERENCES Fahrzeugtyp (ID),<br />
ADD CONSTRAINT Dienstwagen_MI<br />
FOREIGN KEY (Mitarbeiter_ID) REFERENCES Mitarbeiter (ID);<br />
Lösung zu Übung 5 – Optionen bei Neuaufnahmen<br />
Die Aussagen 1, 2, 6 s<strong>in</strong>d wahr. Die Aussagen 3, 4, 5 s<strong>in</strong>d falsch.<br />
320
Lösung zu Übung 6 – Optionen bei Änderungen<br />
Die Aussagen 3, 4 s<strong>in</strong>d wahr. Die Aussagen 1, 2, 5, 6 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 7 – Die Reihenfolge<br />
1. die „neue“ Versicherungsgesellschaft speichern<br />
2. deren Kunden speichern<br />
3. dessen Fahrzeug speichern<br />
4. dessen Versicherungsvertrag speichern<br />
Siehe auch<br />
5. den Fremdkunden der schon registrierten Versicherungsgesellschaft speichern<br />
6. dessen Fahrzeug speichern<br />
7. dessen Versicherungsvertrag speichern<br />
8. den Schadensfall speichern<br />
9. den Schadensfall mit den Fahrzeugen verknüpfen:<br />
a) mit dem Fahrzeug des eigenen Kunden<br />
b) mit dem Fahrzeug des e<strong>in</strong>en Fremdkunden<br />
c) mit dem Fahrzeug des anderen Fremdkunden<br />
29.10. Siehe auch<br />
Weitere E<strong>in</strong>zelheiten s<strong>in</strong>d <strong>in</strong> den folgenden Kapiteln zu f<strong>in</strong>den:<br />
• Relationale Datenbanken 2 – Normalisierung 3<br />
• Tabellenstruktur der Beispieldatenbank 4<br />
• Data Def<strong>in</strong>ition Language (DDL) 5<br />
• DDL – E<strong>in</strong>zelheiten 6 mit E<strong>in</strong>zelheiten zur Tabellendef<strong>in</strong>ition<br />
Wikipedia bietet verschiedene grundlegenden Informationen:<br />
• Fremdschlüssel 7<br />
2 Kapitel 4 auf Seite 17<br />
3 Kapitel 5 auf Seite 25<br />
4 Anhang A auf Seite 419<br />
5 Kapitel 10 auf Seite 79<br />
6 Kapitel 28 auf Seite 285<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Fremdschl%c3%bcssel<br />
321
Fremdschlüssel-Beziehungen<br />
• Referentielle Integrität 8<br />
• Anomalien 9<br />
• Topologische Sortierung 10<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Referentielle%20Integrit%c3%a4t<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Anomalie%20(Informatik)<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/Topologische%20Sortierung<br />
322
30. <strong>SQL</strong>-Programmierung<br />
30.1. Rout<strong>in</strong>en ohne feste Speicherung . . . . . . . . . . . . . . . . . 324<br />
30.2. Programmieren <strong>in</strong>nerhalb von Rout<strong>in</strong>en . . . . . . . . . . . . . 325<br />
30.3. <strong>SQL</strong>-Programmierung mit Firebird . . . . . . . . . . . . . . . . 326<br />
30.4. <strong>SQL</strong>-Programmierung mit MS-<strong>SQL</strong> . . . . . . . . . . . . . . . . 331<br />
30.5. <strong>SQL</strong>-Programmierung mit My<strong>SQL</strong> . . . . . . . . . . . . . . . . . 334<br />
30.6. <strong>SQL</strong>-Programmierung mit Oracle . . . . . . . . . . . . . . . . . 338<br />
30.7. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 342<br />
30.8. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343<br />
30.9. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345<br />
Innerhalb e<strong>in</strong>er Datenbank können Arbeitsabläufe selbst gesteuert werden. Dafür<br />
gibt es Funktionen, Prozeduren und Trigger. Funktionen und Prozeduren<br />
werden oft geme<strong>in</strong>sam als Rout<strong>in</strong>en bezeichnet.<br />
Bei diesen Konstruktionen gibt es relativ wenig Geme<strong>in</strong>samkeiten zwischen<br />
den DBMS. Für Funktionen und Prozeduren lässt bereits der <strong>SQL</strong>-Standard den<br />
Datenbank-Anbietern „alle“ Freiheiten, wie sie diese Möglichkeiten verwirklichen<br />
wollen. Deshalb können auch wir uns nur auf e<strong>in</strong>ige Grundlagen beschränken<br />
und müssen erneut auf die Dokumentation des jeweiligen DBMS verweisen.<br />
Diese Elemente benutzen <strong>in</strong>tegrierte Funktionen, DML-Befehle und teilweise<br />
Datenbank-Operationen, verbunden <strong>in</strong> e<strong>in</strong>er speziellen Programmiersprache,<br />
die prozedurales <strong>SQL</strong> o. ä. bezeichnet wird. In diesem Kapitel gibt es allgeme<strong>in</strong>e<br />
Erklärungen dazu, wie solche Abläufe erstellt und programmiert werden können;<br />
<strong>in</strong> den folgenden Kapiteln werden diese Mittel konkret benutzt.<br />
Funktionen:<br />
E<strong>in</strong>e (benutzerdef<strong>in</strong>ierte Skalar-) Funktion liefert genau e<strong>in</strong>en Wert e<strong>in</strong>es<br />
bestimmten Datentyps. Es handelt sich dabei um e<strong>in</strong>e Ergänzung zu den<br />
<strong>in</strong>ternen Skalarfunktionen des DBMS. Es gibt sie mit und ohne Argumente;<br />
sie werden gezielt vom Anwender bzw. e<strong>in</strong>em Anwendungsprogramm aufgerufen.<br />
E<strong>in</strong>zelheiten dazu werden im Kapitel Eigene Funktionen 1 behandelt.<br />
1 Kapitel 31 auf Seite 347<br />
323
<strong>SQL</strong>-Programmierung<br />
Prozeduren:<br />
E<strong>in</strong>e Prozedur − gespeicherte Prozedur, engl. Stored Procedure (SP) − ist<br />
v<strong>org</strong>esehen für „immer wiederkehrende“ Arbeitsabläufe. Es gibt sie mit<br />
und ohne Argumente und Rückgabewerte; sie werden gezielt vom Anwender<br />
bzw. e<strong>in</strong>em Anwendungsprogramm aufgerufen.<br />
E<strong>in</strong>zelheiten dazu werden unter Prozeduren 2 behandelt.<br />
Trigger:<br />
E<strong>in</strong> Trigger ist e<strong>in</strong> Arbeitsablauf, der automatisch beim Speichern <strong>in</strong> e<strong>in</strong>er<br />
Tabelle ausgeführt wird. Es gibt weder Argumente noch Rückgabewerte<br />
und ke<strong>in</strong>erlei direkte Zusammenarbeit zwischen der Datenbank und dem<br />
Anwender bzw. e<strong>in</strong>em Anwendungsprogramm.<br />
E<strong>in</strong>zelheiten dazu werden unter Trigger 3 behandelt.<br />
30.1. Rout<strong>in</strong>en ohne feste Speicherung<br />
Das, was als Prozedur gespeichert werden kann, kann <strong>in</strong> e<strong>in</strong>em DBMS <strong>in</strong> der Regel<br />
auch direkt ausgeführt werden (ohne Speicherung <strong>in</strong> der Datenbank). Dazu<br />
werden die Def<strong>in</strong>ition von Parametern und Variablen sowie die Anweisungen mit<br />
e<strong>in</strong>er EXECUTE-Anweisung aufgerufen.<br />
Im Kapitel zu Prozeduren gibt es e<strong>in</strong> Beispiel „Testdaten <strong>in</strong> e<strong>in</strong>er Tabelle erzeugen“,<br />
das auch so verwirklicht werden kann:<br />
Firebird-Quelltext<br />
EXECUTE BLOCK ( Anzahl INT = ?anzahl )<br />
RETURNS ( Maxid INT )<br />
AS<br />
DECLARE VARIABLE Temp INT = 0; /* usw. identisch wie bei der Prozedur */<br />
BEGIN<br />
Maxid = 0;<br />
WHILE (Temp < Anzahl) DO<br />
BEGIN /* identischer Arbeitsablauf wie bei der Prozedur */<br />
Temp = Temp + 1;<br />
END<br />
SELECT MAX(ID) FROM Fahrzeug INTO :Maxid;<br />
SUSPEND;<br />
END<br />
Der Aufbau entspricht dem e<strong>in</strong>er Prozedur (siehe unten). Der Unterschied besteht<br />
<strong>in</strong> der direkten Ausführung durch EXECUTE BLOCK.<br />
2 Kapitel 32 auf Seite 357<br />
3 Kapitel 33 auf Seite 377<br />
324
Programmieren <strong>in</strong>nerhalb von Rout<strong>in</strong>en<br />
30.2. Programmieren <strong>in</strong>nerhalb von Rout<strong>in</strong>en<br />
Dieser Abschnitt beschränkt sich auf die wichtigsten Erläuterungen. Die konkreten<br />
<strong>SQL</strong>-Anweisungen s<strong>in</strong>d <strong>in</strong> den folgenden Kapiteln zu f<strong>in</strong>den. Außerdem gibt<br />
es zu fast allen genannten Themen weitere Möglichkeiten.<br />
Bitte haben Sie Nachsicht: Wegen der vielen Varianten bei den DBMS wurde e<strong>in</strong><br />
Teil der folgenden H<strong>in</strong>weise und der Beispiele <strong>in</strong> den nächsten Kapiteln nur nach<br />
der Dokumentation verfasst und nicht <strong>in</strong> der Praxis umgesetzt.<br />
30.2.1. Allgeme<strong>in</strong>es<br />
Rout<strong>in</strong>en − also Funktionen und Prozeduren − werden grundsätzlich mit e<strong>in</strong>er<br />
Syntax ähnlich der folgenden def<strong>in</strong>iert:<br />
CREATE OR ALTER { FUNCTION | PROCEDURE } <br />
( [ ] )<br />
RETURNS <br />
AS<br />
BEGIN<br />
<br />
<br />
END<br />
Die Def<strong>in</strong>ition von Triggern verläuft so ähnlich: Parameter entfallen, aber die Art<br />
der Ausführung kommt h<strong>in</strong>zu. Die H<strong>in</strong>weise zu Variablen und Anweisungen <strong>in</strong><br />
den folgenden Abschnitten gelten für Trigger <strong>in</strong> gleicher Weise wie für Rout<strong>in</strong>en.<br />
Der Teil zwischen BEGIN und END (jeweils e<strong>in</strong>schließlich) wird als Rumpf − engl.<br />
body − bezeichnet, alles davor heißt Kopf − engl. header − der Rout<strong>in</strong>e.<br />
Bitte beachten Sie, dass jedes DBMS se<strong>in</strong>e eigenen Besonderheiten hat. Die<br />
wichtigsten Unterschiede s<strong>in</strong>d:<br />
• Bei My<strong>SQL</strong> müssen CREATE und ALTER getrennt werden, bei Oracle heißt es<br />
CREATE OR REPLACE.<br />
• RETURNS gehört zu Funktionen (bei Oracle: RETURN). Nur Firebird benutzt<br />
es auch bei Prozeduren zur Trennung der Ausgabe-Parameter.<br />
• Ob die Parameter <strong>in</strong> Klammern stehen müssen oder nicht, ist unterschiedlich<br />
geregelt.<br />
• AS kann teilweise auch entfallen, bei Oracle wird auch IS verwendet.<br />
• Die ist Bestandteil der ; bei Firebird und<br />
Oracle steht sie zwischen AS und BEGIN.<br />
Wenn es <strong>in</strong>sgesamt (e<strong>in</strong>schließlich Variablen) nur e<strong>in</strong>e e<strong>in</strong>zige Anweisung gibt,<br />
kann auf BEGIN und END verzichtet werden; der Übersichtlichkeit halber ist ihre<br />
Verwendung aber fast immer zu empfehlen.<br />
325
<strong>SQL</strong>-Programmierung<br />
Gleiches gilt <strong>in</strong>nerhalb e<strong>in</strong>zelner Abschnitte (wie Verzweigungen oder Schleifen):<br />
E<strong>in</strong>e e<strong>in</strong>zelne Anweisung kann ohne BEGIN. . . END angegeben werden; wenn es<br />
die Übersichtlichkeit oder Verschachtelung erfordern, ist die Verwendung dieser<br />
Schlüsselwörter vorzuziehen.<br />
E<strong>in</strong>e Funktion benötigt als (letzte) Anweisung RETURN, mit der e<strong>in</strong> bestimmter<br />
Wert zurückgegeben wird.<br />
Bei Verzweigungen und Schleifen kann durch LABELs der Zusammenhang deutlich<br />
gemacht werden. Meistens gibt es ke<strong>in</strong>e Notwendigkeit dazu, sodass wir <strong>in</strong><br />
der Regel darauf verzichten. Aber BEGIN. . . END sollten vor allem bei verschachtelten<br />
Prüfungen immer benutzt werden; durch E<strong>in</strong>rückungen sollte der Zusammenhang<br />
herv<strong>org</strong>ehoben werden.<br />
30.2.2. Spalten, Variable und Parameter<br />
In diesem Buch werden nur e<strong>in</strong>fache lokale Variable benutzt; deren Gültigkeitsbereich<br />
beschränkt sich auf die aktuelle Rout<strong>in</strong>e. Je nach DBMS stehen auch<br />
globale Variable zur Verfügung. Außerdem kann man über das Schlüsselwort<br />
CURSOR e<strong>in</strong>e ganze Zeile von Tabellen oder Ergebnismengen mit e<strong>in</strong>er Variablen<br />
benutzen.<br />
In allen Fällen, <strong>in</strong> denen die Namen von Variablen oder Parametern auf die Namen<br />
von Tabellenspalten treffen, muss dem DBMS klar se<strong>in</strong>, um welche Art von<br />
Namen es sich handelt:<br />
• MS-<strong>SQL</strong> regelt das mit '@' am Anfang des Namens von Variablen oder Parametern.<br />
• My<strong>SQL</strong> und Oracle unterscheiden nicht. Sie müssen selbst für unterschiedliche<br />
Bezeichner s<strong>org</strong>en.<br />
• Firebird verlangt <strong>in</strong> diesen Situationen e<strong>in</strong>en Doppelpunkt vor dem Namen<br />
von Variablen und Parametern.<br />
Wegen der vielfältigen Unterschiede werden die wichtigsten Möglichkeiten getrennt<br />
behandelt.<br />
30.3. <strong>SQL</strong>-Programmierung mit Firebird<br />
30.3.1. Parameter deklarieren<br />
Jeder Parameter, der <strong>in</strong>nerhalb der Anweisungen benutzt wird und dessen Wert<br />
an die Rout<strong>in</strong>e übergeben oder durch die Bearbeitung zurückgegeben wird,<br />
326
<strong>SQL</strong>-Programmierung mit Firebird<br />
muss im Kopf der Rout<strong>in</strong>e festgelegt werden: Name, Datentyp, V<strong>org</strong>abe- oder Anfangswert.<br />
Mehrere Parameter werden mit Komma verbunden; nach dem letzten<br />
Parameter fehlt es.<br />
Bei Funktionen kann es nur E<strong>in</strong>gabe-Parameter geben; der Ausgabe-Parameter<br />
wird durch RETURNS immer getrennt angegeben.<br />
E<strong>in</strong>gabe-Parameter stehen nach dem Namen der Rout<strong>in</strong>e vor der RETURNS-<br />
Klausel, Ausgabe-Parameter s<strong>in</strong>d Teil der RETURNS-Klausel. Als Datentypen<br />
s<strong>in</strong>d ab Version 2.1 auch DOMAINs zulässig. E<strong>in</strong> e<strong>in</strong>zelner Parameter wird so deklariert:<br />
[ = | DEFAULT ]<br />
Bei E<strong>in</strong>gabe-Parametern s<strong>in</strong>d auch V<strong>org</strong>abewerte möglich, die durch '=' oder<br />
DEFAULT gekennzeichnet werden. Wichtig ist: Wenn e<strong>in</strong> Parameter e<strong>in</strong>en V<strong>org</strong>abewert<br />
erhält und deshalb beim Aufruf <strong>in</strong> der Liste nicht benutzt wird, müssen<br />
alle nachfolgenden Parameter ebenfalls mit V<strong>org</strong>abewert arbeiten.<br />
30.3.2. Variable deklarieren<br />
Jede Variable, die <strong>in</strong>nerhalb der Anweisungen benutzt wird, muss im Kopf der<br />
Rout<strong>in</strong>e festgelegt werden, nämlich zwischen AS und BEGIN: Name, Datentyp,<br />
V<strong>org</strong>abe- oder Anfangswert. Jede Deklaration gilt als e<strong>in</strong>e e<strong>in</strong>zelne Anweisung<br />
und ist mit Semikolon abzuschließen.<br />
DECLARE [VARIABLE] [ =|DEFAULT ];<br />
Als V<strong>org</strong>abewert ist auch e<strong>in</strong> <strong>SQL</strong>-Ausdruck möglich.<br />
30.3.3. Zuweisungen von Werten zu Variablen und Parametern<br />
Der e<strong>in</strong>fachste Weg ist die direkte Zuweisung e<strong>in</strong>es Wertes oder e<strong>in</strong>es Ausdrucks<br />
(e<strong>in</strong>er <strong>in</strong>ternen oder e<strong>in</strong>er eigenen Funktion) zu e<strong>in</strong>er Variablen oder e<strong>in</strong>em Parameter:<br />
= ; /* Standard: nur das Gleichheitszeichen */<br />
Sehr oft werden die Werte aus e<strong>in</strong>em SELECT-Befehl mit Variablen weiterverarbeitet.<br />
Dazu gibt es die INTO-Klausel:<br />
327
<strong>SQL</strong>-Programmierung<br />
SELECT <br />
FROM <br />
INTO ;<br />
Die Liste der Variablen muss von Anzahl und Typ her der Liste der Spalten entsprechen.<br />
Bitte beachten Sie, dass bei Firebird die INTO-Klausel erst am Ende<br />
des Befehls stehen darf.<br />
In ähnlicher Weise kann auch das Ergebnis e<strong>in</strong>er Prozedur übernommen und<br />
<strong>in</strong> der aktuellen Rout<strong>in</strong>e verarbeitet werden:<br />
EXECUTE PROCEDURE [ ]<br />
RETURNING_VALUES ; /* Variablen mit Doppelpunkt */<br />
Jede hier genannte Variable muss (<strong>in</strong> Reihenfolge und Typ) e<strong>in</strong>em der <br />
der Prozedur entsprechen.<br />
Achtung: Um zwischen den Namen von Variablen oder Parametern und den<br />
Namen von Tabellenspalten zu unterscheiden, verlangt Firebird e<strong>in</strong>en Doppelpunkt<br />
vor dem Namen von Variablen und Parametern.<br />
Erst die SUSPEND-Anweisung s<strong>org</strong>t dafür, dass e<strong>in</strong> Ausgabe-Parameter vom „rufenden“<br />
Programm entgegengenommen werden kann. Bei e<strong>in</strong>er Rückgabe e<strong>in</strong>facher<br />
Werte steht diese Anweisung am Ende e<strong>in</strong>er Prozedur; bei e<strong>in</strong>er Funktion<br />
übernimmt RETURN diese Aufgabe. Wenn aber (wie durch e<strong>in</strong>en SELECT-<br />
Befehl) mehrere Zeilen zu übergeben s<strong>in</strong>d, muss SUSPEND bei jeder dieser Zeilen<br />
stehen. E<strong>in</strong> Beispiel steht im Kapitel zu Prozeduren unter Ersatz für e<strong>in</strong>e View<br />
mit Parametern.<br />
30.3.4. Verzweigungen<br />
Die IF-Abfrage steuert den Ablauf nach Bed<strong>in</strong>gungen:<br />
IF ( ) THEN<br />
BEGIN<br />
<br />
END<br />
[ ELSE<br />
BEGIN<br />
<br />
END<br />
]<br />
Diese Abfrage sieht so aus:<br />
328
<strong>SQL</strong>-Programmierung mit Firebird<br />
• Die muss <strong>in</strong> Klammern stehen; sie kann auch mit AND und OR<br />
sowie weiteren Klammern verschachtelt werden.<br />
• Der ELSE-Zweig ist optional; die IF-Abfrage kann auch auf den IF-THEN-<br />
Abschnitt beschränkt werden.<br />
• Der ELSE-Zweig kann durch weitere IF-Abfragen verschachtelt werden.<br />
30.3.5. Schleifen<br />
Es gibt zwei Arten von Schleifen: e<strong>in</strong>e Schleife mit e<strong>in</strong>er Bed<strong>in</strong>gung und e<strong>in</strong>e<br />
Schleife mit e<strong>in</strong>er Ergebnismenge für e<strong>in</strong>e SELECT-Abfrage.<br />
Die WHILE-Schleife prüft e<strong>in</strong>e Bed<strong>in</strong>gung und wird so lange durchlaufen, wie<br />
diese Bed<strong>in</strong>gung wahr ist:<br />
WHILE ( ) DO<br />
BEGIN<br />
<br />
END<br />
Diese Schleife sieht so aus:<br />
• Die muss <strong>in</strong> Klammern stehen; sie kann auch mit AND und OR<br />
sowie weiteren Klammern verschachtelt werden.<br />
• Die wird jeweils am Anfang e<strong>in</strong>es Durchgangs geprüft. Wenn ihr<br />
Wert von Anfang an FALSE ist, wird die Schleife überhaupt nicht durchlaufen.<br />
Die FOR SELECT-Schleife erstellt durch e<strong>in</strong>en SELECT-Befehl e<strong>in</strong>e Ergebnismenge<br />
und führt für jede Zeile des Ergebnisses etwas aus:<br />
FOR SELECT <br />
INTO <br />
DO BEGIN<br />
<br />
END<br />
Diese Schleife sieht so aus:<br />
• Beim SELECT-Befehl handelt es sich um e<strong>in</strong>e beliebige Abfrage.<br />
• Für jede Zeile des Ergebnisses wird der DO-Block e<strong>in</strong>mal durchlaufen.<br />
• Die Ergebnisspalten und ihre Werte s<strong>in</strong>d nur <strong>in</strong>nerhalb des SELECT-Befehls<br />
bekannt, nicht <strong>in</strong>nerhalb der DO-Anweisungen. Die Werte müssen deshalb zunächst<br />
an Variablen übergeben (für jede Spalte e<strong>in</strong>e Variable oder e<strong>in</strong> Ausgabe-<br />
Parameter, mit Doppelpunkt gekennzeichnet), bevor sie <strong>in</strong> den <br />
benutzt werden können.<br />
329
<strong>SQL</strong>-Programmierung<br />
• Wenn diese Werte für jede Zeile e<strong>in</strong>zeln zurückgegeben werden sollen, wird<br />
SUSPEND als e<strong>in</strong>e der Anweisungen benötigt.<br />
• Wenn SUSPEND die e<strong>in</strong>zige Anweisung ist und ke<strong>in</strong> E<strong>in</strong>zelwert außerhalb der<br />
Schleife benötigt wird, kann auf die INTO-Klausel verzichtet werden.<br />
30.3.6. Zulässige Befehle<br />
Innerhalb von Rout<strong>in</strong>en s<strong>in</strong>d ausschließlich DML-Befehle zulässig. Ke<strong>in</strong>erlei anderer<br />
Befehl wird akzeptiert, also vor allem ke<strong>in</strong> DDL-Befehl, aber auch nicht<br />
GRANT/REVOKE (DCL) oder COMMIT/ROLLBACK (TCL).<br />
Sämtliche DML-Befehle, die <strong>in</strong>nerhalb e<strong>in</strong>er Rout<strong>in</strong>e ausgeführt werden, gehören<br />
zur selben Transaktion wie die Befehle, durch die sie aufgerufen bzw. ausgelöst<br />
werden.<br />
30.3.7. Anweisungen begrenzen<br />
Jede e<strong>in</strong>zelne Anweisung <strong>in</strong>nerhalb e<strong>in</strong>er Rout<strong>in</strong>e und jeder <strong>SQL</strong>-Befehl müssen<br />
mit e<strong>in</strong>em Semikolon abgeschlossen werden. Das ist ke<strong>in</strong> Problem, wenn nur e<strong>in</strong><br />
e<strong>in</strong>zelnes CREATE PROCEDURE o. ä. ausgeführt werden soll; dann wird das abschließende<br />
Semikolon weggelassen, und es gibt ke<strong>in</strong>e Unklarheiten (siehe e<strong>in</strong><br />
Firebird-Beispiel im Trigger-Kapitel).<br />
Bei mehreren Rout<strong>in</strong>en nache<strong>in</strong>ander − wie im Skript zur Beispieldatenbank −<br />
muss das DBMS zwischen den verschiedenen Abschlusszeichen unterscheiden.<br />
Dazu dient SET TERM (TERM steht für Term<strong>in</strong>ator, also Begrenzer):<br />
Firebird-Quelltext<br />
SET TERM ˆ ;<br />
CREATE OR ALTER TRIGGER Abteilung_BI0 FOR Abteilung<br />
ACTIVE BEFORE INSERT POSITION 0<br />
AS<br />
BEGIN<br />
IF ((new.ID IS NULL) OR (new.ID = 0))<br />
THEN new.ID = NEXT VALUE FOR Abteilung_ID;<br />
END<br />
ˆ<br />
SET TERM ; ˆ<br />
Zuerst wird der Begrenzer für <strong>SQL</strong>-Befehle auf 'ˆ' geändert; das Abschlusszeichen<br />
für e<strong>in</strong>e e<strong>in</strong>zelne Anweisung bleibt das Semikolon. Dann folgen alle Befehle zur<br />
Trigger-Def<strong>in</strong>ition; jeder e<strong>in</strong>zelne Trigger wird mit dem neuen Begrenzer beendet.<br />
Abschließend wird der Begrenzer wieder auf das Semikolon zurückgesetzt.<br />
330
30.4. <strong>SQL</strong>-Programmierung mit MS-<strong>SQL</strong><br />
<strong>SQL</strong>-Programmierung mit MS-<strong>SQL</strong><br />
Als erste Anweisung e<strong>in</strong>er Rout<strong>in</strong>e sollte immer SET NOCOUNT ON; verwendet werden;<br />
dies beschleunigt die Ausführung.<br />
30.4.1. Parameter deklarieren<br />
Jeder Parameter, dessen Wert der Rout<strong>in</strong>e übergeben oder durch die Bearbeitung<br />
zurückgegeben wird, muss im Kopf festgelegt werden: Name, Datentyp, Verwendung<br />
für E<strong>in</strong>gabe und/oder Ausgabe, V<strong>org</strong>abe- oder Anfangswert. Mehrere Parameter<br />
werden mit Komma verbunden; nach dem letzten Parameter fehlt es.<br />
Bei Funktionen kann es nur E<strong>in</strong>gabe-Parameter geben; der Ausgabe-Parameter<br />
wird durch RETURNS immer getrennt angeben.<br />
E<strong>in</strong> e<strong>in</strong>zelner Parameter wird so deklariert:<br />
[ = ] [ OUT | OUTPUT ]<br />
Parameternamen müssen immer mit '@' beg<strong>in</strong>nen. Ausgabe-Parameter werden<br />
mit OUT bzw. OUTPUT markiert; alle anderen s<strong>in</strong>d E<strong>in</strong>gabe-Parameter. Durch<br />
das Gleichheitszeichen kann e<strong>in</strong> V<strong>org</strong>abewert zugewiesen werden.<br />
30.4.2. Variable deklarieren<br />
Jede Variable, die <strong>in</strong>nerhalb der Anweisungen benutzt wird, ist im Rumpf der<br />
Rout<strong>in</strong>e festzulegen: Name, Datentyp, V<strong>org</strong>abe- oder Anfangswert. Jede Deklaration<br />
gilt als e<strong>in</strong>e e<strong>in</strong>zelne Anweisung und ist mit Semikolon abzuschließen.<br />
DECLARE [AS] [ = ];<br />
Bei MS-<strong>SQL</strong> muss der Name immer mit '@' beg<strong>in</strong>nen. Als V<strong>org</strong>abewert ist auch<br />
e<strong>in</strong> <strong>SQL</strong>-Ausdruck möglich.<br />
30.4.3. Zuweisungen von Werten zu Variablen und Parametern<br />
Der e<strong>in</strong>fachste Weg ist die direkte Zuweisung e<strong>in</strong>es Wertes oder e<strong>in</strong>es Ausdrucks<br />
(e<strong>in</strong>er <strong>in</strong>ternen oder e<strong>in</strong>er eigenen Funktion) zu e<strong>in</strong>er Variablen oder e<strong>in</strong>em Parameter<br />
mit dem SET-Befehl:<br />
331
<strong>SQL</strong>-Programmierung<br />
SET = ;<br />
Sehr oft werden die Werte aus e<strong>in</strong>em SELECT-Befehl mit Variablen weiterverarbeitet.<br />
E<strong>in</strong> e<strong>in</strong>zelner Wert wie das Ergebnis von SELECT COUNT(*) wird ebenfalls<br />
durch SET der Variablen zugewiesen. Mehrere Werte können <strong>in</strong>nerhalb des<br />
SELECT direkt zugewiesen werden:<br />
SELECT @Variable1 = , @Variable2 = <br />
FROM /* usw. weitere Bed<strong>in</strong>gungen */<br />
Für e<strong>in</strong>e Ergebnismenge wird e<strong>in</strong> CURSOR benötigt, der mit FETCH e<strong>in</strong>en Datensatz<br />
holt und den Wert e<strong>in</strong>er Spalte mit INTO an e<strong>in</strong>e Variable übergibt.<br />
E<strong>in</strong>e Rout<strong>in</strong>e wird meistens mit EXECUTE ausgeführt. Rückgabewerte, die über<br />
OUTPUT-Parameter bestimmt werden, werden vorher deklariert und mit dem<br />
der Prozedur verbunden.<br />
DECLARE @<strong>in</strong>putvariable VARCHAR(25);<br />
SET @<strong>in</strong>putvariable = ’Meier’;<br />
DECLARE @outputvariable MONEY;<br />
EXECUTE myprocedure<br />
@<strong>in</strong>putparameter = @<strong>in</strong>putvariable,<br />
@outputparameter = @outputvariable OUTPUT;<br />
SELECT @outputvariable;<br />
Jede hier genannte Variable muss (<strong>in</strong> Reihenfolge und Typ) allen Parametern der<br />
Prozedur entsprechen.<br />
Für EXECUTE (auch die Abkürzung EXEC ist möglich) gibt es viele Varianten für<br />
Aufruf und Zuweisung der Parameter.<br />
30.4.4. Verzweigungen<br />
Die IF-Abfrage steuert den Ablauf nach Bed<strong>in</strong>gungen:<br />
IF <br />
BEGIN<br />
<br />
END<br />
[ ELSE IF <br />
BEGIN<br />
<br />
END ]<br />
[ ELSE<br />
BEGIN<br />
332
END ]<br />
END<br />
Diese Abfrage sieht so aus:<br />
<strong>SQL</strong>-Programmierung mit MS-<strong>SQL</strong><br />
• Bei handelt es sich um e<strong>in</strong>e e<strong>in</strong>fache Prüfung, die nicht mit AND<br />
oder OR erweitert werden kann.<br />
• Wenn e<strong>in</strong> SELECT Teil der ist, muss es <strong>in</strong> Klammern stehen.<br />
• Der ELSE-Zweig ist optional. Es ist auch möglich, dass nur der IF-THEN-<br />
Abschnitt ausgeführt werden muss.<br />
• Durch ELSE IF-Zweige s<strong>in</strong>d Verschachtelungen, also auch weitere Prüfungen<br />
möglich.<br />
30.4.5. Schleifen<br />
Die WHILE-Schleife prüft e<strong>in</strong>e Bed<strong>in</strong>gung und wird so lange durchlaufen, wie<br />
diese Bed<strong>in</strong>gung wahr ist:<br />
WHILE <br />
BEGIN END<br />
Dabei s<strong>in</strong>d folgende Punkte wichtig:<br />
• Bei handelt es sich um e<strong>in</strong>e e<strong>in</strong>fache Prüfung, die nicht mit AND<br />
oder OR erweitert werden kann.<br />
• Wenn e<strong>in</strong> SELECT Teil der ist, muss es <strong>in</strong> Klammern stehen.<br />
• Die wird jeweils am Anfang e<strong>in</strong>es Durchgangs geprüft. Wenn ihr<br />
Wert von Anfang an FALSE ist, wird die Schleife überhaupt nicht durchlaufen.<br />
• Schleifen können verschachtelt werden. Vor allem dann sollten BEGIN. . . END<br />
immer benutzt und durch E<strong>in</strong>rückung den Zusammenhang deutlich machen.<br />
E<strong>in</strong>e Schleife oder e<strong>in</strong> IF-Zweig kann mit BREAK vorzeitig abgebrochen; mit<br />
CONTINUE kann direkt der nächste Durchlauf begonnen werden. Bei Verschachtelung<br />
wird mit BREAK zur nächsthöheren Ebene gesprungen.<br />
Um alle Datensätze e<strong>in</strong>er Ergebnismenge, also e<strong>in</strong>es SELECT-Befehls zu durchlaufen,<br />
wird e<strong>in</strong> CURSOR benötigt. Dies wird <strong>in</strong> diesem Buch nicht behandelt.<br />
333
<strong>SQL</strong>-Programmierung<br />
30.4.6. Zulässige Befehle<br />
E<strong>in</strong>ige Anweisungen können nicht <strong>in</strong> e<strong>in</strong>er Rout<strong>in</strong>e verwendet werden. Dazu<br />
gehören vor allem der Wechsel der aktuellen Datenbank sowie CREATE/ALTER<br />
für Views, Rout<strong>in</strong>en und Trigger. Im Gegensatz zu anderen DBMS ist aber z. B.<br />
CREATE TABLE und unter Umständen auch COMMIT bzw. ROLLBACK möglich.<br />
30.4.7. Anweisungen begrenzen<br />
Bei MS-<strong>SQL</strong> gibt es ke<strong>in</strong>e Notwendigkeit, zwischen Anweisungen <strong>in</strong>nerhalb e<strong>in</strong>er<br />
Rout<strong>in</strong>e und getrennten <strong>SQL</strong>-Befehlen zu unterscheiden: Jede Anweisung<br />
wird mit Semikolon abgeschlossen; e<strong>in</strong> „selbständiger“ <strong>SQL</strong>-Befehl wird durch<br />
GO abgeschlossen und ausgeführt.<br />
30.5. <strong>SQL</strong>-Programmierung mit My<strong>SQL</strong><br />
My<strong>SQL</strong> hat gespeicherte Prozeduren und Funktionen erst mit Version 5 e<strong>in</strong>geführt,<br />
sodass noch nicht alle Wünsche erfüllt werden. Aber das wird natürlich<br />
von Version zu Version besser.<br />
Bitte achten Sie unbed<strong>in</strong>gt darauf, dass sich die Namen von Parametern und Variablen<br />
von Spaltennamen unterscheiden, die <strong>in</strong> derselben Anweisung vorkommen.<br />
Andernfalls gibt es unvorhersehbare Ergebnisse, weil die Namen der Variablen<br />
Vorrang haben gegenüber gleichnamigen Spalten. E<strong>in</strong> gängiges Verfahren<br />
ist es (wie bei MS-<strong>SQL</strong>), dass diese Namen mit '@' beg<strong>in</strong>nen.<br />
30.5.1. Parameter deklarieren<br />
Jeder Parameter, der <strong>in</strong>nerhalb der Anweisungen benutzt wird und dessen Wert<br />
an die Rout<strong>in</strong>e übergeben oder durch die Bearbeitung zurückgegeben wird, ist<br />
im Kopf der Rout<strong>in</strong>e festzulegen: Name, Datentyp, Verwendung für E<strong>in</strong>gabe und/oder<br />
Ausgabe, V<strong>org</strong>abe- oder Anfangswert. Mehrere Parameter werden mit<br />
Komma verbunden; nach dem letzten Parameter fehlt das Komma.<br />
Bei Funktionen kann es nur E<strong>in</strong>gabe-Parameter geben; der Ausgabe-Parameter<br />
wird durch RETURNS immer getrennt angeben.<br />
E<strong>in</strong> e<strong>in</strong>zelner Parameter wird so deklariert:<br />
[ IN | OUT | INOUT ] <br />
Die Festlegung als E<strong>in</strong>gabe-Parameter kann entfallen; IN ist der Standardwert.<br />
334
30.5.2. Variable deklarieren<br />
<strong>SQL</strong>-Programmierung mit My<strong>SQL</strong><br />
Jede Variable, die <strong>in</strong>nerhalb der Anweisungen benutzt wird, ist im Rumpf der<br />
Rout<strong>in</strong>e festzulegen: Name, Datentyp, V<strong>org</strong>abe- oder Anfangswert. Jede Deklaration<br />
gilt als e<strong>in</strong>e e<strong>in</strong>zelne Anweisung und ist mit Semikolon abzuschließen.<br />
DECLARE [ DEFAULT ];<br />
Als V<strong>org</strong>abewert ist auch e<strong>in</strong> <strong>SQL</strong>-Ausdruck möglich.<br />
30.5.3. Zuweisungen von Werten zu Variablen und Parameter<br />
Der e<strong>in</strong>fachste Weg ist die direkte Zuweisung e<strong>in</strong>es Wertes oder e<strong>in</strong>es Ausdrucks<br />
(e<strong>in</strong>er <strong>in</strong>ternen oder e<strong>in</strong>er eigenen Funktion) zu e<strong>in</strong>er Variablen oder e<strong>in</strong>em Parameter<br />
mit dem SET-Befehl:<br />
SET = ;<br />
Sehr oft werden die Werte aus e<strong>in</strong>em SELECT mit Variablen weiterverarbeitet.<br />
Dazu gibt es die INTO-Klausel direkt nach der . Die Liste der Variablen<br />
muss von Anzahl und Typ her der Liste der Spalten entsprechen.<br />
SELECT INTO <br />
FROM ;<br />
E<strong>in</strong>e Prozedur wird mit CALL ausgeführt. Rückgabewerte, die über OUT-<br />
Parameter bestimmt werden, werden vorher deklariert.<br />
DECLARE @<strong>in</strong>putvariable VARCHAR(25);<br />
SET @<strong>in</strong>putvariable = ’Meier’;<br />
DECLARE @outputvariable MONEY = 0;<br />
CALL myprocedure (@<strong>in</strong>putvariable, @outputvariable);<br />
SELECT @outputvariable;<br />
Jede hier genannte Variable muss (<strong>in</strong> Reihenfolge und Typ) e<strong>in</strong>em der <br />
der Prozedur entsprechen.<br />
30.5.4. Verzweigungen<br />
My<strong>SQL</strong> kennt zwei Arten, um auf unterschiedliche Situationen zu reagieren:<br />
Die IF-Abfrage steuert den Ablauf nach Bed<strong>in</strong>gungen:<br />
335
<strong>SQL</strong>-Programmierung<br />
IF THEN<br />
BEGIN<br />
<br />
END<br />
[ ELSEIF THEN<br />
BEGIN<br />
<br />
END ]<br />
[ ELSE<br />
BEGIN<br />
<br />
END ]<br />
END IF<br />
Diese Ablaufprüfung sieht so aus:<br />
• Bei handelt es sich um e<strong>in</strong>e e<strong>in</strong>fache Prüfung, die nicht mit AND<br />
oder OR erweitert werden kann.<br />
• Der ELSE-Zweig ist optional. Es ist auch möglich, dass nur der IF-THEN-<br />
Abschnitt ausgeführt werden muss.<br />
• Durch ELSEIF-Zweige s<strong>in</strong>d Verschachtelungen, also auch weitere Prüfungen<br />
möglich.<br />
• Die Verzweigung wird mit END IF abgeschlossen.<br />
Die CASE-Prüfung entspricht der Fallunterscheidung aus dem Kapitel Nützliche<br />
Erweiterungen:<br />
-- Variante 1<br />
CASE <br />
WHEN THEN BEGIN END<br />
[ WHEN THEN BEGIN END ] /* usw. */<br />
[ ELSE BEGIN END ]<br />
END CASE<br />
-- Variante 2<br />
CASE<br />
WHEN THEN BEGIN END<br />
[ WHEN THEN BEGIN END ] /* usw. */<br />
[ ELSE BEGIN END ]<br />
END CASE<br />
Bei dieser Prüfung gelten die gleichen Prüfungen wie bei der Fallunterscheidung<br />
mit folgenden Abweichungen:<br />
• Im ELSE-Zweig darf es ke<strong>in</strong>e NULL-Anweisung geben.<br />
• Die Verzweigung muss mit END CASE abgeschlossen werden.<br />
336
30.5.5. Schleifen<br />
<strong>SQL</strong>-Programmierung mit My<strong>SQL</strong><br />
My<strong>SQL</strong> kennt mehrere Arten von Schleifen, aber ke<strong>in</strong>e FOR-Schleife. Wenn<br />
ITERATE oder LEAVE verwendet werden, müssen LABELs gesetzt werden.<br />
Die LOOP-Schleife wird – ohne Prüfung am Anfang oder Ende – solange durchlaufen,<br />
bis (vor allem nach e<strong>in</strong>er „<strong>in</strong>neren“ Bed<strong>in</strong>gung) die LEAVE-Anweisung<br />
getroffen wird. Mit ITERATE wird direkt der nächste Durchgang gestartet.<br />
LOOP<br />
BEGIN END<br />
END LOOP ;<br />
Die REPEAT-Schleife wird m<strong>in</strong>destens e<strong>in</strong>mal durchlaufen, und zwar solange,<br />
bis die Ende-Bed<strong>in</strong>gung FALSE ergibt oder bis − aufgrund e<strong>in</strong>er „<strong>in</strong>neren“ Bed<strong>in</strong>gung<br />
− die LEAVE-Anweisung getroffen wird.<br />
REPEAT<br />
BEGIN END<br />
UNTIL <br />
END REPEAT<br />
Die WHILE-Schleife prüft e<strong>in</strong>e Bed<strong>in</strong>gung und wird so lange durchlaufen, wie<br />
diese Bed<strong>in</strong>gung wahr ist:<br />
WHILE DO<br />
BEGIN END<br />
END WHILE<br />
Die wird jeweils am Anfang e<strong>in</strong>es Durchgangs geprüft. Wenn ihr<br />
Wert von Anfang an FALSE ist, wird diese Schleife überhaupt nicht durchlaufen.<br />
Um alle Datensätze e<strong>in</strong>er Ergebnismenge, also e<strong>in</strong>es SELECT-Befehls zu durchlaufen,<br />
wird e<strong>in</strong> CURSOR benötigt. Das wird <strong>in</strong> diesem Buch nicht behandelt.<br />
30.5.6. Zulässige Befehle<br />
Innerhalb von eigenen Funktionen, Prozeduren und Triggern s<strong>in</strong>d nicht alle<br />
<strong>SQL</strong>-Befehle zulässig. Bitte lesen Sie <strong>in</strong> Ihrer Dokumentation E<strong>in</strong>zelheiten nach.<br />
30.5.7. Anweisungen begrenzen<br />
Jede e<strong>in</strong>zelne Anweisung <strong>in</strong>nerhalb e<strong>in</strong>er Rout<strong>in</strong>e und jeder <strong>SQL</strong>-Befehl müssen<br />
mit e<strong>in</strong>em Semikolon abgeschlossen werden. Das ist ke<strong>in</strong> Problem, wenn nur e<strong>in</strong><br />
337
<strong>SQL</strong>-Programmierung<br />
e<strong>in</strong>zelner CREATE PROCEDURE o. ä. ausgeführt werden soll; dann wird das abschließende<br />
Semikolon weggelassen, und es gibt ke<strong>in</strong>e Unklarheiten (siehe e<strong>in</strong><br />
My<strong>SQL</strong>-Beispiel zu Trigger).<br />
Wenn mehrere Rout<strong>in</strong>en nache<strong>in</strong>ander erzeugt werden, muss das DBMS zwischen<br />
den verschiedenen Arten von Abschlusszeichen unterscheiden. Dazu<br />
dient der delimiter-Befehl:<br />
My<strong>SQL</strong>-Quelltext<br />
delimiter //<br />
CREATE OR ALTER TRIGGER Mitarbeiter_BD<br />
ACTIVE BEFORE DELETE ON Mitarbeiter<br />
FOR EACH ROW<br />
BEGIN<br />
UPDATE Dienstwagen<br />
SET Mitarbeiter_ID = NULL<br />
WHERE Mitarbeiter_ID = old.ID;<br />
END<br />
//<br />
delimiter ;<br />
Zuerst wird der Begrenzer für <strong>SQL</strong>-Befehle auf '//' geändert; das Abschlusszeichen<br />
für e<strong>in</strong>zelne Anweisungen bleibt das Semikolon. Dann folgt jede e<strong>in</strong>zelne<br />
Trigger-Def<strong>in</strong>ition mit allen ihren Befehlen und dem neuen Begrenzer als Ende<br />
der Def<strong>in</strong>ition. Abschließend wird der Begrenzer wieder auf das Semikolon zurückgesetzt.<br />
30.6. <strong>SQL</strong>-Programmierung mit Oracle<br />
Bei Oracle gilt das prozedurale <strong>SQL</strong> nicht als Teil des DBMS, sondern als spezielle<br />
Programmiersprache PL/<strong>SQL</strong>. In der Praxis wird beides gemischt verwendet.<br />
Bitte achten Sie unbed<strong>in</strong>gt darauf, dass sich die Namen von Parametern und Variablen<br />
von Spaltennamen unterscheiden, die <strong>in</strong> derselben Rout<strong>in</strong>e vorkommen.<br />
30.6.1. Parameter deklarieren<br />
Jeder Parameter, dessen Wert an die Rout<strong>in</strong>e übergeben oder durch die Bearbeitung<br />
zurückgegeben wird, muss im Kopf der Rout<strong>in</strong>e festgelegt werden: Name,<br />
Datentyp, Verwendung für E<strong>in</strong>gabe und/oder Ausgabe, V<strong>org</strong>abe- oder Anfangswert.<br />
Mehrere Parameter werden mit Komma verbunden; nach dem letzten Parameter<br />
fehlt dies.<br />
338
<strong>SQL</strong>-Programmierung mit Oracle<br />
Bei Funktionen kann es nur E<strong>in</strong>gabe-Parameter geben; der Ausgabe-Parameter<br />
wird durch RETURN (nicht wie sonst oft durch RETURNS) getrennt angegeben.<br />
E<strong>in</strong> e<strong>in</strong>zelner Parameter wird so deklariert:<br />
[ IN | OUT | IN OUT ] <br />
Die Festlegung als E<strong>in</strong>gabe-Parameter kann entfallen; IN ist der Standardwert.<br />
Beim Datentyp darf die Größe nicht angegeben werden: VARCHAR2 ist zulässig,<br />
aber VARCHAR2(20) nicht.<br />
30.6.2. Variable deklarieren<br />
Jede Variable, die <strong>in</strong>nerhalb der Rout<strong>in</strong>e benutzt wird, muss <strong>in</strong> ihrem Kopf festgelegt<br />
werden, nämlich zwischen AS/IS und BEGIN: Name, Datentyp und ggf.<br />
V<strong>org</strong>abewert; das Schlüsselwort DECLARE entfällt. Jede Deklaration gilt als e<strong>in</strong>e<br />
e<strong>in</strong>zelne Anweisung und ist mit Semikolon abzuschließen.<br />
[ := ] ;<br />
Als V<strong>org</strong>abewert ist auch e<strong>in</strong> <strong>SQL</strong>-Ausdruck möglich.<br />
30.6.3. Zuweisungen von Werten zu Variablen und Parameter<br />
Der e<strong>in</strong>fachste Weg ist die direkte Zuweisung e<strong>in</strong>es Wertes oder e<strong>in</strong>es Ausdrucks<br />
zu e<strong>in</strong>er Variablen oder e<strong>in</strong>em Parameter:<br />
:= ;<br />
Bitte beachten Sie, dass (wie bei Pascal) Doppelpunkt und Gleichheitszeichen zu<br />
verwenden s<strong>in</strong>d.<br />
Sehr oft werden die Werte aus e<strong>in</strong>em SELECT-Befehl mit Variablen weiterverarbeitet.<br />
Dazu gibt es die INTO-Klausel, die direkt nach der kommt:<br />
SELECT INTO <br />
FROM ;<br />
Die Liste der Variablen muss von Anzahl und Typ her zur Spaltenliste passen.<br />
In ähnlicher Weise kann auch das Ergebnis e<strong>in</strong>er Prozedur übernommen und<br />
<strong>in</strong> der aktuellen Rout<strong>in</strong>e verarbeitet werden:<br />
339
<strong>SQL</strong>-Programmierung<br />
1. Deklaration der Übergabevariablen:<br />
• direkt:<br />
;<br />
• oder unter Bezug auf den Typ e<strong>in</strong>er def<strong>in</strong>ierten Variablen:<br />
;<br />
2. Aufruf der Prozedur:<br />
( , );<br />
3. Weiterarbeiten mit den Ausgabeparametern aus der Prozedur<br />
Jede hier genannte Variable muss (<strong>in</strong> Reihenfolge und Typ) e<strong>in</strong>em der <br />
der Prozedur entsprechen.<br />
30.6.4. Verzweigungen<br />
Oracle kennt zwei Arten, um auf unterschiedliche Situationen zu reagieren:<br />
Die IF-Abfrage steuert den Ablauf nach Bed<strong>in</strong>gungen:Verzweigung<br />
IF THEN<br />
BEGIN END<br />
[ ELSE<br />
BEGIN END<br />
]<br />
END IF<br />
Diese Abfrage sieht so aus:<br />
• Bei handelt es sich um e<strong>in</strong>e e<strong>in</strong>fache Prüfung, die nicht mit AND<br />
oder OR erweitert werden kann.<br />
• Der ELSE-Zweig ist optional. Es ist auch möglich, dass nur der IF-THEN-<br />
Abschnitt ausgeführt werden muss.<br />
• Durch ELSIF-Zweige s<strong>in</strong>d auch Verschachtelungen möglich.<br />
• Die Verzweigung wird mit END IF abgeschlossen.<br />
Die CASE-Prüfung entspricht der Fallunterscheidung aus dem Kapitel Nützlichen<br />
Erweiterungen:<br />
-- Variante 1<br />
CASE <br />
WHEN THEN BEGIN END<br />
[ WHEN THEN BEGIN END ] /* usw. */<br />
[ ELSE BEGIN END ]<br />
END CASE<br />
340
<strong>SQL</strong>-Programmierung mit Oracle<br />
-- Variante 2<br />
CASE<br />
WHEN THEN BEGIN END<br />
[ WHEN THEN BEGIN END ] /* usw. */<br />
[ ELSE BEGIN END ]<br />
END CASE<br />
Bei dieser Prüfung gelten die gleichen Prüfungen wie bei der Fallunterscheidung<br />
mit folgenden Abweichungen:<br />
• Im ELSE-Zweig darf es ke<strong>in</strong>e NULL-Anweisung geben.<br />
• Die Verzweigung muss mit END CASE abgeschlossen werden.<br />
30.6.5. Schleifen<br />
Oracle kennt mehrere Arten von Schleifen.<br />
Die LOOP-Schleife arbeitet ohne Bed<strong>in</strong>gung am Anfang oder Ende und wird solange<br />
durchlaufen, bis − vor allem aufgrund e<strong>in</strong>er „<strong>in</strong>neren“ Bed<strong>in</strong>gung − die<br />
EXIT-Anweisung getroffen wird.<br />
LOOP<br />
BEGIN END<br />
END LOOP ;<br />
Oracle-Quelltext<br />
DECLARE x NUMBER := 1;<br />
BEGIN<br />
LOOP<br />
X := X + 1;<br />
EXIT WHEN x > 10;<br />
END LOOP;<br />
END;<br />
Die WHILE-Schleife prüft e<strong>in</strong>e Bed<strong>in</strong>gung und wird so lange durchlaufen, wie<br />
diese Bed<strong>in</strong>gung TRUE ist:<br />
WHILE LOOP<br />
BEGIN END<br />
END LOOP;<br />
Die wird jeweils am Anfang e<strong>in</strong>es Durchgangs geprüft. Wenn ihr<br />
Wert von Anfang an FALSE ist, wird die Schleife überhaupt nicht durchlaufen.<br />
Die FOR-Schleife durchläuft e<strong>in</strong>e Liste von Werten von Anfang bis Ende:<br />
341
<strong>SQL</strong>-Programmierung<br />
FOR IN LOOP<br />
BEGIN END<br />
END LOOP;<br />
Bei der FOR-Schleife wird der Wert der „Laufvariablen“ am Beg<strong>in</strong>n jedes weiteren<br />
Durchlaufs automatisch erhöht; bei LOOP und WHILE müssen Sie sich selbst<br />
um die Änderung e<strong>in</strong>er solchen Variablen kümmern.<br />
Um alle Datensätze e<strong>in</strong>er Ergebnismenge, also e<strong>in</strong>es SELECT-Befehls zu durchlaufen,<br />
wird e<strong>in</strong> CURSOR benötigt. Das wird <strong>in</strong> diesem Buch nicht behandelt.<br />
30.6.6. Zulässige Befehle<br />
Neben Anweisungen und den DML-Befehlen s<strong>in</strong>d auch TCL-Befehle (Steuerung<br />
von Transaktionen) sowie die Sperre von Tabellen durch LOCK TABLE möglich.<br />
30.6.7. Anweisungen begrenzen<br />
Jede e<strong>in</strong>zelne Anweisung <strong>in</strong>nerhalb e<strong>in</strong>er Rout<strong>in</strong>e und jeder <strong>SQL</strong>-Befehl müssen<br />
mit e<strong>in</strong>em Semikolon abgeschlossen werden, auch das abschließende END. Das<br />
ist ke<strong>in</strong> Problem, wenn nur e<strong>in</strong> e<strong>in</strong>zelner CREATE PROCEDURE o. ä. ausgeführt<br />
werden soll. Andernfalls folgt <strong>in</strong> e<strong>in</strong>er eigenen Zeile e<strong>in</strong> e<strong>in</strong>zelner Schrägstrich:<br />
BEGIN<br />
<br />
END;<br />
/<br />
30.7. Zusammenfassung<br />
In diesem Kapitel wurden die wichtigsten Bestandteile besprochen, mit denen<br />
<strong>SQL</strong>-Befehle <strong>in</strong> eigenen Funktionen, <strong>in</strong> gespeicherten Prozeduren oder <strong>in</strong> Triggern<br />
verarbeitet werden:<br />
• Deklaration von Parametern und Variablen<br />
• Verwendung von Parametern und Variablen<br />
• Verzweigungen mit IF u. a. sowie Schleifen<br />
• Zulässigkeit von DML- und DDL-Befehlen<br />
• Trennung zwischen e<strong>in</strong>er Rout<strong>in</strong>e <strong>in</strong>sgesamt und e<strong>in</strong>er e<strong>in</strong>zelnen Anweisung<br />
Bei allen E<strong>in</strong>zelheiten müssen die Besonderheiten des DBMS beachtet werden.<br />
342
30.8. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 344.<br />
Übung 1 – Erklärungen<br />
1. Erläutern Sie den Zweck e<strong>in</strong>er (benutzerdef<strong>in</strong>ierten) Funktion.<br />
2. Erläutern Sie den Zweck e<strong>in</strong>er (gespeicherten) Prozedur.<br />
3. Erläutern Sie den Zweck e<strong>in</strong>es Triggers.<br />
4. Wor<strong>in</strong> unterscheiden sich „Eigene Funktionen“ und Prozeduren?<br />
5. Wor<strong>in</strong> unterscheiden sich Prozeduren und Trigger?<br />
Übung 2 – Der Kopf e<strong>in</strong>er Rout<strong>in</strong>e<br />
Übungen<br />
Erläutern Sie die folgenden Bestandteile aus dem „Kopf“ (header) e<strong>in</strong>er Rout<strong>in</strong>e,<br />
d. h. e<strong>in</strong>er Funktion oder e<strong>in</strong>er Prozedur. Erwähnen Sie auch, was zu diesen<br />
Bestandteilen gehört.<br />
1. CREATE OR ALTER bzw. CREATE OR REPLACE<br />
2. <br />
3. E<strong>in</strong>gabe-Parameter<br />
4. Ausgabe-Parameter<br />
5. RETURNS bzw. RETURN<br />
Übung 3 – Der Rumpf e<strong>in</strong>er Rout<strong>in</strong>e<br />
Erläutern Sie die folgenden Bestandteile aus dem „Rumpf“ (body) e<strong>in</strong>er Rout<strong>in</strong>e,<br />
d. h. e<strong>in</strong>er Funktion oder e<strong>in</strong>er Prozedur. Erwähnen Sie auch, was zu diesen Bestandteilen<br />
gehört; die Antworten können sehr knapp ausfallen, weil sie sich <strong>in</strong><br />
Abhängigkeit vom DBMS sehr unterscheiden müssten.<br />
1. BEGIN und END am Anfang und Ende des Rumpfes<br />
2. BEGIN und END <strong>in</strong>nerhalb des Rumpfes<br />
3. Variablen<br />
4. Verzweigungen<br />
5. Schleifen<br />
6. Zuweisung von Werten<br />
343
<strong>SQL</strong>-Programmierung<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 343.<br />
Lösung zu Übung 1 – Erklärungen<br />
1. E<strong>in</strong>e benutzerdef<strong>in</strong>ierte Funktion ist e<strong>in</strong>e Ergänzung zu den <strong>in</strong>ternen<br />
Funktionen des DBMS und liefert immer e<strong>in</strong>en bestimmten Wert zurück.<br />
2. E<strong>in</strong>e gespeicherte Prozedur (StoredProcedure, SP) ist e<strong>in</strong> Arbeitsablauf, der<br />
fest <strong>in</strong>nerhalb der Datenbank gespeichert ist und Aufgaben ausführt, die<br />
<strong>in</strong>nerhalb der Datenbank erledigt werden sollen – im Wesentlichen ohne<br />
„Kommunikation“ mit dem Benutzer bzw. dem auslösenden Programm.<br />
3. E<strong>in</strong> Trigger ist e<strong>in</strong> Arbeitsablauf, der fest <strong>in</strong>nerhalb der Datenbank gespeichert<br />
ist und Aufgaben ausführt, die automatisch beim Speichern <strong>in</strong>nerhalb<br />
der Datenbank erledigt werden sollen.<br />
4. E<strong>in</strong>e Funktion liefert genau e<strong>in</strong>en Wert zurück, der durch RETURNS festgelegt<br />
wird. Prozeduren gibt es mit und ohne Rückgabewert.<br />
5. E<strong>in</strong> Trigger wird automatisch bei e<strong>in</strong>em Speichern-Befehl ausgeführt, und<br />
es gibt ke<strong>in</strong>en Rückgabewert. E<strong>in</strong>e Prozedur wird bewusst aufgerufen, und<br />
sie kann Rückgabewerte liefern.<br />
Lösung zu Übung 2 – Der Kopf e<strong>in</strong>er Rout<strong>in</strong>e<br />
344<br />
1. Dies def<strong>in</strong>iert e<strong>in</strong>e Rout<strong>in</strong>e. Dazu gehören e<strong>in</strong>er der Begriffe FUNCTION<br />
und PROCEDURE sowie der Name der Rout<strong>in</strong>e.<br />
2. Die Parameterliste enthält diejenigen Parameter, die beim Aufruf an<br />
die Rout<strong>in</strong>e übergeben werden (E<strong>in</strong>gabe-Parameter), sowie diejenigen,<br />
die nach Erledigung an den Aufrufer zurückgegeben werden (Ausgabe-<br />
Parameter). Wie E<strong>in</strong>gabe- und Ausgabe-Parameter gekennzeichnet bzw.<br />
unterschieden werden, hängt vom DBMS ab.<br />
3. Alle E<strong>in</strong>gabe-Parameter s<strong>in</strong>d (durch Komma getrennt) aufzuführen; die<br />
Reihenfolge muss beim Aufruf beachtet werden. Zu jedem Parameter gehören<br />
Name und Datentyp; unter Umständen ist e<strong>in</strong> Standardwert möglich.<br />
4. Alle Ausgabe-Parameter s<strong>in</strong>d (durch Komma getrennt) aufzuführen; die<br />
Reihenfolge muss bei der Auswertung im aufrufenden Programm o. ä. beachtet<br />
werden. Zu jedem Parameter gehören Name und Datentyp; unter<br />
Umständen ist e<strong>in</strong> Standardwert möglich.<br />
5. RETURNS bzw. RETURN gibt bei Funktionen den Ausgabe-Parameter, d. h.<br />
das Ergebnis der Funktion an. Nur Firebird benutzt dies, um auch die<br />
Ausgabe-Parameter e<strong>in</strong>er Prozedur anzugeben.
Lösung zu Übung 3 – Der Rumpf e<strong>in</strong>er Rout<strong>in</strong>e<br />
Siehe auch<br />
1. Dies beschreibt Anfang und Ende des gesamten Rumpfes. Wenn der Rumpf<br />
nur e<strong>in</strong>en e<strong>in</strong>zelnen Befehl enthält, kann es entfallen.<br />
2. Dies begrenzt e<strong>in</strong>zelne Abschnitte <strong>in</strong>nerhalb des Rumpfes, vor allem<br />
bei Schleifen und IF-Verzweigungen. Bei e<strong>in</strong>em e<strong>in</strong>zelnen Befehl kann<br />
BEGIN/END entfallen; die Verwendung wird aber stets empfohlen.<br />
3. Dies def<strong>in</strong>iert Werte, die (nur) <strong>in</strong>nerhalb der Rout<strong>in</strong>e benutzt werden. Sie<br />
müssen mit Namen und Datentyp ausdrücklich deklariert werden.<br />
4. Mit IF-ELSE o. ä. können – abhängig von Bed<strong>in</strong>gungen – unterschiedliche<br />
Befehle ausgeführt werden.<br />
5. Mit WHILE, FOR o. a. können e<strong>in</strong>zelne Befehle oder Abschnitte von Befehlen<br />
wiederholt ausgeführt werden. Wie oft bzw. wie lange die Schleife<br />
durchlaufen wird, hängt von der Art der Schleife (unterschiedlich nach<br />
DBMS) und Bed<strong>in</strong>gungen ab.<br />
6. Den Variablen und Parametern können Werte zugewiesen werden, und<br />
zwar durch das Gleichheitszeichen mit konkreten Angaben oder als Ergebnis<br />
von Funktionen, Prozeduren oder SELECT-Befehlen.<br />
30.9. Siehe auch<br />
Verschiedene E<strong>in</strong>zelheiten stehen <strong>in</strong> den folgenden Kapiteln:<br />
• <strong>SQL</strong>-Befehle 4 mit Erläuterung zu <strong>SQL</strong>-Ausdrücken<br />
• Funktionen 5 und Funktionen (2) 6<br />
• Nützliche Erweiterungen 7<br />
Zur <strong>SQL</strong>-Programmierung mit Oracle gibt es das Wikibook PL/<strong>SQL</strong> 8 , das noch<br />
unvollständig ist.<br />
4 Kapitel 7 auf Seite 47<br />
5 Kapitel 14 auf Seite 109<br />
6 Kapitel 16 auf Seite 141<br />
7 Kapitel 23 auf Seite 213<br />
8 http://de.wikibooks.<strong>org</strong>/wiki/PL/<strong>SQL</strong><br />
345
31. Eigene Funktionen<br />
31.1. Funktion def<strong>in</strong>ieren . . . . . . . . . . . . . . . . . . . . . . . . . . 347<br />
31.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349<br />
31.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 351<br />
31.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351<br />
31.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356<br />
Auch wenn e<strong>in</strong> DBMS viele Funktionen zur Verfügung stellt, kommt man <strong>in</strong> der<br />
Praxis immer wieder e<strong>in</strong>mal zu weiteren Wünschen und Anforderungen. Dafür<br />
kann e<strong>in</strong> Benutzer eigene Funktionen def<strong>in</strong>ieren und dem DBMS bekannt geben<br />
oder <strong>in</strong> e<strong>in</strong>er Datenbank speichern. E<strong>in</strong>mal def<strong>in</strong>iert, erspart dies künftig,<br />
die gleiche Rout<strong>in</strong>e immer neu zu erstellen; stattdessen wird die Funktion aufgerufen<br />
und liefert den Rückgabewert.<br />
Firebird hat solche Funktionen erst für Version 3 angekündigt. Zurzeit kann e<strong>in</strong>e<br />
Funktion nur als UDF (user-def<strong>in</strong>ed function) aus e<strong>in</strong>er externen DLL, die mit e<strong>in</strong>er<br />
Programmiersprache erstellt wurde, e<strong>in</strong>gebunden werden.<br />
Bitte haben Sie Nachsicht: Wegen der vielen Varianten bei den DBMS wurde e<strong>in</strong><br />
Teil der H<strong>in</strong>weise und Beispiele auch <strong>in</strong> diesem Kapitel nur nach der Dokumentation<br />
verfasst und nicht <strong>in</strong> der Praxis umgesetzt.<br />
31.1. Funktion def<strong>in</strong>ieren<br />
Benutzerdef<strong>in</strong>ierte Funktionen geben (ebenso wie die eigenen Funktionen des<br />
DBMS) genau e<strong>in</strong>en Wert zurück, s<strong>in</strong>d also Skalarfunktionen.<br />
31.1.1. Funktion erstellen<br />
Mit CREATE FUNCTION <strong>in</strong> der folgenden Syntax (vere<strong>in</strong>fachte Version des <strong>SQL</strong>-<br />
Standards) wird e<strong>in</strong>e Funktion def<strong>in</strong>iert:<br />
347
Eigene Funktionen<br />
CREATE FUNCTION <br />
( [ ] )<br />
RETURNS <br />
[ ]<br />
<br />
Der der Funktion muss <strong>in</strong>nerhalb e<strong>in</strong>es gewissen Bereichs<br />
(„Schema“ genannt) e<strong>in</strong>deutig se<strong>in</strong> und darf auch nicht als Name e<strong>in</strong>er Prozedur<br />
verwendet werden. Teilweise wird verlangt, dass der Name der Datenbank<br />
ausdrücklich angegeben wird. Es empfiehlt sich, ke<strong>in</strong>en Namen e<strong>in</strong>er e<strong>in</strong>gebauten<br />
Funktion zu verwenden.<br />
Die Klammern s<strong>in</strong>d erforderlich und kennzeichnen e<strong>in</strong>e Rout<strong>in</strong>e. E<strong>in</strong>gabeparameter<br />
können vorhanden se<strong>in</strong>, müssen es aber nicht. Mehrere Parameter werden<br />
durch Kommata getrennt, der e<strong>in</strong>zelne wird wie folgt def<strong>in</strong>iert:<br />
[ IN ] <br />
Der muss <strong>in</strong>nerhalb der Funktion e<strong>in</strong>deutig se<strong>in</strong>. Der Zusatz<br />
IN kann entfallen, weil es bei Funktionen nur E<strong>in</strong>gabe-Parameter (ke<strong>in</strong>e<br />
Ausgabe-Parameter) gibt.<br />
Der RETURNS-Parameter ist wesentlich und kennzeichnet e<strong>in</strong>e Funktion.<br />
Der sowohl für den RETURNS-Parameter als auch für die E<strong>in</strong>gabe-<br />
Parameter muss e<strong>in</strong>er der <strong>SQL</strong>-Datentypen des DBMS se<strong>in</strong>.<br />
Für gibt es diverse Festlegungen, wie die Funktion arbeiten<br />
soll, z. B. durch LANGUAGE mit der benutzten Programmiersprache.<br />
– der Rumpf – enthält den eigentlichen Arbeitsablauf, also die<br />
Anweisungen, die <strong>in</strong>nerhalb der Rout<strong>in</strong>e ausgeführt werden sollen. Bei diesen<br />
Befehlen handelt es sich um „normale“ <strong>SQL</strong>-Befehle, die mit Bestandteilen<br />
der <strong>SQL</strong>-Programmiersprache verbunden werden. Zum Semikolon, das den Abschluss<br />
des CREATE-Befehls darstellen sollte, aber <strong>in</strong>nerhalb des Inhalts bei jeder<br />
e<strong>in</strong>zelnen Anweisung steht, beachten Sie bitte auch die DBMS-spezifischen<br />
H<strong>in</strong>weise zur <strong>SQL</strong>-Programmierung unter Anweisungen begrenzen 1 .<br />
Bei e<strong>in</strong>er Funktion muss − beispielsweise durch e<strong>in</strong>e RETURN-Anweisung − der<br />
gesuchte Rückgabewert ausdrücklich festgelegt werden. In der Regel werden die<br />
Befehle durch BEGIN. . . END zusammengefasst; bei e<strong>in</strong>em e<strong>in</strong>zelnen Befehl ist<br />
dies nicht erforderlich.<br />
1 Kapitel 30 auf Seite 323<br />
348
31.1.2. Funktion ausführen<br />
Beispiele<br />
E<strong>in</strong>e benutzerdef<strong>in</strong>ierte Funktion kann wie jede <strong>in</strong>terne Funktion des DBMS benutzt<br />
werden. Sie kann an jeder Stelle benutzt werden, an der e<strong>in</strong> e<strong>in</strong>zelner Wert<br />
erwartet wird, wie <strong>in</strong> den Beispielen zu sehen ist.<br />
31.1.3. Funktion ändern oder löschen<br />
Mit ALTER FUNCTION wird die Def<strong>in</strong>ition e<strong>in</strong>er Funktion geändert. Dafür gilt<br />
die gleiche Syntax:<br />
ALTER FUNCTION <br />
( [ ] )<br />
RETURNS <br />
[ ]<br />
<br />
Mit DROP FUNCTION wird die Def<strong>in</strong>ition e<strong>in</strong>er Funktion gelöscht.<br />
DROP FUNCTION ;<br />
31.2. Beispiele<br />
31.2.1. E<strong>in</strong>fache Bestimmung e<strong>in</strong>es Wertes<br />
Das folgende Beispiel erstellt den Rückgabewert direkt aus der E<strong>in</strong>gabe.<br />
Aufgabe: Zur Begrüßung wird e<strong>in</strong> Name mit e<strong>in</strong>em Standardtext, der von der<br />
Tageszeit abhängt, verbunden.<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE FUNCTION Hello (s CHAR(20))<br />
RETURNS CHAR(50)<br />
RETURN WHEN<br />
CASE CURRENT_TIME < ’12:00’ THEN ’Guten M<strong>org</strong>en, ’ + s + ’!’<br />
CASE CURRENT_TIME < ’18:00’ THEN ’Guten Tag, ’ + s + ’!’<br />
ELSE ’Guten Abend, ’ + s + ’!’<br />
END;<br />
Bei diesem Beispiel kann auf BEGIN. . . END verzichtet werden, weil der „Rumpf“<br />
der Funktion nur e<strong>in</strong>e e<strong>in</strong>zige Anweisung enthält, nämlich RETURN unter Benutzung<br />
des WHEN-Ausdrucks.<br />
349
Eigene Funktionen<br />
E<strong>in</strong>e solche Funktion wird wie jede e<strong>in</strong>gebaute Funktion benutzt.<br />
SELECT Hello(’Juetho’) [from fiktiv];<br />
’Guten Abend, Juetho!’<br />
31.2.2. Text <strong>in</strong> Anführungszeichen e<strong>in</strong>schließen<br />
Im folgenden Beispiel werden mehr Schritte bis zum RETURN-Wert benötigt.<br />
Der Nutzen liegt im Export von Daten aus e<strong>in</strong>em System zu e<strong>in</strong>em anderen.<br />
Aufgabe: E<strong>in</strong> gegebener Text soll <strong>in</strong> Anführungszeichen e<strong>in</strong>geschlossen werden;<br />
Gänsefüßchen <strong>in</strong>nerhalb des Textes müssen verdoppelt werden.<br />
My<strong>SQL</strong>-Quelltext<br />
CREATE FUNCTION Quot<strong>in</strong>g<br />
( <strong>in</strong>str<strong>in</strong>g VARCHAR(80) )<br />
RETURNS ( VARCHAR(100) )<br />
AS<br />
BEGIN<br />
IF (<strong>in</strong>str<strong>in</strong>g CONTAINING(’"’)) THEN BEGIN<br />
<strong>in</strong>str<strong>in</strong>g = REPLACE( <strong>in</strong>str<strong>in</strong>g, ’"’, ’""’ );<br />
END<br />
RETURN CONCAT( ’"’, <strong>in</strong>str<strong>in</strong>g, ’"’);<br />
END<br />
Diese Funktion kann direkt aufgerufen werden:<br />
SELECT Quot<strong>in</strong>g(’Schulze’), Quot<strong>in</strong>g(’Restaurant "Abendrot"’) [from fiktiv];<br />
"Schulze" "Restaurant ""Abendrot"""<br />
31.2.3. Anzahl der Mitarbeiter e<strong>in</strong>er Abteilung<br />
Bei der folgenden Funktion werden zunächst weitere Informationen benötigt.<br />
Aufgabe: Suche zu e<strong>in</strong>er Abteilung gemäß Kuerzel die Anzahl der Mitarbeiter.<br />
350<br />
Oracle-Quelltext<br />
CREATE OR REPLACE FUNCTION AnzahlMitarbeiter<br />
( abt CHAR(4) )<br />
RETURN ( INTEGER )<br />
AS
anzahl INTEGER;<br />
BEGIN<br />
SELECT COUNT(*) INTO anzahl<br />
FROM Mitarbeiter mi<br />
JOIN Abteilung ab ON ab.ID = mi.Abteilung_ID<br />
WHERE ab.Kuerzel = abt;<br />
RETURN anzahl;<br />
END<br />
Zusammenfassung<br />
Damit erhalten wir die Anzahl der Mitarbeiter e<strong>in</strong>er bestimmten Abteilung:<br />
SELECT AnzahlMitarbeiter(’Vert’) [from fiktiv] ;<br />
AnzahlMitarbeiter : 4<br />
31.3. Zusammenfassung<br />
In diesem Kapitel lernten wir die Grundregeln für das Erstellen eigener Funktionen<br />
kennen:<br />
• Benutzerdef<strong>in</strong>ierte Funktionen s<strong>in</strong>d immer Skalarfunktionen, die genau e<strong>in</strong>en<br />
Wert zurückgeben.<br />
• Der Datentyp des Rückgabewerts ist <strong>in</strong> der RETURNS-Klausel anzugeben, der<br />
Wert selbst durch e<strong>in</strong>e RETURN-Anweisung.<br />
• Komplexe Maßnahmen müssen <strong>in</strong> BEGIN. . . END e<strong>in</strong>geschlossen werden; e<strong>in</strong>e<br />
Funktion kann aber auch nur aus der RETURN-Anweisung bestehen.<br />
31.4. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 353.<br />
Wenn bei den folgenden Übungen der Begriff „Funktion“ ohne nähere Erklärung<br />
verwendet wird, ist immer e<strong>in</strong>e „Eigene Funktion“, d. h. e<strong>in</strong>e benutzerdef<strong>in</strong>ierte<br />
Funktion geme<strong>in</strong>t.<br />
Unter „Skizzieren“ (Übung 3, 5) ist geme<strong>in</strong>t: E<strong>in</strong>gabe- und Ausgabeparameter sowie<br />
Variablen mit s<strong>in</strong>nvollen Namen und Datentypen benennen, Arbeitsablauf<br />
möglichst genau mit Pseudo-Code oder normaler Sprache beschreiben.<br />
Tipp: Die Parameter ergeben sich <strong>in</strong> der Regel aus der Aufgabenstellung. Aus der<br />
Überlegung zum Arbeitsablauf folgen die Variablen.<br />
351
Eigene Funktionen<br />
Übung 1 – Def<strong>in</strong>ition e<strong>in</strong>er Funktion<br />
Welche der folgenden Aussagen s<strong>in</strong>d richtig, welche s<strong>in</strong>d falsch?<br />
1. E<strong>in</strong>e Funktion dient zur Bestimmung genau e<strong>in</strong>es Wertes.<br />
2. E<strong>in</strong>e Funktion kann höchstens e<strong>in</strong>en E<strong>in</strong>gabe-Parameter enthalten.<br />
3. E<strong>in</strong>e Funktion kann mehrere Ausgabe-Parameter enthalten.<br />
4. E<strong>in</strong>e Funktion kann e<strong>in</strong>en oder mehrere <strong>SQL</strong>-Befehle verwenden.<br />
5. Der Rückgabewert wird mit e<strong>in</strong>er RETURN-Anweisung angegeben.<br />
6. Der Datentyp des Rückgabewerts ergibt sich automatisch aus der<br />
RETURN-Anweisung.<br />
7. Die Def<strong>in</strong>ition e<strong>in</strong>er Funktion kann nicht geändert werden; sie kann nur<br />
gelöscht und mit anderem Inhalt neu aufgenommen werden.<br />
Übung 2 – Def<strong>in</strong>ition e<strong>in</strong>er Funktion prüfen<br />
Nennen Sie <strong>in</strong> der folgenden Def<strong>in</strong>ition Punkte, die unabhängig vom <strong>SQL</strong>-<br />
Dialekt falsch s<strong>in</strong>d. (Je nach DBMS können noch andere Punkte falsch se<strong>in</strong>, danach<br />
wird aber nicht gefragt.) Die Funktion soll folgende Aufgabe erledigen:<br />
• Es wird e<strong>in</strong>e Telefonnummer <strong>in</strong> beliebiger Formatierung als E<strong>in</strong>gabe-<br />
Parameter übergeben, z. B. als '(0049 / 030) 99 68-32 53'.<br />
• Das Ergebnis soll diese Nummer ganz ohne Trennzeichen zurückgeben.<br />
• Sofern die Ländervorwahl enthalten ist, soll das führende '00' durch '+' ersetzt<br />
werden.<br />
352<br />
1. CREATE Telefon_Standard AS Function<br />
2. ( IN E<strong>in</strong>gabe VARCHAR(20),<br />
3. OUT Ausgabe VARCHAR(20) )<br />
4. AS<br />
5. DECLARE INTEGER x1, i1;<br />
6. DECLARE CHAR c1;<br />
7. BEGIN<br />
8. -- Rückgabewert vorbelegen<br />
9. Ausgabe = ”;<br />
10. -- Länge der Schleife bestimmen<br />
11. i1 = CHAR_LENGTH(E<strong>in</strong>gabe);<br />
12. -- die Schleife für jedes vorhandene Zeichen verarbeiten<br />
13. WHILE (i1 < x1)<br />
14. DO<br />
15. -- das nächste Zeichen auslesen<br />
16. x1 = x1 + 1<br />
17. c1 = SUBSTRING(E<strong>in</strong>gabe FROM x1 FOR 1);<br />
18. -- dieses Zeichen prüfen: ist es e<strong>in</strong>e Ziffer<br />
19. if (c1 >= ’0’ and (c1
23. END<br />
24. END<br />
25.<br />
26. -- Zusatzprüfung: ’00’ durch ’+’ ersetzen<br />
27. IF (Ausgabe STARTS WITH ’00’)<br />
28. THEN<br />
29. Ausgabe = ’+’ || SUBSTRING(Ausgabe FROM 3 FOR 20)<br />
30. END<br />
31.<br />
32. END<br />
Übung 3 – Funktion DateToStr<strong>in</strong>g erstellen<br />
Übungen<br />
Skizzieren Sie e<strong>in</strong>e Funktion DateToStr<strong>in</strong>g: Aus e<strong>in</strong>em Date- oder DateTime-<br />
Wert als E<strong>in</strong>gabe-Parameter soll e<strong>in</strong>e Zeichenkette mit genau 8 Zeichen der Form<br />
'JJJJMMTT' gemacht werden. Benutzen Sie dafür nur die EXTRACT- und LPAD-<br />
Funktionen sowie die „Addition“ von Zeichenketten; Sie können davon ausgehen,<br />
dass e<strong>in</strong>e Zahl bei LPAD korrekt als Zeichenkette verstanden wird.<br />
Übung 4 – Funktion DateToStr<strong>in</strong>g erstellen<br />
Erstellen Sie die Funktion aus der vorigen Übung.<br />
Übung 5 – Funktion Str<strong>in</strong>g_Insert erstellen<br />
Skizzieren Sie e<strong>in</strong>e Funktion Str<strong>in</strong>g_Insert: In e<strong>in</strong>e Zeichenkette soll an e<strong>in</strong>er bestimmten<br />
Position e<strong>in</strong>e zweite Zeichenkette e<strong>in</strong>gefügt werden; alle Zeichenketten<br />
sollen max. 80 Zeichen lang se<strong>in</strong>. Benutzen Sie dafür nur SUBSTRING und<br />
CAST sowie die „Addition“ von Zeichenketten; stellen Sie außerdem sicher, dass<br />
das Ergebnis die mögliche Maximallänge nicht überschreitet und schneiden Sie<br />
ggf. ab.<br />
Übung 6 – Funktion Str<strong>in</strong>g_Insert erstellen<br />
Erstellen Sie die Funktion aus der vorigen Übung.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 351.<br />
Lösung zu Übung 1 – Def<strong>in</strong>ition e<strong>in</strong>er Funktion<br />
Die Aussagen 1, 4, 5 s<strong>in</strong>d richtig. Die Aussagen 2, 3, 6, 7 s<strong>in</strong>d falsch.<br />
353
Eigene Funktionen<br />
Lösung zu Übung 2 – Def<strong>in</strong>ition e<strong>in</strong>er Funktion prüfen<br />
• Zeile 1: Die Def<strong>in</strong>ition erfolgt durch CREATE FUNCTION .<br />
• Zeile 2: Bei e<strong>in</strong>er Funktion gehören <strong>in</strong> die Klammern nur die E<strong>in</strong>gabe-<br />
Parameter.<br />
• Zeile 3: Der Ausgabe-Parameter muss mit RETURNS festgelegt werden.<br />
• Zeile 4: Bei jeder Variablen ist zuerst der Name, dann der Typ anzugeben.<br />
• Zeile 12: Der Startwert für die Schleifenvariable x1 = 0 fehlt.<br />
• Zeile 14: Die Schleife verarbeitet mehrere Befehle, muss also h<strong>in</strong>ter DO auch<br />
e<strong>in</strong> BEGIN erhalten.<br />
• Zeile 16 und 29: Jeder e<strong>in</strong>zelne Befehl muss mit e<strong>in</strong>em Semikolon abgeschlossen<br />
werden.<br />
• Zeile 19: Die Klammern s<strong>in</strong>d falsch gesetzt; am e<strong>in</strong>fachsten wird die zweite öffnende<br />
Klammer entfernt.<br />
• Zeile 20/23: Entweder es wird THEN BEGIN...END verwendet, oder das END<br />
wird gestrichen.<br />
• Zeile 28/30 enthält den gleichen Fehler.<br />
• Zeile 31: Es fehlt die RETURN-Anweisung, mit der der erzeugte Ausgabe-Str<strong>in</strong>g<br />
zurückgegeben wird.<br />
Lösung zu Übung 3 – Funktion DateToStr<strong>in</strong>g erstellen<br />
• E<strong>in</strong>gabe-Parameter: e<strong>in</strong> DateTime-Wert E<strong>in</strong>gabe<br />
• Ausgabe-Parameter: e<strong>in</strong> CHAR(8) Ausgabe<br />
• drei INTEGER-Variable: jjjj, mm, tt<br />
• mit 3x EXTRACT werden die Bestandteile jjjj, mm, tt herausgeholt<br />
• diese werden mit LPAD auf CHAR(2) für Monat und Tag gesetzt<br />
• schließlich werden diese Werte per „Addition“ verknüpft und<br />
• als Ergebnis zurückgegeben<br />
Lösung zu Übung 4 – Funktion DateToStr<strong>in</strong>g erstellen<br />
354<br />
CREATE FUNCTION DateToStr<strong>in</strong>g<br />
( E<strong>in</strong>gabe DATE )<br />
RETURNS ( Ausgabe CHAR(8) )<br />
AS<br />
DECLARE VARIABLE jjjj <strong>in</strong>t;<br />
DECLARE VARIABLE mm <strong>in</strong>t;<br />
DECLARE VARIABLE tt <strong>in</strong>t;<br />
BEGIN<br />
jjjj = EXTRACT(YEAR FROM E<strong>in</strong>gabe);<br />
mm = EXTRACT(MONTH FROM E<strong>in</strong>gabe);
tt = EXTRACT(DAY FROM E<strong>in</strong>gabe);<br />
Ausgabe = jjjj || LPAD(mm, 2, ’0’) || LPAD(tt, 2, ’0’);<br />
RETURN Ausgabe;<br />
END<br />
Lösung zu Übung 5 – Funktion Str<strong>in</strong>g_Insert erstellen<br />
Übungen<br />
• E<strong>in</strong>gabe-Parameter: Orig<strong>in</strong>al-Str<strong>in</strong>g E<strong>in</strong>gabe, pos als Position (der Name Position<br />
ist als <strong>in</strong>terne Funktion verboten), der Zusatz-Str<strong>in</strong>g part<br />
• Ausgabe-Parameter: die neue Zeichenkette Ausgabe<br />
• Variable: temp mit Platz für 160 Zeichen<br />
• es wird e<strong>in</strong>fach e<strong>in</strong>e neue Zeichenkette temp zusammengesetzt aus drei Teilen:<br />
• der Anfang von E<strong>in</strong>gabe mit pos Zeichen<br />
• dann der Zusatz-Str<strong>in</strong>g part<br />
• dann der Rest von E<strong>in</strong>gabe ab Position pos + 1<br />
• falls die Zeichenkette temp zu lang ist, werden mit SUBSTRING nur noch die<br />
ersten 80 Zeichen verwendet<br />
• das Ergebnis muss wegen der seltsamen Def<strong>in</strong>ition von SUBSTRING mit CAST<br />
auf VARCHAR(80) gebracht werden und<br />
• kann dann als Rückgabewert verwendet werden<br />
Lösung zu Übung 6 – Funktion Str<strong>in</strong>g_Insert erstellen<br />
CREATE FUNCTION Str<strong>in</strong>g_Insert<br />
( E<strong>in</strong>gabe VARCHAR(80), pos INTEGER, part VARCHAR(80) )<br />
RETURNS ( Ausgabe VARCHAR(80) )<br />
AS<br />
-- genügend Zwischenspeicher bereitstellen<br />
DECLARE VARIABLE temp VARCHAR(160);<br />
BEGIN<br />
-- Teil 1, dazu Zwischenteil, dann Teil 2<br />
temp = SUBSTRING( E<strong>in</strong>gabe FROM 1 FOR pos )<br />
|| part<br />
|| SUBSTRING( E<strong>in</strong>gabe FROM pos + 1 );<br />
-- auf jeden Fall auf die Maximallänge von 80 Zeichen br<strong>in</strong>gen<br />
IF (CHAR_LENGTH(temp) > 80) THEN<br />
Ausgabe = CAST( SUBSTRING(temp FROM 1 FOR 80) AS VARCHAR(80));<br />
ELSE<br />
Ausgabe = CAST( temp AS VARCHAR(80));<br />
RETURN Ausgabe;<br />
END<br />
355
Eigene Funktionen<br />
31.5. Siehe auch<br />
Verschiedene E<strong>in</strong>zelheiten werden <strong>in</strong> den folgenden Kapiteln behandelt:<br />
• <strong>SQL</strong>-Programmierung 2<br />
• Datentypen 3<br />
• Funktionen 4 und Funktionen (2) 5<br />
2 Kapitel 30 auf Seite 323<br />
3 Kapitel 13 auf Seite 97<br />
4 Kapitel 14 auf Seite 109<br />
5 Kapitel 16 auf Seite 141<br />
356
32. Prozeduren<br />
32.1. Die Def<strong>in</strong>ition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358<br />
32.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360<br />
32.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 370<br />
32.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370<br />
32.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375<br />
E<strong>in</strong>e Prozedur − gespeicherte Prozedur, engl. Stored Procedure (SP) − ist v<strong>org</strong>esehen<br />
für Arbeitsabläufe, die „immer wiederkehrende“ Arbeiten direkt <strong>in</strong>nerhalb<br />
der Datenbank ausführen sollen.<br />
Bitte haben Sie Nachsicht: Wegen der vielen Varianten bei den DBMS beschränkt<br />
sich dieses Kapitel bei den H<strong>in</strong>weisen und Beispielen auf Firebird. Zusammen mit<br />
den speziellen H<strong>in</strong>weisen zur <strong>SQL</strong>-Programmierung sollten sie problemlos an andere<br />
DBMS-Regeln angepasst werden können.<br />
Zur Verwendung von Prozeduren gibt es zwei gegensätzliche Standpunkte, die<br />
mit den Begriffen Fat Server und Fat Client zusammengefasst werden können:<br />
• Beim Fat Server wird so viel Funktionalität wie möglich <strong>in</strong> die (zentral gespeicherte)<br />
Datenbank gelegt.<br />
• Beim Fat Client ist die Datenbank nur für die Speicherung der Daten v<strong>org</strong>esehen;<br />
sämtliche Verarbeitung erfolgt auf dem Arbeitsplatzrechner.<br />
Der große Vorteil e<strong>in</strong>es Fat Server liegt dar<strong>in</strong>, dass Daten direkt <strong>in</strong> der Datenbank<br />
verarbeitet werden können, sofern ke<strong>in</strong>e Ansicht der Daten auf dem Arbeitsplatz<br />
benötigt wird. Es ist natürlich überflüssig, die Daten zuerst von der Datenbank<br />
zum Arbeitsplatz zu kopieren, dann automatisch zu verarbeiten und schließlich<br />
das Arbeitsergebnis (ohne manuelle Überprüfung) auf die Datenbank zurückzukopieren.<br />
E<strong>in</strong>facher ist es, alles <strong>in</strong> der Datenbank ausführen zu lassen.<br />
Der große Vorteil e<strong>in</strong>es Fat Client liegt dar<strong>in</strong>, dass das Client-Programm meistens<br />
schneller und e<strong>in</strong>facher geändert werden kann als die Arbeitsabläufe <strong>in</strong>nerhalb<br />
der Datenbank.<br />
In der Praxis ist e<strong>in</strong>e Mischung beider Verfahren am s<strong>in</strong>nvollsten. Zur Steuerung<br />
der Arbeitsabläufe <strong>in</strong> der Datenbank werden die gespeicherten Prozeduren verwendet;<br />
Beispiele dafür werden <strong>in</strong> diesem Abschnitt behandelt.<br />
357
Prozeduren<br />
32.1. Die Def<strong>in</strong>ition<br />
32.1.1. Prozedur erstellen<br />
Die Syntax für die Def<strong>in</strong>ition e<strong>in</strong>er Prozedur sieht so aus:<br />
CREATE OR ALTER PROCEDURE <br />
( [ ] )<br />
AS<br />
BEGIN<br />
[ ]<br />
<br />
END<br />
Notwendig s<strong>in</strong>d folgende Angaben:<br />
• neben dem Befehlsnamen CREATE PROCEDURE der Name der Prozedur<br />
• das Schlüsselwort AS als Zeichen für den Inhalt<br />
• die Schlüsselwörter BEGIN und END als Begrenzer für den Rumpf, also den<br />
eigentlichen Inhalt<br />
H<strong>in</strong>zu kommen die folgenden Angaben:<br />
• e<strong>in</strong>e Liste von Parametern, sofern Werte übergeben oder abgefragt werden<br />
• Bei den meisten DBMS werden E<strong>in</strong>gabe- und Ausgabe-Parameter durch<br />
Schlüsselwörter wie IN und OUT unterschieden, sie stehen dabei <strong>in</strong>nerhalb<br />
derselben Liste.<br />
• Bei Firebird enthält die Parameterliste nur die E<strong>in</strong>gabe-Parameter; die<br />
Ausgabe-Parameter stehen h<strong>in</strong>ter RETURNS <strong>in</strong> e<strong>in</strong>er eigenen Liste mit<br />
Klammern.<br />
• e<strong>in</strong>e Liste von Variablen, die <strong>in</strong>nerhalb der Prozedur verwendet werden; diese<br />
Liste steht je nach DBMS zwischen AS und BEGIN oder <strong>in</strong>nerhalb des Rumpfes<br />
• die Anweisungen, die <strong>in</strong>nerhalb der Prozedur auszuführen s<strong>in</strong>d<br />
Bei den Befehlen <strong>in</strong>nerhalb der Prozedur handelt es sich um „normale“ <strong>SQL</strong>-<br />
Befehle sowie um Bestandteile der <strong>SQL</strong>-Programmiersprache. Zum Semikolon,<br />
das den Abschluss des CREATE-Befehls darstellen sollte, aber <strong>in</strong>nerhalb des Inhalts<br />
bereits für jede e<strong>in</strong>zelne Anweisung benutzt wird, beachten Sie bitte auch<br />
die H<strong>in</strong>weise zur <strong>SQL</strong>-Programmierung unter Anweisungen begrenzen 1 .<br />
1 Kapitel 30 auf Seite 323<br />
358
32.1.2. Prozedur ausführen<br />
Die Def<strong>in</strong>ition<br />
Es gibt mehrere Verfahren, wie e<strong>in</strong>e Prozedur vom Nutzer, aus e<strong>in</strong>em Anwendungsprogramm<br />
oder aus e<strong>in</strong>er anderen Prozedur oder e<strong>in</strong>em Trigger heraus<br />
benutzt werden kann.<br />
Der direkte Weg geht bei Firebird über Execute Procedure:<br />
EXECUTE PROCEDURE [ ]<br />
[ RETURNING_VALUES ] ;<br />
Dem Namen der Prozedur folgen (soweit erforderlich) die E<strong>in</strong>gabe-Parameter;<br />
mehrere werden mit Komma getrennt. Sofern die Prozedur Werte zurückgibt,<br />
werden sie <strong>in</strong> e<strong>in</strong>e oder mehrere Variablen e<strong>in</strong>getragen, die dem Begriff<br />
RETURNING_VALUES folgen.<br />
Bei den anderen DBMS gehören zu EXECUTE (My<strong>SQL</strong>: CALL) nicht nur die<br />
E<strong>in</strong>gabe-, sondern auch die Ausgabe-Parameter. Beispiele stehen im Kapitel zur<br />
<strong>SQL</strong>-Programmierung bei den Erläuterungen des jeweiligen DBMS.<br />
E<strong>in</strong>e Prozedur kann auch mit Select Procedure wie e<strong>in</strong>e Abfrage verwendet werden.<br />
Dies ist vor allem dann erforderlich, wenn e<strong>in</strong>e Liste von Werten oder Datensätzen<br />
zurückgeliefert werden.<br />
SELECT * FROM [ ] ;<br />
Die Ausgabe-Parameter stehen dann <strong>in</strong> der Liste der Spalten, also <strong>in</strong> der Ergebnismenge<br />
des SELECT-Befehls.<br />
Man könnte auch an EXECUTE BLOCK denken, aber das passt hier überhaupt<br />
nicht. E<strong>in</strong> Block ist e<strong>in</strong>e Menge von Anweisungen, die e<strong>in</strong>malig benutzt werden,<br />
aber eben nicht <strong>in</strong> der Datenbank gespeichert werden. E<strong>in</strong> solcher Block ersetzt<br />
e<strong>in</strong>e gespeicherte Prozedur, wenn das Speichern überflüssig ist, und wird bei der<br />
<strong>SQL</strong>-Programmierung unter Rout<strong>in</strong>en ohne feste Speicherung beschrieben.<br />
32.1.3. Prozedur ändern oder löschen<br />
E<strong>in</strong>e Änderung e<strong>in</strong>er Prozedur ist nur möglich durch e<strong>in</strong>e vollständige Neudef<strong>in</strong>ition<br />
mit allen Bestandteilen. Deshalb wird es heutzutage fast immer durch<br />
CREATE OR ALTER zusammengefasst. Wenn dies nicht möglich ist, dann muss<br />
e<strong>in</strong> e<strong>in</strong>zelner ALTER-Befehl benutzt werden, der alle Bestandteile der obigen Erläuterungen<br />
enthält.<br />
359
Prozeduren<br />
Das Löschen geht wie üblich mit dem DROP-Befehl:<br />
DROP PROCEDURE ;<br />
32.2. Beispiele<br />
Um die Beispiele für e<strong>in</strong> anderes DBMS zu übertragen, s<strong>in</strong>d vor allem folgende<br />
Punkte zu ändern:<br />
• Die Ausgabe-Parameter folgen nicht nach RETURNS, sondern gehören <strong>in</strong> die<br />
Parameterliste.<br />
• Die Deklaration der Variablen ist anzupassen, ebenso die Kennzeichnung mit<br />
Doppelpunkt.<br />
• Schleifen und ähnliche Maßnahmen s<strong>in</strong>d umzuschreiben.<br />
• Dies gilt auch dafür, wie Werte − z. B. e<strong>in</strong>es SELECT − übernommen werden.<br />
32.2.1. Ersatz für e<strong>in</strong>e View mit Parametern<br />
Im Kapitel zu VIEWs wollten wir e<strong>in</strong>e Ansicht mit variabler Selektion erstellen,<br />
aber variable Bed<strong>in</strong>gungen waren nicht möglich. Mit e<strong>in</strong>er Prozedur f<strong>in</strong>den wir<br />
e<strong>in</strong>e passende Lösung.<br />
Aufgabe: Mache aus der View Mitarbeiter_<strong>in</strong>_Abteilung e<strong>in</strong>e Prozedur<br />
Mitarbeiter_aus_Abteilung, die die Abteilungsnummer als Parameter erhält.<br />
Firebird-Quelltext<br />
CREATE OR ALTER PROCEDURE Mitarbeiter_aus_Abteilung<br />
( Abt INTEGER)<br />
RETURNS ( Personalnummer VARCHAR(10),<br />
Name VARCHAR(30),<br />
Vorname VARCHAR(30),<br />
Geburtsdatum DATE )<br />
AS<br />
BEGIN<br />
FOR SELECT Personalnummer, Name, Vorname, Geburtsdatum<br />
FROM Mitarbeiter<br />
WHERE Abteilung_ID = :Abt<br />
ORDER BY Ist_Leiter, Name, Vorname<br />
INTO :Personalnummer, :Name, :Vorname, :Geburtsdatum<br />
DO SUSPEND;<br />
END<br />
Als Prozedur enthält die Abfrage folgende Bestandteile:<br />
360
Beispiele<br />
• Als E<strong>in</strong>gabe-Parameter wird die gewünschte Abteilungsnummer erwartet und<br />
<strong>in</strong> der Variablen Abt gespeichert.<br />
• Als Ausgabe-Parameter werden die gewünschten Spalten der Tabelle Mitarbeiter<br />
aufgeführt.<br />
• Weitere Variable werden nicht benötigt.<br />
Innerhalb von BEGIN. . . END steht der eigentliche Arbeitsablauf mit Anweisungen<br />
gemäß <strong>SQL</strong>-Programmierung.<br />
• Es handelt sich dabei vor allem um e<strong>in</strong>en SELECT-Befehl mit der gewünschten<br />
WHERE-Klausel, die bei der VIEW nicht möglich war.<br />
• Diese Abfrage wird <strong>in</strong> e<strong>in</strong>e FOR-DO-Schleife e<strong>in</strong>gebunden.<br />
• Dabei werden die Spalten e<strong>in</strong>es jeden ausgewählten Datensatzes mit INTO <strong>in</strong><br />
die Ausgabe-Parameter übertragen und per SUSPEND zurückgegeben.<br />
Damit haben wir die gewünschte variable Abfrage. Bei der Verwendung ist es<br />
für den Anwender gleichgültig, ob er sie wie e<strong>in</strong>e View oder wie e<strong>in</strong>e Prozedur<br />
aufzurufen hat:<br />
Aufgabe: Hole alle Mitarbeiter e<strong>in</strong>er bestimmten Abteilung.<br />
SELECT * FROM Mitarbeiter_<strong>in</strong>_Abteilung<br />
WHERE Abt = 5;<br />
Aufruf als View<br />
Aufruf als Prozedur<br />
SELECT * FROM Mitarbeiter_aus_Abteilung (5);<br />
32.2.2. INSERT <strong>in</strong> mehrere Tabellen<br />
Wenn <strong>in</strong> der Beispieldatenbank e<strong>in</strong> neuer Versicherungsvertrag e<strong>in</strong>gegeben werden<br />
soll, benötigt man <strong>in</strong> der Regel drei INSERT-Befehle:<br />
• für e<strong>in</strong>en neuen E<strong>in</strong>trag <strong>in</strong> der Tabelle Fahrzeug<br />
• für e<strong>in</strong>en neuen E<strong>in</strong>trag <strong>in</strong> der Tabelle Versicherungsnehmer<br />
• für e<strong>in</strong>en neuen E<strong>in</strong>trag <strong>in</strong> der Tabelle Versicherungsvertrag mit Verweisen auf<br />
die beiden anderen E<strong>in</strong>träge<br />
Bei den ersten beiden E<strong>in</strong>trägen ist durch SELECT zunächst jeweils die neue<br />
ID abzufragen und beim dritten INSERT zu verwenden. Warum sollte man sich<br />
die Arbeit <strong>in</strong> dieser Weise erschweren? Soll doch die Datenbank e<strong>in</strong>en e<strong>in</strong>zigen<br />
INSERT-Befehl entgegennehmen und die Parameter selbständig auf die beteiligten<br />
Tabellen verteilen.<br />
361
Prozeduren<br />
Aufgabe: Speichere e<strong>in</strong>en neuen Versicherungsvertrag mit allen E<strong>in</strong>zelheiten.<br />
Firebird-Quelltext<br />
CREATE OR ALTER PROCEDURE Insert_Versicherungsvertrag<br />
( /* Bestandteile des Vertrags */<br />
Vertragsnummer VARCHAR(20),<br />
Abschlussdatum DATE,<br />
Art CHAR(2),<br />
Praemiensatz INTEGER,<br />
Basispraemie DECIMAL(9,0),<br />
Mitarbeiter_ID INTEGER,<br />
/* Angaben zum Versicherungsnehmer */<br />
Name VARCHAR(30),<br />
Vorname VARCHAR(30),<br />
Geschlecht CHAR(1),<br />
Geburtsdatum DATE,<br />
Fuehrersche<strong>in</strong> DATE,<br />
Ort VARCHAR(30),<br />
PLZ CHAR(5),<br />
Strasse VARCHAR(30),<br />
Hausnummer VARCHAR(10),<br />
Eigener_Kunde CHAR(1),<br />
Versicherungsgesellschaft_ID INTEGER,<br />
/* Angaben zum Fahrzeug */<br />
Kennzeichen VARCHAR(10),<br />
Farbe VARCHAR(30),<br />
Fahrzeugtyp_ID INTEGER<br />
)<br />
RETURNS( NewID INTEGER )<br />
AS<br />
DECLARE VARIABLE NewFahrzeugID INTEGER;<br />
DECLARE VARIABLE NewVersnehmerID INTEGER;<br />
BEGIN<br />
NewFahrzeugID = NEXT VALUE FOR Fahrzeug_ID;<br />
INSERT INTO Fahrzeug<br />
VALUES ( :NewFahrzeugID, :Kennzeichen, :Farbe, :Fahrzeugtyp_ID );<br />
NewVersnehmerID = NEXT VALUE FOR Versicherungsnehmer_ID;<br />
INSERT INTO Versicherungsnehmer<br />
VALUES ( :NewVersnehmerID, :Name, :Vorname, :Geburtsdatum,<br />
:Fuehrersche<strong>in</strong>, :Ort, :PLZ, :Strasse, :Hausnummer,<br />
:Eigener_Kunde, :Versicherungsgesellschaft_ID, :Geschlecht );<br />
NewID = NEXT VALUE FOR Versicherungsvertrag_ID;<br />
INSERT INTO Versicherungsvertrag<br />
VALUES ( :NewID, :Vertragsnummer, :Abschlussdatum, :Art, :Mitarbeiter_ID,<br />
:NewFahrzeugID, :NewVersnehmerID, :Praemiensatz,<br />
:Abschlussdatum, :Basispraemie );<br />
SUSPEND;<br />
END<br />
Als E<strong>in</strong>gabe-Parameter werden alle Werte benötigt, die der Sachbearbeiter für<br />
die e<strong>in</strong>zelnen Tabellen e<strong>in</strong>geben muss (siehe die durch Kommentare getrennten<br />
Abschnitte).<br />
362
Als Ausgabe-Parameter wollen wir die ID für den neuen Vertrag erhalten.<br />
Beispiele<br />
Als Variable werden die Verweise auf die zugeordneten Tabellen Fahrzeug und<br />
Versicherungsnehmer v<strong>org</strong>esehen.<br />
Der Arbeitsablauf ist ganz e<strong>in</strong>fach strukturiert:<br />
• Hole die nächste ID für die Tabelle Fahrzeug und speichere den nächsten E<strong>in</strong>trag<br />
<strong>in</strong> dieser Tabelle.<br />
• Hole die nächste ID für die Tabelle Versicherungsnehmer und speichere den<br />
nächsten E<strong>in</strong>trag <strong>in</strong> dieser Tabelle.<br />
• Hole die nächste ID für die Tabelle Versicherungsvertrag und speichere den<br />
nächsten E<strong>in</strong>trag <strong>in</strong> dieser Tabelle. Benutze dafür die beiden anderen IDs.<br />
Bei e<strong>in</strong>em DBMS mit automatischem Zähler wird statt der „nächsten ID“ die gerade<br />
neu vergebene ID abgefragt. Möglichkeiten dafür enthält der Abschnitt „Die<br />
letzte ID abfragen“ des Kapitels Tipps und Tricks 2 .<br />
Damit wird e<strong>in</strong> neuer Vertrag mit e<strong>in</strong>em e<strong>in</strong>zigen Befehl erstellt:<br />
Aufgabe: Speichere e<strong>in</strong>en neuen Vertrag mit allen Angaben.<br />
Firebird-Quelltext<br />
EXECUTE PROCEDURE Insert_Versicherungsvertrag<br />
( ’HS-38’, ’03.11.2009’, ’VK’, 125, 870, 11, ’Graef<strong>in</strong>g’, ’Charlotte’,<br />
’W’, ’09.11.1989’, ’26.02.2008’, ’Hatt<strong>in</strong>gen’, ’45529’,<br />
’Laakerweg’, ’17 b’, ’J’, NULL, ’BO-MC 413’, ’gelb’, 8 );<br />
------ Procedure execut<strong>in</strong>g results: ------<br />
NEWID = 29 /* die ID des neuen Vertrags */<br />
32.2.3. Automatisches UPDATE gemäß Bed<strong>in</strong>gungen<br />
Bei e<strong>in</strong>er Versicherungsgesellschaft muss regelmäßig der Prämiensatz e<strong>in</strong>es Vertrags<br />
neu berechnet werden: Bei verschuldeten Schadensfällen wird er (abhängig<br />
von der Höhe des Schadens) erhöht, bei Schadensfreiheit verr<strong>in</strong>gert. Dies ist<br />
e<strong>in</strong>e Aufgabe, die die Datenbank selbständig erledigen kann und soll. Das ist e<strong>in</strong><br />
komplexer Arbeitsablauf mit mehreren Prüfungen; vor allem die unterschiedliche<br />
Zuweisung neuer Werte wird <strong>in</strong> diesem Beispiel stark vere<strong>in</strong>facht.<br />
Aufgabe: Berechne für alle (eigenen) Verträge, ob e<strong>in</strong>e neue Prämienrechnung<br />
ansteht. Dabei ist zu prüfen, ob wegen neuer Schadensfälle der Prämiensatz zu<br />
erhöhen oder wegen Schadensfreiheit zu verr<strong>in</strong>gern ist.<br />
2 Abschnitt 34.1 auf Seite 389<br />
363
Prozeduren<br />
Die e<strong>in</strong>zelnen Schritte werden anhand des folgenden Codes erläutert.<br />
364<br />
Firebird-Quelltext<br />
CREATE OR ALTER PROCEDURE Update_Praemiensatz<br />
( Aktualisierung DATE DEFAULT CURRENT_DATE )<br />
RETURNS ( Erledigt INTEGER )<br />
AS<br />
DECLARE VARIABLE current_id INTEGER;<br />
DECLARE VARIABLE current_fz INTEGER;<br />
DECLARE VARIABLE current_aenderung DATE;<br />
DECLARE VARIABLE vergleich_aenderung DATE;<br />
DECLARE VARIABLE current_satz INTEGER;<br />
DECLARE VARIABLE current_value DECIMAL(16,2);<br />
BEGIN<br />
Erledigt = 0;<br />
SUSPEND;<br />
/* Vorarbeit: Anfänger werden auf Praemiensatz 200 gesetzt; das kann aber<br />
nur für noch nicht behandelte Verträge gelten und muss deshalb noch<br />
vor der nächsten IS NULL-Prüfung erledigt werden. */<br />
UPDATE Versicherungsvertrag vv<br />
SET Praemiensatz = 200<br />
WHERE Praemienaenderung IS NULL<br />
AND Versicherungsnehmer_ID<br />
/* bestimme die Liste der Anfänger, aber nur für eigene Kunden */<br />
IN ( SELECT ID<br />
FROM Versicherungsnehmer vn<br />
/* wenn der Führersche<strong>in</strong> beim Vertragsabschluss<br />
weniger als 2 Jahre alt ist */<br />
WHERE vn.Eigener_kunde = ’J’<br />
AND ( ( DATEADD( YEAR, 2, vn.Fuehrersche<strong>in</strong> ) > vv.Abschlussdatum<br />
/* wenn der VersNehmer beim Führersche<strong>in</strong>-Erwerb<br />
noch nicht 21 Jahre alt ist */<br />
OR ( DATEADD( YEAR, 21, vn.Geburtsdatum ) ><br />
vn.Fuehrersche<strong>in</strong> ) ) );<br />
/* Schritt 1: zu bearbeiten s<strong>in</strong>d alle Verträge für eigene Kunden, deren letzte<br />
Prämienänderung „zu lange“ zurückliegt */<br />
FOR SELECT vv.ID, Fahrzeug_ID,<br />
CASE WHEN Praemienaenderung IS NULL THEN Abschlussdatum<br />
ELSE Praemienaenderung<br />
END,<br />
Praemiensatz<br />
FROM Versicherungsvertrag vv<br />
JOIN Versicherungsnehmer vn ON vn.Id = vv.Versicherungsnehmer_id<br />
WHERE vn.Eigener_Kunde = ’J’<br />
AND ( Praemienaenderung IS NULL OR Praemienaenderung
* Schritt 3: weitere Bearbeitung, sofern die Aktualisierung über das<br />
Vergleichsdatum h<strong>in</strong>ausgeht */<br />
IF (Aktualisierung >= vergleich_aenderung) THEN<br />
BEGIN<br />
/* Schritt 4: suche zu diesem Vertrag, d.h. diesem Fahrzeug alle<br />
Schadensfälle <strong>in</strong> dieser Zeit<br />
und summiere die Schadenssumme nach Schuldanteil */<br />
SELECT SUM( sf.Schadenshoehe * zu.Schuldanteil / 100 )<br />
FROM Zuordnung_SF_FZ zu<br />
JOIN Schadensfall sf ON zu.Schadensfall_id = sf.Id<br />
WHERE zu.Fahrzeug_ID = :current_fz<br />
AND sf.Datum BETWEEN :current_aenderung AND :vergleich_aenderung<br />
INTO :current_value;<br />
Beispiele<br />
/* Schritt 5: abhängig von (anteiliger) Schadenssumme und<br />
bisherigem Prämiensatz<br />
wird der neue Prämiensatz bestimmt und das Datum weitergesetzt */<br />
UPDATE Versicherungsvertrag<br />
SET Praemiensatz =<br />
CASE<br />
WHEN :current_value IS NULL THEN CASE<br />
WHEN :current_satz > 100<br />
THEN :current_satz - 20<br />
WHEN :current_satz BETWEEN 40 AND 100<br />
THEN :current_satz - 10<br />
ELSE 30<br />
END<br />
WHEN :current_value = 0 THEN :current_satz - 10<br />
WHEN :current_value < 500 THEN :current_satz<br />
WHEN :current_value < 1000 THEN :current_satz + 10<br />
WHEN :current_value >= 1000 THEN :current_satz + 30<br />
END,<br />
Praemienaenderung = DATEADD( YEAR, 1, :current_aenderung )<br />
WHERE ID = :current_id;<br />
Erledigt = Erledigt + 1;<br />
END<br />
END<br />
SUSPEND;<br />
END<br />
Als E<strong>in</strong>gabe-Parameter wird nur das Datum der derzeitigen Aktualisierung benötigt.<br />
E<strong>in</strong> Vertrag wird dann geprüft, wenn der Zeitraum der nächsten Prämienrechnung<br />
− vom Datum der letzten Prämienänderung aus e<strong>in</strong> Jahr − das Datum<br />
der Aktualisierung enthält. Mit diesem Verfahren werden immer nur „neue“<br />
Schadensfälle und die jeweils anstehenden Prämienrechnungen berücksichtigt.<br />
Als Ausgabe-Parameter nehmen wir nur die Anzahl der berechneten Verträge,<br />
also der geänderten Erledigt-Vermerke.<br />
Als Variable benötigen wir Zwischenwerte für alle Angaben, die im Arbeitsablauf<br />
benutzt werden.<br />
Der Arbeitsablauf umfasst die folgenden Punkte:<br />
365
Prozeduren<br />
• Der Rückgabewert wird mit dem Anfangswert belegt und angezeigt zum Zeichen<br />
dafür, dass die Prozedur arbeitet.<br />
• Als Vorarbeit wird e<strong>in</strong> „Anfänger“, der als Eigener_Kunde gespeichert ist, auf<br />
e<strong>in</strong>en Prämiensatz von 200 gesetzt. Als Anfänger gilt, wenn der Führersche<strong>in</strong><br />
beim Abschluss des Vertrags noch nicht zwei Jahre alt ist oder wenn der Führersche<strong>in</strong><br />
vor dem 21. Geburtstag erworben wurde.<br />
Bei der Prüfung auf „zwei Jahre“ handelt es sich nicht um e<strong>in</strong>en sachlichen<br />
Fehler: Dieser Arbeitsablauf wird <strong>in</strong>nerhalb des ersten Versicherungsjahres<br />
ausgeführt; später ist die Hauptbed<strong>in</strong>gung „Praemienaenderung<br />
IS NULL“ niemals mehr gegeben. In der Praxis müssen solche<br />
Bed<strong>in</strong>gungen wegen möglicher Missverständnisse vorher ganz genau<br />
formuliert werden.<br />
• Schritt 1 wählt die Verträge aus, die „im Moment“ zu prüfen und ggf. zu bearbeiten<br />
s<strong>in</strong>d: alle eigenen Kunden, deren Prämiensatz noch nie geprüft wurde<br />
(dann wird das Abschlussdatum, also das Datum der ersten Rechnung, zur Berechnung<br />
verwendet) oder deren letzte Prämienänderung vor dem Datum der<br />
Aktualisierung liegt. Durch die FOR-SELECT-Schleife wird jeder dieser Verträge<br />
<strong>in</strong>nerhalb von DO BEGIN. . . END e<strong>in</strong>zeln bearbeitet; die dazu benötigten<br />
Werte werden <strong>in</strong> den Variablen zwischengespeichert.<br />
• Schritt 2 berechnet das Schlussdatum der letzten Prämienrechnung. Wir arbeiten<br />
hier nur mit Jahresrechnungen; da bei BETWEEN beide Grenzwerte e<strong>in</strong>bezogen<br />
werden, müssen wir das Schlussdatum um e<strong>in</strong>en Tag verr<strong>in</strong>gern.<br />
• Schritt 3 erledigt die letzte Vorprüfung:<br />
• Wenn das Datum der Aktualisierung vor dem Schlussdatum liegt, soll noch<br />
ke<strong>in</strong>e neue Rechnung erfolgen.<br />
• Aber wenn das Datum der Aktualisierung das Schlussdatum der letzten Prämienrechnung<br />
überschreitet, ist e<strong>in</strong>e neue Rechnung und damit e<strong>in</strong>e Überprüfung<br />
des Prämiensatzes fällig.<br />
• Schritt 4 berechnet die Summe aller Schadensfälle:<br />
• Berücksichtigt wird die Summe der Schadenshöhe je Vorfall, also aus der Tabelle<br />
Schadensfall.<br />
• Dieser Wert wird bei der Summierung anteilig nach dem Schuldanteil aus<br />
der Tabelle Zuordnung_SF_FZ berücksichtigt.<br />
• Das Ergebnis bei current_value lautet NULL, wenn das Fahrzeug nicht <strong>in</strong><br />
e<strong>in</strong>en Schadensfall verwickelt wurde, es lautet 0 bei fehlender Teilschuld und<br />
größer als 0 bei Mit- oder Vollschuld.<br />
• Schritt 5 berechnet den Prämiensatz neu:<br />
366<br />
• Ohne Schadensfall wird er um 10 Punkte verr<strong>in</strong>gert, wenn er bisher kle<strong>in</strong>er<br />
oder gleich 100 beträgt, und um 20 Punkte, wenn er bisher größer ist.<br />
• Das M<strong>in</strong>imum von 30 kann nicht unterschritten werden.
Beispiele<br />
• Mit Schadensfall ohne Teilschuld wird der Prämiensatz um 10 Punkte verr<strong>in</strong>gert.<br />
• Bei e<strong>in</strong>em sehr ger<strong>in</strong>gen Schaden bleibt der Prämiensatz unverändert.<br />
• Bei e<strong>in</strong>em kle<strong>in</strong>eren Schaden wird er um 10 Punkte, sonst um 30 Punkte<br />
erhöht.<br />
• Gleichzeitig wird das Datum der Prämienänderung um 1 Jahr weitergesetzt.<br />
Mit dieser Prozedur können die neuen Prämiensätze berechnet und für die<br />
nächste Prämienrechnung vorbereitet werden:<br />
Aufgabe: Berechne die Prämiensätze für alle (eigenen) Verträge, bei denen im<br />
3. Quartal 2009 e<strong>in</strong>e neue Prämienrechnung ansteht.<br />
EXECUTE PROCEDURE Update_Praemiensatz(’30.09.2009’);<br />
------ Procedure execut<strong>in</strong>g results: ------<br />
Erledigt = 7<br />
32.2.4. Testdaten <strong>in</strong> e<strong>in</strong>er Tabelle erstellen<br />
Das übliche V<strong>org</strong>ehen, wenn <strong>in</strong> e<strong>in</strong>er Tabelle viele Testdaten gespeichert werden<br />
sollen, werden wir im Kapitel Testdaten erzeugen verwenden:<br />
• In Zusatztabellen werden geeignete Feld<strong>in</strong>halte gesammelt: e<strong>in</strong>e Tabelle mit<br />
möglichen Vornamen, e<strong>in</strong>e mit Nachnamen usw.<br />
• Wie im Kapitel E<strong>in</strong>fache Tabellenverknüpfung beschrieben, werden als „kartesisches<br />
Produkt“ alle Zeilen und Spalten aller Zusatztabellen mite<strong>in</strong>ander<br />
komb<strong>in</strong>iert.<br />
• Oder die Zeilen und Spalten der Zusatztabellen werden per Zufallsfunktion<br />
mite<strong>in</strong>ander verknüpft.<br />
Bei wenigen Spalten der Zieltabelle kann auf Zusatztabellen verzichtet werden;<br />
dieses Verfahren nutzen wir im folgenden Beispiel. (Das Verfahren ist theoretisch<br />
auch für komplexere Tabellen möglich, wird dann aber schnell unübersichtlich;<br />
schon die folgenden CASE-Konstruktionen deuten solche Probleme an.)<br />
Aufgabe: Erzeuge e<strong>in</strong>e bestimmte Anzahl von Datensätzen <strong>in</strong> der Tabelle<br />
Fahrzeug; die Anzahl der neuen E<strong>in</strong>träge soll als Parameter v<strong>org</strong>egeben werden.<br />
Firebird Quelltext<br />
CREATE OR ALTER PROCEDURE Insert_Into_Fahrzeug<br />
( Anzahl INTEGER = 0)<br />
RETURNS ( Maxid INTEGER)<br />
AS<br />
367
Prozeduren<br />
DECLARE VARIABLE Temp INTEGER = 0;<br />
DECLARE VARIABLE Listekz CHAR( 66) = ’E E E DU DU BOTRE RE OB OB GE<br />
GE HERHAMBO BO DO DO UN UN EN EN ’;<br />
DECLARE VARIABLE Listekza CHAR( 26) = ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’;<br />
DECLARE VARIABLE Listefrb CHAR(100)<br />
= ’elfenbe<strong>in</strong> schwarz gelb orange ocker blau<br />
silbern grau braun weiss ’;<br />
DECLARE VARIABLE Kz VARCHAR( 3);<br />
DECLARE VARIABLE Name VARCHAR( 30);<br />
DECLARE VARIABLE Rand1 INTEGER = 0;<br />
DECLARE VARIABLE Rand2 INTEGER = 0;<br />
DECLARE VARIABLE Rand3 INTEGER = 0;<br />
DECLARE VARIABLE Rand4 INTEGER = 0;<br />
DECLARE VARIABLE Rand5 INTEGER = 0;<br />
BEGIN<br />
Maxid = 0;<br />
WHILE (Temp < Anzahl) DO<br />
BEGIN<br />
/* neue Zufallszahlen */<br />
Rand1 = FLOOR(1 + (RAND() * 21)); /* für Kfz-Kz. e<strong>in</strong>e Zufallszahl 1 bis 22 */<br />
Rand2 = FLOOR(0 + (RAND() * 26)); /* ebenso e<strong>in</strong>e Zufallszahl 0 bis 26 */<br />
Rand3 = FLOOR(1 + (RAND() * 25)); /* ebenso e<strong>in</strong>e Zufallszahl 1 bis 26 */<br />
Rand4 = FLOOR(1 + (RAND() * 9)); /* für Farbe e<strong>in</strong>e Zufallszahl 1 bis 10 */<br />
Rand5 = FLOOR(1 + (RAND() * 22)); /* für Fz.Typ e<strong>in</strong>e Zufallszahl 1 bis 23 */<br />
/* hole per Zufall Rand1 e<strong>in</strong>es der Kürzel aus Listekz */<br />
Kz = TRIM(SUBSTRING(:Listekz FROM (:Rand1*3 - 2) FOR 3));<br />
/* mache daraus e<strong>in</strong> vollständiges Kfz-Kennzeichen */<br />
Name = Kz || ’-’ || CASE :Rand2<br />
WHEN 0 THEN ”<br />
ELSE SUBSTRING(:Listekza FROM :Rand2 FOR 1)<br />
END<br />
|| SUBSTRING(:Listekza FROM :Rand3 FOR 1)<br />
|| ’ ’ || CAST( (CASE CHAR_LENGTH(Kz)<br />
WHEN 1 THEN FLOOR(1 + (RAND() * 9998))<br />
ELSE FLOOR(1 + (RAND() * 998))<br />
END)<br />
AS INT);<br />
/* Speichern des neuen Datensatzes */<br />
INSERT INTO Fahrzeug (Kennzeichen, Farbe, Fahrzeugtyp_ID)<br />
VALUES ( :Name,<br />
TRIM(SUBSTRING(:Listefrb FROM (:Rand4*10-9) FOR 10)),<br />
:Rand5 );<br />
Temp = Temp + 1;<br />
END<br />
SELECT MAX(ID) FROM fahrzeug INTO :Maxid;<br />
SUSPEND;<br />
END<br />
Dies def<strong>in</strong>iert die Prozedur mit folgenden Bestandteilen:<br />
• Mit Anzahl als e<strong>in</strong>zigem E<strong>in</strong>gabe-Parameter wird festgelegt, wie viele Datensätze<br />
erzeugt werden sollen. Dazu gehören der Name und Typ der Variablen<br />
sowie e<strong>in</strong> V<strong>org</strong>abewert.<br />
368
Beispiele<br />
• Mit Maxid als e<strong>in</strong>zigem Ausgabe-Parameter wird angegeben, dass e<strong>in</strong><br />
INTEGER-Wert mit diesem Namen erwartet wird. Der Anfangswert wird im<br />
ersten Befehl des Rumpfes der Prozedur festgelegt.<br />
• Zwischen AS und BEGIN stehen die verwendeten Variablen mit ihren Anfangswerten.<br />
• Listekz ist e<strong>in</strong>e Liste von 22 möglichen Kfz-Kennzeichen, Listekza enthält das<br />
Alphabet für die Zufallssuche nach dem zweiten Teil des Kennzeichens und<br />
Listefrb e<strong>in</strong>e Liste von 10 Farben (zu je 10 Zeichen). Diese Listen ersetzen<br />
die temporären Tabellen. Die „langen“ Texte müssen unbed<strong>in</strong>gt <strong>in</strong> e<strong>in</strong>er Zeile<br />
stehen; es geht hier nur wegen der Textbreite nicht anders.<br />
• Temp wird als Zähler für die neuen Datensätze verwendet, Kz und Name als<br />
Zwischenspeicher bei der Bildung des neuen Kennzeichens.<br />
• Rand1 usw. s<strong>in</strong>d Variablen für Zufallszahlen. Darauf könnte auch verzichtet<br />
werden, weil die Formeln für die Zufallszahlen auch direkt <strong>in</strong> der Str<strong>in</strong>g-<br />
Verknüpfung und im VALUES-Teil verwendet werden könnten; aber die<br />
Trennung macht es übersichtlicher.<br />
Innerhalb von BEGIN. . . END steht der eigentliche Arbeitsablauf mit Anweisungen<br />
gemäß <strong>SQL</strong>-Programmierung. Innerhalb der WHILE-Schleife zwischen<br />
DO BEGIN und END wird jeweils e<strong>in</strong> neuer Datensatz erzeugt und gespeichert:<br />
• Zuerst werden neue Zufallszahlen geholt.<br />
• Durch Rand1 wird e<strong>in</strong>e der Zahlen 1, 4, 7 usw. berechnet und gemäß diesem<br />
Wert e<strong>in</strong>es der Kennzeichen aus Listekz geholt.<br />
• Anschließend wird daraus e<strong>in</strong> vollständiges Kfz-Kennzeichen:<br />
• Wenn Rand2 ungleich 0 ist, folgt e<strong>in</strong> Buchstabe nach der Zufallszahl Rand2<br />
aus dem Alphabet, sonst nichts.<br />
• Danach folgt e<strong>in</strong> weiterer Buchstabe nach der Zufallszahl Rand3 aus dem<br />
Alphabet.<br />
• Danach folgt e<strong>in</strong>e (Zufalls-) Zahl: bei e<strong>in</strong>stelligem Kennzeichen e<strong>in</strong>e vierstellige<br />
Zahl, sonst e<strong>in</strong>e dreistellige.<br />
• Schließlich wird e<strong>in</strong> neuer Datensatz e<strong>in</strong>getragen mit dem eben erzeugten<br />
Kennzeichen sowie:<br />
• e<strong>in</strong>er Farbe, die mit der Zufallszahl Rand4 aus Listefrb geholt wird<br />
• e<strong>in</strong>er Fahrzeugtyp-ID, die nach der Zufallszahl Rand5 e<strong>in</strong>em der vorhandenen<br />
Werte <strong>in</strong> der Tabelle Fahrzeugtyp entspricht<br />
Zusätzlich wird die Variable Temp weitergezählt; nach dem Ende der Arbeit wird<br />
der größte Wert von ID bestimmt, <strong>in</strong> die Variable Maxid e<strong>in</strong>getragen und per<br />
SUSPEND als Rückgabewert übernommen.<br />
Ausgeführt wird diese Prozedur durch e<strong>in</strong>en e<strong>in</strong>fachen Befehl.<br />
369
Prozeduren<br />
Aufgabe: Erzeuge 10 neue Datensätze <strong>in</strong> der Tabelle Fahrzeug.<br />
EXECUTE PROCEDURE Insert_<strong>in</strong>to_Fahrzeug (10);<br />
------ Procedure execut<strong>in</strong>g results: ------<br />
MAXID = 145<br />
32.3. Zusammenfassung<br />
In diesem Kapitel lernten wir Prozeduren für Arbeitsabläufe kennen, die „nur“<br />
<strong>in</strong>nerhalb der Datenbank ausgeführt werden:<br />
• E<strong>in</strong>e Prozedur kann mit oder ohne Argumenten und mit oder ohne Rückgabewerte<br />
ausgeführt werden.<br />
• Sie dient zur automatischen Erledigung von Aufgaben, die „von außen“ angestoßen<br />
werden, aber ke<strong>in</strong>e zusätzlichen Maßnahmen des Anwenders oder der<br />
Anwendung benötigen.<br />
• Dabei werden viele Bestandteile e<strong>in</strong>er Programmiersprache benutzt.<br />
32.4. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 372.<br />
Unter „Skizzieren“ (Übung 3, 5, 6) ist wie im vorigen Kapitel geme<strong>in</strong>t: E<strong>in</strong>gabeund<br />
Ausgabeparameter sowie Variablen mit s<strong>in</strong>nvollen Namen und Datentypen<br />
benennen, Arbeitsablauf möglichst genau mit Pseudo-Code oder normaler Sprache<br />
beschreiben.<br />
Tipp: Die Parameter ergeben sich <strong>in</strong> der Regel aus der Aufgabenstellung. Aus der<br />
Überlegung zum Arbeitsablauf folgen die Variablen.<br />
Übung 1 – Prozeduren verwenden<br />
In welchen der folgenden Situationen ist e<strong>in</strong>e Prozedur s<strong>in</strong>nvoll, <strong>in</strong> welchen<br />
nicht? Gehen Sie davon aus, dass alle Informationen <strong>in</strong> der Datenbank gespeichert<br />
s<strong>in</strong>d.<br />
370<br />
1. Erstelle neue Rechnungen nach den aktuellen Prämiensätzen.<br />
2. Berechne die Weihnachtsgratifikationen der Mitarbeiter nach den erfolgten<br />
Abschlüssen.<br />
3. E<strong>in</strong> Versicherungsvertrag wird gekündigt.
Übung 2 – Def<strong>in</strong>ition e<strong>in</strong>er Prozedur kontrollieren<br />
Übungen<br />
Nennen Sie <strong>in</strong> der folgenden Def<strong>in</strong>ition Punkte, die unabhängig vom <strong>SQL</strong>-<br />
Dialekt falsch s<strong>in</strong>d. (Je nach DBMS s<strong>in</strong>d noch andere Punkte falsch, danach wird<br />
aber nicht gefragt.) Die Prozedur soll folgende Aufgabe erledigen:<br />
Mit e<strong>in</strong>em Aufruf sollen bis zu 5 Abteilungen neu gespeichert werden. Die Angaben<br />
sollen wie folgt <strong>in</strong> jeweils e<strong>in</strong>em Str<strong>in</strong>g zusammengefasst werden:<br />
’AbCd-Name der Abteilung-Ort der Abteilung’<br />
zuerst 4 Zeichen für das Kürzel, B<strong>in</strong>destrich, der Name der Abteilung,<br />
B<strong>in</strong>destrich, der Ort der Abteilung<br />
In e<strong>in</strong>er Schleife werden die 5 Zeichenketten verarbeitet, nämlich aufgeteilt und<br />
als Neuaufnahme <strong>in</strong> der Tabelle Abteilung gespeichert. Als Rückgabewert soll die<br />
letzte neu vergebene ID dienen.<br />
1. CREATE Insert_Abteilungen AS PROCEDURE<br />
2. /* E<strong>in</strong>gabe: mehrere neue Abteilungen e<strong>in</strong>zutragen <strong>in</strong> der Schreibweise */<br />
3. ’AbCd-Name der Abteilung-Ort der Abteilung’ */<br />
4. INPUTS Inhalt1, Inhalt2, Inhalt3, Inhalt4, Inhalt5 VARCHAR(70)<br />
5. OUTPUTS lastid INTEGER<br />
6. VARIABLES x1, pos INTEGER,<br />
7. temp VARCHAR(70),<br />
8. krz CHAR(4),<br />
9. name, ort VARCHAR(30)<br />
10. AS BEGIN<br />
11. x1 = 0;<br />
12. WHILE (x1 < 5)<br />
13. DO BEGIN<br />
14. x1 = x1 + 1;<br />
15. temp = CASE x1<br />
16. WHEN 1 THEN Inhalt1<br />
17. WHEN 2 THEN Inhalt2<br />
18. WHEN 3 THEN Inhalt3<br />
19. WHEN 4 THEN Inhalt4<br />
20. WHEN 5 THEN Inhalt5<br />
21. END<br />
22. /* vorzeitiger Abbruch, falls NULL übergeben wird */<br />
23. IF (temp IS NULL)<br />
24. THEN BREAK;<br />
25. /* bestimme durch ’-’, wo der Name aufhört und der Ort anfängt */<br />
26. pos = POSITION( ’-’, temp, 6 );<br />
27. krz = SUBSTRING( temp FROM 1 FOR 4 );<br />
28. name = SUBSTRING( temp FROM 5 FOR (pos-5) );<br />
29. ort = SUBSTRING( temp FROM (pos+1) );<br />
30. /* neuen Datensatz speichern */<br />
31. INSERT INTO Abteilung<br />
32. ( Kuerzel, Bezeichnung, Ort )<br />
33. VALUES ( krz, name, ort );<br />
34. lastid = SELECT ID FROM Abteilung WHERE Kuerzel = krz;<br />
35. END<br />
36. END<br />
371
Prozeduren<br />
Übung 3 – Prozedur Insert_Fahrzeugtyp erstellen<br />
Skizzieren Sie e<strong>in</strong>e Prozedur Insert_Fahrzeugtyp zum Speichern von Fahrzeugtyp<br />
und Hersteller <strong>in</strong> e<strong>in</strong>em Schritt: Es ist zu prüfen, ob der Hersteller schon gespeichert<br />
ist; wenn ja, ist diese ID zu verwenden, andernfalls ist e<strong>in</strong> neuer E<strong>in</strong>trag zu<br />
erstellen und dessen ID zu übernehmen. Mit dieser Hersteller-ID ist der Fahrzeugtyp<br />
zu registrieren.<br />
Übung 4 – Prozedur Insert_Fahrzeugtyp erstellen<br />
Erstellen Sie die Prozedur Insert_Fahrzeugtyp aus der vorigen Übung.<br />
Übung 5 – Prozedur Insert_Schadensfall erstellen<br />
Skizzieren Sie e<strong>in</strong>e Prozedur Insert_Schadensfall zum Speichern e<strong>in</strong>es neuen<br />
Schadens; dabei soll nur das Fahrzeug des Versicherungsnehmers beteiligt se<strong>in</strong>.<br />
Übung 6 – Prozedur Update_Schadensfall erstellen<br />
Skizzieren Sie e<strong>in</strong>e Prozedur Update_Schadensfall zum Ändern e<strong>in</strong>es Schadensfalls;<br />
dabei soll jeweils e<strong>in</strong> weiteres beteiligtes Fahrzeug registriert werden. (H<strong>in</strong>weise:<br />
Sie müssen auch berücksichtigen, wie sich die Schadenshöhe neu verteilt.<br />
Sie können davon ausgehen, dass der Versicherungsnehmer schon gespeichert<br />
ist − egal, ob es sich um e<strong>in</strong>en eigenen Kunden handelt oder nicht.) Beschreiben<br />
Sie zusätzlich, welche Punkte beim Arbeitsablauf und damit bei den E<strong>in</strong>gabe-<br />
Parametern noch geklärt werden müssen.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 370.<br />
Lösung zu Übung 1 – Prozeduren verwenden<br />
372<br />
1. s<strong>in</strong>nvoll, weil es nach den gespeicherten Informationen automatisch erledigt<br />
werden kann<br />
2. nicht s<strong>in</strong>nvoll, weil zwar die Angaben automatisch zusammengestellt werden<br />
können, aber die Gratifikation e<strong>in</strong>e <strong>in</strong>dividuelle Entscheidung der Geschäftsleitung<br />
ist<br />
3. s<strong>in</strong>nvoll, weil mehrere zusammenhängende Maßnahmen auszuführen<br />
s<strong>in</strong>d
Lösung zu Übung 2 – Def<strong>in</strong>ition e<strong>in</strong>er Prozedur kontrollieren<br />
• Zeile 1: Der Befehl lautet: CREATE PROCEDURE .<br />
• Zeile 2/3: Der Kommentar ist falsch abgeschlossen.<br />
Übungen<br />
• Zeile 4 ff.: Die geme<strong>in</strong>same Deklaration mehrerer Parameter oder Variablen ist<br />
nicht zulässig.<br />
• Zeile 4: Die E<strong>in</strong>gabe-Parameter müssen <strong>in</strong> Klammern stehen.<br />
• Zeile 5: Die Ausgabe-Parameter werden bei ke<strong>in</strong>em DBMS durch e<strong>in</strong>e<br />
OUTPUTS-Klausel angegeben.<br />
• Zeile 6 ff.: Die Variablen folgen erst h<strong>in</strong>ter AS und werden anders deklariert.<br />
• Zeile 21: Es fehlt das Semikolon am Ende der Zuweisung.<br />
• Zeile 28: Die Längenangabe im SUBSTRING ist nicht korrekt.<br />
• Zeile 31 ff.: Die E<strong>in</strong>deutigkeit der Namen von Variablen und Spalten wird teilweise<br />
nicht beachtet.<br />
Lösung zu Übung 3 – Prozedur Insert_Fahrzeugtyp erstellen<br />
• E<strong>in</strong>gabe-Parameter: TypBezeichnung Varchar(30), HerstellerName Varchar(30),<br />
HerstellerLand Varchar(30)<br />
• Ausgabe-Parameter: TypID Integer, HerstellerID Integer<br />
• Variable: werden nicht benötigt<br />
• Arbeitsablauf:<br />
1. Suche <strong>in</strong> der Tabelle Fahrzeughersteller den gegebenen HerstellerName<br />
und registriere die gefundene ID im Parameter HerstellerID.<br />
2. Wenn dieser Parameter jetzt NULL ist, fehlt der E<strong>in</strong>trag noch. Also ist er<br />
neu aufzunehmen unter Verwendung der Angaben aus HerstellerName<br />
und HerstellerLand; das liefert den Wert von HerstellerID.<br />
3. Damit kann der neue Datensatz <strong>in</strong> der Tabelle Fahrzeugtyp registriert werden;<br />
die neue ID dieses Datensatzes wird als Wert TypID zurückgegeben.<br />
Lösung zu Übung 4 – Prozedur Insert_Fahrzeugtyp erstellen<br />
Diese Variante der Lösung benutzt die Firebird-Syntax, vor allem h<strong>in</strong>sichtlich der<br />
Trennung von E<strong>in</strong>gabe- und Ausgabeparametern.<br />
CREATE OR ALTER PROCEDURE Insert_Fahrzeugtyp<br />
( TypBezeichnung VARCHAR(30),<br />
HerstellerName VARCHAR(30),<br />
HerstellerLand VARCHAR(30)<br />
)<br />
RETURNS( TypID INTEGER, HerstellerID INTEGER )<br />
AS<br />
BEGIN<br />
373
Prozeduren<br />
/* ist der Hersteller schon registriert? */<br />
SELECT ID FROM Fahrzeughersteller<br />
WHERE Name = :HerstellerName<br />
INTO :HerstellerID;<br />
/* ne<strong>in</strong>, dann als Hersteller neu aufnehmen */<br />
IF (HerstellerID IS NULL) THEN<br />
BEGIN<br />
HerstellerID = NEXT VALUE FOR Fahrzeughersteller_ID;<br />
INSERT INTO Fahrzeughersteller<br />
VALUES ( :HerstellerID, :HerstellerName, :HerstellerLand );<br />
END<br />
/* anschließend den Typ registrieren */<br />
TypID = NEXT VALUE FOR Fahrzeugtyp_ID;<br />
INSERT INTO Fahrzeugtyp<br />
VALUES ( :TypID, :TypBezeichnung, :HerstellerID );<br />
END<br />
Lösung zu Übung 5 – Prozedur Insert_Schadensfall erstellen<br />
• E<strong>in</strong>gabe-Parameter: die Angaben zum Schadensfall (Datum Date, Ort varchar(200),<br />
Beschreibung varchar(1000), Schadenshoehe number, Verletzte<br />
char(1), Mitarbeiter_ID Integer), Fahrzeugbeteiligung (ID Integer oder Kennzeichen<br />
Varchar(10), Schuldanteil Integer)<br />
• Ausgabe-Parameter: neue ID des Schadensfalls<br />
• Arbeitsablauf:<br />
1. Insert <strong>in</strong>to Schadensfall: Registriere den Schadensfall, notiere die neue ID<br />
2. Select from Fahrzeug: Suche ggf. zum Kennzeichen die Fahrzeug-ID<br />
3. Insert <strong>in</strong>to Zuordnung_SF_FZ: Registriere die Zuordnung zwischen Schadensfall<br />
und Fahrzeug<br />
• Variable werden voraussichtlich nicht benötigt.<br />
Lösung zu Übung 6 – Prozedur Change_Schadensfall erstellen<br />
• E<strong>in</strong>gabe-Parameter: Schadensfall-ID Integer, Fahrzeugbeteiligung (Kennzeichen<br />
Varchar(10), anteilige Schadenshöhe Number)<br />
• Ausgabe-Parameter: eigentlich nicht erforderlich, aber als „Erfolgsmeldung“<br />
die ID der neuen Zuordnung<br />
• Arbeitsablauf:<br />
374<br />
1. Select from Fahrzeug: Suche zum Kennzeichen die Fahrzeug-ID.<br />
2. Insert <strong>in</strong>to Zuordnung_SF_FZ: Registriere die Zuordnung zwischen Schadensfall<br />
und dem neuen Fahrzeug.<br />
3. Übernimm die ID dieser neuen Zuordnung als Ausgabe-Parameter.<br />
4. Update Zuordnung_SF_FZ: Ändere im ersten E<strong>in</strong>trag zu diesem Schadensfall<br />
die anteilige Schadenshöhe unter Berücksichtigung des neu re-
Siehe auch<br />
gistrierten beteiligten Fahrzeugs. (Der benötigte „erste E<strong>in</strong>trag“ kann<br />
durch e<strong>in</strong>e passende WHERE-Klausel direkt geändert werden; ersatzweise<br />
kann er auch durch e<strong>in</strong>en eigenen SELECT-Befehl bestimmt werden −<br />
dann wird für die ID e<strong>in</strong>e Variable benötigt.)<br />
• Variable werden voraussichtlich nicht benötigt.<br />
• Unklarheiten: Bei diesem Arbeitsablauf wird davon ausgegangen, dass zu jedem<br />
später registrierten Fahrzeug e<strong>in</strong> Teil des ursprünglichen Schadens gehört;<br />
es wird nicht berücksichtigt, dass sich die Schadensverteilung <strong>in</strong>sgesamt<br />
ändern kann. Völlig offen ist, wie sich der Verschuldensanteil beim ersten E<strong>in</strong>trag<br />
und bei jedem weiteren E<strong>in</strong>trag ändern kann. In beiden Punkten ist lediglich<br />
klar, dass alle E<strong>in</strong>zelwerte zu summieren s<strong>in</strong>d und den Maximalwert (100<br />
beim Schuldanteil bzw. den Gesamtschaden) nicht überschreiten dürfen.<br />
32.5. Siehe auch<br />
Teile dieses Kapitels beziehen sich auf Erläuterungen <strong>in</strong> den folgenden Kapiteln:<br />
• <strong>SQL</strong>-Programmierung 3<br />
• Erstellen von Views 4 im Abschnitt „E<strong>in</strong>e View mit variabler Selektion“<br />
• Testdaten erzeugen 5<br />
• E<strong>in</strong>fache Tabellenverknüpfung 6 − alle Komb<strong>in</strong>ationen aller Datensätze<br />
• Funktionen (2) 7 − u. a. RAND für Zufallszahlen<br />
• Tipps und Tricks 8<br />
Bei Wikipedia gibt es grundlegende H<strong>in</strong>weise:<br />
• Fat Client 9 als e<strong>in</strong> Pr<strong>in</strong>zip der Datenverarbeitung<br />
3 Kapitel 30 auf Seite 323<br />
4 Abschnitt 27.1.2 auf Seite 273<br />
5 Kapitel 36 auf Seite 407<br />
6 Kapitel 19 auf Seite 173<br />
7 Kapitel 16 auf Seite 141<br />
8 Kapitel 34 auf Seite 389<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Fat%20Client<br />
375
33. Trigger<br />
33.1. Die Def<strong>in</strong>ition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378<br />
33.2. Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379<br />
33.3. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 381<br />
33.4. Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382<br />
33.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386<br />
E<strong>in</strong> Trigger ist so etwas wie e<strong>in</strong>e Rout<strong>in</strong>e zur Ereignisbehandlung: Immer dann,<br />
wenn sich <strong>in</strong> der Datenbank e<strong>in</strong>e bestimmte Situation ereignet, wird e<strong>in</strong>e spezielle<br />
Prozedur automatisch ausgeführt.<br />
Schon die kurze E<strong>in</strong>leitung weist darauf h<strong>in</strong>: E<strong>in</strong> Trigger wird niemals vom Benutzer<br />
gezielt aufgerufen, sondern immer automatisch vom DBMS erledigt; dar<strong>in</strong><br />
unterscheidet er sich wesentlich von eigenen Funktionen oder Prozeduren.<br />
E<strong>in</strong>e deutsche Übersetzung wäre eigentlich Auslöser; besser passen<br />
würde „etwas, das ausgelöst wird“. Allenfalls spricht man noch von<br />
Ereignisbehandlungsrout<strong>in</strong>en. Solche Formulierungen s<strong>in</strong>d aber ungebräuchlich;<br />
der Begriff Trigger hat sich e<strong>in</strong>gebürgert.<br />
E<strong>in</strong> Trigger ist <strong>in</strong> folgenden Situationen nützlich:<br />
• Werte <strong>in</strong> e<strong>in</strong>er Zeile e<strong>in</strong>er Tabelle sollen festgelegt werden.<br />
• Dies wird vor allem benutzt, wenn ke<strong>in</strong>e Spalte als AUTO_INCREMENT festgelegt<br />
ist, siehe das Beispiel mit der nächsten ID.<br />
• Werte sollen vor dem Speichern auf ihre Plausibilität geprüft werden.<br />
• Veränderungen <strong>in</strong> der Datenbank sollen automatisch protokolliert werden.<br />
• Die Regeln der referenziellen Integrität sollen mit Hilfe der Fremdschlüssel-<br />
Beziehungen überwacht werden.<br />
Bitte haben Sie Nachsicht: Wegen der vielen Varianten bei den DBMS beschränkt<br />
sich auch dieses Kapitel bei den H<strong>in</strong>weisen und Beispielen weitgehend auf Firebird.<br />
Zusammen mit den speziellen H<strong>in</strong>weisen zur <strong>SQL</strong>-Programmierung sollten<br />
sie problemlos an andere DBMS-Regeln angepasst werden können.<br />
377
Trigger<br />
33.1. Die Def<strong>in</strong>ition<br />
33.1.1. Trigger erstellen<br />
Die Syntax für die Def<strong>in</strong>ition e<strong>in</strong>es Triggers sieht grundsätzlich so aus:<br />
CREATE OR ALTER TRIGGER FOR <br />
[ ACTIVE | INACTIVE ]<br />
{ BEFORE | AFTER } /* bei MS-<strong>SQL</strong> heißt es INSTEAD OF */<br />
{ INSERT | UPDATE | DELETE }<br />
[ POSITION ]<br />
AS<br />
BEGIN<br />
[ ]<br />
<br />
END<br />
Auch dabei s<strong>in</strong>d wieder die Besonderheiten des jeweiligen DBMS zu beachten.<br />
Aber die Ähnlichkeit zu den Rout<strong>in</strong>en fällt auf.<br />
Notwendig s<strong>in</strong>d folgende Angaben:<br />
• neben dem Befehlsnamen CREATE TRIGGER der Name des Triggers<br />
• dazu die Tabelle, zu der er gehört<br />
• mehrere Angaben, bei welchem Befehl und an welcher Stelle er wirksam se<strong>in</strong><br />
soll; Oracle kennt neben den Varianten, die bei der Syntax genannt werden,<br />
auch e<strong>in</strong>e WHEN-Bed<strong>in</strong>gung.<br />
• das Schlüsselwort AS als Zeichen für den Inhalt<br />
• die Schlüsselwörter BEGIN und END als Begrenzer für den Rumpf, also den<br />
eigentlichen Inhalt<br />
H<strong>in</strong>zu kommen die folgenden Angaben:<br />
• e<strong>in</strong>e Liste von Variablen, die <strong>in</strong>nerhalb der Rout<strong>in</strong>e verwendet werden; diese<br />
Liste steht je nach DBMS zwischen AS und BEGIN oder <strong>in</strong>nerhalb des Rumpfes<br />
• die Anweisungen, die <strong>in</strong>nerhalb der Prozedur ausgeführt werden sollen<br />
E<strong>in</strong>gabe- und Ausgabe-Parameter gibt es nicht, weil es sich um automatische<br />
Arbeitsabläufe handelt.<br />
Die folgenden Variablen stehen immer zur Verfügung. MS-<strong>SQL</strong> benutzt andere<br />
Verfahren; bei Oracle ist die Dokumentation nicht e<strong>in</strong>deutig. 1<br />
• OLD ist der Datensatz vor e<strong>in</strong>er Speicherung.<br />
1 E<strong>in</strong>erseits heißt es, dass diese Variablen immer bekannt s<strong>in</strong>d; andererseits s<strong>in</strong>d sie sowohl <strong>in</strong> der<br />
Def<strong>in</strong>ition als auch <strong>in</strong> den meisten Beispielen <strong>in</strong> e<strong>in</strong>er REFERENCING-Klausel aufgeführt.<br />
378
• NEW ist der Datensatz nach e<strong>in</strong>er Speicherung.<br />
Beispiele<br />
Mit diesen Variablen zusammen mit den Spaltennamen können die Werte „vorher“<br />
und „nachher“ geprüft und bearbeitet werden (siehe die Beispiele).<br />
Die Anweisungen <strong>in</strong>nerhalb des Triggers s<strong>in</strong>d sowohl „normale“ <strong>SQL</strong>-Befehle als<br />
auch Bestandteile der <strong>SQL</strong>-Programmiersprache. Zum Semikolon, das den Abschluss<br />
des CREATE-Befehls darstellen sollte, aber <strong>in</strong>nerhalb des Inhalts bereits<br />
für jede e<strong>in</strong>zelne Anweisung benutzt wird, beachten Sie bitte wiederum die H<strong>in</strong>weise<br />
zur <strong>SQL</strong>-Programmierung unter Anweisungen begrenzen 2 .<br />
33.1.2. Trigger deaktivieren<br />
Natürlich kann e<strong>in</strong> Trigger gelöscht werden:<br />
DROP TRIGGER ;<br />
Wenn das DBMS es ermöglicht, kann er auch vorübergehend deaktiviert werden.<br />
ALTER TRIGGER INACTIVE;<br />
Bei ALTER s<strong>in</strong>d nur die Angaben erforderlich, die sich ändern.<br />
E<strong>in</strong>en Trigger vorübergehend abzuschalten, ist vor allem während e<strong>in</strong>es längeren<br />
Arbeitsablaufs <strong>in</strong> zwei Situationen hilfreich:<br />
• E<strong>in</strong> Trigger bremst sehr stark; dann kann z. B. auf e<strong>in</strong>e Protokollierung jeder<br />
kle<strong>in</strong>en Änderung verzichtet werden.<br />
• Wenn Widersprüche <strong>in</strong> den Daten auftreten könnten, die sich nach vollständiger<br />
Erledigung „von selbst auflösen“, können sie durch die Deaktivierung<br />
vermieden werden.<br />
33.2. Beispiele<br />
33.2.1. Lege die nächste ID fest<br />
Wenn e<strong>in</strong> DBMS ke<strong>in</strong>en automatischen Zähler, also ke<strong>in</strong>e AUTO_INCREMENT-<br />
Spalte ermöglicht, kann e<strong>in</strong>e ID vom Anwender vergeben werden. Viel besser ist<br />
aber, wenn sich das DBMS selbst darum kümmert. Dies wird <strong>in</strong> der Beispieldatenbank<br />
bei Firebird und Oracle benutzt. Zum e<strong>in</strong>en wird e<strong>in</strong>e SEQUENCE def<strong>in</strong>iert;<br />
der Trigger holt den nächsten Wert:<br />
2 Kapitel 30 auf Seite 323<br />
379
Trigger<br />
Oracle-Quelltext<br />
CREATE OR REPLACE TRIGGER Abteilung_BI<br />
BEFORE INSERT ON Abteilung<br />
FOR EACH ROW<br />
WHEN (new.ID IS NULL)<br />
BEGIN<br />
SELECT Abteilung_ID.NEXTVAL INTO :new.ID FROM DUAL;<br />
END<br />
Der Trigger wird vor e<strong>in</strong>em INSERT-Befehl aufgerufen. Wenn für den neuen Datensatz<br />
− repräsentiert durch die Variable new − ke<strong>in</strong>e ID angegeben ist, dann<br />
soll der NEXTVAL gemäß der SEQUENCE Abteilung_ID geholt werden.<br />
Der Name des Triggers Abteilung_BI soll andeuten, dass er zur Tabelle Abteilung<br />
gehört und wie folgt aktiv ist: B (= Before) I (= Insert). Bei Firebird fügen wir noch<br />
e<strong>in</strong>e '0' an zur Festlegung, dass er an Position Null auszuführen ist.<br />
33.2.2. Protokolliere Zeit und Benutzer bei Speicherungen<br />
In vielen Situationen ist es wichtig, dass Änderungen <strong>in</strong> e<strong>in</strong>er Datenbank kontrolliert<br />
werden können. (Das DBMS macht es für sich sowieso, aber oft soll es<br />
auch „nach außen h<strong>in</strong>“ sichtbar se<strong>in</strong>.) Dafür gibt es solche Spalten (<strong>in</strong> der Beispieldatenbank<br />
verzichten wir darauf):<br />
CREATE TABLE /* usw. bis */<br />
Last_User VARCHAR(30),<br />
Last_Change TIMESTAMP,<br />
/* usw. */<br />
Wenn solche Spalten bei der Tabelle Mitarbeiter vorhanden wären, gäbe es e<strong>in</strong>en<br />
passenden Trigger. Ohne dass sich der Anwender darum kümmern müsste, werden<br />
Benutzer (laut Anmeldung an der Datenbank) und aktuelle Zeit immer gespeichert.<br />
380<br />
Firebird-Quelltext<br />
CREATE OR ALTER TRIGGER Mitarbeiter_BU1 FOR Mitarbeiter<br />
ACTIVE BEFORE INSERT OR UPDATE POSITION 1<br />
AS<br />
BEGIN<br />
new.Last_User = CURRENT_USER;<br />
new.Last_Change = CURRENT_TIMESTAMP;<br />
END
33.2.3. Aktualisiere abhängige Daten<br />
Zusammenfassung<br />
In vielen Fällen gibt es Abhängigkeiten zwischen Tabellen. Durch Fremdschlüssel<br />
können diese Verb<strong>in</strong>dungen bei Änderungen automatisch berücksichtigt<br />
werden. Solche Anpassungen s<strong>in</strong>d auch durch Trigger möglich.<br />
Mit e<strong>in</strong>er Prozedur (siehe dort) hatten wir e<strong>in</strong> INSERT <strong>in</strong> mehrere Tabellen ausgeführt.<br />
Ähnlich kann e<strong>in</strong> Trigger abhängige Datensätze löschen:<br />
• Der Anwender ruft e<strong>in</strong> DELETE für e<strong>in</strong>en Versicherungsvertrag auf.<br />
• Der Trigger löscht dazu den Versicherungsnehmer und das Fahrzeug.<br />
• E<strong>in</strong> Trigger zur Tabelle Fahrzeug löscht alle registrierten Schadensfälle usw.<br />
Aber das ist so komplex und hat Auswirkungen auf weitere Tabellen, dass wir<br />
darauf verzichten.<br />
In e<strong>in</strong>er „echten“ Firma müssten solche Informationen sowieso dauerhaft<br />
gespeichert werden − zum<strong>in</strong>dest so lange wie die Daten der<br />
Buchhaltung. E<strong>in</strong> Vertrag wird also im normalen Betrieb niemals gelöscht,<br />
sondern als „nicht mehr aktiv“ markiert.<br />
Aber auch <strong>in</strong> unserer Firma gibt es s<strong>in</strong>nvolle Maßnahmen:<br />
Aufgabe: Beim Ausscheiden e<strong>in</strong>es Mitarbeiters wird (soweit vorhanden) e<strong>in</strong><br />
persönlicher Dienstwagen gestrichen.<br />
Firebird-Quelltext<br />
CREATE OR ALTER TRIGGER Mitarbeiter_BD1 for Mitarbeiter<br />
ACTIVE BEFORE DELETE POSITION 1<br />
AS<br />
BEGIN<br />
UPDATE Dienstwagen<br />
SET Mitarbeiter_ID = NULL<br />
WHERE Mitarbeiter_ID = old.ID;<br />
END<br />
Bevor e<strong>in</strong> Mitarbeiter gelöscht wird, wird geprüft, ob ihm e<strong>in</strong> persönlicher<br />
Dienstwagen zugewiesen ist. Dieser Vermerk wird (sofern vorhanden) auf NULL<br />
gesetzt; die WHERE-Klausel greift dabei auf die ID des bisherigen Datensatzes<br />
der Tabelle Mitarbeiter zu.<br />
33.3. Zusammenfassung<br />
Dieses Kapitel erläuterte Trigger als automatisch ausgeführte Rout<strong>in</strong>en:<br />
• E<strong>in</strong> Trigger wird vor oder nach e<strong>in</strong>em Speichern-Befehl (Insert, Update, Delete)<br />
ausgeführt.<br />
381
Trigger<br />
• Er dient zur automatischen Prüfung oder Vervollständigung von Werten.<br />
• Damit vere<strong>in</strong>facht er die Arbeit für den Anwender und<br />
• s<strong>org</strong>t für die E<strong>in</strong>haltung von Sicherheitsregeln <strong>in</strong> der Datenbank.<br />
i H<strong>in</strong>weis<br />
Bedenken Sie die konkreten Situationen aus den letzten Kapiteln und den<br />
Übungen. Diese Zusammenhänge zwischen Tabellen müssen bei der Planung<br />
e<strong>in</strong>er Datenbank mit Fremdschlüsseln, Prozeduren und Triggern für<br />
„gleichzeitiges“ E<strong>in</strong>fügen, Ändern oder Löschen und bei notwendigen Maßnahmen<br />
<strong>in</strong> mehreren Tabellen berücksichtigt werden.<br />
Auch <strong>in</strong> dieser H<strong>in</strong>sicht bietet unsere Beispieldatenbank nur e<strong>in</strong>en kle<strong>in</strong>en<br />
E<strong>in</strong>blick <strong>in</strong> das, was mit <strong>SQL</strong> möglich ist.<br />
33.4. Übungen<br />
Die Lösungen beg<strong>in</strong>nen auf Seite 384.<br />
Übung 1 – Trigger def<strong>in</strong>ieren<br />
Welche der folgenden Aussagen s<strong>in</strong>d wahr, welche s<strong>in</strong>d falsch?<br />
1. E<strong>in</strong> Trigger wird durch e<strong>in</strong>e Prozedur aufgerufen.<br />
2. E<strong>in</strong> Trigger hat ke<strong>in</strong>e E<strong>in</strong>gabe-Parameter.<br />
3. E<strong>in</strong> Trigger hat ke<strong>in</strong>e (lokalen) Variablen.<br />
4. E<strong>in</strong> Trigger kann nur die Tabelle bearbeiten, der er zugeordnet ist.<br />
5. E<strong>in</strong> Trigger kann sowohl „normale“ <strong>SQL</strong>-Befehle als auch Elemente der<br />
<strong>SQL</strong>-Programmierung enthalten.<br />
Übung 2 – Trigger verwenden<br />
In welchen der folgenden Situationen ist e<strong>in</strong> Trigger s<strong>in</strong>nvoll, <strong>in</strong> welchen nicht<br />
bzw. nicht möglich? Gehen Sie davon aus, dass alle benötigten Informationen <strong>in</strong><br />
der Firmen-Datenbank zur Speicherung v<strong>org</strong>esehen s<strong>in</strong>d.<br />
382<br />
1. Wenn e<strong>in</strong> Versicherungsvertrag neu aufgenommen wird, sollen auch alle<br />
Datensätze <strong>in</strong> zugeordneten Tabellen neu aufgenommen werden.<br />
2. V<strong>org</strong>abewerte für e<strong>in</strong>zelne Spalten können e<strong>in</strong>getragen werden (wie bei e<strong>in</strong>em<br />
DEFAULT-Wert).<br />
3. Wenn e<strong>in</strong>e Abteilung gelöscht (d. h. geschlossen) wird, sollen auch alle ihre<br />
Mitarbeiter gelöscht werden (weil die Abteilung ausgelagert wird).
Übungen<br />
4. Wenn e<strong>in</strong> Versicherungsvertrag gelöscht wird, sollen auch alle Datensätze<br />
<strong>in</strong> zugeordneten Tabellen bearbeitet werden: Datensätze, die noch <strong>in</strong> anderem<br />
Zusammenhang benutzt werden (z. B. Mitarbeiter), bleiben unverändert;<br />
Datensätze, die sonst nicht mehr benutzt werden (z. B. Fahrzeug),<br />
werden gelöscht.<br />
Übung 3 – Trigger-Def<strong>in</strong>ition kontrollieren<br />
Nennen Sie <strong>in</strong> der folgenden Def<strong>in</strong>ition Punkte, die unabhängig vom <strong>SQL</strong>-<br />
Dialekt falsch s<strong>in</strong>d. Je nach DBMS s<strong>in</strong>d noch andere Punkte falsch, danach wird<br />
aber nicht gefragt. Der Trigger soll folgende Aufgabe erledigen:<br />
• Bei der Aufnahme e<strong>in</strong>es neues Fahrzeugtyps ist der Hersteller zu überprüfen.<br />
• Ist der angegebene Hersteller (d. h. die Hersteller_ID) <strong>in</strong> der Tabelle Fahrzeughersteller<br />
gespeichert? Wenn ja, dann ist nichts weiter zu erledigen.<br />
• Wenn ne<strong>in</strong>, dann ist e<strong>in</strong> Fahrzeughersteller mit dem Namen „unbekannt“ zu<br />
suchen.<br />
• Wenn dieser vorhanden ist, ist dessen ID als Hersteller_ID zu übernehmen.<br />
• Andernfalls ist er mit der angegebenen Hersteller_ID neu als „unbekannt“<br />
zu registrieren.<br />
1. CREATE Fahrzeugtyp_Check_Hersteller AS TRIGGER<br />
2. TO Fahrzeugtyp<br />
3. FOR INSERT<br />
4. AS<br />
5. DECLARE VARIABLE INTEGER fh_id<br />
6. BEGIN<br />
7. -- Initialisierung<br />
8. fh_id = NULL;<br />
9. -- prüfen, ob ID vorhanden ist<br />
10. SELECT ID<br />
11. FROM Fahrzeughersteller<br />
12. WHERE ID = new.Hersteller_ID<br />
13. -- wenn sie nicht gefunden wurde<br />
14. IF (fh_id IS NULL) THEN<br />
15. -- suche stattdessen den Hersteller ’unbekannt’<br />
16. SELECT ID<br />
17. FROM Fahrzeughersteller<br />
18. WHERE Name = ’unbekannt’<br />
19. -- wenn auch dazu die ID nicht gefunden wurde<br />
20. IF (fh_id IS NULL) THEN<br />
21. fh_id = new.Hersteller_ID;<br />
22. INSERT INTO Fahrzeughersteller<br />
23. VALUES ( :fh_id, ’unbekannt’, ’unbekannt’ )<br />
24. END<br />
25. END<br />
26. new.Hersteller_ID = :fh_id<br />
27. END<br />
383
Trigger<br />
Übung 4 – Trigger-Def<strong>in</strong>ition kontrollieren<br />
Berichtigen Sie den Code der vorigen Aufgabe.<br />
Übung 5 – Trigger Mitarbeiter_On_Delete erstellen<br />
Skizzieren Sie den Inhalt e<strong>in</strong>es Triggers Mitarbeiter_On_Delete für die Tabelle<br />
Mitarbeiter, der folgende Aufgaben ausführen soll:<br />
• Zu behandeln ist die Situation, dass e<strong>in</strong> Mitarbeiter aus der Firma ausscheidet,<br />
also zu löschen ist.<br />
• Suche die ID des zugehörigen Abteilungsleiters.<br />
• In allen Tabellen und Datensätzen, <strong>in</strong> denen der Mitarbeiter als Sachbearbeiter<br />
registriert ist, ist stattdessen der Abteilungsleiter als „zuständig“ e<strong>in</strong>zutragen.<br />
Ignorieren Sie die Situation, dass der Abteilungsleiter selbst ausscheidet.<br />
Übung 6 – Trigger Mitarbeiter_On_Delete erstellen<br />
Erstellen Sie den Trigger Mitarbeiter_On_Delete aus der vorigen Übung.<br />
Lösungen<br />
Die Aufgaben beg<strong>in</strong>nen auf Seite 382.<br />
Lösung zu Übung 1 – Trigger def<strong>in</strong>ieren<br />
Die Aussagen 2, 5 s<strong>in</strong>d wahr. Die Aussagen 1, 3, 4 s<strong>in</strong>d falsch.<br />
Lösung zu Übung 2 – Trigger verwenden<br />
1. nicht möglich, weil die Werte für die Datensätze <strong>in</strong> den zugeordneten Tabellen<br />
nicht bekannt s<strong>in</strong>d<br />
2. s<strong>in</strong>nvoll, aber die Festlegung per DEFAULT ist vorzuziehen<br />
3. zur Not s<strong>in</strong>nvoll, wenn die Fremdschlüssel nicht durch ON DELETE geeignet<br />
gesteuert werden können; aber dies ist riskannt wegen der „Nebenwirkungen“,<br />
siehe Übung 5/6<br />
4. zur Not möglich, aber eher weniger s<strong>in</strong>nvoll wegen der vielen zugeordneten<br />
Tabellen und vieler „Nebenwirkungen“<br />
Lösung zu Übung 3 – Trigger-Def<strong>in</strong>ition kontrollieren<br />
• Zeile 1: Der Befehl lautet: CREATE TRIGGER .<br />
384
• Zeile 2: Die Tabelle steht je nach DBMS bei ON oder FOR, nicht bei TO.<br />
Übungen<br />
• Zeile 3: Die Aktivierung BEFORE oder AFTER (bei MS-<strong>SQL</strong> INSTEAD OF) fehlt.<br />
• Zeile 5: Zuerst kommt der Name der Variablen, dann der Datentyp; das abschließende<br />
Semikolon fehlt.<br />
• Zeile 10/12 und 16/18: Es fehlt die Übergabe der ID, die durch SELECT gefunden<br />
wurde, an die Variable fh_id. Je nach DBMS erfolgt dies unterschiedlich,<br />
aber es muss gemacht werden, damit das Ergebnis der Abfrage <strong>in</strong> der Variablen<br />
gespeichert und danach verwendet werden kann.<br />
• Zeile 12, 18, 23, 26: Es fehlt jeweils das abschließende Semikolon.<br />
• Zeile 14 und 20: Mehrere Anweisungen s<strong>in</strong>d <strong>in</strong> BEGIN...END e<strong>in</strong>zuschließen.<br />
Lösung zu Übung 4 – Trigger-Def<strong>in</strong>ition kontrollieren<br />
Firebird-Quelltext<br />
CREATE OR ALTER TRIGGER Fahrzeugtyp_Check_Hersteller<br />
FOR Fahrzeugtyp<br />
ACTIVE BEFORE INSERT POSITION 10<br />
AS<br />
DECLARE VARIABLE fh_id INTEGER;<br />
BEGIN<br />
fh_id = NULL;<br />
SELECT ID<br />
FROM Fahrzeughersteller<br />
WHERE ID = new.Hersteller_ID<br />
INTO :fh_id;<br />
IF (fh_id IS NULL) THEN<br />
BEGIN<br />
SELECT Id<br />
FROM Fahrzeughersteller<br />
WHERE Name = ’unbekannt’<br />
INTO :fh_id;<br />
IF (fh_id IS NULL) THEN<br />
BEGIN<br />
fh_id = new.Hersteller_ID;<br />
INSERT INTO Fahrzeughersteller<br />
VALUES ( :fh_id, ’unbekannt’, ’unbekannt’ );<br />
END<br />
END<br />
new.Hersteller_ID = :fh_id;<br />
END<br />
Lösung zu Übung 5 – Trigger Mitarbeiter_On_Delete erstellen<br />
• Wir brauchen e<strong>in</strong>en Trigger BEFORE DELETE zur Tabelle Mitarbeiter.<br />
• Wir brauchen e<strong>in</strong>e Variable für die ID des Abteilungsleiters.<br />
385
Trigger<br />
• Durch e<strong>in</strong>en SELECT auf die Abteilung_ID des ausscheidenden Mitarbeiters<br />
bekommen wir die ID des Abteilungsleiters; die wird <strong>in</strong> der Variablen gespeichert.<br />
• Für alle betroffenen Tabellen ist e<strong>in</strong> UPDATE zu machen, durch das die<br />
Mitarbeiter_ID durch die ID des Abteilungsleiters ersetzt wird. Dies betrifft<br />
die Tabellen Schadensfall, Versicherungsvertrag.<br />
• Die Tabelle Dienstwagen kann unberücksichtigt bleiben; das erledigt der Trigger<br />
aus dem obigen Beispiel „Aktualisiere abhängige Daten“.<br />
Lösung zu Übung 6 – Trigger Mitarbeiter_On_Delete erstellen<br />
Firebird-Quelltext<br />
CREATE OR ALTER TRIGGER Mitarbeiter_On_Delete<br />
FOR Mitarbeiter<br />
ACTIVE BEFORE DELETE POSITION 10<br />
AS<br />
DECLARE VARIABLE ltr_id INTEGER;<br />
BEGIN<br />
ltr_id = NULL;<br />
-- hole die ID des Abteilungsleiters <strong>in</strong> die Variable<br />
SELECT ID<br />
FROM Mitarbeiter<br />
WHERE Abteilung_id = old.Abteilung_ID<br />
AND Ist_Leiter = ’J’<br />
INTO :ltr_id;<br />
-- ändere die Mitarbeiter_ID für die Schadensfälle<br />
UPDATE Schadensfall<br />
SET Mitarbeiter_ID = :ltr_id<br />
WHERE Mitarbeiter_ID = old.ID;<br />
-- ändere die Mitarbeiter_ID für die Verträge<br />
UPDATE Versicherungsvertrag<br />
SET Mitarbeiter_ID = :ltr_id<br />
WHERE Mitarbeiter_ID = old.ID;<br />
END<br />
33.5. Siehe auch<br />
Verschiedene E<strong>in</strong>zelheiten stehen <strong>in</strong> folgenden Kapiteln:<br />
• Fremdschlüssel-Beziehungen 3<br />
• <strong>SQL</strong>-Programmiersprache 4<br />
3 Kapitel 29 auf Seite 305<br />
4 Kapitel 30 auf Seite 323<br />
386
• Prozeduren 5<br />
Über Wikipedia s<strong>in</strong>d grundlegende Informationen zu erhalten:<br />
• Datenbanktrigger 6<br />
• Ereignisse 7 <strong>in</strong> der Programmierung, Behandlung von Ereignissen<br />
• Referentielle Integrität 8<br />
5 Kapitel 32 auf Seite 357<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/Datenbanktrigger<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Ereignis%20(Programmierung)<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Referentielle%20Integrit%c3%a4t<br />
Siehe auch<br />
387
34. Tipps und Tricks<br />
34.1. Die letzte ID abfragen . . . . . . . . . . . . . . . . . . . . . . . . . 389<br />
34.2. Tabellenstruktur auslesen . . . . . . . . . . . . . . . . . . . . . . 391<br />
34.3. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395<br />
Dieses Kapitel stellt e<strong>in</strong> paar nützliche Verfahren vor, die <strong>in</strong> ke<strong>in</strong>es der anderen<br />
Kapitel passten und „zu wenig Stoff“ für e<strong>in</strong> eigenes Kapitel enthalten.<br />
34.1. Die letzte ID abfragen<br />
Wenn bei e<strong>in</strong>er Tabelle für die Spalte ID die neuen Werte automatisch als<br />
AUTO_INCREMENT vergeben werden, benötigt man den neu vergebenen Wert<br />
häufig für die korrekte Behandlung der Fremdschlüssel-Beziehungen.<br />
34.1.1. Firebird: Rückgabewerte benutzen<br />
Seit Firebird 2.x kann der INSERT-Befehl Maßnahmen, die durch e<strong>in</strong>en Before-<br />
Insert-Trigger ausgeführt werden – also auch die Zuweisung e<strong>in</strong>er Sequence –<br />
durch e<strong>in</strong>e RETURNING-Klausel abfragen:<br />
INSERT INTO ( )<br />
VALUES ( )<br />
RETURNING [ INTO ]<br />
Die INTO-Klausel wird bei Aufgaben <strong>in</strong>nerhalb der <strong>SQL</strong>-Programmierung benutzt.<br />
Beispiel für direkten Aufruf:<br />
Firebird-Quelltext<br />
INSERT INTO Fahrzeug ( Kennzeichen, Fahrzeugtyp_ID )<br />
VALUES ( ’B-JT 1234’, 7 )<br />
RETURNING ID, Farbe;<br />
389
Tipps und Tricks<br />
------ Inserted values ------<br />
ID = 652<br />
FARBE = <br />
Die RETURNING-Klausel gibt es auch bei DB2 und Oracle.<br />
34.1.2. MS-<strong>SQL</strong>: spezielle Abfragen<br />
Je nach Situation wird nach e<strong>in</strong>em INSERT <strong>in</strong> e<strong>in</strong>em weiteren Befehl der neu<br />
zugeordnete Wert abgefragt.<br />
• Variante 1 mit e<strong>in</strong>er lokalen Variablen:<br />
SELECT @@IDENTITY<br />
Dies liefert den letzten für e<strong>in</strong>e IDENTITY-Spalte vergebenen Wert der aktuellen<br />
Verb<strong>in</strong>dung zurück. Hierbei wird ke<strong>in</strong>e Tabelle angegeben; es kann aber<br />
auch der Wert e<strong>in</strong>er anderen Tabelle geliefert werden (beispielsweise wenn <strong>in</strong>direkt<br />
über e<strong>in</strong>en Trigger e<strong>in</strong>e weitere Tabelle bearbeitet wird).<br />
• Variante 2 mit e<strong>in</strong>er Funktion:<br />
SELECT SCOPE_IDENTITY();<br />
Dies liefert den letzten für e<strong>in</strong>e IDENTITY-Spalte vergebenen Wert der aktuellen<br />
Verb<strong>in</strong>dung zurück.<br />
• Variante 3 mit e<strong>in</strong>er Funktion, die sich auf e<strong>in</strong>e bestimmte Tabelle bezieht:<br />
SELECT IDENT_CURRENT(’Fahrzeug’);<br />
Zu den Unterschieden zwischen diesen Verfahren siehe MSDN: @@IDENTITY 1 .<br />
34.1.3. My<strong>SQL</strong>: spezielle Abfragen<br />
Unmittelbar nach e<strong>in</strong>em INSERT wird <strong>in</strong> e<strong>in</strong>em weiteren Befehl der neu zugeordnete<br />
Wert mit e<strong>in</strong>er speziellen Funktion abgefragt:<br />
SELECT LAST_INSERT_ID();<br />
1 http://msdn.microsoft.com/de-de/library/ms187342.aspx<br />
390
Tabellenstruktur auslesen<br />
Dies liefert immer den letzten für e<strong>in</strong>e AUTO_INCREMENT-Spalte vergebenen<br />
Wert der aktuellen Verb<strong>in</strong>dung zurück. Hierbei wird ke<strong>in</strong>e Tabelle angegeben. Je<br />
nach Arbeitsumgebung gibt es auch die <strong>in</strong>terne Funktion mysql_<strong>in</strong>sert_id(), die<br />
manchmal (siehe Dokumentation) abweichende Ergebnisse liefert.<br />
34.1.4. Oracle: Wert abfragen<br />
In Oracle dient die SEQUENCE zur Vergabe e<strong>in</strong>es automatischen Zählers.<br />
Mit .NEXTVAL wird der nächste Wert zugewiesen, mit<br />
.CURRVAL der aktuelle Wert abgefragt.<br />
Im Skript zur Beispieldatenbank wird für die Tabelle Mitarbeiter e<strong>in</strong>e SEQUENCE<br />
Mitarbeiter_ID def<strong>in</strong>iert und verwendet. Für e<strong>in</strong>en neuen Mitarbeiter erhält<br />
man so die zugewiesene ID:<br />
Oracle Quelltext<br />
INSERT INTO MITARBEITER<br />
( ID, PERSONALNUMMER, NAME, VORNAME /* und weitere Angaben */ )<br />
VALUES ( Mitarbeiter_ID.NEXTVAL, ’80017’, ’Schicker’, ’Madela<strong>in</strong>e’ );<br />
SELECT CONCAT(’Last ID = ’ , TO_CHAR(Mitarbeiter_ID.CURRVAL)) FROM dual;<br />
1 row <strong>in</strong>serted;<br />
Last ID = 23<br />
Weitere Informationen über Sequenzen bei Oracle siehe Sequenzen 2 .<br />
34.2. Tabellenstruktur auslesen<br />
Die Tabellen, Spalten, Views, Fremdschlüssel usw. werden <strong>in</strong> der Datenbank <strong>in</strong><br />
systemeigenen Strukturen gespeichert. Sie können genauso wie die „eigenen“<br />
Daten per SELECT abgefragt werden. Auch wenn der <strong>SQL</strong>-Standard dafür mit<br />
INFORMATION_SCHEMA e<strong>in</strong> detailliertes Muster v<strong>org</strong>esehen hat, gibt es Unterschiede<br />
zwischen den DBMS.<br />
34.2.1. DB2<br />
Bei DB2 spricht man von Katalog-Tabellen. Sie bef<strong>in</strong>den sich im Schema<br />
SYSIBM. E<strong>in</strong>ige Beispiel-Zugriffe auf die Katalog-Tabellen.<br />
2 http://de.wikibooks.<strong>org</strong>/wiki/Oracle%3a%20Sequenzen<br />
391
Tipps und Tricks<br />
-- Liste aller Tabellen <strong>in</strong> e<strong>in</strong>er Datenbank, die von P123 erstellt wurden.<br />
SELECT creator, name<br />
FROM sysibm.systables<br />
WHERE creator = ’P123’<br />
AND type = ’T’<br />
--<br />
-- Liste aller Views <strong>in</strong> e<strong>in</strong>er Datenbank, die von P123 erstellt wurden.<br />
SELECT creator, name<br />
FROM sysibm.systables<br />
WHERE creator = ’P123’<br />
AND type = ’V’<br />
--<br />
-- Liste aller Fremdschlüssel-Beziehungen, die von P123 erstellt wurden.<br />
-- TBNAME ist die (Detail-)Tabelle mit dem Fremdschlüssel<br />
-- REFTBNAME ist die (Master-)Tabelle, auf die der Fremdschlüssel verweist<br />
SELECT creator, tbname, reftbname<br />
FROM sysibm.sysrels<br />
WHERE creator = ’P123’<br />
34.2.2. Firebird<br />
Bei Firebird und Interbase beg<strong>in</strong>nen alle Bezeichner der „Systemtabellen“ mit<br />
RDB$.<br />
392<br />
-- listet die Tabellen e<strong>in</strong>er Datenbank auf<br />
SELECT rdb$relation_name FROM rdb$relations<br />
WHERE rdb$system_flag = 0<br />
AND rdb$relation_type = 0;<br />
-- listet die Views e<strong>in</strong>er Datenbank auf<br />
SELECT rdb$relation_name FROM rdb$relations<br />
WHERE rdb$system_flag = 0<br />
AND rdb$relation_type = 1;<br />
-- listet die Spaltennamen mit den dazugehörigen Datentypen e<strong>in</strong>er Tabelle auf<br />
SELECT rdb$relation_name, rdb$field_name, rdb$field_source<br />
FROM rdb$relation_fields<br />
WHERE rdb$system_flag = 0<br />
AND rdb$view_context IS NULL<br />
ORDER BY rdb$relation_name, rdb$field_position<br />
-- listet die Fremdschlüssel e<strong>in</strong>er Tabelle auf (i.d.R. nur zwischen je e<strong>in</strong>er Spalte)<br />
SELECT rel.RDB$Constra<strong>in</strong>t_Name AS ForeignKey,<br />
co1.RDB$Relation_Name AS DetailTable,<br />
CASE idx.RDB$Segment_Count<br />
WHEN 1 THEN fl1.RDB$Field_Name<br />
ELSE idx.RDB$Segment_Count<br />
END AS Fields,<br />
rel.RDB$Const_Name_UQ AS PrimaryKey,<br />
co2.RDB$Relation_Name AS MasterTable,<br />
fl2.RDB$Field_Name AS MasterField<br />
FROM RDB$Ref_Constra<strong>in</strong>ts rel
* RDB$Relation_Constra<strong>in</strong>ts wird zwei Mal benötigt:<br />
als co1 für den Tabellennamen des ForeignKey<br />
als co2 für den Tabellennamen des PrimaryKey,<br />
auf den sich der ForeignKey bezieht */<br />
/* ebenso RDB$Index_Segments<br />
als fl1 für den Spaltennamen des FK<br />
als fl2 für den Spaltennamen des PK */<br />
JOIN RDB$Relation_Constra<strong>in</strong>ts co1<br />
ON rel.RDB$Constra<strong>in</strong>t_Name = co1.RDB$Constra<strong>in</strong>t_Name<br />
JOIN RDB$Indices idx<br />
ON rel.RDB$Constra<strong>in</strong>t_Name = idx.RDB$Index_Name<br />
JOIN RDB$Relation_Constra<strong>in</strong>ts co2<br />
ON rel.RDB$Const_Name_UQ = co2.RDB$Constra<strong>in</strong>t_Name<br />
JOIN RDB$Index_Segments fl1<br />
ON rel.RDB$Constra<strong>in</strong>t_Name = fl1.RDB$Index_Name<br />
JOIN RDB$Index_Segments fl2<br />
ON rel.RDB$Const_Name_UQ = fl2.RDB$Index_Name<br />
WHERE (NOT rel.RDB$Constra<strong>in</strong>t_Name LIKE ’RDB$’)<br />
ORDER BY rel.RDB$Constra<strong>in</strong>t_Name<br />
/* Liste der Indizes<br />
die Zugehörigkeit zu den Tabellen und die Bedeutung<br />
ergibt sich aus dem Namen des Index:<br />
PK = Primary Key<br />
FK = Foreign Key<br />
MI usw. = Foreign Key auf die Tabelle mi = Mitarbeiter<br />
Unter anderen Bed<strong>in</strong>gungen braucht man geeignete JOINs. */<br />
SELECT * FROM RDB$Index_Segments<br />
WHERE rdb$<strong>in</strong>dex_name NOT STARTING WITH ’RDB$’<br />
ORDER BY rdb$<strong>in</strong>dex_name, rdb$field_position<br />
34.2.3. MS-<strong>SQL</strong> Server<br />
Tabellenstruktur auslesen<br />
-- listet die Tabellen e<strong>in</strong>er Datenbank auf<br />
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES<br />
WHERE TABLE_TYPE = ’BASE TABLE’<br />
-- listet die Views e<strong>in</strong>er Datenbank auf<br />
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES<br />
WHERE TABLE_TYPE = ’VIEW’<br />
-- listet die Spaltennamen mit den dazugehörigen Datentypen e<strong>in</strong>er Tabelle auf<br />
SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS<br />
WHERE TABLE_NAME = ’Tabellenname’<br />
34.2.4. Oracle<br />
In Oracle gibt es fast alle Dictionary-Views <strong>in</strong> dreifacher Ausführung:<br />
• Views mit dem Präfix USER_ zeigen die eigenen Objekte an, also die Objekte,<br />
die im eigenen Schema erstellt s<strong>in</strong>d.<br />
393
Tipps und Tricks<br />
• Views mit dem Präfix ALL_ zeigen alle Objekte an, für die man das Zugriffsrecht<br />
hat. Das s<strong>in</strong>d die Objekte im eigenen Schema und auch Objekte <strong>in</strong> anderen<br />
Schemata, für die man durch den GRANT-Befehl e<strong>in</strong>e Zugriffsberechtigung<br />
erhalten hat.<br />
• Auf Views mit dem Präfix DBA_ kann man nur zugreifen, wenn man das Recht<br />
zur Adm<strong>in</strong>istration hat. In dieser View werden alle Objekte der gesamten Datenbank<br />
angezeigt, also auch die, auf die man ke<strong>in</strong>e Zugriffsrechte besitzt.<br />
Alle Oracle Dictionary-Views s<strong>in</strong>d im Manual Reference (nicht: <strong>SQL</strong>-Reference)<br />
beschrieben.<br />
Beispiele:<br />
-- listet alle eigenen Tabellen e<strong>in</strong>er Datenbank auf.<br />
SELECT TABLE_NAME FROM USER_TABLES<br />
-- listet alle Tabellen auf, auf die man zugriffsberechtigt ist.<br />
SELECT TABLE_NAME FROM ALL_TABLES<br />
-- listet alle Tabellen auf, die es <strong>in</strong> der gesamten Datenbank gibt.<br />
SELECT TABLE_NAME FROM DBA_TABLES<br />
-- listet die Views auf<br />
SELECT VIEW_NAME FROM USER_VIEWS<br />
-- listet die Indizes auf. In der Spalte UNIQUENESS ist angegeben,<br />
-- ob es sich um e<strong>in</strong>en e<strong>in</strong>deutigen Index handelt (UNIQUE)<br />
-- oder ob die Index-Werte <strong>in</strong> der Tabelle mehrmals vorkommen dürfen (NONUNIQUE)<br />
SELECT INDEX_NAME, TABLE_NAME, UNIQUENESS FROM USER_INDEXES<br />
-- listet die Spaltennamen mit den dazugehörigen Datentypen e<strong>in</strong>er Tabelle auf<br />
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH,<br />
DATA_PRECISION, DATA_SCALE, NULLABLE, COLUMN_ID<br />
FROM USER_TAB_COLUMNS<br />
WHERE TABLE_NAME = ’Tabellenname’<br />
ORDER BY COLUMN_ID<br />
-- Liste aller Fremdschlüssel-Beziehungen und anderen Constra<strong>in</strong>ts<br />
-- Fremdschlüssel-Beziehungen haben den Typ ’R’<br />
-- Bei DELETE_RULE =’CASCADE’ handelt es sich<br />
um e<strong>in</strong>e Beziehung mit Löschweitergabe<br />
-- bei ’NO ACTION’ um e<strong>in</strong>e Beziehung mit Lösch-Restriktion.<br />
SELECT CONSTRAINT_NAME, TABLE_NAME, R_CONSTRAINT_NAME,<br />
REFERENCED_TABLE, DELETE_RULE<br />
FROM USER_CONSTRAINTS<br />
WHERE CONSTRAINT_TYPE = ’R’<br />
Es gibt noch e<strong>in</strong>e weitere Gruppe von Dictionary-Views. Diese geben über den<br />
Zustand der Datenbank Auskunft. Diese Views haben den Präfix V$; sie s<strong>in</strong>d<br />
hauptsächlich für die Adm<strong>in</strong>istration wichtig.<br />
Beispiele:<br />
394
-- Anzeigen aller Sessions, die gerade aktiv s<strong>in</strong>d oder zuletzt aktiv waren<br />
SELECT * FROM V$SESSION<br />
-- Anzeigen von Informationen über die aktuelle Datenbank-Instanz.<br />
-- Datenbank-Name, auf welchem Server die Datenbank läuft, Oracle-Version,<br />
-- seit wann die Datenbank aktiv ist, ob aktuell Log<strong>in</strong>s möglich s<strong>in</strong>d<br />
-- uns <strong>in</strong> welchem Status sich die Datenbank bef<strong>in</strong>det.<br />
SELECT INSTANCE_NAME, HOST_NAME, VERSION, STARTUP_TIME, LOGINS, DATABASE_STATUS<br />
FROM V$INSTANCE<br />
Siehe auch<br />
Weitere Erläuterungen dazu stehen im Wikibook über Oracle unter Table 3 ; dort<br />
gibt es auch e<strong>in</strong>e Anleitung, um die E<strong>in</strong>fügereihenfolge 4 zu ermitteln.<br />
34.3. Siehe auch<br />
Weitere Erläuterungen s<strong>in</strong>d <strong>in</strong> den folgenden Kapiteln zu f<strong>in</strong>den:<br />
• Fremdschlüssel-Beziehungen 5<br />
• <strong>SQL</strong>-Programmierung 6<br />
3 http://de.wikibooks.<strong>org</strong>/wiki/Oracle%3a%20Table<br />
4 http://de.wikibooks.<strong>org</strong>/wiki/Oracle%3a%20Table%23E<strong>in</strong>f%c3%bcgereihenfolge%<br />
20ermitteln<br />
5 Kapitel 29 auf Seite 305<br />
6 Kapitel 30 auf Seite 323<br />
395
35. Änderung der<br />
Datenbankstruktur<br />
35.1. Spalten h<strong>in</strong>zufügen und ändern . . . . . . . . . . . . . . . . . . 397<br />
35.2. E<strong>in</strong>schränkungen auf Spalten . . . . . . . . . . . . . . . . . . . . 399<br />
35.3. Indizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401<br />
35.4. Fremdschlüssel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402<br />
35.5. Weitere Anpassungen . . . . . . . . . . . . . . . . . . . . . . . . . 402<br />
35.6. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 405<br />
35.7. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405<br />
In diesem Kapitel werden wir die Erkenntnisse aus den vorhergehenden Kapiteln<br />
benutzen, um die Beispieldatenbank zu ergänzen und zu erweitern, wie es dort<br />
<strong>in</strong> den Anmerkungen angesprochen wurde.<br />
35.1. Spalten h<strong>in</strong>zufügen und ändern<br />
Dazu wird der Befehl ALTER TABLE ... ADD/ALTER verwendet.<br />
Die hier besprochenen Änderungen sowie weitere Angaben zu Spalten s<strong>in</strong>d im<br />
Skript-Spalten 1 zusammengefasst.<br />
35.1.1. Neue Spalten e<strong>in</strong>fügen<br />
Aufgabe: Die Tabelle Versicherungsvertrag benötigt mehrere zusätzliche Spalten<br />
wegen Prämienberechnung und Schadenfreiheitsrabatt.<br />
ALTER TABLE Versicherungsvertrag<br />
ADD [COLUMN] Basispraemie DECIMAL<br />
DEFAULT 500 NOT NULL<br />
CONSTRAINT Vertrag_Basispraemie_Check CHECK(Basispraemie > 0),<br />
ADD [COLUMN] Praemiensatz INTEGER<br />
1 Erläuterungen zu diesem und den anderen Skripts stehen am Ende dieses Kapitels.<br />
397
Änderung der Datenbankstruktur<br />
DEFAULT 100 NOT NULL<br />
CONSTRAINT Vertrag_Praemiensatz_Check CHECK(Praemiensatz > 0),<br />
ADD [COLUMN] Praemienaenderung DATE;<br />
Die CHECK-Prüfungen werden als CONSTRAINT mit Namen festgelegt. Ob das<br />
Wort COLUMN benötigt wird oder weggelassen werden kann oder muss, hängt<br />
(wie mehrfach gesagt) vom DBMS ab.<br />
Der V<strong>org</strong>abewert wird bei den e<strong>in</strong>zelnen Verträgen noch an die Versicherungsart<br />
angepasst, siehe später unter „Weitere Anpassungen“, ebenso Prämiensatz<br />
und Prämienänderung auf e<strong>in</strong>en e<strong>in</strong>heitlichen Stand zum 1. Januar 2007. Danach<br />
werden sie durch e<strong>in</strong>e Stored Procedure nach und nach auf den aktuellen<br />
Stand gebracht − siehe das Beispiel für e<strong>in</strong>e Prozedur „automatisches UPDATE<br />
gemäß Bed<strong>in</strong>gungen“.<br />
Normalerweise sollte der V<strong>org</strong>abewert automatisch überall e<strong>in</strong>getragen werden.<br />
Falls das nicht geschieht, genügt e<strong>in</strong> e<strong>in</strong>ziger UPDATE-Befehl dafür.<br />
35.1.2. Datentyp e<strong>in</strong>er Spalte ändern<br />
Dabei ist unbed<strong>in</strong>gt darauf zu achten, dass die bisherigen Daten zum neuen Datentyp<br />
passen, d. h. also automatisch (implizit) zu konvertieren s<strong>in</strong>d.<br />
Aufgabe: In der Tabelle Abteilung ist die Spalte Kuerzel als VARCHAR(10) festgelegt.<br />
Tatsächlich s<strong>in</strong>d die Kürzel maximal vier Zeichen lang. Deshalb soll die<br />
Def<strong>in</strong>ition an die Realität angepasst werden.<br />
My<strong>SQL</strong>-Quelltext<br />
ALTER TABLE Abteilung<br />
ALTER COLUMN Kuerzel TYPE CHAR(4);<br />
Bei MySql wird dieser Befehl ausgeführt. Firebird erkennt die Möglichkeit der<br />
impliziten Konvertierung durch Abschneiden zu langer Werte nicht. (Zu lange<br />
Werte gibt es sowieso nur <strong>in</strong> der Theorie, aber nicht wirklich.) Dann s<strong>in</strong>d, wie<br />
schon bei den DDL-E<strong>in</strong>zelheiten für ALTER COLUMN erwähnt, mehrere E<strong>in</strong>zelmaßnahmen<br />
mit e<strong>in</strong>er temporären Spalte erforderlich:<br />
398<br />
Firebird-Quelltext<br />
/* Erzeugen Sie e<strong>in</strong>e neue, temporäre Spalte. */<br />
ALTER TABLE Abteilung ADD [COLUMN] Temp VARCHAR(10);<br />
/* Kopieren Sie alle Inhalte aus der „alten“ Spalte <strong>in</strong> die temporäre Spalte. */<br />
UPDATE Abteilung SET Temp = Kuerzel;<br />
/* Löschen Sie die „alte“ Spalte. */<br />
ALTER TABLE Abteilung DROP [COLUMN] Kuerzel;
* Erzeugen Sie e<strong>in</strong>e neue Spalte unter dem „alten“ Namen mit den<br />
„neuen“ Eigenschaften. */<br />
ALTER TABLE Abteilung ADD [COLUMN] Kuerzel CHAR(4) NOT NULL;<br />
E<strong>in</strong>schränkungen auf Spalten<br />
/* Kopieren Sie alle Inhalte aus der temporären Spalte <strong>in</strong> die neue Spalte,<br />
wobei sie passend konvertiert werden müssen. */<br />
UPDATE Abteilung SET Kuerzel = SUBSTRING(Temp FROM 1 FOR 4);<br />
/* Löschen Sie die temporäre Spalte. */<br />
ALTER TABLE Abteilung DROP [COLUMN] Temp;<br />
35.1.3. V<strong>org</strong>abewert h<strong>in</strong>zufügen<br />
Für diese Aufgabe gibt es mehrere Varianten; wir beschränken uns auf e<strong>in</strong>e.<br />
Aufgabe: In der Tabelle Mitarbeiter soll die Spalte Ist_Leiter <strong>in</strong> der Regel den<br />
Wert 'N' erhalten.<br />
My<strong>SQL</strong>-Quelltext<br />
ALTER TABLE Abteilung MODIFY Ist_Leiter CHAR(1) NOT NULL DEFAULT ’N’;<br />
Bei Firebird beispielsweise kann e<strong>in</strong> DEFAULT-Wert nicht e<strong>in</strong>fach h<strong>in</strong>zugefügt<br />
werden; dann ist der „Umweg“ mit e<strong>in</strong>er temporären Spalte nötig.<br />
Die gleiche Änderung wäre auch denkbar <strong>in</strong> der Tabelle Versicherungsnehmer für<br />
die Spalte Eigener_Kunde. Da aber nicht abgeschätzt werden kann, welcher Fall<br />
„wesentlich häufiger“ vorkommt, lohnt sich e<strong>in</strong>e solche E<strong>in</strong>schränkung nicht.<br />
35.2. E<strong>in</strong>schränkungen auf Spalten<br />
Dazu wird der Befehl ALTER TABLE − ADD CONSTRAINT verwendet.<br />
Die hier besprochenen Änderungen sowie weitere E<strong>in</strong>schränkungen s<strong>in</strong>d im<br />
Skript-Constra<strong>in</strong>ts zusammengefasst.<br />
35.2.1. E<strong>in</strong>deutigkeit festlegen<br />
Bei mehreren Tabellen gibt es Spalten, deren Werte e<strong>in</strong>deutig se<strong>in</strong> sollen. Dies<br />
wird durch e<strong>in</strong>e UNIQUE-E<strong>in</strong>schränkung geregelt.<br />
Aufgabe: In der Tabelle Mitarbeiter ist die Personalnummer nicht als Primary<br />
Key v<strong>org</strong>esehen, muss aber dennoch e<strong>in</strong>deutig se<strong>in</strong>.<br />
399
Änderung der Datenbankstruktur<br />
ALTER TABLE Mitarbeiter<br />
ADD CONSTRAINT Mitarbeiter_Nummer UNIQUE (Personalnummer)<br />
USING INDEX Mitarbeiter_Nummer_UK;<br />
Wir benutzen dafür e<strong>in</strong>en CONSTRAINT mit Namen und legen außerdem fest,<br />
dass e<strong>in</strong> bestimmter Index (ebenfalls mit Namen) benutzt werden soll.<br />
35.2.2. CHECK-Bed<strong>in</strong>gungen für e<strong>in</strong>e Spalte<br />
Bei verschiedenen Spalten <strong>in</strong> mehreren Tabellen s<strong>in</strong>d nur bestimmte Werte zulässig.<br />
Das wurde oben unter „Neue Spalten e<strong>in</strong>fügen“ berücksichtigt, gilt aber<br />
auch für e<strong>in</strong>ige schon vorhandene Spalten.<br />
Aufgabe: In der Tabelle Versicherungsvertrag gibt es für die Spalte Art nur e<strong>in</strong>e<br />
begrenzte Anzahl von Werten.<br />
ALTER TABLE Versicherungsvertrag<br />
ADD CONSTRAINT Versicherungsvertrag_Art<br />
CHECK( Art = ’HP’ OR Art = ’TK’ OR Art = ’VK’ );<br />
In gleicher Weise ist es vor allem bei den Spalten zu regeln, die als Ersatz für e<strong>in</strong><br />
boolesches Feld nur die Werte 'J' und 'N' akzeptieren.<br />
35.2.3. CHECK-Bed<strong>in</strong>gungen für mehrere Spalten<br />
Die bisherigen Prüfungen beziehen sich immer auf e<strong>in</strong>e e<strong>in</strong>zige Spalte. Genauso<br />
kann auch geprüft werden, ob die Werte mehrerer Spalten zusammenpassen.<br />
Bitte beachten Sie, dass für das folgende Beispiel die Spalte Geschlecht benutzt<br />
wird, die oben durch das Skript-Spalten e<strong>in</strong>gefügt wird.<br />
Aufgabe: In der Tabelle Versicherungsnehmer muss es sich entweder um e<strong>in</strong>e<br />
Firma handeln, d. h. Geschlecht IS NULL, oder e<strong>in</strong>e Reihe anderer Spalten benötigt<br />
für Personen geeignete Werte.<br />
400<br />
ALTER TABLE Versicherungsnehmer<br />
ADD CONSTRAINT Versicherungsnehmer_Person<br />
CHECK( ( Geschlecht IS NULL )<br />
OR ( Vorname IS NOT NULL<br />
AND Geburtsdatum IS NOT NULL<br />
AND Fuehrersche<strong>in</strong> IS NOT NULL<br />
AND Fuehrersche<strong>in</strong> >= Geburtsdatum + 365*16 )<br />
);
Indizes<br />
Gleichzeitig wird geprüft, dass der Versicherungsnehmer den Führersche<strong>in</strong> frühestens<br />
im Alter von 16 Jahren (vere<strong>in</strong>fachte Berechnung, soweit möglich) erhalten<br />
haben kann.<br />
35.3. Indizes<br />
Dazu wird der Befehl CREATE INDEX verwendet.<br />
Die hier besprochenen Indizes sowie weitere Suchschlüssel s<strong>in</strong>d im Skript-<br />
Indizes zusammengefasst.<br />
Aufgabe: In der Tabelle Versicherungsvertrag ist häufig nach dem Fahrzeug,<br />
also nach Fahrzeug_ID zu suchen.<br />
CREATE INDEX Versicherungsvertrag_FZ<br />
ON Versicherungsvertrag (Fahrzeug_ID);<br />
Aufgabe: In der Tabelle Schadensfall ist häufig nach dem Datum zu suchen;<br />
jüngere Schadensfälle sollten zuerst kommen.<br />
CREATE DESC INDEX Schadensfall_Datum<br />
ON Schadensfall (Datum);<br />
Für diesen Zweck benötigen wir also mit DESC e<strong>in</strong>en absteigenden Index.<br />
Aufgabe: In der Tabelle Versicherungsnehmer ist häufig nach dem Namen −<br />
mit oder ohne Vorname − zu suchen.<br />
CREATE INDEX Versicherungsnehmer_Name<br />
ON Versicherungsnehmer (Name, Vorname);<br />
Sie sehen, dass e<strong>in</strong> Schlüssel auch mehrere Spalten benutzen kann. Die DBMS<br />
arbeiten unterschiedlich, ob daraus für Name e<strong>in</strong> eigener Index wird oder ob es<br />
nur den „e<strong>in</strong>heitlichen, geme<strong>in</strong>samen“ Index gibt.<br />
Aufgabe: In der Tabelle Versicherungsnehmer ist oft auch nach der Versicherungsgesellschaft<br />
zu suchen.<br />
CREATE INDEX Versicherungsnehmer_Ges<br />
ON Versicherungsnehmer (Versicherungsgesellschaft_ID);<br />
Es ist nicht sicher, dass e<strong>in</strong> solcher Index e<strong>in</strong>gerichtet werden kann; denn diese<br />
Spalte kann auch NULL-Werte enthalten (nämlich für die „Eigenen Kunden“),<br />
und dabei verhalten sich die DBMS unterschiedlich.<br />
401
Änderung der Datenbankstruktur<br />
35.4. Fremdschlüssel<br />
Dazu wird der Befehl ALTER TABLE − ADD CONSTRAINT − FOREIGN KEY verwendet.<br />
Alle E<strong>in</strong>zelheiten dazu wurden bei den Fremdschlüssel-Beziehungen<br />
behandelt.<br />
Die Fremdschlüssel, die für die Beispieldatenbank vorzusehen s<strong>in</strong>d, s<strong>in</strong>d im<br />
Skript-ForeignKeys zusammengefasst.<br />
35.5. Weitere Anpassungen<br />
Durch die nachträglichen Änderungen <strong>in</strong> den vorstehenden Abschnitten müssen<br />
die bisherigen Daten angepasst werden. Das wollen wir jetzt erledigen.<br />
Die hier besprochenen Anpassungen s<strong>in</strong>d im Skript-Anpassen zusammengefasst.<br />
Dort ist e<strong>in</strong>e der Versionen mit allen Werten enthalten.<br />
35.5.1. Geschlecht ändern<br />
In den Tabellen Mitarbeiter und Versicherungsnehmer wurde als Standardwert<br />
'W' (= weiblich) e<strong>in</strong>getragen. Wir müssen also noch die Männer markieren; das<br />
geht durch manuelle Änderung jeder e<strong>in</strong>zelnen Zeile, durch Bezug auf männliche<br />
Vornamen oder mit e<strong>in</strong>er Liste der IDs von Männern. Im folgenden Code<br />
stehen alle drei Varianten; s<strong>in</strong>nvoll ist es natürlich, nur e<strong>in</strong>e davon zu benutzen.<br />
UPDATE Mitarbeiter<br />
SET Geschlecht = ’M’<br />
WHERE ID = 1<br />
OR Vorname IN (’Kurt’, ’Walter’, ’Michael’)<br />
OR ID IN (10, 11, 12);<br />
Die gleiche Änderung ist <strong>in</strong> der Tabelle Versicherungsnehmer nötig. Mangels V<strong>org</strong>abewert<br />
setzen wir vorher alle Kunden, die ke<strong>in</strong>e Firma s<strong>in</strong>d, auf 'W'.<br />
Quelltext Falsch<br />
UPDATE Versicherungsnehmer<br />
SET Geschlecht = ’W’<br />
WHERE Vorname IS NOT NULL;<br />
Operation violates CHECK constra<strong>in</strong>t on view or table.<br />
Operation violates CHECK constra<strong>in</strong>t VERSICHERUNGSNEHMER_PERSON<br />
on view or table VERSICHERUNGSNEHMER.<br />
402
Weitere Anpassungen<br />
Ach, was ist da denn wieder passiert? Bei der Firma mit ID=1 steht der Vorname<br />
als leere Zeichenkette, aber der oben e<strong>in</strong>gerichtete CHECK mit der Prüfung „Ist<br />
Person“ vergleicht den Vornamen mit dem NULL-Wert. Also muss entweder der<br />
konkrete Wert auf NULL gesetzt werden, oder der CHECK muss geändert werden.<br />
Bei e<strong>in</strong>er „echten“ Datenbank wäre der zweite Weg unbed<strong>in</strong>gt vorzuziehen;<br />
wir machen es uns hier e<strong>in</strong>facher:<br />
UPDATE Versicherungsnehmer<br />
SET Vorname = NULL<br />
WHERE Vorname = ”;<br />
Danach funktioniert der obige Befehl; wir müssten also wie folgt v<strong>org</strong>ehen:<br />
• CHECK für „Ist Person“ löschen.<br />
• CHECK für „Ist Person“ neu registrieren, sodass auch e<strong>in</strong>e leere Zeichenkette<br />
für den Vornamen bei e<strong>in</strong>er Firma zulässig ist.<br />
• Geschlecht auf den V<strong>org</strong>abewert 'W' setzen.<br />
• alle männlichen Personen auf 'M' ändern wie im ersten Beispiel.<br />
35.5.2. V<strong>org</strong>abewert der Basisprämie<br />
In der Tabelle Versicherungsvertrag wurde dieser Wert allgeme<strong>in</strong> auf 500 (Euro<br />
pro Jahr) gesetzt. Damit wir später richtig damit arbeiten können, muss er natürlich<br />
von der Vertragsart abhängen. (Wir unterscheiden dabei nicht nach Fahrzeugtypen<br />
und ignorieren Prämienänderungen im Laufe der Jahre, sondern tun<br />
so, als ob sich Versicherungsprämien niemals erhöhen würden.) Da der V<strong>org</strong>abewert<br />
sowieso auf 500 gesetzt wurde, brauchen nur die davon abweichenden<br />
Werte geändert zu werden.<br />
Quelltext Falsch<br />
UPDATE Versicherungsvertrag<br />
SET Basispraemie = CASE Art<br />
WHEN ’TK’ THEN 550<br />
WHEN ’VK’ THEN 800<br />
END ;<br />
The update failed because a column def<strong>in</strong>ition <strong>in</strong>cludes validation constra<strong>in</strong>ts.<br />
validation error for column BASISPRAEMIE, value "*** null ***".<br />
In der Tat, man muss aufpassen. Bei den „Nützlichen Erweiterungen“ hieß es zur<br />
CASE-Anweisung: „wenn ELSE nicht vorhanden ist, wird NULL als Wert genommen.“<br />
Der bisherige V<strong>org</strong>abewert darf also nicht weggelassen werden, sondern<br />
muss im ELSE-Zweig erneut zugewiesen werden:<br />
403
Änderung der Datenbankstruktur<br />
UPDATE Versicherungsvertrag<br />
SET Basispraemie = CASE Art<br />
WHEN ’TK’ THEN 550<br />
WHEN ’VK’ THEN 800<br />
ELSE 500<br />
END ;<br />
35.5.3. Prämiensatz und Prämienänderung vorbereiten<br />
Da sich unser Datenbestand nicht <strong>in</strong> der Wirklichkeit entwickelt hat, müssen wir<br />
diese Entwicklung simulieren. Die ersten Schadensfälle s<strong>in</strong>d 2007 registriert; also<br />
muss der Datenbestand auf das Jahresende 2006 gebracht werden. Dazu benutzen<br />
wir e<strong>in</strong>e Rout<strong>in</strong>e ohne feste Speicherung (siehe <strong>SQL</strong>-Programmierung).<br />
Firebird-Quelltext<br />
EXECUTE BLOCK<br />
AS<br />
DECLARE VARIABLE jj INTEGER;<br />
DECLARE VARIABLE T1 INTEGER;<br />
DECLARE VARIABLE T2 INTEGER;<br />
BEGIN<br />
FOR SELECT vv.ID, EXTRACT(YEAR FROM Abschlussdatum), Praemiensatz<br />
FROM Versicherungsvertrag vv<br />
JOIN Versicherungsnehmer vn ON vn.Id = vv.Versicherungsnehmer_Id<br />
WHERE Praemienaenderung
Zusammenfassung<br />
Der Prämiensatz 2006 errechnet sich aus der Dauer des Vertrags, also nach dem<br />
Abschlussdatum sowie natürlich dem bisher notierten pauschalen Prämiensatz.<br />
Im UPDATE-Befehl werden für jeden Vertrag − notiert <strong>in</strong> der Variablen T1 − die<br />
neuen Werte berechnet:<br />
• Das Datum Praemienaenderung wird vom Abschlussdatum aus auf das Jahr<br />
2006 weitergesetzt.<br />
• Der Prämiensatz wird neu berechnet, und zwar:<br />
• für neuere Verträge um je 20 Punkte pro Jahr bis zu e<strong>in</strong>em M<strong>in</strong>imum von 80<br />
• für andere Verträge um je 10 Punkte pro Jahr bis zu e<strong>in</strong>em M<strong>in</strong>imum von 30<br />
• Werte, die von den Anfangssätzen 100 oder 200 abweichen, bleiben stehen<br />
35.6. Zusammenfassung<br />
In diesem Kapitel wurden alle Änderungen, die sich für die Beispieldatenbank als<br />
s<strong>in</strong>nvoll erwiesen, besprochen. Für jede Situation wurden Beispiele angegeben;<br />
alle erforderlichen Befehle s<strong>in</strong>d <strong>in</strong> Skripten zusammengefasst:<br />
• Skript-Spalten zum H<strong>in</strong>zufügen und Ändern e<strong>in</strong>zelner Spalten<br />
• Skript-Constra<strong>in</strong>ts zum Anlegen von UNIQUE KEYs und CHECK-Constra<strong>in</strong>ts<br />
• Skript-Indizes zum Anlegen weiterer Schlüssel<br />
• Skript-ForeignKeys zum Anlegen der Fremdschlüssel<br />
• Skript-Anpassung zum Anpassen der betroffenen Datensätze<br />
Weitere H<strong>in</strong>weise dazu gibt es im Kapitel Downloads 2 .<br />
35.7. Siehe auch<br />
Die hier verwendeten Verfahren werden <strong>in</strong> den folgenden Kapiteln behandelt:<br />
• Beispieldatenbank 3 unter „Anmerkungen“<br />
• Prozeduren 4 mit dem Beispiel „automatisches UPDATE gemäß Bed<strong>in</strong>gungen“<br />
• DDL – E<strong>in</strong>zelheiten 5 unter ALTER COLUMN<br />
• Fremdschlüssel-Beziehungen 6<br />
2 Anhang B auf Seite 423<br />
3 Abschnitt 6.4 auf Seite 43<br />
4 Abschnitt 32.2.3 auf Seite 363<br />
5 Abschnitt 28.3.3 auf Seite 292<br />
6 Kapitel 29 auf Seite 305<br />
405
Änderung der Datenbankstruktur<br />
• Nützliche Erweiterungen 7<br />
• <strong>SQL</strong>-Programmierung 8 bei „Rout<strong>in</strong>e ohne feste Speicherung“<br />
35.7.1. Was ist e<strong>in</strong> Skript?<br />
Unter e<strong>in</strong>em Skript 9 versteht man <strong>in</strong> der EDV e<strong>in</strong>e Liste von Befehlen, die durch<br />
e<strong>in</strong>en e<strong>in</strong>zelnen Befehl aufgerufen und automatisch nache<strong>in</strong>ander ausgeführt<br />
werden. Diese Befehle s<strong>in</strong>d meist <strong>in</strong> e<strong>in</strong>er Datei zusammengefasst und werden<br />
dadurch ausgeführt, dass die betreffende Datei aufgerufen wird.<br />
Bei <strong>SQL</strong> s<strong>in</strong>d solche Dateien unter W<strong>in</strong>dows üblicherweise mit der Endung<br />
".sql" gespeichert.<br />
7 Kapitel 23 auf Seite 213<br />
8 Abschnitt 30.1 auf Seite 324<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Skript<br />
406
36. Testdaten erzeugen<br />
36.1. Neue Fahrzeuge registrieren . . . . . . . . . . . . . . . . . . . . . 408<br />
36.2. Neue Versicherungsverträge registrieren . . . . . . . . . . . . . 408<br />
36.3. Probleme mit Testdaten . . . . . . . . . . . . . . . . . . . . . . . 414<br />
36.4. Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . 415<br />
36.5. Siehe auch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416<br />
In diesem Kapitel werden wir die Erkenntnisse aus den vorhergehenden Kapiteln<br />
benutzen, um die Beispieldatenbank um e<strong>in</strong>e Vielzahl zusätzlicher Datensätze<br />
zu ergänzen.<br />
Auch hier gilt: Wegen der vielen Varianten bei den DBMS beschränkt sich dieses<br />
Kapitel bei den H<strong>in</strong>weisen und Beispielen auf Firebird. Zusammen mit den<br />
speziellen H<strong>in</strong>weisen zur <strong>SQL</strong>-Programmierung sollten sie problemlos an andere<br />
DBMS-Regeln angepasst werden können.<br />
Damit man <strong>in</strong> e<strong>in</strong>er Datenbank viele Tests ausführen kann, s<strong>in</strong>d umfangreiche,<br />
möglichst unterschiedliche Testdaten nötig. Dies wollen wir jetzt ansatzweise –<br />
unter Berücksichtigung der bisherigen E<strong>in</strong>sichten – besprechen.<br />
Folgende Grundgedanken s<strong>in</strong>d bei solchen Testdaten immer zu beachten:<br />
• Die e<strong>in</strong>zelnen Datensätze sollen zufällig erzeugte Werte erhalten.<br />
• Die Werte <strong>in</strong> den Spalten sollen halbwegs realistisch se<strong>in</strong>: E<strong>in</strong> Vorname wie<br />
„Blabla“ oder e<strong>in</strong>e Schadenshöhe wie 0,15 =C verbieten sich.<br />
• Verschiedene Spalten müssen zusammenpassen: Zur PLZ 50667 darf es nur<br />
den Ortsnamen 'Köln' geben.<br />
• Am besten sollte auch die Verteilung der Datensätze halbwegs realistisch se<strong>in</strong>:<br />
Es sollte m<strong>in</strong>destens so viele Fahrzeuge mit dem Kfz-Kennzeichen 'D' (Stadt<br />
Düsseldorf) wie mit 'RE' (Stadt- und Landkreis Reckl<strong>in</strong>ghausen) geben; ähnlich<br />
sollten auch die Versicherungsnehmer auf Stadt- und Landkreis passend<br />
verteilt se<strong>in</strong>.<br />
i H<strong>in</strong>weis<br />
Die hier genannten Schritte s<strong>in</strong>d nur e<strong>in</strong> denkbares Verfahren. Wichtig ist<br />
e<strong>in</strong> E<strong>in</strong>blick <strong>in</strong> mögliche V<strong>org</strong>ehensweisen und Probleme.<br />
407
Testdaten erzeugen<br />
36.1. Neue Fahrzeuge registrieren<br />
Für viele neue Datensätze <strong>in</strong> der Tabelle Fahrzeug lernten wir bereits zwei Varianten<br />
des gleichen Verfahrens kennen:<br />
• Bei den Prozeduren werden mit e<strong>in</strong>er WHILE-Schleife mehrere zufällige Werte<br />
für neue Zeilen zusammengesetzt.<br />
• Dies geht auch als Rout<strong>in</strong>e ohne feste Speicherung.<br />
Beide Varianten s<strong>in</strong>d nur s<strong>in</strong>nvoll, wenn e<strong>in</strong>e ger<strong>in</strong>ge Anzahl von Spalten mit<br />
e<strong>in</strong>er ger<strong>in</strong>gen Anzahl möglicher Werte „vervielfältigt“ werden.<br />
36.2. Neue Versicherungsverträge registrieren<br />
Die Prozedur (siehe dort) Insert_Versicherungsvertrag ist für e<strong>in</strong>en solchen automatischen<br />
V<strong>org</strong>ang nicht geeignet, weil sie immer wieder mit neuen Werten aufgerufen<br />
werden müsste. In diesem Kapitel sollen deshalb neue Datensätze durch<br />
das „kartesische Produkt“ zusammengestellt werden. Damit die erforderlichen<br />
Beziehungen gesichert s<strong>in</strong>d, müssen zuerst Fahrzeuge und Versicherungsnehmer<br />
erstellt werden; erst zuletzt dürfen die Verträge registriert werden.<br />
Die folgenden Maßnahmen s<strong>in</strong>d im Skript-Testdaten der Download-Seite 1 zusammengefasst.<br />
Dort stehen auch diejenigen (e<strong>in</strong>facheren) <strong>SQL</strong>-Befehle, die im<br />
Folgenden nicht ausführlich angegeben und besprochen werden. Der Vermerk<br />
Skript verweist bei den folgenden Schritten auf die Befehle, die <strong>in</strong> diesem<br />
Skript stehen.<br />
36.2.1. Die Tabelle Fahrzeug<br />
E<strong>in</strong>e Menge von Fahrzeugen wurde bereits gespeichert mithilfe der Prozedur<br />
Insert_Into_Fahrzeug. Wir prüfen zunächst, wie viele Fahrzeuge (noch) nicht zu<br />
e<strong>in</strong>em Vertrag gehören:<br />
SELECT COUNT(ID) FROM Fahrzeug<br />
WHERE ID NOT IN (SELECT Fahrzeug_ID FROM Versicherungsvertrag);<br />
COUNT : 120<br />
1 Anhang B auf Seite 423<br />
408
Neue Versicherungsverträge registrieren<br />
Wenn das noch nicht genügt, speichern Sie jetzt mit der o. g. Prozedur weitere<br />
Datensätze. Auf diese Daten greifen wir <strong>in</strong> den nächsten Schritten zurück, damit<br />
alle E<strong>in</strong>träge zusammenpassen.<br />
Bei den späteren Maßnahmen gibt es Anmerkungen dazu, wie viele Datensätze<br />
vorkommen können. Die Zahl der Fahrzeuge sollte dem ungefähr entsprechen.<br />
36.2.2. Hilfstabellen<br />
Für die zufällige Komb<strong>in</strong>ation erstellen wir uns zunächst e<strong>in</strong>ige temporäre Tabellen<br />
mit passenden Werten:<br />
• TempName mit e<strong>in</strong>er Liste von 20 Nachnamen, TempVorname mit e<strong>in</strong>er Liste<br />
von 20 Vornamen und Geschlecht Skript<br />
• TempPLZOrt mit e<strong>in</strong>er Liste von etwa 30 Postleitzahlen und zugehörigen Ortsnamen<br />
Skript<br />
• TempStrasse mit e<strong>in</strong>er Liste von etwa 20 Straßennamen (eigentlich müssten<br />
diese zu den Orten passen; aber da es ke<strong>in</strong> „freies“ Straßenverzeichnis für kle<strong>in</strong>ere<br />
Städte und Orte gibt, soll uns das nicht <strong>in</strong>teressieren) Skript<br />
36.2.3. Die Tabelle Versicherungsnehmer<br />
Für e<strong>in</strong>e Menge potenzieller Versicherungsnehmer mischen wir Zufallsfunktionen<br />
und die direkte Verknüpfung der Daten aus den Hilfstabellen.<br />
Aufgabe: Erzeuge viele Testdaten Versicherungsnehmer durch das kartesische<br />
Produkt aus den Hilfstabellen.<br />
Firebird-Quelltext<br />
INSERT INTO Versicherungsnehmer<br />
( Name, Vorname, Geschlecht,<br />
PLZ, Ort,<br />
Strasse,<br />
Hausnummer,<br />
Geburtsdatum, Fuehrersche<strong>in</strong>, Eigener_Kunde, Versicherungsgesellschaft_ID )<br />
SELECT n.Text, v.Text, v.Geschlecht,<br />
p.Plz, p.Ort,<br />
s.Name,<br />
CAST( CAST( FLOOR( 1 + RAND()* 98) AS INTEGER) AS VARCHAR(10)),<br />
’01.01.1950’, ’31.12.2009’, ’J’, NULL<br />
FROM TempName n, TempVorname v, TempPLZOrt p, TempStrasse s<br />
WHERE n.Text
Testdaten erzeugen<br />
gibt ke<strong>in</strong>e Verknüpfung zwischen den vier Hilfstabellen). Dies ist also das „kartesische<br />
Produkt“, das als im Normalfall ungeeignet bezeichnet wurde, aber <strong>in</strong><br />
dieser Spezialsituation nützlich ist.<br />
Geburtsdatum und Fuehrersche<strong>in</strong> werden nachträglich geändert. Der SELECT-<br />
Befehl wäre zu kompliziert, wenn zunächst das Geburtsdatum mit mehreren Zufallsfunktionen<br />
erstellt würde und davon abhängig mit weiteren Zufallszahlen<br />
das Datum des Führersche<strong>in</strong>erwerbs berechnet würde.<br />
i Achtung<br />
Diese Art der Verknüpfung mehrerer Tabellen kann sehr viele Datensätze<br />
erzeugen und belastet deshalb das DBMS für längere Zeit erheblich.<br />
Nur deshalb wird die WHERE-Bed<strong>in</strong>gung benutzt. Mit allen Datensätzen der<br />
temporären Tabellen entstünden 20 · 20 · 47 · 22 (= 413 600) Zeilen, was bei e<strong>in</strong>em<br />
Testlauf etwa 15 M<strong>in</strong>uten dauerte. Mit der E<strong>in</strong>schränkung und e<strong>in</strong>er kle<strong>in</strong>eren<br />
PLZ/Orte-Tabelle gibt es etwa 12 · 12 · 25 · 13 (= 46 800) Zeilen <strong>in</strong> 15 Sekunden.<br />
Aufgabe: Nun werden zufällige Daten für Geburtsdatum und Fuehrersche<strong>in</strong> erzeugt<br />
und bei den neu erstellten Versicherungsnehmern gespeichert.<br />
UPDATE Versicherungsnehmer<br />
SET Geburtsdatum = DATEADD( DAY, CAST( FLOOR(RAND()*27) AS INTEGER),<br />
DATEADD( MONTH, CAST( FLOOR(RAND()*11) AS INTEGER),<br />
DATEADD( YEAR, CAST( FLOOR(RAND()*40) AS INTEGER),<br />
Geburtsdatum )))<br />
WHERE Geburtsdatum = ’01.01.1950’;<br />
COMMIT;<br />
UPDATE Versicherungsnehmer<br />
SET Fuehrersche<strong>in</strong> = DATEADD( DAY, CAST( FLOOR(RAND()*27) AS INTEGER),<br />
DATEADD( MONTH, CAST( FLOOR(RAND()*11) AS INTEGER),<br />
DATEADD( YEAR, CAST( FLOOR(18 + RAND()*(DATEDIFF<br />
(YEAR, Geburtsdatum, CAST(’31.12.2008’ AS DATE))-18)<br />
) AS INTEGER), Geburtsdatum)))<br />
WHERE Fuehrersche<strong>in</strong> = ’31.12.2009’;<br />
COMMIT;<br />
Das Geburtsdatum kann dabei nicht später als der '31.12.1990' liegen; der Führersche<strong>in</strong><br />
kann frühestens mit dem 18. Geburtstag erworben worden se<strong>in</strong>.<br />
36.2.4. Die Tabelle Versicherungsvertrag<br />
Um die bisherigen Teildaten zu Verträgen zusammenzufassen, müssen wir anders<br />
v<strong>org</strong>ehen. Folgende Bed<strong>in</strong>gungen s<strong>in</strong>d zu berücksichtigen:<br />
410
• Jedes Fahrzeug darf nur e<strong>in</strong>mal benutzt werden.<br />
Neue Versicherungsverträge registrieren<br />
• E<strong>in</strong> Versicherungsnehmer darf auch <strong>in</strong> mehreren Verträgen stehen.<br />
• Aber der Wohnort des Kunden sollte zum Kfz-Kennzeichen passen; wir machen<br />
es zur Bed<strong>in</strong>gung.<br />
• Das Abschlussdatum des Vertrags muss nach dem Führersche<strong>in</strong>erwerb liegen.<br />
• Prämiensatz und Prämienänderung müssen ebenso vorbereitet werden wie im<br />
Kapitel Änderung der Datenbankstruktur.<br />
E<strong>in</strong> erster Versuch (zunächst beschränkt auf Fahrzeuge mit 'RE' als Kennzeichen)<br />
konnte nicht s<strong>in</strong>nvoll funktionieren – das DBMS „hängte sich auf“:<br />
Firebird-Quelltext Falsch<br />
SELECT fz.id, fz.Kennzeichen,<br />
vn.Id, vn.Name, vn.Vorname, vn.PLZ<br />
FROM Fahrzeug fz<br />
JOIN Versicherungsnehmer vn ON vn.ID =<br />
( SELECT FIRST 1 ID<br />
FROM Versicherungsnehmer<br />
WHERE ID NOT IN ( SELECT Versicherungsnehmer_ID<br />
FROM Versicherungsvertrag )<br />
AND PLZ = ( SELECT FIRST 1 PLZ<br />
FROM TempPLZOrt<br />
WHERE Kreis = ’RE’<br />
ORDER BY RAND()<br />
)<br />
ORDER BY RAND()<br />
)<br />
WHERE fz.Kennzeichen STARTS WITH ’RE-’;<br />
Dieser Versuch sollte so v<strong>org</strong>ehen (lesen Sie den Befehl „von <strong>in</strong>nen nach außen“):<br />
• Hole nach Zufallsreihenfolge e<strong>in</strong>e PLZ aus der Liste der Orte, die zum Kreis 'RE'<br />
gehören.<br />
• Hole nach Zufallsreihenfolge e<strong>in</strong>en Kunden mit dieser PLZ, sofern er noch<br />
nicht mit e<strong>in</strong>em Vertrag registriert ist.<br />
• Nur dessen Daten sollen mit e<strong>in</strong>em e<strong>in</strong>zelnen Fahrzeug verknüpft werden.<br />
Wenn man auf das JOIN verzichten würde, würde der erste Versicherungsnehmer<br />
mit jedem Fahrzeug verknüpft.<br />
Dieses Verfahren verlangt bei jedem Fahrzeug zwei neue abhängige SELECT-<br />
Befehle, die per Zufallsfunktion aus fast 50 000 Datensätzen jeweils genau e<strong>in</strong>en<br />
Datensatz liefern sollen − e<strong>in</strong>e völlig unsachgemäße Belastung des DBMS.<br />
Stattdessen wird mit e<strong>in</strong>er weiteren Hilfstabelle TempVertrag schrittweise v<strong>org</strong>egangen:<br />
411
Testdaten erzeugen<br />
• Schreibe alle noch „freien“ Fahrzeuge aus den oben verwendeten Kreisen <strong>in</strong><br />
die Hilfstabelle. Skript<br />
• Sortiere sie nach Kreis und nummeriere sie <strong>in</strong> dieser Reihenfolge. Skript<br />
• Hole per Zufallsreihenfolge − getrennt nach jedem Kreis − e<strong>in</strong>en E<strong>in</strong>trag aus<br />
der Tabelle Versicherungsnehmer.<br />
• Übertrage diese Zusammenstellung <strong>in</strong> die Tabelle Versicherungsvertrag.<br />
• Passe Prämiensatz und Prämienänderung an (wie im früheren Kapitel ausgeführt).<br />
Der erste, entscheidende Schritt ist die Zuordnung e<strong>in</strong>es potenziellen Kunden zu<br />
jedem „freien“ Fahrzeug.<br />
Aufgabe: Erzeuge e<strong>in</strong>e zufällige Reihenfolge der Kunden und trage sie <strong>in</strong> die<br />
(temporäre) Liste der Fahrzeuge e<strong>in</strong>.<br />
412<br />
Firebird-Quelltext<br />
EXECUTE BLOCK<br />
AS<br />
DECLARE VARIABLE nextid INTEGER = 0;<br />
DECLARE VARIABLE tempid INTEGER;<br />
DECLARE VARIABLE tkreis VARCHAR(3);<br />
DECLARE VARIABLE Tname CHAR(2);<br />
DECLARE VARIABLE m<strong>in</strong>nr INTEGER;<br />
DECLARE VARIABLE maxnr INTEGER;<br />
DECLARE VARIABLE Tdatum DATE;<br />
BEGIN<br />
FOR SELECT fz_Kreis, M<strong>in</strong>(Nr), Max(Nr)<br />
FROM TempVertrag<br />
GROUP BY fz_Kreis<br />
ORDER BY fz_Kreis<br />
INTO :tkreis, :M<strong>in</strong>nr, :Maxnr<br />
DO BEGIN<br />
/* hole alle möglichen potenziellen Kunden für diesen Kreis<br />
<strong>in</strong> Zufallsreihenfolge */<br />
nextid = :M<strong>in</strong>nr - 1;<br />
FOR SELECT ID,<br />
/* diese komplizierte Konstruktion mit TRIM, CAST ist<br />
wegen SUBSTRING nötig */<br />
CAST( TRIM(SUBSTRING(Name FROM 1 FOR 1))<br />
|| TRIM(SUBSTRING(Ort FROM 1 FOR 1)) AS CHAR(2)),<br />
Fuehrersche<strong>in</strong><br />
FROM Versicherungsnehmer<br />
WHERE PLZ IN ( SELECT PLZ<br />
FROM TempPLZOrt<br />
WHERE Kreis = :Tkreis<br />
)<br />
AND ID NOT IN<br />
( SELECT Versicherungsnehmer_ID<br />
FROM Versicherungsvertrag<br />
)<br />
ORDER BY RAND()
Neue Versicherungsverträge registrieren<br />
INTO :Tempid, :Tname, :Tdatum<br />
DO BEGIN<br />
/* registriere jeden dieser Kunden nache<strong>in</strong>ander<br />
für e<strong>in</strong>es der Fahrzeuge <strong>in</strong> diesem Kreis */<br />
nextid = nextid + 1;<br />
UPDATE TempVertrag<br />
SET vn_ID = :Tempid,<br />
vn_Name = :Tname,<br />
/* per Zufall variable Daten vorbereiten */<br />
Abschlussdatum = DATEADD( DAY, CAST( FLOOR(RAND()*27) AS INTEGER),<br />
DATEADD( MONTH, CAST( FLOOR(RAND()*11) AS INTEGER),<br />
DATEADD( YEAR, CAST( FLOOR(RAND()*<br />
(DATEDIFF (YEAR, :Tdatum,<br />
CAST(’31.12.2008’ AS DATE))<br />
) ) AS INTEGER), :Tdatum)))<br />
WHERE Nr = :nextid;<br />
END<br />
END<br />
END<br />
Dieser Arbeitsablauf berücksichtigt die o. g. Grundgedanken:<br />
• In der Hauptschleife werden die Fahrzeuge nach dem Kreis gruppiert.<br />
• Für jeden Kreis werden der kle<strong>in</strong>ste und der größte Wert der laufenden Nummer<br />
aus der Hilfstabelle notiert.<br />
• Der nächste SELECT bereitet e<strong>in</strong>e Schleife mit potenziellen Kunden vor:<br />
• Zum aktuellen Kreis werden alle Postleitzahlen aus der Hilfstabelle<br />
TempPLZOrte registriert.<br />
• Die E<strong>in</strong>träge der Kunden, die zu diesen PLZ gehören, werden <strong>in</strong> e<strong>in</strong>e zufällige<br />
Reihenfolge gebracht.<br />
• In dieser Schleife wird jeder dieser Kunden bei e<strong>in</strong>em Fahrzeug mit der nächsten<br />
laufenden Nummer e<strong>in</strong>getragen.<br />
• Der Anfangsbuchstabe vom Namen und Ort wird registriert, damit dieser Wert<br />
für die Vertragsnummer zur Verfügung steht.<br />
• Zusätzlich wird als Abschlussdatum des Vertrags e<strong>in</strong> zufälliges Datum zwischen<br />
dem Führersche<strong>in</strong>erwerb und e<strong>in</strong>em festen Schlussdatum erzeugt.<br />
Aufgabe: Jetzt wird für jede dieser Komb<strong>in</strong>ationen von Fahrzeugen und Kunden<br />
aus der Hilfstabelle TempVertrag e<strong>in</strong> Versicherungsvertrag erzeugt:<br />
Firebird-Quelltext<br />
INSERT INTO Versicherungsvertrag<br />
SELECT NULL, /* ID nach Generator */<br />
Vn_name || ’-’ || CAST(Nr AS VARCHAR(3)), /* Vertragsnummer */<br />
Abschlussdatum, /* Vertragsabschluss */<br />
CASE MOD(Fz_id, 4) /* Vertragsart nach Fahrzeug-ID */<br />
WHEN 0 THEN ’VK’<br />
WHEN 1 THEN ’HP’<br />
413
Testdaten erzeugen<br />
ELSE ’TK’<br />
END,<br />
CASE MOD(Fz_id, 4) /* Mitarbeiter-ID nach Fahrzeug */<br />
WHEN 0 THEN 9<br />
WHEN 1 THEN 10<br />
WHEN 2 THEN 11<br />
ELSE 12<br />
END,<br />
Fz_id, /* Fahrzeug-ID */<br />
Vn_id, /* Versicherungsnehmer-ID */<br />
100, /* Prämiensatz */<br />
Abschlussdatum, /* letzte Prämienänderung */<br />
CASE MOD(Fz_id, 4) /* Basisprämie nach Vertragsart */<br />
WHEN 0 THEN 800<br />
WHEN 1 THEN 500<br />
ELSE 550<br />
END<br />
FROM TempVertrag t;<br />
Dabei wird jede Spalte <strong>in</strong> der Tabelle Versicherungsvertrag mit e<strong>in</strong>em Wert versehen,<br />
überwiegend direkt aus der Hilfstabelle TempVertrag. E<strong>in</strong>ige Werte werden<br />
statt e<strong>in</strong>er weiteren Zufallsfunktion aus der Fahrzeug-ID errechnet.<br />
36.3. Probleme mit Testdaten<br />
Bei den vorstehenden E<strong>in</strong>zelschritten gab es wiederholt Fehlermeldungen. Das<br />
gilt natürlich auch für viele andere Versuche. Bitte vergessen Sie deshalb niemals<br />
diesen Ratschlag, der fast immer für jeden e<strong>in</strong>zelnen Teilschritt gilt:<br />
i H<strong>in</strong>weis<br />
Vor jeder umfangreichen oder wichtigen Änderung ist e<strong>in</strong>e<br />
Datensicherung vorzunehmen.<br />
Die hier behandelten Verfahren s<strong>in</strong>d beileibe nicht die e<strong>in</strong>zig möglichen. Sie sollten<br />
aber sehen, wie zum e<strong>in</strong>en verschiedene konstante Daten komb<strong>in</strong>iert und<br />
zum anderen Zufallswerte erzeugt werden können, um viele unterschiedliche<br />
Daten mit s<strong>in</strong>nvollen Komb<strong>in</strong>ationen <strong>in</strong> verschiedenen Tabellen e<strong>in</strong>zufügen. In<br />
gleicher Weise könnten wir noch viele weitere Datensätze <strong>in</strong> den Tabellen der<br />
Beispieldatenbank erzeugen.<br />
Vor allem weitere Schadensfälle wären äußerst nützlich. Dabei s<strong>in</strong>d aber viele<br />
Probleme zu beachten:<br />
• Jeder Schadensfall benötigt e<strong>in</strong>e Schadenssumme, die nicht nur zufällig, sondern<br />
auch s<strong>in</strong>nvoll festgelegt werden sollte.<br />
414
Zusammenfassung<br />
• Die Beschreibung des Schadensfalls sollte wenigstens annähernd realistisch<br />
zur Schadenssumme passen.<br />
• Wir benötigen Schadensfälle mit 1, 2 oder „vielen“ beteiligten Fahrzeugen.<br />
Auch deren Anzahl sowie die anteiligen Schadenssummen sollten annähernd<br />
realistisch zur Schadenssumme und zur Beschreibung passen.<br />
• Wenn e<strong>in</strong> beteiligtes Fahrzeug (genauer: der Versicherungsnehmer) nicht zu<br />
den eigenen Kunden gehört, muss gleichzeitig e<strong>in</strong> Versicherungsvertrag (mit<br />
Versicherungsnehmer und Fahrzeugdaten) gespeichert werden.<br />
• Zum<strong>in</strong>dest <strong>in</strong> der Praxis gibt es auch Unfallfahrer ohne Versicherungsschutz.<br />
Wie soll man das berücksichtigen?<br />
Eher unproblematisch s<strong>in</strong>d Ergänzungen für die folgenden Tabellen; aber weil es<br />
sich <strong>in</strong> der Regel um kle<strong>in</strong>ere Tabellen handelt, muss man sich darüber auch ke<strong>in</strong>e<br />
Gedanken machen, sondern kann jederzeit e<strong>in</strong>zelne Datensätze oder kle<strong>in</strong>e<br />
Prozeduren erstellen:<br />
• Weitere Abteilungen s<strong>in</strong>d kaum nötig; bei Bedarf macht man e<strong>in</strong>zelne INSERT-<br />
Befehle.<br />
• Weitere Mitarbeiter s<strong>in</strong>d nur nötig, wenn die Datenbank auch für andere Arbeitsbereiche<br />
benutzt werden soll.<br />
• Ebenso s<strong>in</strong>d weitere Dienstwagen nicht wichtig.<br />
• Viele weitere Fahrzeugtypen und Fahrzeughersteller wären nützlich, und zwar<br />
schon vor der Maßnahme mit den vielen neuen Versicherungsverträgen. Dafür<br />
s<strong>in</strong>d aber ke<strong>in</strong>e zufällig entstehenden Komb<strong>in</strong>ationen s<strong>in</strong>nvoll; besser ist e<strong>in</strong>e<br />
e<strong>in</strong>fache Prozedur: Der neue Typ und se<strong>in</strong> Hersteller werden im Klartext angegeben;<br />
wenn der Hersteller noch nicht existiert, wird er im gleichen Schritt<br />
(automatisch) registriert (siehe die Übungen zu den Prozeduren).<br />
Insgesamt gibt es so viele praktische Probleme, die nur sehr schwer <strong>in</strong> zufällig<br />
erzeugten Datensätzen berücksichtigt werden könnten. Wir verzichten deshalb<br />
darauf.<br />
i H<strong>in</strong>weis<br />
Für Ihre praktische Arbeit sollten Sie solche „Folgeprobleme“ immer<br />
beachten.<br />
36.4. Zusammenfassung<br />
In diesem Kapitel wurden e<strong>in</strong>e Reihe Verfahren besprochen, mit denen automatisch<br />
e<strong>in</strong>e Menge zusätzlicher Datensätze erstellt werden können:<br />
415
Testdaten erzeugen<br />
• Mit dem kartesischen Produkt können aus Hilfstabellen schnell sehr viele Datensätze<br />
erzeugt werden.<br />
• Prozeduren (oder nicht gespeicherte Rout<strong>in</strong>en) s<strong>in</strong>d <strong>in</strong> anderen Fällen hilfreich.<br />
• In aller Regel s<strong>in</strong>d viele Nebenbed<strong>in</strong>gungen zu berücksichtigen.<br />
36.5. Siehe auch<br />
Dieses Kapitel benutzt Erkenntnisse der folgenden Kapitel.<br />
• E<strong>in</strong>fache Tabellenverknüpfung 2 zum „kartesischen Produkt“<br />
• <strong>SQL</strong>-Programmierung 3 mit allgeme<strong>in</strong>er Beschreibung dazu sowie dem Abschnitt<br />
„Rout<strong>in</strong>en ohne feste Speicherung“<br />
• Prozeduren 4 mit den Abschnitten „Testdaten <strong>in</strong> e<strong>in</strong>er Tabelle erzeugen“,<br />
„INSERT <strong>in</strong> mehrere Tabellen“ und den Übungen<br />
• Änderung der Datenbankstruktur 5 mit dem Abschnitt „Prämiensatz und Prämienänderung<br />
vorbereiten“<br />
Das Skript-Testdaten − siehe Downloads 6 − enthält alle Befehle, die für die Erzeugung<br />
der Testdaten benutzt werden.<br />
2 Kapitel 19 auf Seite 173<br />
3 Kapitel 30 auf Seite 323<br />
4 Kapitel 32 auf Seite 357<br />
5 Kapitel 35 auf Seite 397<br />
6 Anhang B auf Seite 423<br />
416
Teil V.<br />
Anhang<br />
417
A. Tabellenstruktur der<br />
Beispieldatenbank<br />
Hier stehen die Def<strong>in</strong>itionen der e<strong>in</strong>zelnen Tabellen; dabei ist <strong>in</strong> Klammern jeweils<br />
der Tabellen-Alias genannt.<br />
In den Tabellen werden folgende Markierungen verwendet:<br />
• ⋆ – Spalten, die durch die nachträglichen Änderungen e<strong>in</strong>gefügt oder geändert<br />
werden<br />
• PK – PrimaryKey, Primärschlüssel<br />
• FK – ForeignKey, Fremdschlüssel als Verweis auf e<strong>in</strong>e Tabellenverknüpfung<br />
Versicherungsvertrag (vv)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Vertragsnummer varchar(20) Pflicht e<strong>in</strong>deutig<br />
Abschlussdatum date Pflicht<br />
Art char(2) Pflicht nur 'HP' oder 'TK' oder 'VK'<br />
Mitarbeiter_ID <strong>in</strong>teger Pflicht FK Mitarbeiter<br />
Fahrzeug_ID <strong>in</strong>teger Pflicht FK Fahrzeug<br />
Versicherungsnehmer_ID<br />
<strong>in</strong>teger Pflicht FK Versicherungsnehmer<br />
Basispraemie ⋆ number Pflicht größer als 0<br />
Praemiensatz ⋆ <strong>in</strong>teger Pflicht größer als 0<br />
Praemienaenderung ⋆ date optional<br />
Zuordnung_SF_FZ (zu)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Schadensfall_ID <strong>in</strong>teger Pflicht FK Schadensfall<br />
Fahrzeug_ID <strong>in</strong>teger Pflicht Verweis auf e<strong>in</strong> e<strong>in</strong>zelnes<br />
beteiligtes Fahrzeug<br />
419
Tabellenstruktur der Beispieldatenbank<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
Schadenshoehe number optional anteiliger Schaden dieses<br />
Fahrzeugs<br />
Schuldanteil ⋆ <strong>in</strong>teger Pflicht größer/gleich 0<br />
Schadensfall (sf )<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Datum date Pflicht<br />
Ort varchar(200) Pflicht genaue Angabe e<strong>in</strong>schl.<br />
Straße und Umgebung<br />
Beschreibung varchar(1000) Pflicht Angabe der Umstände<br />
Schadenshoehe number optional Angabe, soweit möglich<br />
Verletzte char(1) Pflicht nur 'J' oder 'N'<br />
Mitarbeiter_ID <strong>in</strong>teger Pflicht FK Mitarbeiter<br />
Versicherungsnehmer (vn)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Name varchar(30) Pflicht<br />
Vorname<br />
Geburtsdatum<br />
Fuehrersche<strong>in</strong><br />
varchar(30)<br />
date<br />
date<br />
optional<br />
optional<br />
optional<br />
bei natürlicher Person<br />
Pflicht<br />
Ort varchar(30) Pflicht<br />
PLZ char(5) Pflicht<br />
Strasse varchar(30) Pflicht<br />
Hausnummer varchar(10) Pflicht<br />
Eigener_Kunde char(1) Pflicht nur 'J' oder 'N'<br />
Versicherungsgesell- <strong>in</strong>teger optional FK Versicherungsgesellschaft<br />
schaft_ID<br />
bei Fremdkunden<br />
Geschlecht ⋆ char(1) optional nur 'W' oder 'M' oder NULL<br />
Fahrzeug (fz)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Kennzeichen varchar(10) Pflicht e<strong>in</strong>deutig<br />
Farbe varchar(30) optional<br />
Fahrzeugtyp_ID <strong>in</strong>teger Pflicht FK Fahrzeugtyp<br />
420
Dienstwagen (dw)<br />
Tabellenstruktur der Beispieldatenbank<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Kennzeichen varchar(10) Pflicht e<strong>in</strong>deutig<br />
Farbe varchar(30) optional<br />
Fahrzeugtyp_ID <strong>in</strong>teger Pflicht FK Fahrzeugtyp<br />
Mitarbeiter_ID <strong>in</strong>teger optional ggf. FK Mitarbeiter<br />
Mitarbeiter (mi)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Personalnummer varchar(10) Pflicht e<strong>in</strong>deutig<br />
Name varchar(30) Pflicht<br />
Vorname varchar(30) Pflicht<br />
Geburtsdatum date Pflicht<br />
Telefon varchar(30) optional<br />
Mobil varchar(30) optional<br />
Email varchar(50) optional<br />
Raum varchar(10) optional<br />
Ist_Leiter char(1) Pflicht nur 'J' oder 'N'<br />
Abteilung_ID <strong>in</strong>teger Pflicht FK Abteilung<br />
Geschlecht ⋆ char(1) Pflicht nur 'W' oder 'M'<br />
Versicherungsgesellschaft (vg)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Bezeichnung varchar(30) Pflicht<br />
Ort varchar(30) optional<br />
Fahrzeugtyp (ft)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Bezeichnung varchar(30) Pflicht<br />
Hersteller_ID <strong>in</strong>teger Pflicht FK Fahrzeughersteller<br />
421
Tabellenstruktur der Beispieldatenbank<br />
Abteilung (ab)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Kuerzel varchar(10) Pflicht ⋆Datentyp auf CHAR(4)<br />
ändern<br />
Bezeichnung varchar(30) Pflicht<br />
Ort varchar(30) optional<br />
Fahrzeughersteller (fh)<br />
Spaltenname Datentyp Eigenschaft Erläuterung<br />
ID <strong>in</strong>teger PK<br />
Name varchar(30) Pflicht<br />
Land varchar(30) optional<br />
Änderungen<br />
In den folgenden Kapiteln werden Änderungen an dieser Struktur behandelt:<br />
• Änderung der Datenbankstruktur 1<br />
• Erzeugen von Testdaten 2<br />
1 Kapitel 35 auf Seite 397<br />
2 Kapitel 36 auf Seite 407<br />
422
B. Downloads<br />
B.1. Die Download-Seite . . . . . . . . . . . . . . . . . . . . . . . . . . 423<br />
B.2. Verb<strong>in</strong>dung zu den Datenbanksystemen . . . . . . . . . . . . . 424<br />
B.3. Die vollständige Beispieldatenbank . . . . . . . . . . . . . . . . 427<br />
B.4. Erstellen der Beispieldatenbank . . . . . . . . . . . . . . . . . . 427<br />
B.5. Skripte für nachträgliche Änderungen . . . . . . . . . . . . . . 428<br />
Hier wird beschrieben, wie die Dateien der Download-Seite zu diesem Buch verwendet<br />
werden können.<br />
So kommen Sie zur Beispieldatenbank:<br />
• Entweder Sie holen sich e<strong>in</strong>e fertige Version der Datenbank:<br />
• Laden Sie die komprimierte Datei herunter.<br />
• Benennen Sie die Datei um, soweit erforderlich.<br />
• Extrahieren Sie die Daten und speichern Sie das Ergebnis an e<strong>in</strong>er passenden<br />
Stelle.<br />
• Oder Sie erzeugen die Datenbank mit e<strong>in</strong>em Hilfsprogramm des von Ihnen<br />
verwendeten DBMS:<br />
• Erstellen Sie zunächst e<strong>in</strong>e leere Datenbank, soweit erforderlich.<br />
• Laden bzw. kopieren Sie das passende Skript zum Erzeugen der Datenbanktabellen<br />
und Daten.<br />
• Führen Sie das Skript mit e<strong>in</strong>em Hilfsprogramm des von Ihnen verwendeten<br />
DBMS aus.<br />
B.1. Die Download-Seite<br />
Verschiedene Dateien, die zur Beispieldatenbank gehören, stehen unter <strong>E<strong>in</strong>führung</strong><br />
<strong>in</strong> <strong>SQL</strong> (Downloads) 1 zur Verfügung − sozusagen statt e<strong>in</strong>er Buch-CD:<br />
• Vollständige Beispieldatenbanken<br />
• Skripte zur Erstellung der Beispieldatenbank<br />
1 http://www.vs-polis.de/content/download_sql.html<br />
423
Downloads<br />
• Skripte zur späteren Erweiterung<br />
In den folgenden Abschnitten erhalten Sie Erläuterungen dazu.<br />
B.1.1. Was ist e<strong>in</strong> Skript?<br />
Unter e<strong>in</strong>em Skript 2 versteht man <strong>in</strong> der EDV e<strong>in</strong>e Liste von Befehlen, die durch<br />
e<strong>in</strong>en e<strong>in</strong>zelnen Befehl aufgerufen und automatisch nache<strong>in</strong>ander ausgeführt<br />
werden. Diese Befehle s<strong>in</strong>d meist <strong>in</strong> e<strong>in</strong>er Datei zusammengefasst und werden<br />
dadurch ausgeführt, dass die betreffende Datei aufgerufen wird.<br />
Bei <strong>SQL</strong> werden solche Dateien unter W<strong>in</strong>dows üblicherweise mit der Endung<br />
’.sql’ gespeichert.<br />
B.2. Verb<strong>in</strong>dung zu den Datenbanksystemen<br />
In der Regel gibt es verschiedene Möglichkeiten: über e<strong>in</strong>e Befehlszeile oder mit<br />
e<strong>in</strong>em GUI-Programm. Der jeweils gängigste Weg hängt vom DBMS ab und wird<br />
<strong>in</strong> dessen Dokumentation beschrieben; im Folgenden wird nur e<strong>in</strong> Verfahren besprochen.<br />
B.2.1. Firebird<br />
Wenn Sie nicht mit e<strong>in</strong>em GUI-Programm arbeiten, funktioniert immer das Basisprogramm<br />
I<strong>SQL</strong>. Registrieren Sie zunächst e<strong>in</strong> Kürzel für die Datenbank, wie<br />
<strong>in</strong> der QuickStart-Beschreibung unter Use database aliases beschrieben: In der<br />
Datei aliases.conf im Firebird-Verzeichnis ist e<strong>in</strong> E<strong>in</strong>trag wie folgt e<strong>in</strong>zufügen:<br />
# unter W<strong>in</strong>dows<br />
wb-datenbank = C:\Users\Public\Documents\WikiBooks\<strong>SQL</strong>\Beispieldatenbank.fdb<br />
# unter L<strong>in</strong>ux<br />
wb-datenbank = /home/wikibooks/sql/Beispieldatenbank.fdb<br />
Für diese Änderung werden Adm<strong>in</strong>istrator-Rechte benötigt. Virtuelle Laufwerke<br />
werden nicht erkannt (auch das spricht für den E<strong>in</strong>trag e<strong>in</strong>es Alias-Namens für<br />
die Datenbank). Selbstverständlich müssen Sie im Verzeichnis der Datenbank<br />
alle erforderlichen Rechte erhalten.<br />
2 http://de.wikipedia.<strong>org</strong>/wiki/Skriptsprache<br />
424
Verb<strong>in</strong>dung zu den Datenbanksystemen<br />
Öffnen Sie nun e<strong>in</strong>e Command-Box und wechseln <strong>in</strong> das Verzeichnis, <strong>in</strong> dem<br />
die Datenbank stehen soll oder sich bereits bef<strong>in</strong>det. Starten Sie den <strong>SQL</strong>-<br />
Interpreter:<br />
C:\Programme\Firebird\b<strong>in</strong>\isql.exe<br />
Jeder der folgenden Befehle muss mit e<strong>in</strong>em Semikolon abgeschlossen werden.<br />
Er kann sich auch auf mehrere Zeilen verteilen.<br />
--- So erstellen Sie die neue Datenbank:<br />
<strong>SQL</strong>> create database ’wb-datenbank’<br />
CON> user ’SYSDBA’ password ’masterkey’ default character set UTF8;<br />
--- Führen Sie das Skript zur Erstellung der Datenbank aus:<br />
<strong>SQL</strong>> <strong>in</strong>put ’CreateScript-Firebird.sql’;<br />
--- Schließen Sie den <strong>SQL</strong>-Interpreter:<br />
<strong>SQL</strong>> quit;<br />
--- So öffnen Sie die die vorbereitete oder die erzeugte Datenbank zur Bearbeitung:<br />
<strong>SQL</strong>> connect ’wb-datenbank’ user ’SYSDBA’ password ’masterkey’;<br />
B.2.2. MS-<strong>SQL</strong><br />
Für dieses DBMS gibt es das Programm Microsoft <strong>SQL</strong> Server Management Studio<br />
(SSMS), und zwar für jede Server-Version e<strong>in</strong>e eigene Studio-Version. Beim<br />
Programmstart melden Sie sich am Server an; die Zusammenarbeit mit der Datenbank<br />
geht über den Objekt-Explorer im Abschnitt Databases.<br />
So registrieren Sie e<strong>in</strong>e vorhandene Datenbank, beispielsweise die fertig vorbereitete<br />
Beispieldatenbank:<br />
• Wählen Sie mit e<strong>in</strong>em Rechtsklick auf Databases die Option Attach.<br />
• Wählen Sie über die Schaltfläche Add (H<strong>in</strong>zufügen) im oberen Teil des Fensters<br />
die betreffende mdf-Datei aus und bestätigen Sie die Auswahl mit OK .<br />
So erstellen Sie die Beispieldatenbank mit dem Skript neu:<br />
• Gehen Sie über die Menü-Befehle File | Open | File und wählen Sie die Datei<br />
CreateScript-MS<strong>SQL</strong>2005.sql aus.<br />
• Führen Sie das Skript mit der Schaltfläche Execute bzw. F5 aus.<br />
In gleicher Weise können Sie zu der Beispieldatenbank spätere Skripte laden und<br />
ausführen.<br />
425
Downloads<br />
B.2.3. My<strong>SQL</strong><br />
H<strong>in</strong>weis: Die Datenbankstruktur wurde während der Erstellung dieses Buches<br />
überarbeitet. Für My<strong>SQL</strong> wurde auch das Skript zur Erstellung der Beispieldatenbank<br />
angepasst; es fehlen noch saubere Formulierungen <strong>in</strong> diesem Abschnitt und<br />
e<strong>in</strong> abschließender Test durch e<strong>in</strong>en My<strong>SQL</strong>-Fachmann.<br />
Melden Sie sich am Datenbanksystem an.<br />
Erstellen Sie die neue Datenbank:<br />
> create database Beispieldatenbank;<br />
Geben Sie an, dass diese Datenbank benutzt werden soll:<br />
> use Beispieldatenbank;<br />
Führen Sie das Skript aus:<br />
> source Script-My<strong>SQL</strong>.sql<br />
B.2.4. Oracle<br />
H<strong>in</strong>weis: Die Datenbankstruktur wurde während der Erstellung dieses Buches<br />
überarbeitet. Für Oracle wurde auch das Skript zur Erstellung der Beispieldatenbank<br />
angepasst; es fehlen noch saubere Formulierungen <strong>in</strong> diesem Abschnitt und<br />
e<strong>in</strong> abschließender Test durch e<strong>in</strong>en Oracle-Fachmann.<br />
Für dieses DBMS gibt es das Programm Database Configuration Assistant<br />
(DBCA). Starten Sie es mit den Rechten als Adm<strong>in</strong>istrator des Betriebssystems.<br />
So benutzen Sie e<strong>in</strong>e vorhandene Datenbank, beispielsweise die fertig vorbereitete<br />
Beispieldatenbank:<br />
• Dieser Punkt fehlt leider noch.<br />
So erstellen Sie die Beispieldatenbank mit dem Skript neu:<br />
• Wählen Sie den Punkt Create a Database.<br />
• Bei allen folgenden Schritten können Sie die e<strong>in</strong>fachsten E<strong>in</strong>stellungen übernehmen.<br />
• Im Schritt Database Content wählen Sie das für die Datenbank vorbereitete<br />
Skript CreateScript-Oracle.sql aus.<br />
Nach Fertigstellen bzw. F<strong>in</strong>ish steht die Datenbank zur Verfügung.<br />
426
B.2.5. <strong>SQL</strong>ite<br />
Die vollständige Beispieldatenbank<br />
H<strong>in</strong>weis: Die Datenbankstruktur wurde während der Erstellung dieses Buches<br />
überarbeitet. Für <strong>SQL</strong>ite wurde auch das Skript zur Erstellung der Beispieldatenbank<br />
angepasst; es fehlen noch saubere Formulierungen <strong>in</strong> diesem Abschnitt und<br />
e<strong>in</strong> abschließender Test durch e<strong>in</strong>en <strong>SQL</strong>ite-Fachmann.<br />
Für dieses DBMS gibt es das Kommandozeilen-Tool <strong>SQL</strong>ite3.<br />
So benutzen Sie e<strong>in</strong>e vorhandene Datenbank, beispielsweise die fertig vorbereitete<br />
Beispieldatenbank:<br />
sqlite3 Beispieldatenbank.db<br />
So erstellen Sie die Beispieldatenbank mit dem Skript neu:<br />
sqlite3 -<strong>in</strong>it CreateScript-Sqlite.sql Beispieldatenbank.db<br />
B.3. Die vollständige Beispieldatenbank<br />
E<strong>in</strong>e fertige Beispieldatenbank steht für das e<strong>in</strong>e oder andere DBMS als zip-<br />
Datei zur Verfügung. Sie müssen also so v<strong>org</strong>ehen:<br />
• Drücken Sie auf der Download-Seite im Abschnitt Vollständige Beispieldatenbank<br />
3 auf die Schaltfläche, die zu Ihrem DBMS gehört.<br />
• Speichern Sie die zip-Datei an e<strong>in</strong>er geeigneten Stelle, <strong>in</strong> der Regel <strong>in</strong> Ihrem<br />
Download-Bereich.<br />
• Öffnen Sie die zip-Datei zum Extrahieren.<br />
• Speichern Sie die dar<strong>in</strong> enthaltenen Dateien <strong>in</strong> e<strong>in</strong>em Arbeitsverzeichnis für<br />
die Beispieldatenbank.<br />
Danach können Sie die Beispieldatenbank direkt öffnen und bearbeiten, wie es<br />
oben beim DBMS beschrieben ist.<br />
B.4. Erstellen der Beispieldatenbank<br />
Wenn Sie die Beispieldatenbank selbst erstellen wollen, sollen oder müssen, gehen<br />
Sie bitte so vor:<br />
3 http://www.vs-polis.de/content/download_sql.html#3<br />
427
Downloads<br />
• Drücken Sie auf der Download-Seite im Abschnitt Skripte zur Erstellung 4 auf<br />
die Schaltfläche, die zu Ihrem DBMS gehört.<br />
• Wenn mit der l<strong>in</strong>ken Maustaste der Download nicht automatisch gestartet<br />
wird, dann verwenden Sie die rechte Maustaste und wählen Sie Ziel speichern<br />
unter.<br />
• Speichern Sie die Datei <strong>in</strong> dem Verzeichnis (Ordner), <strong>in</strong> dem die fertige Datenbank<br />
liegen soll, z. B. unter C:\Users\Public\Documents\WikiBooks\<strong>SQL</strong>.<br />
• Speichern Sie die Datei dort unter e<strong>in</strong>em s<strong>in</strong>nvollen Namen, z. B. als<br />
CreateScript-My<strong>SQL</strong>.sql (meistens wird e<strong>in</strong> richtiger Name v<strong>org</strong>egeben).<br />
Danach können Sie die Beispieldatenbank mit Hilfe dieses Skripts erzeugen, wie<br />
es oben beim DBMS beschrieben ist.<br />
Wenn es zu Ihrer Version des DBMS ke<strong>in</strong>e vorbereitete Skript-Datei gibt, dann<br />
holen Sie sich e<strong>in</strong>e andere (möglichst für e<strong>in</strong>e ähnliche Version) und gehen Sie<br />
genauso vor. In diesem Fall s<strong>in</strong>d Fehler beim Erzeugen der Datenbank zu erwarten.<br />
Dann müssen Sie nach der Beschreibung des betreffenden <strong>SQL</strong>-Dialekts<br />
nach und nach im Skript alle Fehler beseitigen, bis die Tabellen und Datensätze<br />
gespeichert werden.<br />
i Bitte arbeiten Sie mit!<br />
Die Autoren und die künftigen Leser s<strong>in</strong>d Ihnen sehr dankbar, wenn Sie danach<br />
die fehlerfreie Fassung der sql-Datei und die Beispieldatenbank dazu<br />
zur Verfügung stellen, wie auf der Download-Seite beschrieben. Damit helfen<br />
Sie bei der Verbesserung und der Erweiterung dieses Buches.<br />
B.5. Skripte für nachträgliche Änderungen<br />
Für verschiedene Arbeiten <strong>in</strong> den Kapiteln Änderung der Datenbankstruktur 5<br />
und Testdaten erzeugen 6 stehen ebenfalls Skript-Dateien bereit.<br />
Bitte beachten Sie unbed<strong>in</strong>gt: Diese Änderungen dürfen erst an den entsprechenden<br />
Stellen <strong>in</strong> diesen Kapiteln ausgeführt werden; vorher fehlt die notwendige<br />
Sachkenntnis. Außerdem muss – wie dort erläutert – die Reihenfolge der Änderungen<br />
beachtet werden, und vergessen Sie nicht die Datensicherung vorher<br />
und zwischendurch.<br />
4 http://www.vs-polis.de/content/download_sql.html#4<br />
5 Kapitel 35 auf Seite 397<br />
6 Kapitel 36 auf Seite 407<br />
428
Um e<strong>in</strong>es dieser Skripte zu nutzen, gehen Sie bitte so vor:<br />
Skripte für nachträgliche Änderungen<br />
• Drücken Sie auf der Download-Seite im Abschnitt Skripte zur späteren Erweiterung<br />
7 auf die Schaltfläche zu dem gewünschten Arbeitsschritt.<br />
• Wenn mit der l<strong>in</strong>ken Maustaste der Download nicht automatisch gestartet<br />
wird, dann verwenden Sie die rechte Maustaste und wählen Sie Ziel speichern<br />
unter.<br />
• Speichern Sie die Datei <strong>in</strong> dem Verzeichnis (Ordner), <strong>in</strong> dem die fertige Datenbank<br />
liegt, z. B. unter C:\Users\Public\Documents\WikiBooks\<strong>SQL</strong>.<br />
• Speichern Sie die Datei dort unter e<strong>in</strong>em s<strong>in</strong>nvollen Namen, z. B. als<br />
Skript-Spalten.sql (normalerweise wird e<strong>in</strong> richtiger Name v<strong>org</strong>egeben).<br />
• Öffnen Sie die Skript-Datei und ändern Sie all das, was „offensichtlich“ nicht<br />
zu Ihrem <strong>SQL</strong>-Dialekt passt.<br />
Achtung<br />
Diese Änderungen s<strong>in</strong>d teilweise sehr komplex. Gehen Sie deshalb immer<br />
schrittweise vor:<br />
1. Machen Sie e<strong>in</strong> Backup der Datenbank mit dem vorherigen,<br />
fehlerfreien Bestand.<br />
2. Führen Sie den nächsten <strong>SQL</strong>-Befehl aus (niemals das vollständige<br />
Skript!).<br />
3. Kontrollieren Sie das Ergebnis.<br />
4. Beim Auftreten von Fehlern ist das letzte Backup zurückzuholen<br />
(Restore).<br />
Anstelle der e<strong>in</strong>zelnen sql-Dateien können Sie auch alle Dateien „am Stück“ als<br />
zip-Datei holen. Dann s<strong>in</strong>d die sql-Dateien zu extrahieren und im Arbeitsverzeichnis<br />
der Datenbank zu speichern.<br />
7 http://www.vs-polis.de/content/download_sql.html#5<br />
429
C. Befehlsreferenz<br />
C.1. DDL (Data Def<strong>in</strong>ition Language) . . . . . . . . . . . . . . . . . . 431<br />
C.2. DML – Data Manipulation Language . . . . . . . . . . . . . . . . 435<br />
C.3. TCL – Transaction Control Language . . . . . . . . . . . . . . . 438<br />
C.4. DCL – Data Control Language . . . . . . . . . . . . . . . . . . . . 439<br />
C.5. Schlüsselwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440<br />
Für diese Übersicht 1 gelten die gleichen H<strong>in</strong>weise, wie sie <strong>in</strong> der E<strong>in</strong>leitung 2 genannt<br />
s<strong>in</strong>d.<br />
Die Struktur der Befehle richtet sich nach der <strong>SQL</strong>-Dokumentation. Beispiele<br />
s<strong>in</strong>d nur dann erwähnt, wenn sie <strong>in</strong> diesem Buch nicht enthalten s<strong>in</strong>d, aber dennoch<br />
von Bedeutung se<strong>in</strong> können.<br />
Bitte beachten Sie vor allem die <strong>SQL</strong>-Dialekte: Verschiedene Optionen gelten nur<br />
manchmal, nicht immer oder mit anderen Schreibweisen.<br />
C.1. DDL (Data Def<strong>in</strong>ition Language)<br />
Zu dieser Kategorie gehören Befehle, die die Datenbank-Objekte beschreiben.<br />
Die grundlegenden Befehle lauten:<br />
CREATE -- erzeugt e<strong>in</strong> Objekt<br />
ALTER -- ändert e<strong>in</strong> Objekt<br />
DROP -- löscht e<strong>in</strong> Objekt<br />
1 Diese Befehlsreferenz war Teil e<strong>in</strong>es nicht mehr vorhandenen Buches <strong>SQL</strong>. Bei der Übernahme<br />
wurden Gliederung und Struktur angepasst, Beispiele weitgehend gestrichen, weil sie sowieso<br />
Teil des Buches s<strong>in</strong>d, und auf die WebL<strong>in</strong>ks verzichtet, weil diese <strong>in</strong> der E<strong>in</strong>leitung {Kapitel 3 auf<br />
Seite 11} und den Webl<strong>in</strong>ks {Anhang D auf Seite 445} stehen. Die Versionsgeschichte und die<br />
Diskussionsseite der Onl<strong>in</strong>e-Version enthalten die früheren Beiträge weiterh<strong>in</strong>.<br />
2 Kapitel 3 auf Seite 11<br />
431
Befehlsreferenz<br />
C.1.1. TABLE – Tabelle bearbeiten<br />
CREATE TABLE<br />
Dieser Befehl legt e<strong>in</strong>e neue Tabelle an. Basissyntax:<br />
CREATE TABLE [() | AS ]<br />
Syntax e<strong>in</strong>er e<strong>in</strong>zelnen :<br />
[()] [] [DEFAULT ]<br />
Als stehen die Datentypen des jeweiligen DBMS zur Verfügung, z. B.<br />
CHAR, VARCHAR, INT, FLOAT.<br />
gibt an, ob das Feld leer bleiben darf (NULL) oder ob e<strong>in</strong> Wert vorhanden<br />
se<strong>in</strong> muss (NOT NULL).<br />
Als kann e<strong>in</strong> Wert v<strong>org</strong>egeben werden, der automatisch gespeichert<br />
wird, falls das Feld nicht leer bleiben darf, aber tatsächlich ke<strong>in</strong> Wert e<strong>in</strong>getragen<br />
wird.<br />
ist e<strong>in</strong>e Abfrage durch SELECT, die als Grundlage für die neue<br />
Tabelle verwendet wird.<br />
Beispiele<br />
-- E<strong>in</strong>e neue Tabelle mit 2 Feldern anlegen<br />
CREATE TABLE testtab (feld1 varchar (20), feld2 number (20))<br />
-- E<strong>in</strong>e neue Tabelle aus e<strong>in</strong>em SELECT-Statement erstellen<br />
CREATE TABLE testtab AS SELECT feld1, feld2 FROM testtab2<br />
ALTER TABLE<br />
Dieser Befehl verändert e<strong>in</strong>e bestehende Tabelle. Basissyntax:<br />
ALTER TABLE <br />
kann e<strong>in</strong>s der folgenden se<strong>in</strong>:<br />
-- Tabelle umbenennen<br />
RENAME TO <br />
432
-- Feld(er) h<strong>in</strong>zufügen<br />
ADD ()<br />
-- Feld ändern<br />
MODIFY <br />
-- Feld(er) löschen<br />
DROP ()<br />
-- Haupt-/ Fremdschlüssel h<strong>in</strong>zufügen<br />
ADD CONSTRAINT <br />
-- Haupt-/ Fremdschlüssel löschen<br />
DROP PRIMARY KEY -- Hauptschlüssel<br />
DROP FOREIGN KEY -- Fremdschlüssel<br />
-- Index erstellen<br />
ADD INDEX [] ()<br />
-- Index löschen<br />
DROP INDEX <br />
kann e<strong>in</strong>e der folgenden se<strong>in</strong>:<br />
DDL (Data Def<strong>in</strong>ition Language)<br />
-- Hauptschlüssel<br />
PRIMARY KEY ()<br />
-- Fremdschlüssel<br />
FOREIGN KEY () REFERENCES ()<br />
kann (<strong>in</strong> den meisten Fällen) folgender se<strong>in</strong>:<br />
UNIQUE<br />
PRIMARY<br />
FULLTEXT<br />
Beispiele<br />
-- Tabelle umbenennen<br />
ALTER, TABLE testtab RENAME TO tab2<br />
-- Feld h<strong>in</strong>zufügen<br />
ALTER TABLE testtab ADD (feld1 number(59))<br />
ALTER TABLE testtab ADD (feld2 date NOT NULL)<br />
ALTER TABLE testtab ADD (feld3 number(50) DEFAULT 10)<br />
-- E<strong>in</strong> Feld auf NUMBER, Länge 50 und NULL ändern:<br />
ALTER TABLE testtab MODIFY feld1 number (50) NULL<br />
-- Feld löschen<br />
433
Befehlsreferenz<br />
ALTER TABLE testtab DROP feld1<br />
-- Primary-Key erstellen:<br />
ALTER TABLE testtab ADD CONSTRAINT primary_key_bezeichnung PRIMARY KEY (feld1)<br />
-- Primary-Key löschen<br />
ALTER TABLE testtab DROP PRIMARY KEY<br />
-- Foreign-Key erstellen:<br />
ALTER TABLE testtab ADD CONSTRAINT foreign_key_bezeichnung<br />
FOREIGN KEY (feld1) REFERENCES tab2 (feld2)<br />
-- Foreign-Key löschen<br />
ALTER TABLE testtab DROP FOREIGN KEY foreign_key_bezeichnung<br />
-- Index erstellen<br />
ALTER TABLE testtab ADD INDEX <strong>in</strong>dex_name <strong>in</strong>dex_typ (feld1)<br />
-- Index löschen<br />
ALTER TABLE testtab DROP INDEX <strong>in</strong>dex_name<br />
DROP TABLE<br />
Tabelle testtab löschen:<br />
DROP TABLE testtab<br />
Tabelle testtab <strong>in</strong>klusive Constra<strong>in</strong>ts löschen:<br />
DROP TABLE testtab CASCADE CONSTRAINTS<br />
C.1.2. INDEX – Index bearbeiten<br />
CREATE INDEX<br />
CREATE INDEX on ();<br />
CREATE INDEX on (,,...);<br />
DROP INDEX<br />
434<br />
DROP INDEX
C.1.3. VIEW – Ansicht auf Tabellen<br />
CREATE VIEW<br />
E<strong>in</strong>e spezielle Sicht auf e<strong>in</strong>e oder mehrere Tabellen erzeugen:<br />
CREATE VIEW AS <br />
DML – Data Manipulation Language<br />
Als kann e<strong>in</strong> „beliebiger“ SELECT-Befehl verwendet werden.<br />
DROP VIEW<br />
DROP VIEW <br />
C.1.4. E<strong>in</strong>zelheiten<br />
Diese Befehle und Varianten werden <strong>in</strong> folgenden Kapiteln genauer behandelt:<br />
• DDL − E<strong>in</strong>zelheiten 3<br />
• Erstellen von Views 4<br />
C.2. DML – Data Manipulation Language<br />
Diese Kategorie umfasst die Befehle, die etwas mit e<strong>in</strong>em Datenbestand machen.<br />
C.2.1. Abfragen mit SELECT<br />
Mit dem SELECT-Befehl werden Daten aus e<strong>in</strong>er oder mehreren Tabellen abgerufen.<br />
SELECT<br />
[ DISTINCT | ALL ]<br />
<br />
FROM <br />
[ WHERE ]<br />
[ GROUP BY ]<br />
[ HAVING ]<br />
3 Kapitel 28 auf Seite 285<br />
4 Kapitel 27 auf Seite 271<br />
435
Befehlsreferenz<br />
[ { UNION [ALL] | INTERSECT | MINUS } ]<br />
[ ORDER BY ]<br />
Dieser Befehl und se<strong>in</strong>e Klauseln werden vor allem unter Ausführliche SELECT-<br />
Struktur 5 behandelt.<br />
C.2.2. Neuaufnahmen mit INSERT<br />
Mit dem INSERT-Befehl werden Daten <strong>in</strong> e<strong>in</strong>er Tabelle erstmals gespeichert. Als<br />
Syntax gibt es vor allem zwei Varianten:<br />
INSERT INTO [ ] VALUES ( )<br />
INSERT INTO [ ] SELECT <br />
Als ist e<strong>in</strong>e vorhandene Tabelle anzugeben. Unter bestimmten starken<br />
E<strong>in</strong>schränkungen kann auch e<strong>in</strong>e VIEW angegeben werden, siehe dort 6 .<br />
Als werden e<strong>in</strong>e oder mehrere Spalten der Tabelle aufgeführt.<br />
• Spalten, die <strong>in</strong> dieser Liste genannt werden, bekommen e<strong>in</strong>en Wert aus der<br />
bzw. dem SELECT-Ausdruck zugewiesen.<br />
• Spalten, die <strong>in</strong> dieser Liste nicht genannt werden, bekommen e<strong>in</strong>en DEFAULT-<br />
Wert oder NULL zugewiesen; dies hängt von der Tabellendef<strong>in</strong>ition für die betreffende<br />
Spalte ab.<br />
• Die Reihenfolge der Spalten <strong>in</strong> der muss mit der Reihenfolge der<br />
Daten <strong>in</strong> der bzw. dem übere<strong>in</strong>stimmen.<br />
• Die Datentypen von Spalten und Werten müssen übere<strong>in</strong>stimmen oder zum<strong>in</strong>dest<br />
soweit „kompatibel“ se<strong>in</strong>, dass sie automatisch (implizit) konvertiert<br />
werden können.<br />
Wenn durch ke<strong>in</strong>e Spalten angegeben werden, müssen alle Spalten<br />
aus der Tabellendef<strong>in</strong>ition <strong>in</strong> dieser Reihenfolge mit Inhalt versehen werden,<br />
und sei es ausdrücklich mit NULL.<br />
Die besteht aus e<strong>in</strong>em oder mehreren Werten (entsprechend der<br />
). Dabei kann es sich um konstante Werte oder Ergebnisse e<strong>in</strong>facher<br />
Funktionen handeln, aber auch um Abfragen. Wenn komplexe Ausdrücke<br />
als Werte zugewiesen werden sollen, ist die Variante mit SELECT zu verwenden.<br />
E<strong>in</strong>zelheiten werden behandelt <strong>in</strong>:<br />
5 Kapitel 15 auf Seite 129<br />
6 Kapitel 27 auf Seite 271<br />
436
• <strong>SQL</strong>-Befehle 7<br />
• DML (2) – Daten speichern 8<br />
• Unterabfragen 9<br />
C.2.3. Änderungen mit UPDATE<br />
DML – Data Manipulation Language<br />
Mit dem UPDATE-Befehl werden Daten <strong>in</strong> e<strong>in</strong>er Tabelle geändert, und zwar für<br />
e<strong>in</strong>e oder mehrere Spalten <strong>in</strong> e<strong>in</strong>em oder mehreren Datensätzen. Der Befehl benutzt<br />
diese Syntax:<br />
UPDATE <br />
SET = [ ,<br />
= , usw.<br />
= ]<br />
[ WHERE ];<br />
Als ist e<strong>in</strong>e vorhandene Tabelle anzugeben. Unter bestimmten starken<br />
E<strong>in</strong>schränkungen kann auch e<strong>in</strong>e VIEW angegeben werden, siehe dazu dort 10 .<br />
Die Wertzuweisungsliste <strong>in</strong> der SET-Klausel besteht aus e<strong>in</strong>er oder (mit Komma<br />
verbunden) mehreren Wertzuweisungen.<br />
Die WHERE-Klausel arbeitet mit e<strong>in</strong>er oder mehreren Bed<strong>in</strong>gungen genauso wie<br />
beim SELECT-Befehl. Ohne WHERE-Bed<strong>in</strong>gung werden alle Zeilen geändert.<br />
E<strong>in</strong>zelheiten werden behandelt <strong>in</strong>:<br />
• <strong>SQL</strong>-Befehle 11<br />
• DML (2) – Daten speichern 12<br />
• Unterabfragen 13<br />
C.2.4. Löschungen mit DELETE<br />
Mit dem DELETE-Befehl werden Daten <strong>in</strong> e<strong>in</strong>er Tabelle gelöscht, und zwar null,<br />
e<strong>in</strong>e, mehrere oder alle Zeilen mit e<strong>in</strong>em e<strong>in</strong>zigen Befehl. Dieser benutzt die folgende<br />
Syntax:<br />
7 Kapitel 7 auf Seite 47<br />
8 Kapitel 9 auf Seite 69<br />
9 Kapitel 26 auf Seite 251<br />
10 Kapitel 27 auf Seite 271<br />
11 Kapitel 7 auf Seite 47<br />
12 Kapitel 9 auf Seite 69<br />
13 Kapitel 26 auf Seite 251<br />
437
Befehlsreferenz<br />
DELETE FROM <br />
[ WHERE ];<br />
Als ist e<strong>in</strong>e vorhandene Tabelle anzugeben. Unter bestimmten starken<br />
E<strong>in</strong>schränkungen kann auch e<strong>in</strong>e VIEW angegeben werden, siehe dazu dort 14 .<br />
Die WHERE-Klausel arbeitet mit e<strong>in</strong>er oder mehreren Bed<strong>in</strong>gungen genauso wie<br />
beim SELECT-Befehl. Ohne WHERE-Bed<strong>in</strong>gung werden alle Zeilen gelöscht.<br />
E<strong>in</strong>zelheiten werden behandelt <strong>in</strong>:<br />
• <strong>SQL</strong>-Befehle 15<br />
• DML (2) – Daten speichern 16<br />
C.2.5. Tabelle leeren mit TRUNCATE<br />
Mit dem TRUNCATE-Befehl werden sämtliche Daten <strong>in</strong> e<strong>in</strong>er Tabelle gelöscht:<br />
TRUNCATE TABLE ;<br />
Dies entspricht dem DELETE-Befehl ohne WHERE-Klausel:<br />
DELETE FROM ;<br />
Dabei gibt es den gravierenden Unterschied, dass die Löschungen nach TRUN-<br />
CATE nicht mehr mit ROLLBACK rückgängig gemacht werden können, da die<br />
Transaktion implizit abgeschlossen wird. Auch deshalb ist diese Anweisung üblicherweise<br />
schneller als e<strong>in</strong>e entsprechende DELETE-Anweisung.<br />
Achtung: TRUNCATE TABLE kann nicht mit Foreign Keys umgehen!<br />
C.3. TCL – Transaction Control Language<br />
Transaktionen 17 s<strong>org</strong>en für die Daten<strong>in</strong>tegrität: Anweisungen, die logisch zusammengehören,<br />
werden entweder alle vollständig oder überhaupt nicht ausgeführt.<br />
Die TCL-Anweisungen steuern Transaktionen.<br />
14 Kapitel 27 auf Seite 271<br />
15 Kapitel 7 auf Seite 47<br />
16 Kapitel 9 auf Seite 69<br />
17 Kapitel 11 auf Seite 89<br />
438
C.3.1. Bestätigen mit COMMIT<br />
DCL – Data Control Language<br />
Abschließen e<strong>in</strong>er Transaktion, wobei alle seit Beg<strong>in</strong>n der Transaktion durchgeführten<br />
Änderungen bestätigt und endgültig gespeichert werden.<br />
COMMIT;<br />
Nicht alle Datenbanken unterstützen Transaktionen. E<strong>in</strong>ige können auch je nach<br />
Konfiguration oder verwendetem Tabellentyp <strong>in</strong> verschiedenen Modi arbeiten.<br />
So f<strong>in</strong>det im sogenannten „Auto Commit“-Modus nach Ausführung jeder DML-<br />
Anweisung sofort e<strong>in</strong> COMMIT statt, die Änderungen können also nicht mehr<br />
über ROLLBACK rückgängig gemacht werden.<br />
C.3.2. Widerruf mit ROLLBACK<br />
Abschließen e<strong>in</strong>er Transaktion, wobei alle seit Beg<strong>in</strong>n der Transaktion durchgeführten<br />
Änderungen rückgängig gemacht werden.<br />
ROLLBACK;<br />
Soweit das verwendete DBMS Transaktionen unterstützt, wird durch die<br />
ROLLBACK-Anweisung der Daten<strong>in</strong>halt auf den Zustand vor Ausführung der<br />
seit dem letzten COMMIT/ROLLBACK durchgeführten Datenmanipulationen<br />
zurückgesetzt.<br />
C.4. DCL – Data Control Language<br />
E<strong>in</strong>e „vollwertige“ <strong>SQL</strong>-Datenbank enthält umfassende Regelungen darüber, wie<br />
Zugriffsrechte 18 auf Objekte (Tabellen, e<strong>in</strong>zelne Felder, <strong>in</strong>terne Funktionen usw.)<br />
vergeben werden. Am Anfang stehen diese Rechte nur dem Ersteller der Datenbank<br />
und dem System-Adm<strong>in</strong>istrator zu. Andere Benutzer müssen ausdrücklich<br />
zu e<strong>in</strong>zelnen Handlungen ermächtigt werden.<br />
Die vielfältigen Abstufungen der Rechte und Optionen s<strong>in</strong>d <strong>in</strong> der jeweiligen<br />
DBMS-Dokumentation nachzulesen.<br />
18 Kapitel 12 auf Seite 95<br />
439
Befehlsreferenz<br />
C.4.1. GRANT<br />
Mit diesem Befehl wird e<strong>in</strong> Recht übertragen:<br />
GRANT ON TO <br />
C.4.2. REVOKE<br />
Mit diesem Befehl wird e<strong>in</strong> Recht widerrufen:<br />
REVOKE ON FROM <br />
C.5. Schlüsselwörter<br />
Mithilfe der Schlüsselwörter kann e<strong>in</strong> DBMS <strong>in</strong> e<strong>in</strong>er beliebigen Zeichenkette<br />
<strong>SQL</strong>-Befehle, weitere feste Begriffe und ergänzende Angaben unterscheiden.<br />
Reservierte Begriffe: Diese werden für <strong>SQL</strong>-Befehle und ähnliche feststehende<br />
Angaben verwendet.<br />
Nicht-reservierte Begriffe: Diese s<strong>in</strong>d nach <strong>SQL</strong>-Standard eigentlich freigegeben<br />
und für spezielle Verwendung nur vorbereitet.<br />
In der Praxis macht diese Unterscheidung ke<strong>in</strong>en S<strong>in</strong>n; <strong>in</strong> der folgenden Aufstellung<br />
wird sie deshalb nur als H<strong>in</strong>weis [res] bzw. [non] erwähnt.<br />
A<br />
i Warnung<br />
Sie sollten Schlüsselwörter niemals für eigene Bezeichner wie Tabellen,<br />
Spalten, Prozeduren usw. verwenden!<br />
[res] ABS ALL ALLOCATE ALTER AND ANY ARE ARRAY AS ASENSITIVE ASYMMETRIC AT<br />
ATOMIC AUTHORIZATION AVG<br />
[non] A ABSOLUTE ACTION ADA ADD ADMIN AFTER ALWAYS ASC ASSERTION ASSIGNMENT<br />
ATTRIBUTE ATTRIBUTES<br />
B<br />
[res] BEGIN BETWEEN BIGINT BINARY BLOB BOOLEAN BOTH BY<br />
440
[non] BEFORE BERNOULLI BREADTH<br />
C<br />
Schlüsselwörter<br />
[res] CALL CALLED CARDINALITY CASCADED CASE CAST CEIL CEILING CHAR CHAR_LENGTH<br />
CHARACTER CHARACTER_LENGTH CHECK CLOB CLOSE COALESCE COLLATE COLLECT COLUMN<br />
COMMIT CONDITION CONNECT CONSTRAINT CONVERT CORR CORRESPONDING COUNT COVAR_POP<br />
COVAR_SAMP CREATE CROSS CUBE CUME_DIST CURRENT CURRENT_DATE<br />
CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH CURRENT_ROLE CURRENT_TIME<br />
CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE CURRENT_USER CURSOR CYCLE<br />
[non] C CASCADE CATALOG CATALOG_NAME CHAIN CHARACTER_SET_CATALOG<br />
CHARACTER_SET_NAME CHARACTER_SET_SCHEMA CHARACTERISTICS CHARACTERS<br />
CLASS_ORIGIN COBOL COLLATION COLLATION_CATALOG COLLATION_NAME COLLATION_SCHEMA<br />
COLUMN_NAME COMMAND_FUNCTION COMMAND_FUNCTION_CODE COMMITTED CONDITION_NUMBER<br />
CONNECTION CONNECTION_NAME CONSTRAINT_CATALOG CONSTRAINT_NAME<br />
CONSTRAINT_SCHEMA CONSTRAINTS CONSTRUCTOR CONTAINS CONTINUE CURSOR_NAME<br />
D<br />
[res] DATE DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULT DELETE DENSE_RANK DEREF<br />
DESCRIBE DETERMINISTIC DISCONNECT DISTINCT DOUBLE DROP DYNAMIC<br />
[non] DATA DATETIME_INTERVAL_CODE DATETIME_INTERVAL_PRECISION DEFAULTS<br />
DEFERRABLE DEFERRED DEFINED DEFINER DEGREE DEPTH DERIVED DESC DESCRIPTOR<br />
DIAGNOSTICS DISPATCH DOMAIN DYNAMIC_FUNCTION DYNAMIC_FUNCTION_CODE<br />
E<br />
[res] EACH ELEMENT ELSE END END-EXEC ESCAPE EVERY EXCEPT EXEC EXECUTE EXISTS<br />
EXP EXTERNAL EXTRACT<br />
[non] EQUALS EXCEPTION EXCLUDE EXCLUDING<br />
F<br />
[res] FALSE FETCH FILTER FLOAT FLOOR FOR FOREIGN FREE FROM FULL FUNCTION<br />
FUSION<br />
[non] FINAL FIRST FOLLOWING FORTRAN FOUND<br />
G<br />
[res] GET GLOBAL GRANT GROUP GROUPING<br />
[non] G GENERAL GENERATED GO GOTO GRANTED<br />
H<br />
[res] HAVING HOLD HOUR<br />
[non] HIERARCHY<br />
441
Befehlsreferenz<br />
I<br />
[res] IDENTITY IN INDICATOR INNER INOUT INSENSITIVE INSERT INT INTEGER<br />
INTERSECT INTERSECTION INTERVAL INTO IS<br />
[non] IMMEDIATE IMPLEMENTATION INCLUDING INCREMENT INITIALLY INPUT INSTANCE<br />
INSTANTIABLE INVOKER ISOLATION<br />
J<br />
[res] JOIN<br />
K<br />
[non] K KEY KEY_MEMBER KEY_TYPE<br />
L<br />
[res] LANGUAGE LARGE LATERAL LEADING LEFT LIKE LN LOCAL LOCALTIME<br />
LOCALTIMESTAMP LOWER<br />
[non] LAST LENGTH LEVEL LOCATOR<br />
M<br />
[res] MATCH MAX MEMBER MERGE METHOD MIN MINUTE MOD MODIFIES MODULE MONTH<br />
MULTISET<br />
[non] M MAP MATCHED MAXVALUE MESSAGE_LENGTH MESSAGE_OCTET_LENGTH MESSAGE_TEXT<br />
MINVALUE MORE MUMPS<br />
N<br />
[res] NATIONAL NATURAL NCHAR NCLOB NEW NO NONE NORMALIZE NOT NULL NULLIF<br />
NUMERIC<br />
[non] NAME NAMES NESTING NEXT NORMALIZED NULLABLE NULLS NUMBER<br />
O<br />
[res] OCTET_LENGTH OF OLD ON ONLY OPEN OR ORDER OUT OUTER OVER OVERLAPS<br />
OVERLAY<br />
[non] OBJECT OCTETS OPTION OPTIONS ORDERING ORDINALITY OTHERS OUTPUT<br />
OVERRIDING<br />
P<br />
[res] PARAMETER PARTITION PERCENT_RANK PERCENTILE_CONT PERCENTILE_DISC<br />
POSITION POWER PRECISION PREPARE PRIMARY PROCEDURE<br />
442
Schlüsselwörter<br />
[non] PAD PARAMETER_MODE PARAMETER_NAME PARAMETER_ORDINAL_POSITION<br />
PARAMETER_SPECIFIC_CATALOG PARAMETER_SPECIFIC_NAME PARAMETER_SPECIFIC_SCHEMA<br />
PARTIAL PASCAL PATH PLACING PLI PRECEDING PRESERVE PRIOR PRIVILEGES PUBLIC<br />
R<br />
[res] RANGE RANK READS REAL RECURSIVE REF REFERENCES REFERENCING REGR_AVGX<br />
REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY<br />
REGR_SYY RELEASE RESULT RETURN RETURNS REVOKE RIGHT ROLLBACK ROLLUP ROW<br />
ROW_NUMBER ROWS<br />
[non] READ RELATIVE REPEATABLE RESTART RESTRICT RETURNED_CARDINALITY<br />
RETURNED_LENGTH RETURNED_OCTET_LENGTH RETURNED_<strong>SQL</strong>STATE ROLE ROUTINE<br />
ROUTINE_CATALOG ROUTINE_NAME ROUTINE_SCHEMA ROW_COUNT<br />
S<br />
[res] SAVEPOINT SCOPE SCROLL SEARCH SECOND SELECT SENSITIVE SESSION_USER SET<br />
SIMILAR SMALLINT SOME SPECIFIC SPECIFICTYPE <strong>SQL</strong> <strong>SQL</strong>EXCEPTION <strong>SQL</strong>STATE<br />
<strong>SQL</strong>WARNING SQRT START STATIC STDDEV_POP STDDEV_SAMP SUBMULTISET SUBSTRING SUM<br />
SYMMETRIC SYSTEM SYSTEM_USER<br />
[non] SCALE SCHEMA SCHEMA_NAME SCOPE_CATALOG SCOPE_NAME SCOPE_SCHEMA SECTION<br />
SECURITY SELF SEQUENCE SERIALIZABLE SERVER_NAME SESSION SETS SIMPLE SIZE<br />
SOURCE SPACE SPECIFIC_NAME STATE STATEMENT STRUCTURE STYLE SUBCLASS_ORIGIN<br />
T<br />
[res] TABLE TABLESAMPLE THEN TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TO<br />
TRAILING TRANSLATE TRANSLATION TREAT TRIGGER TRIM TRUE<br />
[non] TABLE_NAME TEMPORARY TIES TOP_LEVEL_COUNT TRANSACTION TRANSACTION_ACTIVE<br />
TRANSACTIONS_COMMITTED TRANSACTIONS_ROLLED_BACK TRANSFORM TRANSFORMS<br />
TRIGGER_CATALOG TRIGGER_NAME TRIGGER_SCHEMA TYPE<br />
U<br />
[res] UESCAPE UNION UNIQUE UNKNOWN UNNEST UPDATE UPPER USER USING<br />
[non] UNBOUNDED UNCOMMITTED UNDER UNNAMED USAGE USER_DEFINED_TYPE_CATALOG<br />
USER_DEFINED_TYPE_CODE USER_DEFINED_TYPE_NAME USER_DEFINED_TYPE_SCHEMA<br />
V<br />
[res] VALUE VALUES VAR_POP VAR_SAMP VARCHAR VARYING<br />
[non] VIEW<br />
W<br />
[res] WHEN WHENEVER WHERE WIDTH_BUCKET WINDOW WITH WITHIN WITHOUT<br />
[non] WORK WRITE<br />
443
Befehlsreferenz<br />
Y<br />
[res] YEAR<br />
Z<br />
[non] ZONE<br />
Diese Aufstellung wurde übernommen aus <strong>SQL</strong>-2003 Documents 19 :<br />
• Jim Melton: Information technology – Database languages – <strong>SQL</strong> – Part 2:<br />
Foundation (<strong>SQL</strong>/Foundation). August 2003 (zuletzt abgerufen am 24. Juni<br />
2012), dar<strong>in</strong> enthalten 5WD-02-Foundation-2003-09.pdf pp.136<br />
19 http://www.wiscorp.com/sql_2003_standard.zip<br />
444
D. Weitere Informationen<br />
D.1. Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . 445<br />
D.2. Webl<strong>in</strong>ks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446<br />
D.1. Literaturverzeichnis<br />
• Faeskorn-Woyke, Heide; Bertelsmeier, Birgit; Riemer, Petra; Bauer, Elena: Datenbanksysteme,<br />
Theorie und Praxis mit <strong>SQL</strong>2003, Oracle und My<strong>SQL</strong>. Pearson-<br />
Studium, München 2007, ISBN 978-3-8273-7266-6.<br />
• Hernandez, Michael J.; Viescas, John: Go To <strong>SQL</strong>. Addison-Wesley, München<br />
2001, ISBN 3-8273-1772-X.<br />
• Kosch, Andreas: InterBase-Datenbankentwicklung mit Delphi. Software & Support<br />
Verlag, Frankfurt a.M. 2001, ISBN 3-935042-09-4.<br />
• Kuhlmann, Gregor; Müllmerstadt, Friedrich: <strong>SQL</strong>. Rowohlt, Re<strong>in</strong>bek 2004,<br />
ISBN 3-499-61245-3.<br />
• Marsch, Jürgen; Fritze, Jörg: Erfolgreiche Datenbankanwendung mit <strong>SQL</strong>3. Praxisorientierte<br />
Anleitung – effizienter E<strong>in</strong>satz – <strong>in</strong>klusive <strong>SQL</strong>-Tun<strong>in</strong>g. Vieweg +<br />
Teubner, Wiesbaden 2002, ISBN 3-528-55210-7.<br />
• Matthiessen, Günter; Unterste<strong>in</strong>, Michael: Relationale Datenbanken und<br />
Standard-<strong>SQL</strong> − Konzepte der Entwicklung und Anwendung. Addison-Wesley,<br />
München 2007, ISBN 3-8273-2085-2.<br />
• Schicker, Edw<strong>in</strong>: Datenbanken und <strong>SQL</strong> − E<strong>in</strong>e praxisorientierte <strong>E<strong>in</strong>führung</strong><br />
mit H<strong>in</strong>weisen zu Oracle und MS-Access. Vieweg + Teubner, Wiesbaden 2000,<br />
ISBN 3-519-22991-9.<br />
• Türker, Can: <strong>SQL</strong> 1999 & <strong>SQL</strong> 2003. dpunkt.Verlag, Heidelberg 2003, ISBN 3-<br />
89864-219-4.<br />
• Warner, Daniel, Günter Leitenbauer: <strong>SQL</strong>. Franzis, Po<strong>in</strong>g 2003, ISBN 3-7723-<br />
7527-8.<br />
445
Weitere Informationen<br />
D.2. Webl<strong>in</strong>ks<br />
Hier gibt es weitere Informationen zum Thema <strong>SQL</strong>.<br />
D.2.1. Bücher <strong>in</strong> Wikibooks<br />
• Wikibooks: PL/<strong>SQL</strong> 1<br />
• Wikibooks: Oracle 2<br />
D.2.2. Allgeme<strong>in</strong>es zur Datenbank-Programmierung<br />
Wikipedia bietet folgende Erläuterungen:<br />
• <strong>SQL</strong> 3<br />
• Duales Lizenzsystem 4<br />
• Open Source 5<br />
• case <strong>in</strong>sensitive 6<br />
Außerdem gibt es Artikel zum Überblick:<br />
• Relationale Datenbank 7<br />
• Objektrelationale Datenbank 8<br />
• Objektorientiertes Datenbankmodell 9<br />
• Liste der Datenbankmanagementsysteme 10<br />
D.2.3. Beiträge zu <strong>SQL</strong> allgeme<strong>in</strong><br />
• <strong>SQL</strong>-2003 Documents 11 (abgerufen am 24. Juni 2012), darunter vor allem:<br />
Jim Melton: Information technology – Database languages – <strong>SQL</strong>. Part 2: Foundation<br />
(<strong>SQL</strong>/Foundation). August 2003 – 5WD-02-Foundation-2003-09.pdf<br />
1 http://de.wikibooks.<strong>org</strong>/wiki/PL/<strong>SQL</strong><br />
2 http://de.wikibooks.<strong>org</strong>/wiki/Oracle<br />
3 http://de.wikipedia.<strong>org</strong>/wiki/<strong>SQL</strong><br />
4 http://de.wikipedia.<strong>org</strong>/wiki/Mehrfachlizenzierung<br />
5 http://de.wikipedia.<strong>org</strong>/wiki/Open%20Source<br />
6 http://de.wikipedia.<strong>org</strong>/wiki/Case%20sensitivity<br />
7 http://de.wikipedia.<strong>org</strong>/wiki/Relationale%20Datenbank<br />
8 http://de.wikipedia.<strong>org</strong>/wiki/Objektrelationale%20Datenbank<br />
9 http://de.wikipedia.<strong>org</strong>/wiki/Objektorientiertes%20Datenbankmodell<br />
10 http://de.wikipedia.<strong>org</strong>/wiki/Liste%20der%20Datenbankmanagementsysteme<br />
11 http://www.wiscorp.com/sql_2003_standard.zip<br />
446
• L<strong>in</strong>ks zum Thema <strong>SQL</strong> 12 im Open Directory Project 13<br />
Webl<strong>in</strong>ks<br />
• <strong>SQL</strong>-Referenz (deutsch) 14 (Referenz mit Stichwortverzeichnis und Tutorials)<br />
• The 1995 <strong>SQL</strong> Reunion: People, Projects, and Politics 15 (zur frühen Geschichte<br />
von <strong>SQL</strong>)<br />
• Interaktiver <strong>SQL</strong>-Tra<strong>in</strong>er 16<br />
D.2.4. Bestimmte <strong>SQL</strong>-Datenbanksysteme<br />
Wenn nichts anderes gesagt wird, handelt es sich um deutschsprachige Seiten.<br />
• allgeme<strong>in</strong>: Das DB2 <strong>SQL</strong> Cookbook von Graeme Birchall 17 (englisch)<br />
enthält viele nützliche <strong>SQL</strong>-Tipps, die auch bei anderen Datenbanken anwendbar<br />
s<strong>in</strong>d.<br />
• DB2 (IBM):<br />
• DB2 18 (Wikipedia-Artikel)<br />
• Dokumentation zu DB2 V9 LUW 19<br />
• Startseite von IBM zu DB2 z/OS 20 (englisch)<br />
• Firebird:<br />
• Firebird 21<br />
• Über Firebird Documentation Index 22 (englisch) s<strong>in</strong>d viele E<strong>in</strong>zelteile zu f<strong>in</strong>den<br />
− teils als HTML-Seiten, teils als PDF-Dateien.<br />
• Firebird 2.0 P<strong>SQL</strong> Reference Manual 23 (englisch) ist e<strong>in</strong>e Ergänzung für die<br />
Programmierung mit <strong>SQL</strong> 24 .<br />
• Informix 25 (Wikipedia-Artikel)<br />
• Interbase 26 (Wikipedia-Artikel)<br />
• Microsoft <strong>SQL</strong> Server:<br />
12 http://dmoz.<strong>org</strong>/World/Deutsch/Computer/Programmieren/Sprachen/<strong>SQL</strong>/<br />
13 http://de.wikipedia.<strong>org</strong>/wiki/Open%20Directory%20Project<br />
14 http://www.sql-referenz.de<br />
15 http://www.mcjones.<strong>org</strong>/System_R/<strong>SQL</strong>_Reunion_95/<br />
16 http://edb.gm.fh-koeln.de/sqltra<strong>in</strong>er/start.jsp?action=wl/<br />
17 http://mysite.verizon.net/Graeme_Birchall/id1.html<br />
18 http://de.wikipedia.<strong>org</strong>/wiki/DB2<br />
19 http://www-306.ibm.com/software/data/db2/udb/support/manualsNLVv9.html#de_ma<strong>in</strong><br />
20 http://www-306.ibm.com/software/data/db2/zos/<br />
21 http://de.wikipedia.<strong>org</strong>/wiki/Firebird%20(Datenbank)<br />
22 http://www.firebirdsql.<strong>org</strong>/<strong>in</strong>dex.php?op=doc<br />
23 http://www.janus-software.com/fbmanual/<strong>in</strong>dex.php?book=psql<br />
24 Kapitel 30 auf Seite 323<br />
25 http://de.wikipedia.<strong>org</strong>/wiki/Informix<br />
26 http://de.wikipedia.<strong>org</strong>/wiki/Interbase<br />
447
Weitere Informationen<br />
• Microsoft <strong>SQL</strong> Server 27 (Wikipedia-Artikel)<br />
• <strong>SQL</strong> Server 2008: Technische Referenz 28 , vor allem im Bereich Transact-<strong>SQL</strong>-<br />
Sprachreferenz<br />
• <strong>SQL</strong> Server 2005: Transact-<strong>SQL</strong>-Referenz 29<br />
• My<strong>SQL</strong>:<br />
• My<strong>SQL</strong> 30 (Wikipedia-Artikel)<br />
• My<strong>SQL</strong> Documentation 31 (englisch)<br />
• My<strong>SQL</strong> 5.1 Referenzhandbuch 32<br />
• Reference Manual 5.5 33 (englisch) für die neueste Version<br />
• Oracle:<br />
• Oracle 34 (Wikipedia-Artikel)<br />
• Oracle Documentation 35 (englisch) als Überblick, speziell beispielsweise:<br />
• Database Reference 11g Release 2 (11.2) 36<br />
• Database Reference 10g Release 2 (10.2) 37<br />
• Postgre<strong>SQL</strong>:<br />
• Postgre<strong>SQL</strong> 38 (Wikipedia-Artikel)<br />
• Dokumentation 39<br />
• <strong>SQL</strong>ite:<br />
• <strong>SQL</strong>ite 40 (Wikipedia-Artikel)<br />
• Dokumentation 41 (englisch)<br />
• Sybase 42 (Wikipedia-Artikel)<br />
27 http://de.wikipedia.<strong>org</strong>/wiki/Microsoft%20<strong>SQL</strong>%20Server<br />
28 http://msdn.microsoft.com/de-de/library/bb500275.aspx<br />
29 http://msdn.microsoft.com/de-de/library/ms189826%28<strong>SQL</strong>.90%29.aspx<br />
30 http://de.wikipedia.<strong>org</strong>/wiki/My<strong>SQL</strong><br />
31 http://dev.mysql.com/doc/<br />
32 http://dev.mysql.com/doc/refman/5.1/de/<br />
33 http://dev.mysql.com/doc/refman/5.5/en/<br />
34 http://de.wikipedia.<strong>org</strong>/wiki/Oracle%20(Datenbanksystem)<br />
35 http://www.oracle.com/technology/documentation/<strong>in</strong>dex.html<br />
36 http://download.oracle.com/docs/cd/E11882_01/server.112/e17110/toc.htm<br />
37 http://download.oracle.com/docs/cd/B19306_01/server.102/b14237/toc.htm<br />
38 http://de.wikipedia.<strong>org</strong>/wiki/Postgre<strong>SQL</strong><br />
39 http://www.postgresql.<strong>org</strong>/docs/books/pghandbuch.html.de<br />
40 http://de.wikipedia.<strong>org</strong>/wiki/<strong>SQL</strong>ite<br />
41 http://www.sqlite.<strong>org</strong>/docs.html<br />
42 http://de.wikipedia.<strong>org</strong>/wiki/Sybase<br />
448
D.2.5. Andere Datenverwaltung<br />
Über Wikipedia gibt es e<strong>in</strong>führende Erläuterungen und Verweise:<br />
• dBASE 43<br />
• LibreOffice 44 als Nachfolger von OpenOffice 45<br />
• MS Access 46<br />
• Paradox 47<br />
43 http://de.wikipedia.<strong>org</strong>/wiki/dBASE<br />
44 http://de.wikipedia.<strong>org</strong>/wiki/LibreOffice<br />
45 http://de.wikipedia.<strong>org</strong>/wiki/OpenOffice.<strong>org</strong><br />
46 http://de.wikipedia.<strong>org</strong>/wiki/Microsoft%20Access<br />
47 http://de.wikipedia.<strong>org</strong>/wiki/Paradox%20(Datenbank)<br />
Webl<strong>in</strong>ks<br />
449
E. Zu diesem Buch<br />
E.1. H<strong>in</strong>weise zu den Lizenzen<br />
Dieses Werk ist entstanden bei Wikibooks 1 , e<strong>in</strong>er Onl<strong>in</strong>e-Bibliothek im Internet<br />
mit Lehr-, Sach- und Fachbüchern. Jeder kann und darf diese Bücher frei nutzen<br />
und bearbeiten. Alle Inhalte stehen unter den Lizenzen „Creative Commons<br />
Attribution/Share-Alike“ (CC-BY-SA 3.0) und GNU-Lizenz für freie Dokumentation<br />
(GFDL).<br />
Für die Konvertierung wb2pdf 2 gilt die GNU General Public License (GPL).<br />
Zum Textsatzprogramm LAT E X 3 gehört die LaTeX Project Public License (LPPL).<br />
H<strong>in</strong>weise zur Nutzung und für Zitate s<strong>in</strong>d zu f<strong>in</strong>den unter:<br />
• Orig<strong>in</strong>alversion der Lizenz CC-BY-SA 3.0<br />
http://creativecommons.<strong>org</strong>/licenses/by-sa/3.0<br />
• Deutsche Version der Lizenz mit Ergänzungen<br />
http://creativecommons.<strong>org</strong>/licenses/by-sa/3.0/deed.de<br />
• Orig<strong>in</strong>alversion der Lizenz GFDL<br />
http://www.gnu.<strong>org</strong>/copyleft/fdl.html<br />
• Orig<strong>in</strong>alversion der Lizenz GPL<br />
http://www.gnu.<strong>org</strong>/licenses/gpl-3.0.html<br />
• Version der LaTeX PPL<br />
http://www.opensource.<strong>org</strong>/licenses/lppl<br />
• Nutzungsbed<strong>in</strong>gungen der Wikimedia Foundation (deutsch)<br />
http://<strong>wikimedia</strong>foundation.<strong>org</strong>/wiki/Nutzungsbed<strong>in</strong>gungen<br />
• Zitieren aus Wikibooks<br />
http://de.wikibooks.<strong>org</strong>/wiki/Hilfe:Zitieren#Zitieren_aus_Wikibooks<br />
Aus dem gedruckten Buch kann natürlich so zitiert werden, wie es bei Büchern<br />
üblich ist.<br />
1 http://de.wikibooks.<strong>org</strong>/wiki/E<strong>in</strong>f%C3%BChrung_<strong>in</strong>_<strong>SQL</strong><br />
2 http://de.wikibooks.<strong>org</strong>/wiki/Benutzer:Dirk_Huenniger/wb2pdf<br />
3 http://de.wikipedia.<strong>org</strong>/wiki/LaTeX<br />
451
Zu diesem Buch<br />
E.2. Autoren<br />
Die folgende Aufstellung enthält gemäß Wikibooks-Lizenzbed<strong>in</strong>gungen 4 alle Autoren,<br />
die an diesem Buch mitgearbeitet haben, und zwar mit Stand vom 22. Juli<br />
2012. (Autoren, die sich nicht angemeldet haben, müssen nach den Lizenzbed<strong>in</strong>gungen<br />
nicht genannt werden.)<br />
Edits User<br />
4 AK-Palme 5<br />
6 Andorna 6<br />
1 Blauerflummi 7<br />
1 BreKla 8<br />
1 Burnstone 9<br />
1 Bullet4myval 10<br />
1 C.hahn 11<br />
1 CarsracBot 12<br />
2 Complex 13<br />
1 Computator 14<br />
2 Daniel B 15<br />
1 Diba 16<br />
13 Dirk Huenniger 17<br />
1 Dr. Gert Blazejewski 18<br />
2 Ezhik 19<br />
2 Felix Re<strong>in</strong>wald 20<br />
2 Gandi 21<br />
1 Jpp 22<br />
4 http://de.wikibooks.<strong>org</strong>/wiki/Wikibooks:Creative_Commons_Attribution-ShareAlike_3.<br />
0_Unported_License<br />
5 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:AK-Palme<br />
6 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Andorna<br />
7 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Blauerflummi<br />
8 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:BreKla<br />
9 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Burnstone<br />
10 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Bullet4myval<br />
11 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:C.hahn<br />
12 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:CarsracBot<br />
13 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Complex<br />
14 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Computator<br />
15 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Daniel_B<br />
16 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Diba<br />
17 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Dirk_Huenniger<br />
18 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Dr._Gert_Blazejewski<br />
19 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Ezhik<br />
20 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Felix_Re<strong>in</strong>wald<br />
21 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Gandi<br />
22 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Jpp<br />
452
1181 Juetho 23<br />
46 Julius-m 24<br />
4 Klaus Eifert 25<br />
1 Knospe 26<br />
2 Mart<strong>in</strong> Fuchs 27<br />
2 MatthiasKabel 28<br />
67 Mcflash 29<br />
7 Mjchael 30<br />
1 Nicky knows 31<br />
2 Phoenix9999 32<br />
1 Polluks 33<br />
1 Sallynase 34<br />
1 Shogun 35<br />
5 Skittle 36<br />
7 Softeis 37<br />
2 Sparti 38<br />
2 Stefan Majewsky 39<br />
1 Sundance Raphael 40<br />
1 ThE cRaCkEr 41<br />
9 ThePacker 42<br />
2 Tirkon 43<br />
16 Turelion 44<br />
2 VÖRBY 45<br />
23 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Juetho<br />
24 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Julius-m<br />
25 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Klaus_Eifert<br />
26 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Knospe<br />
27 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Mart<strong>in</strong>_Fuchs<br />
28 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:MatthiasKabel<br />
29 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Mcflash<br />
30 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Mjchael<br />
31 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Nicky_knows<br />
32 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Phoenix9999<br />
33 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Polluks<br />
34 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Sallynase<br />
35 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Shogun<br />
36 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Skittle<br />
37 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Softeis<br />
38 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Sparti<br />
39 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Stefan_Majewsky<br />
40 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Sundance_Raphael<br />
41 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:ThE_cRaCkEr<br />
42 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:ThePacker<br />
43 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Tirkon<br />
44 http://de.wikibooks.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:Turelion<br />
45 http://de.wikipedia.<strong>org</strong>/w/<strong>in</strong>dex.php?title=Benutzer:VÖRBY<br />
Autoren<br />
453
Zu diesem Buch<br />
E.3. Bildnachweis<br />
In der nachfolgenden Übersicht s<strong>in</strong>d alle Bilder mit ihren Autoren und Lizenzen<br />
aufgelistet.<br />
Umschlag<br />
Lizenz: cc-by-sa-3.0 – Bearbeiter: User:Juetho<br />
Quelle: http://de.wikibooks.<strong>org</strong>/wiki/File:<strong>SQL</strong>-Titelbild.png<br />
Seite 42<br />
Lizenz: cc-by-sa-3.0 – Bearbeiter: User:Juetho<br />
Quelle: http://de.wikibooks.<strong>org</strong>/wiki/File:<strong>SQL</strong>-Struktur%20Beispieldaten<br />
bank.png<br />
Seite 203<br />
Lizenz: PD – Bearbeiter: User:Lipedia<br />
Quellen:<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1010.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1110.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1100.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1011.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1101.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1000.svg<br />
http://commons.<strong>wikimedia</strong>.<strong>org</strong>/wiki/File:Relation1001.svg<br />
Für die Namen der Lizenzen wurden folgende Abkürzungen verwendet:<br />
• CC-BY-SA-3.0: Commons Attribution ShareAlike 3.0 License. 46<br />
• PD: This image is <strong>in</strong> the public doma<strong>in</strong>, because it consists entirely of <strong>in</strong>formation<br />
that is common property and conta<strong>in</strong>s no orig<strong>in</strong>al authorship.<br />
Diese Abbildung ist geme<strong>in</strong>frei, weil sie nur Allgeme<strong>in</strong>gut enthält und die nötige<br />
Schöpfungshöhe nicht erreicht.<br />
46 Siehe dazu den H<strong>in</strong>weis auf Seite 451<br />
454