2. AUFLAGE
Einführung in SQL
Alan Beaulieu Deutsche Übersetzung von Dorothea Heymann-Reder & Lars Schulten
Beijing · Cambridge · Farnham · Köln · Sebastopol · Taipei · Tokyo
Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen. Kommentare und Fragen können Sie gerne an uns richten: O’Reilly Verlag Balthasarstr. 81 50670 Köln Tel.: 0221/9731600 Fax: 0221/9731608 E-Mail:
[email protected] Copyright der deutschen Ausgabe: © 2009 by O’Reilly Verlag GmbH & Co. KG 1. Auflage 2006 2. Auflage 2009 Die Originalausgabe erschien 2009 unter dem Titel Learning SQL, 2nd Edition bei O’Reilly Media, Inc. Die Darstellung eines Beutelfroschs im Zusammenhang mit dem Thema SQL ist ein Warenzeichen von O’Reilly Media, Inc.
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
Übersetzung und deutsche Bearbeitung: Dorothea Heymann-Reder, Bornheim & Lars Schulten, Köln Lektorat: Alexandra Follenius & Susanne Gerbert, Köln Korrektorat: Sibylle Feldmann, Düsseldorf Satz: III-satz, Husby Umschlaggestaltung: Karen Montgomery, Sebastopol & Michael Oreal, Köln Produktion: Geesche Kieckbusch, Hamburg Belichtung, Druck und buchbinderische Verarbeitung: Druckerei Kösel, Krugzell; www.koeselbuch.de ISBN 978-3-89721-937-3 Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.
Inhalt
Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2
3
4
IX
Der Hintergrund . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
Einführung in Datenbanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was ist SQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was ist MySQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Weiteres Vorgehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 7 13 14
Datenbanken erstellen und mit Daten füllen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine MySQL-Datenbank anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das mysql-Kommandozeilentool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MySQL-Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tabellen anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tabellen füllen und ändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wenn aus guten Anweisungen schlechte werden . . . . . . . . . . . . . . . . . . . . . . . Das Bank-Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15 17 17 25 30 36 38
Datenbankabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Mechanik von Abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abfrageklauseln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Select-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die From-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die where-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Klauseln group by und having . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die order by-Klausel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41 43 43 49 53 55 56 60
Filtern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
Bedingungsauswertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufbau einer Bedingung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bedingungstypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63 66 66
15
41
| V
NULL: Ein böses Wort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
6
7
8
9
76 79
Mehrere Tabellen abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
Was ist ein Join? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Joins mit drei oder mehr Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Self-Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Equi-Joins und Non-Equi-Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Join-Bedingungen und Filterbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81 88 93 94 97 98
Umgang mit Mengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grundlagen der Mengenlehre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mengenlehre in der Praxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mengenoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Regeln für Mengenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
101 104 105 111 114
101
Daten generieren, konvertieren und manipulieren . . . . . . . . . . . . . . . . . . . . . . . .
115
Der Umgang mit String-Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Umgang mit numerischen Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Umgang mit temporalen Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Konvertierungsfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115 128 133 144 145
Gruppieren und Aggregieren von Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147
Gruppieren von Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aggregatfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gruppen generieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gruppen-Filterbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147 150 155 159 161
Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
163
Was ist eine Unterabfrage? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Typen von Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nicht-korrelierte Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Korrelierte Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einsatz von Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusammenfassung zu Unterabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
163 164 165 174 178 188 189
10 Weitere Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
191
Outer Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cross Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
191 201
VI | Inhalt
Natural Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
207 209
11 Bedingungslogik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
211
Was ist Bedingungslogik? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Case-Ausdruck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Beispiele für Case-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
211 212 216 223
12 Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
225
Mehrbenutzerdatenbanken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was ist eine Transaktion? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
225 227 234
13 Indizes und Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
235
Indizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
235 246 251
14 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253
Was sind Views? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Warum Views verwenden? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aktualisierbare Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253 256 260 263
15 Metadaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265
Daten über Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Information_Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mit Metadaten arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testen Sie Ihr Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
265 266 271 278
A
ER-Diagramm der Musterdatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
279
B
MySQL-Erweiterungen für SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
281
C
Lösungen der Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
295
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
317
Inhalt | VII
First
Einleitung
Programmiersprachen kommen und gehen. Nur wenige Sprachen, die heute im Gebrauch sind, haben Wurzeln, die mehr als zehn Jahre zurückreichen. Einige Beispiele sind Cobol, eine Sprache, die immer noch viel in Mainframe-Umgebungen genutzt wird, und C, eine Sprache, die nach wie vor für die Entwicklung von Betriebssystemen, Servern und Embedded-Systemen eingesetzt wird. Doch das im Datenbankbereich gebräuchliche SQL geht bis in die 1970er-Jahre zurück. In SQL werden Daten aus relationalen Datenbanken angelegt, bearbeitet und abgefragt. Relationale Datenbanken sind vor allem populär, weil sie, wenn sie gut entworfen sind, gewaltige Datenmengen bewältigen können. Im Umgang mit großen Datenmengen hat SQL einiges mit den todschicken Digitalkameras und ihren Hochleistungszooms gemeinsam: Mit SQL kann man große Datenmengen durchforsten oder einzelne Zeilen heranzoomen (oder irgendetwas dazwischen). Andere Datenbankmanagementsysteme tendieren dazu, unter hoher Belastung zusammenzubrechen, da ihr Fokus zu eng ist (die Zoomlinse kann einen bestimmten Höchstwert nicht überschreiten). Aus diesem Grund sind alle Versuche, relationale Datenbanken und SQL vom Thron zu stoßen, gescheitert. Auch wenn SQL eine ältere Sprache ist, wird es uns noch lange erhalten bleiben und blickt in eine glänzende Zukunft.
Wozu SQL lernen? Wenn Sie mit einer relationalen Datenbank arbeiten, müssen Sie wissen, wie Sie mit den Daten in Ihrer Datenbank umgehen können, egal ob Sie nun Anwendungen schreiben, administrative Aufgaben erfüllen oder Reports generieren. Selbst wenn Sie die SQLAnweisungen mit einem Tool – etwa einem Reporting-Programm – generieren, müssen Sie gelegentlich die automatische Generierungsfunktion umgehen und eigene SQLAnweisungen schreiben.
Max. Linie
SQL hat zusätzlich den Vorteil, dass es Sie zwingt, die Datenstrukturen, in denen die Informationen Ihres Unternehmens gespeichert sind, zu betrachten und zu verstehen. In
| IX This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links dem Maße, in dem Sie mit den Tabellen Ihrer Datenbank vertrauter werden, fallen Ihnen vielleicht auch selbst Änderungen oder Ergänzungen Ihres Datenbankschemas ein.
Warum SQL mit diesem Buch lernen? SQL ist in mehrere Kategorien unterteilt. Anweisungen, mit denen man Datenbankobjekte anlegt (Tabellen, Indizes, Constraints usw.), bezeichnet man allgemein als SQLSchemaanweisungen. Die Anweisungen, mit denen die in der Datenbank gespeicherten Daten erstellt, bearbeitet und abgefragt werden, heißen SQL-Datenanweisungen. Wenn Sie ein Administrator sind, verwenden Sie sowohl Schema- als auch Datenanweisungen in SQL. Sind Sie hingegen Programmierer oder Report-Verfasser, möchten (oder dürfen) Sie unter Umständen nur SQL-Datenanweisungen benutzen. Dieses Buch zeigt zwar auch viele SQL-Schemaanweisungen, aber sein Schwerpunkt liegt auf den Programmierfunktionen. Mit ihren wenigen Befehlen sehen SQL-Datenanweisungen manchmal enttäuschend simpel aus. Dieser Eindruck wird durch einige SQL-Bücher noch dadurch verstärkt, dass sie nur gerade einmal die Oberfläche dessen ankratzen, was mit dieser Sprache möglich ist. Wenn Sie allerdings richtig mit SQL arbeiten wollen, müssen Sie die Fähigkeiten dieser Sprache vollständig verstehen und wissen, wie man die verschiedenen Features kombinieren kann, um mächtige Ergebnisse zu erzielen. Ich denke, dieses Buch ist das einzige, das SQL wirklich detailliert behandelt, und zwar ohne den eingebauten Türstopper-Effekt (Sie kennen ja diese 1.250-Seiten-Wälzer, die sich »Vollständige Referenz« nennen und die Tendenz haben, auf den Regalen zu verstauben). Die Beispiele in diesem Buch laufen auf MySQL, Oracle Database und SQL Server; allerdings musste ich mich für eines dieser Systeme entscheiden, um meine Musterdatenbank zu hosten und die Ergebnismengen zu formatieren, die mir die Beispielabfragen zurücklieferten. Ich habe MySQL gewählt, weil es frei, leicht zu installieren und einfach zu administrieren ist. Lesern, die einen anderen Server benutzen, empfehle ich, MySQL dennoch herunterzuladen und die Musterdatenbank darauf zu installieren. Auf diese Weise können Sie die Ergebnisse ausführen und mit den Daten experimentieren.
Aufbau dieses Buchs Dieses Buch ist in 15 Kapitel und 3 Anhänge unterteilt: • Kapitel 1, Der Hintergrund, geht auf die Geschichte der Computerdatenbanken ein und schildert den Aufstieg des relationalen Modells und der Sprache SQL.
Max. Linie
• Kapitel 2, Datenbanken erstellen und mit Daten füllen, zeigt, wie man eine MySQLDatenbank anlegt, die Tabellen für die Beispiele dieses Buchs erstellt und Daten in diese Tabellen lädt. • Kapitel 3, Datenbankabfragen, führt die select-Anweisung ein und stellt die gebräuchlichsten Klauseln vor (select, from, where). X | Einleitung
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts • Kapitel 4, Filtern, beschreibt die verschiedenen Arten von Bedingungen, die in der where-Klausel einer select-, update- oder delete-Anweisung verwendet werden können. • Kapitel 5, Mehrere Tabellen abfragen, zeigt, wie man mehrere Tabellen mittels Tabellen-Joins benutzen kann. • Kapitel 6, Umgang mit Mengen, handelt von Datenmengen und der Frage, wie diese innerhalb von Abfragen interagieren. • Kapitel 7, Daten generieren, konvertieren und manipulieren, stellt verschiedene eingebaute Funktionen vor, mit denen man Daten manipulieren oder konvertieren kann. • Kapitel 8, Gruppieren und Aggregieren von Daten, zeigt, wie Daten zusammengefasst werden. • Kapitel 9, Unterabfragen, führt Unterabfragen ein (ich liebe Unterabfragen) und zeigt, wie und wo man sie einsetzen kann. • Kapitel 10, Weitere Joins, geht genauer auf die verschiedenen Join-Typen ein. • Kapitel 11, Bedingungslogik, erklärt, wie man Bedingungslogik (d.h. if-then-else) in select-, insert-, update- und delete-Anweisungen verwendet. • Kapitel 12, Transaktionen, führt Transaktionen ein und zeigt, wie man sie nutzt. • Kapitel 13, Indizes und Constraints, erklärt Indizes und Constraints. • Kapitel 14, Views, zeigt, wie man eine Schnittstelle aufbaut, die Datenkomplexität vor Benutzern verbirgt. • Kapitel 15, Metadaten, zeigt den Nutzen des Data Dictionary. • Anhang A, ER-Diagramm der Musterdatenbank, zeigt das Datenbankschema, das für alle Beispiele dieses Buchs verwendet wird. • Anhang B, MySQL-Erweiterungen für SQL, stellt einige interessante Nicht-ANSIFeatures der SQL-Implementierung von MySQL vor. • Anhang C, Lösungen der Übungen, enthält die Lösungen der Übungsaufgaben.
Verwendete Konventionen In diesem Buch gelten folgende typografische Konventionen: Kursiv Für Datei- und Verzeichnisnamen sowie URLs. Dient auch der Hervorhebung und der Kennzeichnung neuer Fachbegriffe. Nichtproportionalschrift
Wird für Codebeispiele und SQL-Schlüsselwörter im Text verwendet.
Max. Linie
Nichtproportionalschrift kursiv
Max. Linie
Wird für Begriffe verwendet, die der Benutzer eingeben muss.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Einleitung | XI
Links GROSSBUCHSTABEN SQL-Schlüsselwörter in Beispielcode. Nichtproportionalschrift fett
Benutzereingabe in Beispielen, in denen eine Interaktion gezeigt wird, sowie zur besonderen Beachtung hervorgehobener Codeelemente. Ein Tipp, Vorschlag oder allgemeiner Hinweis. Ich weise in solchen Einschüben beispielsweise auf neue Features von Datenbanken hin.
Eine Warnung. So werde ich zum Beispiel darauf aufmerksam machen, dass eine bestimmte SQL-Klausel unerwünschte Folgen haben kann, wenn man sie nicht vorsichtig genug benutzt.
Die Codebeispiele zu diesem Buch Es gibt eine amerikanische und eine deutschsprachige Website von O’Reilly, auf der Sie Beispiele, Errata und weitere Informationen zu diesem Buch finden können: http://oreilly.com/catalog/9780596520830/ http://www.oreilly.de/catalog/learningsql2ger/ Die in diesem Buch abgedruckten Codebeispiele stehen dort zum Herunterladen zur Verfügung. Bitte klicken Sie auf den Beispiel-Link auf der Katalogseite zum Buch.
Verwendung der Codebeispiele Dieses Buch soll Ihnen helfen, Ihre Arbeit zu tun. Sie dürfen generell den Code, den Sie hier finden, in Ihren Programmen und Dokumentationen verwenden, ohne uns um Erlaubnis zu fragen, es sei denn, Sie verwenden einen bedeutenden Teil des Codes. Zum Beispiel: Wenn Sie ein Programm schreiben, das mehrere Codestücke aus diesem Buch verwendet, benötigen Sie keine Erlaubnis, doch wenn Sie eine CD-ROM mit Codebeispielen aus O’Reilly-Büchern verteilen oder verkaufen, müssen Sie eine Genehmigung einholen. Sie können nach Belieben dieses Buch und Beispielcode daraus zitieren, um Fragen zu beantworten, aber wenn Sie einen großen Teil des Beispielcodes in Ihre Produktdokumentation übernehmen, ist dies genehmigungspflichtig.
Max. Linie
Auch wenn wir es nicht verlangen, so freuen wir uns doch über eine Quellenangabe, wenn wir zitiert werden. Zu einer Quellenangabe gehören normalerweise Titel, Autor, Verlag sowie ISBN des Werks, zum Beispiel: »Einführung in SQL, 2. Auflage, von Alan Beaulieu. Copyright 2009 O’Reilly Verlag, ISBN 978-3-89721-937-3«. Wenn Sie das Gefühl haben, Ihre Verwendung unseres Codes übersteige die Grenzen des Erlaubten, sprechen Sie uns bitte an unter
[email protected].
XII | Einleitung
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Danksagungen Ich möchte meiner Lektorin Mary Treseler dafür danken, dass sie mir geholfen hat, diese zweite Auflage Wirklichkeit werden zu lassen. Vielen Dank auch an Kevin Kline, Roy Owens, Richard Sonen und Matthew Russell, die so nett waren, das Buch für mich während der Weihnachtsferien durchzusehen. Außerdem möchte ich den vielen Lesern der ersten Auflage danken, die mir Fragen, Kommentare und Verbesserungsvorschläge haben zukommen lassen. Schließlich möchte ich meiner Frau Nancy und meinen Töchtern Michelle und Nicole für ihre Ermutigung und Inspiration danken.
Max. Linie
Max. Linie This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Einleitung
|
XIII
First
Kapitel 1
KAPITEL 1
Der Hintergrund
Ehe wir nun die Ärmel aufkrempeln und uns in die Arbeit stürzen, wäre es sicherlich hilfreich, einige Grundkonzepte einzuführen und die Geschichte der computergestützten Datenspeicherung und -abfrage genauer anzuschauen.
Einführung in Datenbanken Eine Datenbank ist im Grunde nichts weiter als eine Menge von zusammenhängenden Informationen. So ist zum Beispiel ein Telefonbuch eine Datenbank mit den Namen, Telefonnummern und Adressen aller Bewohner einer bestimmten Gegend. Doch so ein Telefonbuch ist zwar überall zu finden und wird viel genutzt, aber es hat auch einige Mängel: • Wegen der Vielzahl der Einträge kann es zeitraubend sein, die Telefonnummer eines bestimmten Teilnehmers darin zu finden. • Der einzige Index eines Telefonbuchs ist die alphabetische Ordnung nach Nachname und Vorname. Eine Möglichkeit, die Namen der Menschen mit einer bestimmten Adresse herauszufinden, gibt es nur in der Theorie. • Von dem Augenblick an, da das Telefonbuch in Druck geht, fängt es bereits an zu veralten: Menschen ziehen fort, wechseln ihre Telefonnummer oder gehen an eine andere Adresse derselben Gegend.
Max. Linie
Diese Nachteile hat jedes manuelle System der Datenspeicherung, also beispielsweise auch Patientendaten, die in einem Aktenschrank abgelegt sind. Da eine papiergebundene Datensammlung eine derart sperrige Angelegenheit ist, gehörten Datenbanksysteme zu den ersten Computeranwendungen überhaupt. In ihnen werden Daten computergestützt gespeichert und abgefragt. Da ein Datenbanksystem diese Arbeiten nicht auf Papier, sondern elektronisch leistet, kann es die Daten schneller abfragen, mit unterschiedlichen Indizes versehen und seinen Benutzern immer die aktuellsten Informationen liefern. Die frühen Datenbanksysteme verwalteten die gespeicherten Daten noch auf Magnetbändern. Da es im Allgemeinen viel mehr Bänder als Bandlesegeräte gab, mussten permanent | 1 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Techniker Bänder austauschen, wenn bestimmte Informationen angefordert wurden. Da die Computer jener Zeit noch sehr wenig Arbeitsspeicher hatten, mussten dieselben Daten, wenn sie mehrmals angefragt wurden, auch mehrmals von den Bändern gelesen werden. Auch wenn diese Datenbanksysteme bereits viel besser als die papiergebundenen waren, so waren sie doch weit von dem entfernt, was mit unserer heutigen Technologie möglich ist. (Moderne Datenbanksysteme können Terabytes von Daten verwalten, die über eine Vielzahl schneller Festplattenlaufwerke verteilt sind, und sie können zig Gigabytes an Daten in ihren Hochleistungsarbeitsspeichern halten. Aber ich greife vor.)
Nicht-relationale Datenbanksysteme Dieser Abschnitt enthält einige Hintergrundinformationen zu Datenbanksystemen, die den eigentlichen relationalen Datenbanksystemen vorausgingen. Wenn Sie es eilig haben und sich sofort in SQL stürzen wollen, können Sie die folgenden Seiten bis zum nächsten Abschnitt gern überspringen.
In den ersten Dekaden des Computerzeitalters wurden Datenbankdaten auf diverse Arten gespeichert und dargestellt. In einem hierarchischen Datenbanksystem werden sie beispielsweise in Baumstrukturen angeordnet. Abbildung 1-1 zeigt, wie die Daten der Bankkonten von George Blake und Sue Smith als Baumstruktur wiedergegeben werden könnten.
Sue Smith
George Blake Customers
Checking
Checking
Savings
MoneyMkt
Line of credit
Accounts
Debit of $100.00 on 2004-01-22
Debit of $250.00 on 2004-03-09
Debit of $1000.00 on 2004-03-25
Debit of $500.00 on 2004-03-27
Credit of $138.50 on 2004-04-02
Credit of $25.00 on 2004-02-05
Credit of $77.86 on 2004-04-04 Transactions
Max. Linie
Abbildung 1-1: Hierarchische Sicht von Kontendaten
2 |
Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts George und Sue haben jeweils einen eigenen Baum, in dem ihre Konten und Kontenbewegungen gespeichert sind. Das hierarchische Datenbanksystem bietet Mittel und Wege, um den Baum eines bestimmten Bankkunden ausfindig zu machen und ihn dann nach den gewünschten Konten und/oder Kontenbewegungen zu durchforsten. Jeder Knoten im Baum hat null oder einen Elternknoten und null, ein oder mehrere Kinder. Diese Konfiguration bezeichnet man als Single-Parent-Hierarchie. Ein anderes beliebtes Verfahren namens Netzwerkdatenbanksystem stellt Datensätze und Verknüpfungsmengen dar, um die Beziehungen zwischen den verschiedenen Datensätzen zu definieren. Abbildung 1-2 zeigt, wie dieselben Bankkonten von George und Sue in einem solchen System aussehen könnten. Customers
Accounts
Transactions
Checking
Debit of $100.00 on 2004-01-22
Products
Checking
George Blake
Credit of $25.00 on 2004-02-05 Savings
Debit of $250.00 on 2004-03-09
Savings
Debit of $1000.00 on 2004-03-25 MoneyMkt
Checking Credit of $138.50 on 2004-04-02 Sue Smith
MoneyMkt Credit of $77.86 on 2004-04-04 Line of credit
Line of credit Debit of $500.00 on 2004-03-27
Abbildung 1-2: Netzwerksicht von Kontendaten
Um die Bewegungen von Sues Tagesgeldkonto wiederzufinden, wären folgende Schritte vonnöten: 1. Den Kundendatensatz von Sue Smith finden. 2. Der Verknüpfung von diesem Datensatz zu Sues Kontenliste folgen. 3. Die Konten durchgehen, bis das Tagesgeldkonto gefunden ist.
Max. Linie
4. Der Verknüpfung zwischen dem Tagesgeldkonto und seiner Transaktionsliste folgen.
Einführung in Datenbanken This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 3
Max. Linie
Links Ein interessantes Merkmal der Netzwerkdatenbanksysteme wird an der Menge der product-Einträge ganz rechts in Abbildung 1-2 erkennbar. Beachten Sie, dass jeder product-Eintrag (Checking, Savings usw.) auf eine Liste von account-Einträgen verweist, die den gleichen Product-Typ haben. Dadurch kann man von verschiedenen Orten aus auf account-Einträge zugreifen (nämlich sowohl von den customer- als auch von den product-Datensätzen aus). So wird eine Netzwerkdatenbank zur Multi-Parent-Hierarchie. Sowohl hierarchische als auch Netzwerkdatenbanksysteme sind auch heutzutage noch quicklebendig, wenn auch hauptsächlich im Mainframe-Umfeld. Außerdem erlebten hierarchische Datenbanksysteme im Bereich der Verzeichnisdienste eine Renaissance, also zum Beispiel in Active Directory von Microsoft und Directory Server von Red Hat sowie in der Extensible Markup Language (XML). Doch Anfang der 1970er-Jahre kam eine neue Art der Datendarstellung auf, die strenger, aber zugleich auch einfacher zu verstehen und zu implementieren war.
Das relationale Modell Im Jahr 1970 veröffentlichte Dr. E. F. Codd vom IBM-Forschungslabor ein Paper mit dem Titel »A Relational Model of Data for Large Shared Data Banks« (»Ein relationales Datenmodell für große, verteilte Datenbanken«), in dem er vorschlug, Daten als Mengen von Tabellen darzustellen. Anstatt mithilfe von Pointern zwischen verwandten Entitäten zu navigieren, verwendet dieses System redundante Daten, um Datensätze miteinander zu verknüpfen, die in verschiedenen Tabellen vorliegen. Abbildung 1-3 zeigt, wie die Kontendaten von George und Sue in einem solchen Kontext aussehen würden. Abbildung 1-3 zeigt vier Tabellen, um die vier bisher verwendeten Entitäten darzustellen: customer, product, account und transaction. Wenn Sie einen Blick auf den Anfang der customer-Tabelle in Abbildung 1-3 werfen, erkennen Sie drei Spalten: cust_id (mit der Kundennummer), fname (mit dem Vornamen des Kunden) und lname (mit dem Nachnamen des Kunden). Außerdem hat die customer-Tabelle zwei Zeilen, eine mit den Daten von George Blake und eine mit den Daten von Sue Smith. Wie viele Spalten eine Tabelle höchstens haben darf, ist vom Server abhängig, doch die Zahl ist normalerweise groß genug (Microsoft SQL Server gestattet zum Beispiel 1.024 Spalten pro Tabelle). Die Höchstzahl der Zeilen einer Tabelle ist eher eine Frage der physikalischen Grenzen (d.h. des verfügbaren Plattenplatzes) und der Wartbarkeit (d.h., wie umfangreich eine Tabelle werden kann, bevor die Arbeit mit ihr zu kompliziert wird) als eine Frage der Serverlimits.
Max. Linie
Jede Tabelle in einer relationalen Datenbank enthält Informationen, um eine Tabellenzeile eindeutig zu identifizieren (den sogenannten Primärschlüssel), sowie weitere Informationen, die benötigt werden, um die dargestellte Entität vollständig zu beschreiben. Wenn Sie noch einmal die customer-Tabelle anschauen, sehen Sie, dass die cust_id-Spalte für jeden Kunden eine andere Nummer speichert. So ist etwa George Blake durch seine Kundennummer 1 eindeutig identifiziert. Kein anderer Kunde wird jemals diese Kennung bekommen, und es sind keine anderen Informationen erforderlich, um George Blakes Daten in der customer-Tabelle wiederzufinden.
4 |
Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Customer
Account
cust_id
fname
lname
1
George
Blake
103
CHK
1
$75.00
2
Sue
Smith
104
SAV
1
$250.00
105
CHK
2
$783.64
106
MM
2
$500.00
107
LOC
2
0
Product
account_id product_cd
cust_id balance
Transaction
product_cd
name
txn_id txn_type_cd account_id amount
date
CHK
Checking
978
DBT
103
$100.00 2004-01-22
SAV
Savings
979
CDT
103
$25.00
2004-02-05
MM
Money market
980
DBT
104
$250.00
2004-03-09
LOC
Line of credit
981
DBT
105
$1000.00 2004-03-25
982
CDT
105
$138.50 2004-04-02
983
CDT
105
$77.86
984
DBT
106
$500.00 2004-03-27
2004-04-04
Abbildung 1-3: Relationale Sicht der Kontendaten Jeder Datenbankserver bietet einen Mechanismus zur Erstellung eindeutiger Zahlenmengen, die als Primärschlüsselwerte eingesetzt werden können. Sie müssen sich also nicht selbst darum kümmern, welche Zahlen bereits vergeben wurden.
Zwar hätte ich auch eine Kombination aus den Spalten fname und lname als Primärschlüssel auswählen können (einen Primärschlüssel, der aus mehreren Spalten besteht, bezeichnet man als zusammengesetzten Schlüssel), doch es wäre gut möglich, dass mehrere Bankkunden den gleichen Vor- und Nachnamen haben. Daher beschloss ich, extra die Spalte cust_id in die customer-Tabelle einzuführen, um sie als Primärschlüsselspalte zu verwenden.
Max. Linie
Hätte man in diesem Beispiel fname/lname als Primärschlüssel gewählt, würde man das als einen sprechenden Schlüssel bezeichnen, während cust_id als Primärschlüssel als Surrogatschlüssel bezeichnet wird. Die Entscheidung darüber, ob man sprechende Schlüssel oder Surrogatschlüssel einsetzen sollte, ist Thema einer ausgreifenden Diskussion,
Einführung in Datenbanken This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 5
Links aber in diesem speziellen Fall ist die Wahl eindeutig, da sich der Nachname einer Person ändern kann (beispielsweise weil der Nachname des Ehepartners übernommen wird) und sich Primärschlüssel niemals ändern sollten, nachdem der Wert einmal zugewiesen wurde.
Manche der Tabellen enthalten auch Informationen, um zu einer anderen Tabelle zu navigieren; das ist der Punkt, an dem die zuvor erwähnten »redundanten Daten« ins Spiel kommen. So enthält beispielsweise die account-Tabelle eine Spalte namens cust_id mit der eindeutigen Kennung des Kunden, der das Konto eröffnet hat, sowie eine Spalte namens product_cd mit der eindeutigen Kennung des Produkts, dem das Konto entspricht. Diese sogenannten Fremdschlüssel haben denselben Zweck wie die Linien, mit denen die Entitäten in der hierarchischen und der Netzwerkdarstellung der Kontendaten verbunden sind. Wenn Sie einen bestimmten Datensatz mit Kontodaten betrachten und mehr Informationen über den Kunden haben möchten, der das Konto geöffnet hat, würden Sie den Wert der Spalte cust_id einsetzen, um die entsprechende Zeile in der Tabelle customer zu finden (in Fachjargon bezeichnet man einen solchen Vorgang als Join; Joins werden in Kapitel 3 eingeführt und in Kapitel 5 und 10 ausführlich betrachtet). Zunächst mag es nach Verschwendung aussehen, dieselben Daten viele Male zu speichern, aber das relationale Modell sagt ziemlich klar aus, welche redundanten Daten gespeichert werden können. So kann zum Beispiel die account-Tabelle sehr wohl eine Spalte für die Kundennummer des Kontoinhabers haben, aber nicht für seinen Vor- und Nachnamen. Wenn beispielsweise ein Kunde seinen Namen wechselt, möchte man sicher sein, dass dieser Name nur an einer einzigen Stelle der Datenbank gespeichert ist, sonst kann es passieren, dass die Daten nicht überall aktualisiert werden und somit inkonsistent werden. Der einzig richtige Platz für diese Daten ist die customer-Tabelle, und nur die cust_id-Werte sollten in anderen Tabellen verwendet werden. Außerdem darf eine einzelne Spalte nicht mehrere Daten enthalten; es darf also nicht einfach eine name-Spalte geben, die sowohl den Vor- als auch den Nachnamen des Kunden enthält, oder eine address-Spalte, in der Straße, Ort, Staat und Postleitzahl auf einmal stehen. Wird ein Datenbankentwurf so weit verfeinert, dass jede einzelne Information an genau einer Stelle vertreten ist (abgesehen von Fremdschlüsseln), bezeichnet man dies als Normalisierung.
Max. Linie
Vielleicht fragen Sie sich, wie man in den vier Tabellen in Abbildung 1-3 die Kontenbewegungen des Girokontos von George Blake wiederfinden kann. Zuerst suchen Sie sich die Kundennummer von George Blake in der customer-Tabelle heraus. Dann finden Sie in der account-Tabelle die Zeile, deren cust_id-Spalte Georges Kundennummer enthält und deren product_cd-Spalte mit der Zeile der product-Tabelle übereinstimmt, deren nameSpalte den Eintrag »Checking« aufweist. Zum Schluss machen Sie dann die Zeilen der transaction-Tabelle ausfindig, deren account_id-Spalte wiederum die eindeutige ID der account-Tabelle enthält. Das mag vielleicht kompliziert klingen, kann aber mit SQL in einer einzigen Anweisung getan werden, wie Sie gleich noch sehen werden.
6 |
Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Ein wenig Fachjargon Da ich bereits in den vorangegangenen Abschnitten einige neue Fachbegriffe eingeführt habe, ist es nun an der Zeit für ein paar formale Definitionen. Tabelle 1-1 zeigt die Begriffe, die im Rest dieses Buchs immer wieder verwendet werden, zusammen mit ihren Definitionen. Tabelle 1-1: Fachbegriffe und Definitionen Begriff
Definition
Entität
Etwas, das für die Nutzer der Datenbank von Interesse ist, zum Beispiel Kunden, Teile, Orte usw.
Spalte
Eine einzelne Information, die in einer Tabelle gespeichert ist.
Zeile
Eine Menge von Spalten, die zusammen eine Entität oder einen Vorgang einer Entität vollständig beschreiben. Wird auch als Datensatz oder Eintrag bezeichnet.
Tabelle
Eine Menge von Zeilen, die entweder im Arbeitsspeicher (nicht-persistent) oder in einem dauerhaften Speicher (persistent) gespeichert sind.
Ergebnismenge
Ein anderer Name für eine nicht-persistente Tabelle, im Allgemeinen das Ergebnis einer SQL-Abfrage.
Primärschlüssel
Eine oder mehrere Spalten, die als eindeutiger Identifier für jede Zeile der Tabelle dienen.
Fremdschlüssel
Eine oder mehrere Spalten, die zusammen genommen eine einzelne Zeile in einer anderen Tabelle identifizieren.
Was ist SQL? Zusammen mit der Definition des relationalen Modells stellte Codd eine Sprache namens DSL/Alpha vor, um die Daten in relationalen Tabellen zu bearbeiten. Kurz nach der Veröffentlichung von Codds Paper stellte IBM eine Arbeitsgruppe für den Bau eines Prototyps zusammen, der auf Codds Ideen basieren sollte. Diese Gruppe erschuf eine vereinfachte Version von DSL/Alpha namens SQUARE. Durch weitere Verfeinerungen an SQUARE entstand eine Sprache namens SEQUEL, die dann schließlich in SQL umbenannt wurde. SQL hat mittlerweile ein gesetzteres Alter erreicht (wie zu seinem Leidwesen auch der Autor dieses Buchs) und sich mit der Zeit massiv gewandelt. Mitte der 1980er-Jahre begann das American National Standards Institute (ANSI) den ersten Standard für SQL auszuarbeiten, der 1986 veröffentlicht wurde. Weitere Verfeinerungen führten zu neuen Releases des SQL-Standards in den Jahren 1989, 1992, 1999, 2003 und 2006. Doch nicht nur der Sprachkern wurde überarbeitet, SQL erhielt auch neue Features, um beispielsweise objektorientierte Funktionalität zu unterstützen. Der jüngste Standard, SQL:2006, konzentriert sich auf die Integration von SQL und XML und definiert eine XQuery genannte Sprache, mit der Daten aus XML-Dokumenten abgefragt werden.
Max. Linie
SQL geht mit dem relationalen Modell Hand in Hand, da das Ergebnis einer SQL-Abfrage immer eine Tabelle ist (auch wenn es in diesem Kontext Ergebnismenge heißt). So kann eine neue permanente Tabelle in einer relationalen Datenbank einfach schon durch die Speicherung der Ergebnismenge einer Abfrage angelegt werden. Zudem kann eine
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Was ist SQL? | 7
Max. Linie
Links Abfrage sowohl permanente Tabellen als auch die Ergebnismenge anderer Abfragen als Eingabe nutzen (dies wird in Kapitel 9 noch genauer erklärt). Ein Hinweis zum Schluss: SQL ist kein Akronym (obwohl manche darauf bestehen, es sei die Abkürzung für »Structured Query Language«). Sie können den Namen sowohl als einzelne Buchstaben aussprechen (S. Q. L.) als auch wie das englische Wort »Sequel«.
SQL-Anweisungen SQL besteht aus mehreren getrennten Teilen, von denen folgende in diesem Buch behandelt werden: SQL-Schemaanweisungen, mit denen die in der Datenbank gespeicherten Datenstrukturen definiert werden; SQL-Datenanweisungen, mit denen die zuvor durch SQL-Schemaanweisungen angelegten Datenstrukturen bearbeitet werden, und SQLTransaktionsanweisungen, mit denen Transaktionen gestartet, beendet oder zurückgerollt werden können (siehe Kapitel 12). Wenn Sie zum Beispiel eine neue Tabelle in Ihrer Datenbank anlegen möchten, verwenden Sie dazu die SQL-Schemaanweisung create table; um jedoch diese neue Tabelle mit Daten zu bevölkern, verwenden Sie die SQLDatenanweisung insert. Um Ihnen einen Vorgeschmack auf diese Anweisungen zu geben, sehen Sie hier eine SQL-Schemaanweisung, mit der die Tabelle corporation angelegt wird: CREATE TABLE corporation (corp_id SMALLINT, name VARCHAR(30), CONSTRAINT pk_corporation PRIMARY KEY (corp_id) );
Diese Anweisung erstellt eine Tabelle mit den beiden Spalten corp_id und name, wobei die corp_id der Primärschlüssel ist. Die genaueren Einzelheiten dieser Anweisung, wie zum Beispiel die verschiedenen für MySQL verfügbaren Datentypen, werden im nächsten Kapitel erläutert. Als Nächstes sehen Sie hier eine SQL-Datenanweisung, mit der eine Zeile für die Acme Paper Corporation in die Tabelle corporation eingefügt wird: INSERT INTO corporation (corp_id, name) VALUES (27, 'Acme Paper Corporation');
Diese Anweisung schreibt in die corporation-Tabelle eine Zeile mit dem Wert 27 als corp_ id und dem Wert Acme Paper Corporation für die name-Spalte. Abschließend sehen Sie hier eine einfache select-Anweisung, mit der die soeben angelegten Daten abgefragt werden: mysql< SELECT name -> FROM corporation -> WHERE corp_id = 27; +------------------------+ | name | +------------------------+ | Acme Paper Corporation | +------------------------+
Max. Linie 8 |
Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Alle durch SQL-Schemaanweisungen erstellten Datenbankelemente werden in einem speziellen Tabellensatz gespeichert, den man Data Dictionary nennt. Diese »Daten über die Datenbank« bezeichnet man zusammen genommen als Metadaten. Wir werden sie in Kapitel 15 näher beleuchten. Genau wie die von Ihnen selbst angelegten Tabellen können Sie auch die Data Dictionary-Tabellen mit einer select-Anweisung abfragen. So können Sie die aktuellen Datenstrukturen erkennen, die in der Datenbank zur Laufzeit eingesetzt werden. Wenn man Ihnen beispielsweise aufträgt, einen Report zu erstellen, der die im letzten Monat neu angelegten Konten zeigt, können Sie entweder die Namen der Spalten der account-Tabelle hartcodieren, die Ihnen beim Schreiben des Reports bekannt waren, oder Sie können das Data Dictionary abfragen, um festzustellen, welche Spalten aktuell vorhanden sind, und den Report jedes Mal dynamisch generieren. Ein Großteil dieses Buchs befasst sich mit dem datenorientierten Teil von SQL, der aus den Anweisungen select, update, insert und delete besteht. SQL-Schemaanweisungen sehen Sie in Kapitel 2: Dort wird die Musterdatenbank generiert, die im weiteren Verlauf des Buchs verwendet wird. Über SQL-Schemaanweisungen gibt es, abgesehen von ihrer Syntax, normalerweise nicht viel zu sagen, während die SQL-Datenanweisungen trotz ihrer kleinen Zahl eine Fülle von Möglichkeiten bieten, die zu untersuchen sich lohnt. Deswegen werden sich die meisten Kapitel in diesem Buch, obwohl ich versuchen werde, Ihnen viele der SQL-Schemaanweisungen vorzustellen, auf die SQL-Datenanweisungen konzentrieren.
SQL: Eine nicht-prozedurale Sprache Wenn Sie in der Vergangenheit bereits mit Programmiersprachen gearbeitet haben, sind Sie den Umgang mit Variablen, Datenstrukturen, Bedingungslogik (if-then-else) und Schleifenkonstrukten (do while … end) gewohnt und wissen, wie man Code in kleine, wieder verwendbare Module (Objekte, Funktionen, Prozeduren) zerlegt. Sie übergeben Ihren Code an einen Compiler, und die kompilierte, ausführbare Datei tut genau das, was Sie in Ihrem Programm bezweckt haben (nun ja, vielleicht nicht immer genau). Wenn Sie mit Java, C#, C, Visual Basic oder einer anderen prozeduralen Sprache arbeiten, haben Sie alles, was das Programm tut, genau unter Kontrolle. Eine prozedurale Sprache definiert sowohl die gewünschten Ergebnisse als auch den Mechanismus oder Prozess, mit dem die Ergebnisse generiert werden. Nicht-prozedurale Sprachen definieren zwar ebenfalls die gewünschten Ergebnisse, überlassen den Prozess, über den diese Ergebnisse generiert werden, aber einer externen Instanz.
Max. Linie
Doch mit SQL müssen Sie einen Teil dieser Kontrolle abgeben, da SQL-Anweisungen zwar die notwendigen Ein- und Ausgaben definieren, aber die Art und Weise, wie eine Anweisung ausgeführt wird, von einer Komponente Ihrer Datenbank-Engine bestimmt wird: der sogenannten Optimierung. Sie hat die Aufgabe, Ihre SQL-Anweisungen genau zu betrachten und unter Berücksichtigung der Tabellenkonfiguration und der verfügba-
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Was ist SQL? | 9
Max. Linie
Links ren Indizes den effizientesten Ausführungspfad auszutüfteln (nun ja, nicht immer den allereffizientesten). Die meisten Datenbank-Engines ermöglichen es, die Entscheidungen der Optimierung durch Optimierungshinweise (optimizer hints) zu beeinflussen, in denen beispielsweise gesagt wird, dass ein bestimmter Index benutzt werden soll. Jedoch ein Großteil der normalen SQL-Benutzer schwingt sich niemals zu diesen luftigen Höhen auf und überlässt solche Hacks dem Datenbankadministrator oder Performanceexperten. Mit SQL kann man also keine vollständigen Anwendungen schreiben. Entweder schreiben Sie ein einfaches Skript, um bestimmte Daten zu bearbeiten, oder Sie integrieren SQL in Ihre bevorzugte Programmiersprache. Einige Datenbankhersteller haben Ihnen diese Arbeit bereits abgenommen, wie Oracle mit der Programmiersprache PL/SQL, MySQL mit seiner Sprache für gespeicherte Routinen und Microsoft mit der Sprache TransactSQL. Bei diesen Sprachen sind SQL-Datenanweisungen Teil der Grammatik, was eine nahtlose Integration von Datenbankabfragen in prozedurale Befehle ermöglicht. Wenn Sie keine spezielle Datenbanksprache verwenden, also etwa mit Java arbeiten, benötigen Sie ein Toolkit oder eine API, um SQL-Anweisungen in Ihrem Code auszuführen. Manche derartigen Toolkits werden vom Datenbankhersteller, andere von Drittanbietern oder Open Source-Providern zur Verfügung gestellt. Tabelle 1-2 zeigt einige Möglichkeiten, um SQL in eine spezifische Sprache zu integrieren. Tabelle 1-2: Toolkits zur SQL-Integration
Max. Linie
Sprache
Toolkit
Java
JDBC (Java Database Connectivity) (JavaSoft)
C++
RogueWave SourcePro DB (Drittanbietertool zur Einbindung von Oracle, SQL Server, MySQL, Informix, DB2, Sybase und PostgreSQL)
C/C++
Pro*C (Oracle) MySQL C API (Open Source) DB2 Call Level Interface (IBM)
C#
ADO.NET (Microsoft)
Perl
Perl DBI
Python
Python DB
Visual Basic
ADO.NET (Microsoft)
Wenn Sie lediglich SQL-Befehle interaktiv ausführen möchten, finden Sie bei jedem Datenbankhersteller mindestens ein einfaches Kommandozeilentool, um SQL-Befehle an die Datenbank-Engine zu übermitteln und die Ergebnisse zu inspizieren. Die meisten Hersteller liefern auch eine grafische Oberfläche, die in einem Fenster Ihre SQL-Befehle und in einem anderen die Ergebnisse dieser Befehle zeigt. Da die Beispiele in diesem Buch an einer MySQL-Datenbank ausprobiert werden, verwende ich das Kommandozeilentool mysql, das Teil jeder MySQL-Installation ist, um den Code auszuführen und die Ergebnisse zu formatieren.
10 | Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts SQL-Beispiele Weiter oben in diesem Kapitel hatte ich Ihnen versprochen, eine SQL-Anweisung zu zeigen, die alle Bewegungen auf George Blakes Girokonto zurückliefert. Hier ist sie: SELECT t.txn_id, t.txn_type_cd, t.txn_date, t.amount FROM individual i INNER JOIN account a ON i.cust_id = a.cust_id INNER JOIN product p ON p.product_cd = a.product_cd INNER JOIN transaction t ON t.account_id = a.account_id WHERE i.fname = 'George' AND i.lname = 'Blake' AND p.name = 'checking account'; +--------+-------------+---------------------+--------+ | txn_id | txn_type_cd | txn_date | amount | +--------+-------------+---------------------+--------+ | 11 | DBT | 2008-01-05 00:00:00 | 100.00 | +--------+-------------+---------------------+--------+ 1 row in set (0.00 sec)
Ohne an diesem Punkt gleich zu sehr ins Detail zu gehen: Diese Abfrage identifiziert in der Tabelle individual die Zeile für George Blake und in der Tabelle product die Zeile für das »checking«-Produkt, sucht dann in der Tabelle account die Zeile für diese spezielle Person-Produkt-Kombination und liefert vier Zeilen aus der Tabelle transaction mit allen für dieses Konto gemeldeten Transaktionen. Wenn Sie zufällig wissen, dass die Kundennummer von George Blake 8 ist und die entsprechende Kontoart über den Code 'CHK' angezeigt wird, können Sie einfach auf Basis der Kundennummer George Blakes Nummer für dieses Konto in der Tabelle account suchen und die Kontonummer dann einsetzen, um die entsprechenden Transaktionen zu ermitteln: SELECT t.txn_id, t.txn_type_cd, t.txn_date, t.amount FROM account a INNER JOIN transaction t ON t.account_id = a.account_id WHERE a.cust_id = 8 AND a.product_cd = 'CHK';
In den nächsten Kapiteln werden alle Konzepte aus diesen Abfragen (und viele andere mehr) noch genauer behandelt, aber ich wollte, dass Sie vorab zumindest schon einmal gesehen haben, wie solche Abfragen ausschauen. Die obigen Abfragen enthalten drei verschiedene Klauseln: select, from und where. Fast jede Abfrage, die Ihnen jemals unter die Augen kommt, wird mindestens diese drei Klauseln enthalten, auch wenn es für besondere Zwecke noch einige andere Klauseln gibt. Die Rolle der drei Klauseln könnte man folgendermaßen darstellen: SELECT /* (WÄHLE) eine oder mehrere Sachen */ ... FROM /* (AUS) einem oder mehreren Orten */ ... WHERE /* (WOBEI) eine oder mehrere Bedingungen gelten */ ...
Max. Linie
Die meisten SQL-Implementierungen behandeln alles, was zwischen /* und */ steht, als Kommentar.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Was ist SQL? | 11
Max. Linie
Links Beim Aufbau einer Abfrage müssen Sie zuerst herausfinden, welche Tabelle(n) Sie benötigen, und diese dann in Ihre from-Klausel schreiben. Als Nächstes müssen Sie Ihrer whereKlausel Bedingungen hinzufügen, um aus diesen Tabellen die Daten herauszufiltern, die Sie nicht interessieren. Zum Schluss müssen Sie entscheiden, welche Spalten aus den verschiedenen Tabellen abgefragt werden müssen, und diese in Ihre select-Klausel einfügen. Das folgende einfache Beispiel zeigt, wie man alle Kunden mit dem Nachnamen »Smith« findet: SELECT cust_id, fname FROM individual WHERE lname = 'Smith'
Diese Abfrage sucht in der individual-Tabelle alle Zeilen, deren lname-Spalte den String »Smith« aufweist, und gibt die Werte der Spalten cust_id und fname aus diesen Zeilen zurück. Doch vermutlich werden Sie Ihre Datenbank nicht nur abfragen, sondern auch Daten darin einfügen und modifizieren. Das folgende Beispiel zeigt, wie man in die productTabelle eine neue Zeile einfügt: INSERT INTO product (product_cd, name) VALUES ('CD', 'Certificate of Depisit')
Huch, da habe ich mich bei »Deposit« vertippt. Kein Problem. Das lässt sich mit einer update-Anweisung bereinigen: UPDATE product SET name = 'Certificate of Deposit' WHERE product_cd = 'CD';
Beachten Sie, dass die update-Anweisung auch eine where-Klausel enthält, genau wie die select-Anweisung. Die update-Anweisung muss nämlich die zu modifizierenden Zeilen zunächst identifizieren. In diesem Fall sollen nur diejenigen Zeilen geändert werden, deren product_cd-Spalte den String »CD« enthält. Da die Spalte product_cd der Primärschlüssel der product-Tabelle ist, kann man davon ausgehen, dass die update-Anweisung genau eine Zeile ändert (oder gar keine, wenn der Wert in der Tabelle nicht vorkommt). Immer wenn Sie eine SQL-Datenanweisung ausführen, bekommen Sie von der Datenbank-Engine ein Feedback darüber, wie viele Zeilen von Ihrer Anweisung betroffen waren. Wenn Sie ein interaktives Tool wie das zuvor bereits erwähnte Kommandozeilenprogramm mysql benutzen, meldet es Ihnen, wie viele Zeilen: • von der select-Anweisung zurückgegeben, • von der insert-Anweisung eingefügt, • von der update-Anweisung geändert und • von der delete-Anweisung gelöscht wurden.
Max. Linie
Falls Sie eine prozedurale Sprache mit einem der oben genannte Toolkits benutzen, wird das Toolkit nach der Ausführung der SQL-Datenanweisung einen Aufruf absetzen, um dieses Feedback zu bekommen. Schauen Sie sich diese Informationen genau an, um
12 | Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts sicherzustellen, dass Ihre Anweisung nichts Unerwünschtes getan hat (wenn Sie zum Beispiel vergessen, eine where-Klausel in eine delete-Anweisung einzufügen, kann jede einzelne Tabellenzeile gelöscht werden!).
Was ist MySQL? Relationale Datenbanken werden seit 20 Jahren kommerziell angeboten. Besonders ausgereifte und populäre kommerzielle Produkte sind: • Oracle Database von der Oracle Corporation • SQL Server von Microsoft • DB2 Universal Database von IBM • Sybase Adaptive Server von Sybase Alle diese Datenbankserver tun annähernd das Gleiche, auch wenn einige von ihnen besser gerüstet sind, um mit sehr großen Datenmengen oder sehr hohem Durchsatz fertig zu werden. Wieder andere können besser mit Objekten, großen Dateien oder XML-Dokumenten umgehen. Darüber hinaus sind alle diese Server an den neusten ANSI SQL-Standard angepasst worden. Das ist eine gute Sache, und ich werde besonderen Wert darauf legen, Ihnen das Schreiben von SQL-Anweisungen beizubringen, die so gut wie unverändert auf allen diesen Plattformen laufen. Zusätzlich zu den kommerziellen Anbietern hat die Open Source-Gemeinde in den letzten fünf Jahren viel dafür getan, eine gangbare Alternative zu den kommerziellen Datenbankservern zu schaffen. Die beiden beliebtesten Open Source-Datenbankserver sind PostgreSQL und MySQL. Die Website von MySQL geht von mehr als zehn Millionen Installationen aus. Der MySQL-Server ist frei erhältlich und nach meinen Erfahrungen extrem einfach zu laden und zu installieren. Aus diesen Gründen habe ich beschlossen, alle Beispiele in diesem Buch auf meiner MySQL-Datenbank (Version 6.0) auszuführen und das Kommandozeilentool mysql zur Formatierung der Ergebnismengen einzusetzen. Selbst wenn Sie bereits einen anderen Server benutzen und nicht auf MySQL umsteigen möchten, bitte ich Sie, den neusten MySQL-Server zu installieren, das Schema und die Daten der Musterdatenbank zu laden und mit den Daten und Beispielen dieses Buchs zu experimentieren. Behalten Sie aber dennoch Folgendes im Gedächtnis: Dies ist kein Buch über die SQL-Implementierung von MySQL.
Nein, dieses Buch soll vermitteln, wie SQL-Anweisungen geschrieben werden, die ohne Änderungen auf MySQL und mit geringfügigen oder auch ohne Änderungen auf neueren Versionen von Oracle Database, Sybase Adaptive Server und SQL Server laufen.
Max. Linie
Um den Code in diesem Buch möglichst herstellerunabhängig zu gestalten, werde ich im Buch selbst einige interessante Sachen weglassen, die von den SQL-Implementierern von MySQL ersonnen wurden, aber auf anderen Datenbankimplementierungen nicht nach-
Was ist MySQL? | 13 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links vollzogen werden können. Für die Leser, die auch weiterhin mit MySQL arbeiten möchten, stelle ich in Anhang B einige dieser Features vor.
Weiteres Vorgehen Das übergreifende Ziel der nächsten vier Kapitel ist, SQL-Datenanweisungen mit besonderer Berücksichtigung der drei wichtigsten Klauseln der select-Anweisung einzuführen. Darüber hinaus werden viele Beispiele gezeigt, die das Bankschema verwenden, das im folgenden Kapitel eingeführt und für alle Beispiele dieses Buchs benutzt wird. Ich hoffe, dass Sie durch die Vertrautheit mit einer einzigen Datenbank schneller zum Kern eines Beispiels vorstoßen, ohne jedes Mal innehalten und nachschauen zu müssen, welche Tabellen denn dieses Mal wieder benötigt werden. Sollte es Ihnen zu langweilig werden, immer mit der gleichen Menge an Tabellen zu arbeiten, können Sie die Beispieldatenbank gern mit zusätzlichen Tabellen ausstatten oder sich zum Experimentieren Ihre eigene Datenbank ausdenken. Nachdem Sie ein solides Grundlagenwissen aufgebaut haben, gehen die nachfolgenden Kapitel mehr in die Tiefe und stellen zusätzliche Konzepte vor, die zumeist voneinander unabhängig sind. Falls Sie einmal verwirrt sind, blättern Sie etwas weiter und kommen später auf ein Kapitel zurück. Wenn Sie das Buch beendet und alle Beispiele durchgearbeitet haben, sind Sie bereits auf dem besten Wege, ein mit allen Wassern gewaschener SQL-Guru zu werden. Wer noch mehr über relationale Datenbanken, die Geschichte der computergestützten Datenbanksysteme oder die Sprache SQL erfahren möchte, sollte sich folgende Quellen anschauen: • Database in Depth: Relational Theory for Practitioners von C. J. Date (O’Reilly) • An Introduction to Database Systems, Achte Auflage von C. J. Date (AddisonWesley) • The Database Relational Model: A Retrospective Review and Analysis: A Historical Account and Assessment of E. F. Codd’s Contribution to the Field of Database Technology von C. J. Date (Addison-Wesley)
Max. Linie
Max. Linie 14 | Kapitel 1: Der Hintergrund
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 2
KAPITEL 2
Datenbanken erstellen und mit Daten füllen
In diesem Kapitel erfahren Sie alles Notwendige, um Ihre erste eigene Datenbank mit den Tabellen und Daten für die Beispiele dieses Buchs anzulegen. Außerdem lernen Sie die verschiedenen Datentypen kennen und sehen, wie man die passenden Tabellen erzeugt, um mit diesen zu arbeiten. Da die Beispiele dieses Buchs in einer MySQL-Datenbank ausgeführt werden, ist dieses Kapitel von den Features und der Syntax von MySQL beeinflusst, aber das meiste ist auch für alle anderen Server anwendbar.
Eine MySQL-Datenbank anlegen Wenn Sie bereits einen MySQL-Datenbankserver zur Verfügung haben, können Sie die Installationsanweisungen überspringen und mit den Anweisungen in Tabelle 2-1 beginnen. Denken Sie jedoch daran, dass dieses Buch MySQL Version 6.0 oder höher voraussetzt, sodass Sie eventuell Ihren Server aktualisieren oder einen neuen installieren müssen, falls Sie noch ein älteres Release haben. Die folgenden Anleitungen zeigen Ihnen, welche Schritte mindestens zur Installation eines MySQL-Servers auf einem Windows-Computer erforderlich sind: 1. Gehen Sie zur Download-Seite für den MySQL Database Server unter http://dev. mysql.com/downloads/. Wenn Sie Version 6.0 herunterladen, lautet die vollständige URL http://dev.mysql.com/downloads/mysql/6.0.html. 2. Laden Sie das Windows Essentials (x86)-Paket herunter, das nur die häufiger verwendeten Tools enthält. 3. Klicken Sie auf Ausführen, wenn Sie gefragt werden: »Möchten Sie diese Datei ausführen oder speichern?« 4. Das MySQL Server 6.0-Fenster Setup Wizard erscheint. Klicken Sie auf Next. 5. Aktivieren Sie den Radiobutton Typical Install und klicken Sie dann Next.
Max. Linie
Max. Linie
6. Klicken Sie Install. 7. Ein MySQL Enterprise-Fenster erscheint. Klicken Sie zweimal auf Next.
| 15 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Links 8. Wenn die Installation abgeschlossen ist, aktivieren Sie das Kontrollkästchen neben Configure the MySQL-Server now und klicken dann auf Finish. Dadurch wird der Konfigurationsassistent gestartet. 9. Wenn der Konfigurationsassistent erscheint, wählen Sie die Option Standard Configuration aus und aktivieren die Kontrollkästchen Install as Windows Service und Include Bin Directory in Windows Path. Klicken Sie auf Next. 10. Wählen Sie das Kontrollkästchen Modify Security Settings und geben Sie das Passwort für den Benutzer root ein (denken Sie daran, sich das Passwort zu notieren, denn Sie werden es in Kürze benötigen!). Klicken Sie dann auf Next. 11. Klicken Sie auf Ausführen. Wenn alles gut gegangen ist, ist der MySQL-Server jetzt installiert und läuft. Wenn nicht, sollten Sie den Server deinstallieren und den Troubleshooting a MySQL Installation Under Windows-Hinweis lesen (den Sie unter http://dev.mysql.com/doc/refman/6.0/en/windowstroubleshooting.html finden). Haben Sie eine ältere MySQL-Version deinstalliert, bevor Sie 6.0 nun einrichten, müssen Sie eventuell weitere Aufräumarbeiten erledigen (ich musste sogar einige alte Registry-Einträge löschen), um den Configuration Wizard dazu zu bringen, das Programm korrekt zu installieren.
Als Nächstes werden Sie eine Windows-Eingabeaufforderung öffnen, das mysql-Tool starten und Ihre Datenbank sowie Ihren Datenbankbenutzer erstellen. Tabelle 2-1 beschreibt die erforderlichen Schritte. In Schritt 5 können Sie auch ein anderes eigenes Passwort als »xyz« für den Benutzer lrngsql eingeben (aber vergessen Sie nicht, es sich zu notieren). Tabelle 2-1: Die Beispieldatenbank erstellen
Max. Linie
Schritt
Beschreibung
Aktion
1
Im Startmenü das Dialogfenster Ausführung öffnen.
Wählen Sie Start und dann Ausführen.
2
Ein Eingabeaufforderungsfenster starten.
Geben Sie cmd ein und klicken Sie OK.
3
Als root in MySQL einloggen
mysql -u root -p
4
Eine Datenbank für die Beispieldaten erstellen.
create database bank;
5
Den Datenbankbenutzer lrngsql mit vollständigen Rechten für die Datenbank bank erstellen.
grant all privileges on bank.* to 'lrngsql'@'localhost' identified by 'xyz';
6
Das mysql-Tool beenden.
quit;
7
Als lrngsql in MySQL einloggen.
mysql -u lrngsql -p;
8
Mit der Datenbank bank verbinden.
use bank;
Jetzt haben Sie einen MySQL-Server, eine Datenbank und einen Datenbankbenutzer. Sie müssen also nur noch die Datenbanktabellen erstellen und mit den Beispieldaten füllen. Dazu müssen Sie das Skript unter http://examples.oreilly.de.learningsql2ger/ herunterla-
16 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts den und aus dem mysql-Tool heraus ausführen. Vorausgesetzt, Sie haben die Datei unter c:\temp\LearningSQLExample.sql gespeichert, müssten Sie dazu Folgendes tun: 1. Wiederholen Sie die Schritte 7 und 8 aus Tabelle 2-1, wenn Sie sich aus mysql ausgeloggt haben. 2. Geben Sie source c:\temp\LearningSQLExample.sql; ein und drücken Sie Enter. Nun müssten Sie eine funktionierende Datenbank haben, die alle für die Beispiele dieses Buchs erforderlichen Daten enthält.
Das mysql-Kommandozeilentool Immer wenn Sie das mysql-Kommandozeilentool aufrufen, können Sie den Benutzernamen und die zu verwendende Datenbank wie folgt angeben: mysql -u lrngsql -p bank
Das erspart Ihnen die Eingabe von use bank;, jedes Mal wenn Sie das Tool starten. Schließlich werden Sie aufgefordert, ein Passwort einzugeben, und der mysql>-Prompt erscheint. Dort können Sie SQL-Anweisungen geben und die Ergebnisse anschauen. Wenn Sie zum Beispiel das Datum und die Uhrzeit wissen möchten, können Sie folgende Abfrage durchführen: mysql> SELECT now( ); +---------------------+ | now( ) | +---------------------+ | 2009-05-06 16:48:46 | +---------------------+ 1 row in set (0.01 sec)
Die now( )-Funktion ist eine eingebaute MySQL-Funktion, die das Datum und die aktuelle Uhrzeit zurückliefert. Wie Sie sehen, formatiert das mysql-Kommandozeilentool die Ergebnisse Ihrer Abfragen in einem rechteckigen Kasten, der durch die Zeichen +, – und | abgegrenzt wird. Wenn alle Ergebnisse angezeigt wurden (in diesem Fall nur eine einzige Zeile), zeigt das mysql-Kommandozeilentool außerdem, wie viele Zeilen zurückgegeben wurden und wie lange die Ausführung der SQL-Anweisung dauerte. Wenn Sie mit dem mysql-Kommandozeilentool fertig sind, geben Sie einfach quit; oder exit; ein, um zur Windows-Eingabeaufforderung zurückzukehren.
MySQL-Datentypen Max. Linie
Generell haben alle populären Datenbankserver die Kapazität, dieselben Datentypen zu speichern, wie etwa Strings, Datumswerte und Zahlen. Unterschiede gibt es typischerweise in den spezielleren Datentypen, beispielsweise bei XML-Dokumenten oder sehr großen Text- oder Binärdokumenten. Da dieses Buch eine Einführung in SQL ist und
MySQL-Datentypen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 17
Max. Linie
Links 98% aller Spalten, mit denen Sie zu tun haben werden, einfache Datentypen sind, befasst sich dieses Buch nur mit den Datentypen für Zeichen, Datumswerte und Zahlen.
Fehlende from-Klauseln In manchen Datenbankservern ist es gar nicht möglich, eine Abfrage ohne eine from-Klausel abzusetzen, in der nicht mindestens eine Tabelle genannt wird. Die Oracle Database ist beispielsweise ein viel benutzter Server, für den dieses gilt. Für Fälle, in denen lediglich eine Funktion aufgerufen werden muss, bietet Oracle eine Tabelle namens dual mit einer einzigen Spalte namens dummy, die nur eine einzige Datenzeile enthält. Um mit Oracle Database kompatibel zu sein, stellt MySQL ebenfalls eine dual-Tabelle zur Verfügung. Die obige Abfrage des Datums und der Uhrzeit könnte man also auch folgendermaßen schreiben: mysql> SELECT now( ) FROM dual; +---------------------+ | now( ) | +---------------------+ | 2009-05-06 16:48:46 | +---------------------+ 1 row in set (0.01 sec)
Wenn Sie Oracle nicht benutzen und auch keine Notwendigkeit darin sehen, mit Oracle kompatibel zu sein, können Sie die dual-Tabelle vergessen.
Zeichendaten Zeichendaten können als Strings mit fester oder variabler Länge gespeichert werden. Der Unterschied besteht darin, dass Strings fester Länge nach rechts mit Leerzeichen aufgefüllt werden und immer die gleiche Anzahl von Bytes benötigen, Strings mit variabler Länge hingegen nicht mit Leerzeichen aufgefüllt werden und nicht immer die gleiche Anzahl Bytes benötigen. Wenn Sie eine Zeichenspalte definieren, müssen Sie angeben, wie lang ein String, der in dieser Spalte gespeichert wird, maximal sein darf. Möchten Sie zum Beispiel Strings mit höchstens 20 Zeichen speichern, könnten Sie folgende Definitionen verwenden: char(20) /* feste Länge */ varchar(20) /* variable Länge */
Max. Linie
Die Höchstlänge für char-Spalten beträgt aktuell 255 Zeichen. varchar-Spalten können hingegen bis zu 65.535 Bytes enthalten. Wenn Sie längere Strings speichern müssen (E-Mails, XML-Dokumente usw.), sollten Sie einen der Texttypen verwenden (mediumtext oder longtext), die weiter unten in diesem Abschnitt eingeführt werden. Im Allgemeinen sollten Sie den char-Typ benutzen, wenn alle in der Spalte vorkommenden Strings gleich lang sein werden (zum Beispiel Ländercodes), und varchar, wenn die
18 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Strings unterschiedlich lang sein werden. char und varchar werden auf allen wichtigen Datenbankservern gleich verwendet. Die Oracle Database bildet hier bei der Verwendung von varchar eine Ausnahme. Oracle-Benutzer sollten Zeichenspalten variabler Länge mit dem Typ varchar2 definieren.
Zeichensätze Sprachen wie Englisch, die das lateinische Alphabet verwenden, haben so wenige Zeichen, dass ein einziges Byte pro Zeichen ausreicht. Andere Sprachen, darunter Japanisch oder Koreanisch, haben so viele Zeichen, dass man mehrere Bytes benötigt, um sie alle zu speichern. Solche Zeichensätze bezeichnet man als Multibyte-Zeichensätze. MySQL kann Daten mit mehreren character-Sätzen speichern, sowohl mit Single- als auch mit Multibyte-Zeichensätzen. Der Befehl show zeigt Ihnen, welche Zeichensätze Ihr Server unterstützt:
Max. Linie
mysql> SHOW CHARACTER SET; +----------+-----------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+-----------------------------+---------------------+--------+ | big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 | | dec8 | DEC West European | dec8_swedish_ci | 1 | | cp850 | DOS West European | cp850_general_ci | 1 | | hp8 | HP West European | hp8_english_ci | 1 | | koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 | | latin1 | ISO 8859-1 West European | latin1_swedish_ci | 1 | | latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 | | swe7 | 7bit Swedish | swe7_swedish_ci | 1 | | ascii | US ASCII | ascii_general_ci | 1 | | ujis | EUC-JP Japanese | ujis_japanese_ci | 3 | | sjis | Shift-JIS Japanese | sjis_japanese_ci | 2 | | hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 | | tis620 | TIS620 Thai | tis620_thai_ci | 1 | | euckr | EUC-KR Korean | euckr_korean_ci | 2 | | koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 | | gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 | | greek | ISO 8859-7 Greek | greek_general_ci | 1 | | cp1250 | Windows Central European | cp1250_general_ci | 1 | | gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 | | latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 | | armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | ucs2 | UCS-2 Unicode | ucs2_general_ci | 2 | | cp866 | DOS Russian | cp866_general_ci | 1 | | keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 | | macce | Mac Central European | macce_general_ci | 1 | | macroman | Mac West European | macroman_general_ci | 1 | | cp852 | DOS Central European | cp852_general_ci | 1 | | latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 | | cp1251 | Windows Cyrillic | cp1251_general_ci | 1 |
MySQL-Datentypen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 19
Links | cp1256 | Windows Arabic | cp1256_general_ci | 1 | | cp1257 | Windows Baltic | cp1257_general_ci | 1 | | binary | Binary pseudo charset | binary | 1 | | geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 | +----------+-----------------------------+---------------------+--------+ 36 rows in set (0.11 sec)
Ist der Wert in der vierten Spalte, Maxlen, größer als 1, ist der Zeichensatz ein MultibyteZeichensatz. Als ich den MySQL-Server installierte, wurde automatisch der latin1-Zeichensatz als Standardzeichensatz eingestellt. Sie können aber auch für jede Spalte Ihrer Datenbank einen anderen Zeichensatz auswählen und sogar verschiedene Zeichensätze in derselben Tabelle speichern. Um bei der Definition einer Spalte einen anderen als den Standardzeichensatz einzustellen, geben Sie hinter der Typ-Deklaration einfach den Namen eines anderen unterstützten Zeichensatzes an: VARCHAR(20) CHARACTER SET utf8
Mit MySQL können Sie auch den Standardzeichensatz für die gesamte Datenbank ändern: CREATE DATABASE foreign_sales CHARACTER SET utf8;
Mehr möchte ich in einem Einführungsbuch über Zeichensätze nicht sagen, auch wenn die Internationalisierung ein weitaus größeres Feld ist, als hier gezeigt. Wenn Sie mit vielen oder ungewöhnlichen Zeichensätzen arbeiten möchten, können Sie sich beispielsweise in Java Internationalization (O’Reilly) von Andy Deitsch und David Czarnecki oder Unicode Demystified: A Practical Programmer’s Guide to the Encoding Standard (AddisonWesley) von Richard Gillam darüber informieren.
Textdaten Wenn Sie Daten speichern müssen, die das 64-KByte-Zeichen-Limit für varchar-Spalten überschreiten, können Sie einen der Texttypen verwenden. Tabelle 2-2 zeigt die verfügbaren Texttypen und ihre maximalen Größen. Tabelle 2-2: MySQL-Texttypen Texttyp
Höchstzahl der Zeichen
Tinytext
255
Text
65.535
Mediumtext
16.777.215
Longtext
4.294.967.295
Bei der Entscheidung für einen dieser Texttypen sollten Sie Folgendes beachten:
Max. Linie
• Wenn die Menge der Daten, die in eine Textspalte geladen werden sollen, die Maximalgröße dieses Datentyps überschreitet, werden Daten abgeschnitten.
20 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts • Im Gegensatz zur varchar-Spalte werden angehängte Leerzeichen nicht entfernt, wenn Daten in die Spalte geladen werden. • Wenn Sie text-Spalten zum Sortieren oder Gruppieren von Daten nutzen, werden nur die ersten 1.024 Bytes verwendet, ein Limit, das sich jedoch nach Bedarf erhöhen lässt. • Die verschiedenen Texttypen sind spezielle MySQL-Typen. SQL Server kennt nur einen einzigen Typ namens text für größere Mengen Zeichendaten, während DB2 und Oracle den Datentyp clob (Character Large Object) verwenden. • Seitdem MySQL bis zu 65.535 Bytes für varchar-Spalten zulässt (in Version 4 waren auch diese auf 255 Bytes beschränkt), ist es nicht mehr wirklich sinnvoll, die Typen tinytext oder text zu verwenden. Wenn Sie eine Spalte für Dateneinträge in freiem Format anlegen, also zum Beispiel eine notes-Spalte für Daten über Kundenkontakte der Kundendienstabteilung Ihres Unternehmens, ist varchar wahrscheinlich vollkommen ausreichend. Aber möchten Sie vollständige Dokumente speichern, sollten Sie entweder mediumtext oder longtext als Typ wählen. Oracle gestattet für char-Spalten bis zu 2.000 und für varchar2-Spalten bis zu 4.000 Bytes. SQL Server kann sowohl in char als auch in varchar bis zu 8.000 Bytes unterbringen.
Numerische Daten Zwar mag es zunächst sinnvoll erscheinen, nur einen einzigen Zahlentyp namens »numerisch« zu haben, aber in der Realität gibt es mehrere verschiedene Typen, die jeweils zeigen, wie die betreffenden numerischen Daten verwendet werden: Eine Spalte, die anzeigt, ob eine Kundenbestellung versandt wurde Dieser Spaltentyp, ein sogenannter Boolean, enthält entweder 0 für false oder 1 für true. Ein vom System generierter Primärschlüssel für eine Transaktionstabelle Diese Daten würden mit 1 anfangen und in Einserschritten auf eine potenziell sehr große Zahl anwachsen. Die Anzahl der Waren im elektronischen Warenkorb eines Kunden Dieser Spaltentyp würde positive ganze Zahlen zwischen 1 und höchstens 200 (für Shopping-Süchtige) aufnehmen. Positionsdaten für eine Bohrmaschine Hochpräzise Daten in Wissenschaft oder Produktion erfordern oft eine Genauigkeit bis zur achten Nachkommastelle.
Max. Linie
Für den Umgang mit diesen Datentypen (und noch anderen mehr) kennt MySQL mehrere verschiedene numerische Typen. Am gebräuchlichsten sind die ganzzahligen Typen.
MySQL-Datentypen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 21
Max. Linie
Links Bei diesen Typen können Sie auch vorgeben, dass die Daten nicht vorzeichenbehaftet (unsigned) sind. So weiß der Server, dass alle Daten in der betreffenden Spalte größer oder gleich null sind. Tabelle 2-3 zeigt die fünf verschiedenen Typen zur Speicherung ganzer Zahlen (Integer). Tabelle 2-3: Integer-Typen von MySQL Typ
Wertebereich mit Vorzeichen
Wertebereich ohne Vorzeichen
Tinyint
–128 bis 127
0 bis 255
Smallint
–32.768 bis 32.767
0 bis 65.535
Mediumint
–8.388.608 bis 8.388.607
0 bis 16.777.215
Int
–2.147.483.648 bis 2.147.483.647
0 bis 4.294.967.295
Bigint
–9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807
0 bis 18.446.744.073.709.551.615
Wenn Sie eine Spalte mit einem der Integer-Typen definieren, weist MySQL einen entsprechenden Speicherplatz zu. Dieser kann zwischen 1 Byte für einen tinyint und 8 Bytes für einen bigint betragen. Daher sollten Sie versuchen, einen Typ zu wählen, der einerseits groß genug ist, aber andererseits nicht unnötig Platz verschwendet. Für Fließkommazahlen (zum Beispiel 3,1415927) stehen die Datentypen aus Tabelle 2-4 zur Wahl. Tabelle 2-4: Fließkommatypen von MySQL Typ
Wertebereich
Float(p,s)
–3,402823466E+38 bis –1,175494351E-38 und 1,175494351E-38 bis 3,402823466E+38
Double(p,s)
–1,7976931348623157E+308 bis –2,2250738585072014E-308 und 2,2250738585072014E-308 bis 1,7976931348623157E+308
Wenn Sie einen Fließkommatyp verwenden, können Sie eine Genauigkeit (precision) vorgeben (die Anzahl der Stellen inkl. Nachkommastellen) und eine Größenordnung (scale; die Anzahl der Nachkommastellen). Diese Angaben sind jedoch nicht obligatorisch. In Tabelle 2-4 werden sie als p und s gezeigt. Wenn Sie eine Genauigkeit und Größenordnung für Ihre Fließkommaspalte vorgeben, müssen Sie daran denken, dass die darin gespeicherten Daten gerundet werden, sofern die ursprüngliche Zahl mehr Stellen hat, als die Spalte zulässt. Wenn Sie zum Beispiel eine Spalte als float(4,2) definieren, werden darin insgesamt vier Ziffern gespeichert, zwei links und zwei rechts vom Dezimalpunkt. Eine solche Spalte könnte die Zahlen 27,44 und 8,19 also exakt darstellen, während 17,8675 auf 17,87 gerundet würde und ein Versuch, die Zahl 178,5 in Ihrem float(4,2) zu speichern, zu einer Fehlermeldung führen würde.
Max. Linie
Wie Integer, so können auch Fließkommaspalten als unsigned definiert werden. Doch dadurch verhindern Sie lediglich, dass negative Zahlen in der Spalte gespeichert werden können, ohne am Wertebereich etwas zu ändern.
22 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Temporale Daten Nicht nur Strings und Zahlen, sondern auch Datums- und/oder Uhrzeitinformationen sind für die tägliche Arbeit von Belang. Die sogenannten temporalen Daten, die in einer Datenbank gespeichert werden, sind beispielsweise: • Ein in der Zukunft liegendes Datum, zu dem ein bestimmtes Ereignis stattfinden soll, beispielsweise der Versand einer Kundenbestellung. • Das in der Vergangenheit liegende Versanddatum einer Kundenbestellung. • Das Datum und die Uhrzeit, zu der eine bestimmte Zeile einer Tabelle geändert wurde. • Der Geburtstag eines Mitarbeiters. • Das Jahr, das einer Zeile einer yearly_sales-Faktentabelle in einem Data Warehouse entspricht. • Der Zeitraum, der benötigt wurde, um einen Kabelbaum auf einem Fließband für die Autoproduktion fertig zu stellen. MySQL kennt Datentypen für alle diese Situationen. Tabelle 2-5 zeigt die temporalen Datentypen von MySQL. Tabelle 2-5: Die temporalen Datentypen von MySQL Typ
Standardformat
Zulässige Werte
Date
YYYY-MM-DD
1000-01-01 bis 9999-12-31
Datetime
YYYY-MM-DD HH:MI:SS
1000-01-01 00:00:00 bis 9999-12-31 23:59:59
Timestamp
YYYY-MM-DD HH:MI:SS
1970-01-01 00:00:00 bis 2037-12-31 23:59:59
Year
YYYY
1901 bis 2155
Time
HHH:MI:SS
-838:59:59 bis 838:59:59
Während Temporaldaten von Datenbankservern unterschiedlich gespeichert werden, zeigen Format-Strings (zweite Spalte von Tabelle 2-5), wie die Daten formatiert sind, wenn sie abgerufen werden, und wie ein Datums-String zusammengesetzt werden muss, damit man ihn in eine Temporalspalte einfügen oder updaten kann. Wenn Sie das Datum 23. März 2005 im Standardformat YYYY-MM-DD in eine date-Spalte einfügen wollten, würden Sie den String '2005-03-23' verwenden. In Kapitel 7 wird genau erläutert, wie Temporaldaten konstruiert und angezeigt werden.
Max. Linie
Jeder Datenbankserver erlaubt andere Wertebereiche für Temporalspalten. Oracle Database akzeptiert Daten von 4712 v. Chr. bis 9999 n. Chr., während SQL Server nur mit Daten zwischen 1753 n. Chr. und 9999 n. Chr. umgehen kann (es sei denn, Sie nutzen den neuen datetime2-Datentyp von SQL Server 2008, der Daten im Bereich zwischen 1 n. Chr. und 9999 n. Chr. akzeptiert). MySQL liegt in der Mitte zwischen Oracle und SQL Server und kann Daten zwischen 1000 n. Chr. und 9999 n. Chr. spei-
MySQL-Datentypen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 23
Links chern. Das macht zwar für Systeme, die mit aktuellen und zukünftigen Ereignissen zu tun haben, keinen großen Unterschied, muss aber berücksichtigt werden, wenn historische Daten gespeichert werden sollen.
Die verschiedenen Bestandteile der Datumsformate aus Tabelle 2-5 werden in Tabelle 2-6 beschrieben. Tabelle 2-6: Bestandteile der Datumsformate Bestandteil
Definition
Wertebereich
YYYY
Jahr und Jahrhundert
1000 bis 9999
MM
Monat
01 (Januar) bis 12 (Dezember)
DD
Tag
01 bis 31
HH
Stunde
00 bis 23
HHH
Stunden (verstrichen)
–838 bis 838
MI
Minuten
00 bis 59
SS
Sekunden
00 bis 59
Um die oben gezeigten Beispiele zu implementieren, würde man die temporalen Typen wie folgt einsetzen: • Spalten, die ein voraussichtliches Versanddatum einer Kundenbestellung oder den Geburtstag eines Mitarbeiters speichern sollen, würden den Typ date haben, da die Uhrzeit der Geburt niemanden interessiert und es unrealistisch ist, einen Warenversand auf die Sekunde genau zu planen. • Eine Spalte für die Information, wann eine Kundenbestellung tatsächlich versandt wurde, würde den Typ datetime haben, da nicht nur das Datum, sondern auch die Uhrzeit des Versands genau nachvollzogen werden müssen. • Eine Spalte mit dem letzten Änderungsdatum einer Tabellenzeile würde den Typ timestamp haben. Dieser speichert die gleichen Informationen wie datetime (Jahr, Monat, Tag, Stunde, Minute, Sekunde), doch eine timestamp-Spalte wird automatisch vom MySQL-Server mit der aktuellen Zeit gefüllt, wenn eine Zeile in eine Tabelle eingefügt oder später modifiziert wird. • Eine Spalte, die lediglich das Jahr speichern muss, hätte den Typ year.
Max. Linie
• Spalten, die speichern, wie lange die Erfüllung einer Aufgabe dauert, verwenden den Typ time. Bei Daten dieser Art wäre es überflüssig und sogar verwirrend, eine Datumskomponente zu speichern, da Sie sich lediglich für die Anzahl der Stunden/ Minuten/Sekunden interessieren, die für die Aufgabe gebraucht wurde. Diese Informationen könnten zwar auch aus zwei datetime-Spalten erschlossen werden (eine speichert den genauen Beginn und eine andere den Abschluss der Arbeiten; hinterher wird die eine von der anderen subtrahiert), doch es ist einfacher, eine einzige time-Spalte zu verwenden. Kapitel 7 wird zeigen, wie man mit diesen temporalen Datentypen umgeht. 24 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabellen anlegen Da Sie nun wissen, welche Datentypen man in einer MySQL-Datenbank speichern kann, ist es an der Zeit zu schauen, wie man diese in Tabellen-Definitionen einbringt. Als Erstes definieren wir eine Tabelle, die Informationen über eine Person speichert.
Schritt 1: Entwurf Ein guter Ausgangspunkt für einen Tabellenentwurf ist es, sich Gedanken darüber zu machen, welche Art von Informationen man speichern möchte. Ich habe überlegt, welche Informationen eine Person beschreiben, und kam zu folgendem Ergebnis: • Name • Geschlecht • Geburtsdatum • Adresse • Lieblingsspeisen Diese Liste ist vielleicht nicht ganz vollständig, soll aber einstweilen genügen. Im nächsten Schritt werden Spaltennamen und Datentypen zugewiesen. Tabelle 2-7 zeigt meinen ersten Versuch. Tabelle 2-7: person-Tabelle, erster Versuch Spalte
Typ
Name
Varchar(40)
Gender
Char(1)
Birth_date
Date
Address
Varchar(100)
Favorite_foods
Varchar(200)
Zulässige Werte M, F
Die Spalten name, address und favorite_foods haben den Typ varchar und gestatten freie Dateneinträge. Die gender-Spalte erlaubt ein einzelnes Zeichen, entweder M oder F. Die Spalte birth_date hat den Typ date, da kein Uhrzeitbestandteil erforderlich ist.
Schritt 2: Verfeinerung In Kapitel 1 wurden Sie kurz in das Konzept der Normalisierung eingeführt: Dabei handelt es sich um einen Prozess, der gewährleistet, dass (abgesehen von Fremdschlüsseln) keine doppelten oder zusammengesetzten Spalten im Datenbankentwurf existieren. Bei einer zweiten Betrachtung der Spalten in der person-Tabelle fallen folgende Probleme auf:
Max. Linie
• Die name-Spalte ist eigentlich ein zusammengesetztes Objekt, das aus einem Vorund einem Nachnamen besteht.
Tabellen anlegen | 25 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links • Da mehrere Menschen den gleichen Namen, das gleiche Geschlecht, das gleiche Geburtsdatum usw. haben können, enthält die person-Tabelle keine Spalten, die Eindeutigkeit garantieren. • Die address-Spalte ist ebenfalls eine aus Straße, Ort, Staat, Land und Postleitzahl zusammengesetzte Spalte. • Die Spalte favorite_foods kann 0, 1 oder mehr unabhängige Elemente enthalten. Für solche Daten sollte man besser eine separate Tabelle anlegen, die mit einem Fremdschlüssel auf die person-Tabelle verweist, damit Sie wissen, welcher Person eine bestimmte Speise zugeordnet werden kann. Nach diesen Überlegungen zeigt Tabelle 2-8 eine normalisierte Version der personTabelle an. Tabelle 2-8: person-Tabelle, zweiter Versuch Spalte
Typ
Person_id
Smallint (unsigned)
First_name
Varchar(20)
Last_name
Varchar(20)
Gender
Char(1)
Birth_date
Date
Street
Varchar(30)
City
Varchar(20)
State
Varchar(20)
Country
Varchar(20)
Postal_code
Varchar(20)
Zulässige Werte
M, F
Wann ist es genug? Es war definitiv eine gute Idee, die Spalte favorite_foods aus der Tabelle person auszulagern, aber sind wir damit auch am Ende? Was ist beispielsweise, wenn einer »Pasta« als Lieblingsessen anführt und ein anderer »Spaghetti«? Ist das das Gleiche? Um derartige Probleme zu vermeiden, könnten Sie sich dafür entscheiden, die Leute ihre jeweilige Lieblingsspeise aus einer Liste mit verschiedenen Wahlmöglichkeiten auswählen zu lassen. Dazu würden Sie dann wahrscheinlich eine food-Tabelle mit food_id- und food_name-Spalten erstellen und die Tabelle favorite_food so ändern, dass sie einen Fremdschlüssel auf food enthält. Obgleich erst dieser Entwurf vollständig normalisiert wäre, könnten Sie ebenfalls dabei bleiben, einfach die Werte zu speichern, die der Benutzer eingegeben hat, und die Tabelle so zu belassen, wie sie jetzt ist.
Max. Linie
Max. Linie 26 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Rechts Da nun die person-Tabelle einen Primärschlüssel hat (person_id), der Eindeutigkeit garantiert, können wir in einem zweiten Schritt die favorite_food-Tabelle mit einem Fremdschlüssel zur person-Tabelle anlegen. Tabelle 2-9 zeigt das Ergebnis. Tabelle 2-9: favorite_food-Tabelle Spalte
Typ
Person_id
Smallint (unsigned)
Food
Varchar(20)
In den Spalten person_id und food ist der Primärschlüssel zur favorite_food-Tabelle enthalten, und die Spalte person_id ist ein Fremdschlüssel zur person-Tabelle.
Schritt 3: Die SQL Schema-Anweisungen Da der Entwurf der beiden Tabellen mit den Daten zu Personen und ihren Leibspeisen jetzt vollständig ist, müssen wir als Nächstes SQL-Anweisungen formulieren, um die Tabellen in der Datenbank anzulegen. Mit folgender Anweisung wird die person-Tabelle erzeugt: CREATE TABLE person (person_id SMALLINT UNSIGNED, fname VARCHAR(20), lname VARCHAR(20), gender CHAR(1), birth_date DATE, street VARCHAR(30), city VARCHAR(20), state VARCHAR(20), country VARCHAR(20), postal_code VARCHAR(20), CONSTRAINT pk_person PRIMARY KEY (person_id) );
Alle Bestandteile dieser Anweisung verstehen sich von selbst, mit Ausnahme des letzten: Wenn Sie eine Tabelle definieren, müssen Sie dem Datenbankserver mitteilen, welche Spalte bzw. Spalten als Primärschlüssel für die Tabelle dienen. Dazu legen Sie einen Constraint auf der Tabelle an. Es gibt mehrere Arten von Constraints, die man einer TabellenDefinition hinzufügen kann. Hier handelt es sich um einen Primärschlüssel-Constraint. Dieser wird auf die person_id-Spalte gelegt und pk_person genannt.
Max. Linie
Da wir gerade von Constraints sprechen: Es gibt noch eine andere Art von Constraint, die ebenfalls für die person-Tabelle nützlich wäre. In Tabelle 2-8 fügte ich eine dritte Spalte hinzu, um die zulässigen Werte für bestimmte Spalten anzugeben (zum Beispiel 'M' und 'F' für die gender-Spalte). Es gibt auch einen sogenannten Check Constraint, der die zulässigen Werte für eine bestimmte Spalte einschränkt. In MySQL kann man einer Spalten-Definition in folgender Weise einen Check Constraint hinzufügen: gender CHAR(1) CHECK (gender IN ('M','F')),
Tabellen anlegen | 27 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Check Constraints verhalten sich auf den meisten Datenbankservern, wie man es normalerweise erwartet. Nur der MySQL-Server erlaubt es zwar, Check Constraints zu definieren, erzwingt sie aber nicht. Allerdings stellt MySQL noch einen anderen ZeichenDatentyp namens enum zur Verfügung, der den Check Constraint mit der Datentyp-Definition verschmilzt. Für die gender-Spalte sähe das so aus: gender ENUM('M','F'),
Die Definition der person-Tabelle würde mit enum-Datentypen für die gender-Spalte folgendermaßen modifiziert: CREATE TABLE person (person_id SMALLINT UNSIGNED, fname VARCHAR(20), lname VARCHAR(20), gender ENUM('M','F'), birth_date DATE, street VARCHAR(30), city VARCHAR(20), state VARCHAR(20), country VARCHAR(20), postal_code VARCHAR(20), CONSTRAINT pk_person PRIMARY KEY (person_id) );
Weiter unten in diesem Kapitel werden Sie sehen, was geschieht, wenn Sie versuchen, in eine Spalte Daten zu laden, die den Check Constraint (oder, bei MySQL, die Enumeration-Werte) verletzen. Nun können Sie die create table-Anweisung mit dem mysql-Kommandozeilentool ausführen: mysql> CREATE TABLE person -> (person_id SMALLINT UNSIGNED, -> fname VARCHAR(20), -> lname VARCHAR(20), -> gender ENUM('M','F'), -> birth_date DATE, -> street VARCHAR(30), -> city VARCHAR(20), -> state VARCHAR(20), -> country VARCHAR(20), -> postal_code VARCHAR(20), -> CONSTRAINT pk_person PRIMARY KEY (person_id) -> ); Query OK, 0 rows affected (0.27 sec)
Max. Linie
Nach der Verarbeitung der create table-Anweisung gibt der MySQL-Server die Meldung »Query OK, 0 rows affected« zurück. Das bedeutet, dass die Anweisung keine Syntaxfehler hatte. Wenn Sie sicherstellen möchten, dass die person-Tabelle tatsächlich existiert, können Sie mit dem describe-Befehl (oder kurz desc) einen Blick auf die Tabellen-Definition werfen:
28 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> DESC person; +-------------+----------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+----------------------+------+-----+---------+-------+ | person_id | smallint(5) unsigned | | PRI | 0 | | | fname | varchar(20) | YES | | NULL | | | lname | varchar(20) | YES | | NULL | | | gender | enum('M','F') | YES | | NULL | | | birth_date | date | YES | | NULL | | | street | varchar(30) | YES | | NULL | | | city | varchar(20) | YES | | NULL | | | state | varchar(20) | YES | | NULL | | | country | varchar(20) | YES | | NULL | | | postal_code | varchar(20) | YES | | NULL | | +-------------+----------------------+------+-----+---------+-------+ 10 rows in set (0.06 sec)
Die Spalten 1 und 2 der describe-Ausgabe sind selbsterklärend. Spalte 3 zeigt, ob eine bestimmte Spalte weggelassen werden kann oder nicht, wenn Daten in die Tabelle eingefügt werden. Ich bin auf dieses Thema mit Absicht noch nicht eingegangen (siehe Kasten »Was ist null?«), werde es aber in Kapitel 4 genauer behandeln. Die vierte Spalte zeigt, ob eine Spalte an irgendwelchen (Primär- oder Fremd-)Schlüsseln beteiligt ist. In diesem Fall ist die person_id-Spalte als Primärschlüssel markiert. Spalte 5 zeigt, ob eine Spalte den Standardwert erhält, wenn die eingefügten Daten keinen Wert für sie enthalten. Die person_id-Spalte hat den Standardwert 0, doch dieser würde nur ein einziges Mal verwendet, da jede Zeile der person-Tabelle in dieser Spalte einen eindeutigen Wert aufweisen muss (sie ist ja der Primärschlüssel). Die sechste Spalte (Extra) zeigt andere Informationen, die für eine Spalte gelten können.
Was ist null? Manchmal ist es nicht möglich, einen Wert für eine Spalte in einer Tabelle zu liefern. Wenn Sie beispielsweise Daten über eine neue Kundenbestellung hinzufügen, können Sie die ship_ date-Spalte noch nicht festlegen. In diesem Fall ist die Spalte null (aber nicht gleich null) als Zeichen dafür, dass kein Wert vorhanden ist. Null wird in verschiedenen Fällen eingesetzt, wenn kein Wert angegeben werden kann, zum Beispiel: • nicht anwendbar • unbekannt • leere Menge Beim Tabellenentwurf können Sie angeben, welche Spalten null sein dürfen (der Default) und welche nicht (dies wird durch das Schlüsselwort not null hinter der Typ-Definition festgelegt).
Max. Linie
Max. Linie Tabellen anlegen | 29 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Links Da Sie die person-Tabelle nun angelegt haben, erstellen Sie als Nächstes die favorite_ food-Tabelle: mysql> CREATE TABLE favorite_food -> (person_id SMALLINT UNSIGNED, -> food VARCHAR(20), -> CONSTRAINT pk_favorite_food PRIMARY KEY (person_id, food), -> CONSTRAINT fk_person_id FOREIGN KEY (person_id) -> REFERENCES person (person_id) -> ); Query OK, 0 rows affected (0.10 sec)
Das sieht fast wie die create table-Anweisung für die person-Tabelle aus – mit folgenden Ausnahmen: • Da eine Person mehrere Lieblingsspeisen haben kann (deswegen wurde diese Tabelle ja schließlich angelegt), ist mehr als nur die person_id-Spalte erforderlich, um Eindeutigkeit zu gewährleisten. Daher hat diese Tabelle einen zweispaltigen Primärschlüssel, bestehend aus person_id und food. • Die favorite_food-Tabelle enthält eine andere Art von Constraint, den sogenannten Fremdschlüssel-Constraint. Er schränkt die Werte der person_id-Spalte in der favorite_food-Tabelle so ein, dass sie nur Werte enthalten darf, die in der personTabelle vorkommen. Durch diesen Constraint ist es nicht mehr möglich, eine Zeile in die favorite_food-Tabelle einzufügen, die besagt, dass person_id 27 Pizza mag, wenn nicht in der person-Tabelle bereits eine Zeile mit der person_id Nummer 27 vorliegt. Wenn Sie beim Anlegen der Tabelle den Fremdschlüssel-Constraint vergessen, können Sie ihn später mit einer alter table-Anweisung nachtragen.
Describe zeigt nach Ausführung der create table-Anweisung folgendes Bild: mysql> DESC favorite_food; +--------------+----------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+----------------------+------+-----+---------+-------+ | person_id | smallint(5) unsigned | | PRI | 0 | | | food | varchar(20) | | PRI | | | +--------------+----------------------+------+-----+---------+-------+
Da nun die Tabellen fertig sind, besteht der nächste logische Schritt darin, sie mit Daten zu füllen.
Tabellen füllen und ändern Max. Linie
Nachdem jetzt die Tabellen person und favorite_food angelegt sind, können wir als Nächstes die SQL-Datenanweisungen ausprobieren: insert, update, delete und select.
30 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Daten einfügen Da die Tabellen person und favorite_food noch keine Daten enthalten, wird als erste der vier SQL-Datenanweisungen die insert-Anweisung eingesetzt. Eine insert-Anweisung hat drei Bestandteile: • den Namen der Tabelle, in die Daten eingefügt werden, • die Namen der Spalten, in die Daten eingefügt werden, sowie • die Werte, die in die Spalten eingefügt werden. Sie müssen nicht für jede Spalte in der Tabelle Daten liefern (sofern nicht alle Spalten der Tabelle als not null definiert wurden). In manchen Fällen erhalten Spalten, die nicht in der ersten insert-Anweisung vorkommen, später durch update-Anweisungen einen Wert. In anderen Fällen bekommt eine Spalte für eine bestimmte Datenzeile vielleicht niemals einen Wert (zum Beispiel eine Kundenbestellung, die vor dem Versand widerrufen wurde, sodass die ship_date-Spalte nicht mehr anwendbar ist).
Numerische Schlüsseldaten generieren Bevor Sie Daten in die person-Tabelle einfügen, wäre es nützlich zu schauen, wie die Werte für numerische Primärschlüssel generiert werden. Anstatt einfach Zahlen aus der Luft zu greifen, haben Sie zwei Möglichkeiten: • Sie nehmen den größten Wert, der zurzeit in der Tabelle vorliegt, und addieren 1. • Sie lassen den Datenbankserver den Wert liefern. Die erste Möglichkeit scheint zwar zunächst praktikabel, wird aber in einem Mehrbenutzerumfeld problematisch, da vielleicht zwei Benutzer zur selben Zeit dieselbe Tabelle betrachten und denselben Wert für den Primärschlüssel anlegen. Daher bieten alle modernen Datenbankserver eine robustere Methode, um numerische Schlüsselwerte zu generieren. Bei einigen Servern wie beispielsweise Oracle Database wird ein separates Schema-Objekt (eine sogenannte Sequence) verwendet; bei MySQL wird dagegen für die Primärschlüsselspalte einfach die Auto-Inkrement-Funktion eingeschaltet. Das geschieht normalerweise bereits bei der Erstellung einer Tabelle, doch indem wir es erst jetzt tun, haben wir zugleich Gelegenheit, eine andere SQL-Schemaanweisung, alter table, kennenzulernen, die eingesetzt wird, um die Definition einer vorhandenen Tabelle zu ändern: ALTER TABLE person MODIFY person_id SMALLINT UNSIGNED AUTO_INCREMENT;
Diese Anweisung definiert die person_id-Spalte der person-Tabelle neu. Wenn Sie die Tabelle beschreiben, sehen Sie, dass in der Extra-Spalte für person_id jetzt das AutoInkrement-Feature erscheint:
Max. Linie
mysql> DESC person; +-------------+----------------------------+------+-----+---------+-----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+----------------------------+------+-----+---------+-----------------+
Tabellen füllen und ändern | 31 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links | | | |
person_id . . .
| smallint(5) unsigned | | |
| | | |
| PRI | NULL | | | | | |
| auto_increment | | | | | | |
Wenn Sie Daten in die person-Tabelle einfügen, geben Sie für person_id einfach den nullWert an, und MySQL fügt die nächste laufende Nummer in die Spalte ein (standardmäßg beginnt die Nummerierung von Auto-Inkrement-Spalten bei MySQL mit 1).
Die insert-Anweisung Da nun alles an Ort und Stelle ist, können wir als Nächstes Daten hinzufügen. Die folgende Anweisung legt in der person-Tabelle eine Zeile für William Turner an: mysql> INSERT INTO person -> (person_id, fname, lname, gender, birth_date) -> VALUES (null, 'William','Turner', 'M', '1972-05-27'); Query OK, 1 row affected (0.01 sec)
Das Feedback (»Query OK, 1 row affected«) sagt Ihnen, dass Ihre Anweisungssyntax richtig war und dass eine Zeile in die Datenbank eingefügt wurde (da die Anweisung ein insert war). Die neuen Daten der Tabelle können Sie durch eine select-Anweisung abfragen: mysql> SELECT person_id, fname, lname, birth_date -> FROM person; +-----------+---------+--------+------------+ | person_id | fname | lname | birth_date | +-----------+---------+--------+------------+ | 1 | William | Turner | 1972-05-27 | +-----------+---------+--------+------------+ 1 row in set (0.06 sec)
Wie Sie sehen, hat der MySQL-Server den Wert 1 als Primärschlüssel generiert. Da in der person-Tabelle nur eine einzige Zeile gespeichert ist, habe ich nicht weiter präzisiert, für welche Zeile ich mich interessiere, sondern stattdessen einfach alle Zeilen der Tabelle abgefragt. Hätte die Tabelle allerdings mehrere Zeilen, könnte ich in einer where-Klausel angeben, dass ich nur die Zeile mit dem Wert 1 für die Spalte person_id abfragen möchte: mysql> SELECT person_id, fname, lname, birth_date -> FROM person -> WHERE person_id = 1; +-----------+---------+--------+------------+ | person_id | fname | lname | birth_date | +-----------+---------+--------+------------+ | 1 | William | Turner | 1972-05-27 | +-----------+---------+--------+------------+ 1 row in set (0.00 sec)
Max. Linie
Diese Abfrage gibt zwar einen bestimmten Primärschlüsselwert an, aber Sie können auch jede andere Spalte der Tabelle verwenden, um Zeilen zu suchen. Die folgende Abfrage ruft alle Zeilen mit dem Wert 'Turner' in der Spalte lname ab:
32 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT person_id, fname, lname, birth_date -> FROM person -> WHERE lname = 'Turner'; +-----------+---------+--------+------------+ | person_id | fname | lname | birth_date | +-----------+---------+--------+------------+ | 1 | William | Turner | 1972-05-27 | +-----------+---------+--------+------------+ 1 row in set (0.00 sec)
Bevor wir nun weitermachen, sollten einige Einzelheiten der obigen insert-Anweisung nicht unerwähnt bleiben: • Für die Adressspalten wurden keine Werte angegeben. Da null-Werte für diese Spalten zulässig sind, ist das in Ordnung. • Der Wert, der für die birth_date-Spalte geliefert wurde, war ein String. Wenn Sie sich an das in Tabelle 2-5 vorgegebene Format halten, konvertiert MySQL den String in ein Datum. • Die Spaltennamen und die Werte müssen in Anzahl und Typ übereinstimmen. Wenn Sie sieben Spalten angeben und nur sechs Werte übergeben oder wenn Ihre Werte sich nicht in den passenden Datentyp für die betreffende Spalte konvertieren lassen, bekommen Sie eine Fehlermeldung. Da William auch seine Lieblingsspeisen verraten hat, sehen Sie hier drei insert-Anweisungen, um diese zu speichern: mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (1, 'pizza'); Query OK, 1 row affected (0.01 sec) mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (1, 'cookies'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (1, 'nachos'); Query OK, 1 row affected (0.01 sec)
Folgende Abfrage ruft Williams Lieblingsspeisen ab, und zwar durch eine order by-Klausel alphabetisch geordnet:
Max. Linie
mysql> SELECT food -> FROM favorite_food -> WHERE person_id = 1 -> ORDER BY food; +---------+ | food | +---------+ | cookies | | nachos | | pizza | +---------+ 3 rows in set (0.02 sec)
Tabellen füllen und ändern | 33 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Die order by-Klausel sagt dem Server, wie er die Rückgabedaten der Abfrage sortieren soll. Ohne die order by-Klausel ist nicht gewährleistet, dass die Daten aus der Tabelle in einer bestimmten Reihenfolge abgefragt werden. Damit sich William nicht so allein fühlt, können Sie mit einer zweiten insert-Anweisung Susan Smith in die person-Tabelle einfügen: mysql> INSERT INTO person -> (person_id, fname, lname, gender, birth_date, -> street, city, state, country, postal_code) -> VALUES (null, 'Susan','Smith', 'F', '1975-11-02', -> '23 Maple St.', 'Arlington', 'VA', 'USA', '20220'); Query OK, 1 row affected (0.01 sec)
Wenn Sie die Tabelle erneut abfragen, können Sie sehen, dass Susans Zeile den Primärschlüsselwert 2 bekommen hat: mysql> SELECT person_id, fname, lname, birth_date -> FROM person; +-----------+---------+--------+------------+ | person_id | fname | lname | birth_date | +-----------+---------+--------+------------+ | 1 | William | Turner | 1972-05-27 | | 2 | Susan | Smith | 1975-11-02 | +-----------+---------+--------+------------+ 2 rows in set (0.00 sec)
Daten ändern Als die Daten für William Turner in die Tabelle eingefügt wurden, hatten wir die diversen Adressspalten aus der insert-Anweisung weggelassen. Die nächste Anweisung zeigt, wie diese Spalten mit einer update-Anweisung gefüllt werden: mysql> UPDATE person -> SET street = '1225 Tremont St.', -> city = 'Boston', -> state = 'MA', -> country = 'USA', -> postal_code = '02138' -> WHERE person_id = 1; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0
Der Server hat mit einer zweizeiligen Meldung geantwortet: »Rows matched: 1« sagt Ihnen, dass die Bedingungen der where-Klausel auf eine einzige Zeile der Tabelle gepasst haben, und »Changed: 1« bedeutet, dass eine einzige Zeile der Tabelle geändert wurde. Da die where-Klausel den Primärschlüssel für Williams Zeile enthält, können Sie erkennen, dass alles verlaufen ist wie erwartet.
Max. Linie
Je nach Bedingung in Ihrer where-Klausel können mit einer einzigen Anweisung auch mehrere Zeilen geändert werden. Schauen Sie zum Beispiel, was passiert, wenn Ihre where-Klausel folgendermaßen aussieht: WHERE person_id < 10
34 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Gibt es das auch in XML? Wenn Sie mit XML-Daten arbeiten, wird es Sie erfreuen, dass die meisten Datenbankserver ein einfaches Mittel bereitstellen, auf eine Abfrage eine XML-Ausgabe zu generieren. Bei MySQL müssen Sie einfach nur die Option --xml angeben, wenn Sie das mysql-Tool aufrufen, damit alle Ihre Ausgaben automatisch mit XML formatiert werden. So sehen die Leibspeisendaten aus, wenn sie als XML-Dokument formatiert sind: C:\database> mysql -u lrngsql -p --xml bank Enter password: xxxxxx Welcome to the MySQL Monitor... Mysql> SELECT * FROM favorite_food; 1 cookies 1 nachos 1 pizza 3 rows in set (0.00 sec)
Bei SQL Server müssen Sie Ihr Kommandozeilenwerkzeug nicht konfigurieren. Sie müssen einfach nur folgendermaßen die for xml-Klausel ans Ende Ihrer Abfrage anhängen: SELECT * FROM favorite_food FOR XML AUTO, ELEMENTS
Da William und Susan beide einen person_id-Wert kleiner 10 haben, würden beide Zeilen modifiziert. Wenn Sie die where-Klausel ganz weglassen, betrifft Ihre update-Anweisung jede Zeile der Tabelle.
Daten löschen William und Susan können sich nicht leiden, also muss einer von ihnen weg. Da William als Erster da war, wird Susan mit der delete-Anweisung herausgekegelt:
Max. Linie
mysql> DELETE FROM person -> WHERE person_id = 2; Query OK, 1 row affected (0.01 sec)
Tabellen füllen und ändern | 35 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Wieder wird durch den Primärschlüssel eine Zeile festgelegt, sodass auch nur eine einzige Zeile aus der Tabelle gelöscht wird. Analog zur update-Anweisung kann man auch mit delete mehrere Zeilen auf einmal löschen, wenn die Bedingungen der where-Klausel entsprechend formuliert sind, oder alle Zeilen, wenn man die where-Klausel ganz weglässt.
Wenn aus guten Anweisungen schlechte werden Bis jetzt waren alle SQL-Datenanweisungen in diesem Kapitel wohlgeformt und haben die Spielregeln beachtet. Doch bei den Tabellen-Definitionen für person und favorite_ food gibt es eine Menge Möglichkeiten, sich beim Einfügen oder Ändern von Daten zu verhauen. Dieser Abschnitt handelt von häufigen Fehlern und zeigt, wie der MySQL-Server auf diese reagiert.
Nicht eindeutiger Primärschlüssel Da die Tabellen-Definitionen auch die Erstellung von Primärschlüssel-Constraints vorsehen, sorgt MySQL dafür, dass keine Doppelwerte in die Tabellen eingefügt werden. Die nächste Anweisung versucht, das Auto-Inkrement-Feature der person_id-Spalte zu umgehen und in der person-Tabelle eine zweite Zeile mit der person_id 1 anzulegen: mysql> INSERT INTO person -> (person_id, fname, lname, gender, birth_date) -> VALUES (1, 'Charles','Fulton', 'M', '1968-01-15'); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
Nichts hindert Sie daran (zumindest mit den aktuellen Schema-Objekten), zwei Zeilen mit gleichen Daten für Name, Adresse, Geburtsdatum usw. anzulegen, solange diese nur in der person_id-Spalte unterschiedliche Werte haben.
Nicht existenter Fremdschlüssel In der Tabellen-Definition für die favorite_food-Tabelle wird auch ein FremdschlüsselConstraint auf der person_id-Spalte angelegt. Dieser Constraint gewährleistet, dass alle Werte für person_id, die in die favorite_food-Tabelle eingefügt werden, in der personTabelle bereits existieren. Wenn Sie versuchen, eine Zeile anzulegen, die diesen Constraint verletzt, geschieht Folgendes: mysql> INSERT INTO favorite_food (person_id, food) -> VALUES (999, 'lasagna'); ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails ('bank'.'favorite_food', CONSTRAINT 'fk_fav_food_person_id' FOREIGN KEY ('person_id') REFERENCES 'person' ('person_id'))
Max. Linie
In diesem Fall wird die favorite_food-Tabelle als Child und die person-Tabelle als Parent betrachtet, da die favorite_food-Tabelle von einigen Daten in der person-Tabelle abhängt. Wenn Sie Daten in beide Tabellen einfügen möchten, müssen Sie zuerst eine Zeile in parent einfügen, ehe Sie die Daten in favorite_food eingeben können.
36 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Fremdschlüssel-Constraints werden nur durchgesetzt, wenn Tabellen mit dem InnoDB-Speichermodul angelegt werden. Die Speichermodule von MySQL werden in Kapitel 12 erklärt.
Verstöße gegen Spaltenwerte Die gender-Spalte in der person-Tabelle ist auf die Werte 'M' für männlich und 'F' für weiblich beschränkt. Wenn Sie versehentlich versuchen, den Wert dieser Spalte auf etwas anderes zu setzen, bekommen Sie folgende Reaktion: mysql> UPDATE person -> SET gender = 'Z' -> WHERE person_id = 1; ERROR 1265 (01000): Data truncated for column 'gender' at row 1
Diese Fehlermeldung ist etwas verwirrend, sagt Ihnen aber ganz allgemein, dass der Server mit dem für die Spalte gender angegebenen Wert nicht so ganz glücklich ist.
Ungültige Datumskonvertierung Wenn Sie einen String anlegen, um eine date-Spalte zu füllen, und dieser String nicht dem erwarteten Format entspricht, bekommen Sie einen anderen weichen Fehler. Das folgende Beispiel zeigt ein Datumsformat, das nicht dem Standard-Datumsformat von »YYYY-MM-DD« entspricht: mysql> UPDATE person -> SET birth_date = 'DEC-21-1980' -> WHERE person_id = 1; ERROR 1292 (22007): Incorrect date value: 'DEC-21-1980' for column 'birth_date' at row 1
In der Regel ist es ratsam, den Format-String explizit anzugeben und sich nicht auf das Standardformat zu verlassen. Hier ist eine andere Version der Anweisung, die über die Funktion str_to_date angibt, welcher Format-String verwendet werden soll: mysql> UPDATE person -> SET birth_date = str_to_date('DEC-21-1980' , '%b-%d-%Y') -> WHERE person_id = 1; Query OK, 1 row affected (0.12 sec) Rows matched: 1 Changed: 1 Warnings: 0
Jetzt ist nicht nur die Datenbank zufrieden, sondern auch William (wir haben ihn gerade acht Jahre jünger gemacht, ohne dass er sich dazu einer kostspieligen kosmetischen Operation unterziehen musste!).
Max. Linie
Weiter oben in diesem Kapitel, bei der Eröterung der verschiedenen Datentypen für Zeit- und Datumsangaben, habe ich Strings für die Datenformatierung wie »YYYY-MM-DD« angegeben. Diese Art der Formatierung wird zwar von vielen Datenbankservern eingesetzt, aber MySQL
Wenn aus guten Anweisungen schlechte werden This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 37
Links nutzt beispielsweise %Y, um eine vierstellige Jahresangabe anzuzeigen. Hier sind noch ein paar weitere Formatangaben, die Sie eventuell benötigen, wenn Sie unter MySQL Strings in datetimes umwandeln: %a %b %c %d %f %H %h %i %j %M %m %p %s %W %w %Y
Wochentag in Abkürzung wie Sun, Mon, ... Monatsname in Abkürzung wie Jan, Feb, ... Monat, numerisch (1..12) Monatstag, numerisch (00..31) Anzahl Mikrosekunden (000000..999999) Tagesstunde, 24-Stunden-Format (00..23) Tagesstunde, 12-Stunden-Format (01..12) Minuten (00..59) Tag im Jahr (001..366) Monatsname, vollständig (January..December) Monat, numerisch (01..12) AM oder PM Sekunden (00..59) Wochentag, vollständig (Sunday..Saturday) Wochentag, numerisch (0=Sunday..6=Saturday) Jahr, vierstellig
Das Bank-Schema Im Rest dieses Buchs werden wir eine Gruppe von Tabellen verwenden, die eine Bank modellieren, darunter die Tabellen Employee, Branch, Account, Customer, Product und Transaction. Das komplette Schema und die Beispieldaten müssten Sie eigentlich bereits in den abschließenden Schritten am Anfang des vorigen Kapitels angelegt haben, in dem der MySQL-Server geladen und die Musterdaten generiert wurden. Ein Diagramm der Tabellen, Spalten und Beziehungen finden Sie in Anhang A. Tabelle 2-10 zeigt alle Tabellen des Bank-Schemas zusammen mit kurzen Definitionen. Tabelle 2-10: Bank-Schema-Definitionen
Max. Linie
Tabellenname
Definition
Account
Ein bestimmtes Produkt (Konto), das für einen bestimmten Kunden eröffnet wird.
Branch
Ein Ort, an dem die Bankgeschäfte abgewickelt werden.
Business
Ein Firmenkunde (Untertyp der Customer-Tabelle).
Customer
Eine Person oder Firma, die der Bank bekannt ist.
Department
Eine Gruppe von Bankmitarbeitern, die eine bestimmte Banking-Funktion implementiert.
Employee
Eine Person, die für die Bank arbeitet.
Individual
Ein Nicht-Firmenkunde (Untertyp der Customer-Tabelle).
Officer
Eine Person, die Geschäfte für einen Firmenkunden tätigen darf.
Product
Eine Banking-Funktion, die den Kunden angeboten wird.
Product_type
Eine Gruppe von Produkten mit ähnlichen Funktionen.
Transaction
Eine Änderung an einem Kontensaldo.
38 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Mit diesen Tabellen können Sie jetzt nach Belieben herumexperimentieren und auch eigene Tabellen hinzufügen, um die Geschäftsfunktion der Bank auszuweiten. Sie können die Datenbank ja jederzeit löschen und sie aus der heruntergeladenen Datei neu erstellen, wenn Sie sichergehen möchten, dass die Beispieldaten intakt sind. Möchten Sie sehen, welche Tabellen in Ihrer Datenbank verfügbar sind, können Sie den Befehl show TABLES verwenden: mysql> SHOW TABLES; +----------------+ | Tables_in_bank | +----------------+ | account | | branch | | business | | customer | | department | | employee | | favorite_food | | individual | | officer | | person | | product | | product_type | | transaction | +----------------+ 13 rows in set (0.10 sec)
Zusammen mit den elf Tabellen des Bank-Schemas enthält diese Liste auch die anderen beiden Tabellen, die in diesem Kapitel angelegt wurden: person und favorite_food. Da diese Tabellen in den weiteren Kapiteln nicht mehr gebraucht werden, können Sie sie ruhig mit folgenden Befehlen löschen: mysql> DROP Query OK, 0 mysql> DROP Query OK, 0
TABLE favorite_food; rows affected (0.56 sec) TABLE person; rows affected (0.05 sec)
Um einen Blick auf die Spalten einer Tabelle zu werfen, nutzen Sie den describe-Befehl. Das folgende Beispiel zeigt die describe-Ausgabe für die customer-Tabelle:
Max. Linie
mysql> DESC customer; +--------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+----------------+ | cust_id | int(10) unsigned | | PRI | NULL | auto_increment | | fed_id | varchar(12) | | | | | | cust_type_cd | enum('I','B') | | | I | | | address | varchar(30) | YES | | NULL | | | city | varchar(20) | YES | | NULL | | | state | varchar(20) | YES | | NULL | | | postal_code | varchar(10) | YES | | NULL | | +--------------+------------------+------+-----+---------+----------------+ 7 rows in set (0.03 sec)
Das Bank-Schema This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 39
Links Je mehr Sie sich mit diesen Daten vertraut machen, umso besser können Sie die Beispiele und somit auch die Erklärungen der folgenden Kapitel verstehen.
Max. Linie
Max. Linie 40 | Kapitel 2: Datenbanken erstellen und mit Daten füllen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 3
KAPITEL 3
Datenbankabfragen
In den ersten beiden Kapiteln dieses Buchs haben Sie hier und dort bereits ein paar Beispiele für Datenbankabfragen (select-Anweisungen) gesehen. Nun ist es an der Zeit, sich die verschiedenen Teile der select-Anweisung und ihre Wechselbeziehungen einmal genauer anzuschauen.
Die Mechanik von Abfragen Bevor wir die select-Anweisung in ihre Einzelteile zerlegen, ist es vielleicht interessant zu sehen, wie Abfragen vom MySQL-Server (oder jedem x-beliebigen Datenbankserver) überhaupt ausgeführt werden. Wenn Sie das mysql-Kommandozeilentool verwenden (und davon gehe ich aus), haben Sie sich bereits mit Ihrem Benutzernamen und Passwort in den MySQL-Server eingeloggt (und möglicherweise auch mit einem Hostnamen, wenn der MySQL-Server auf einem anderen Computer läuft). Sobald der Server die Richtigkeit Ihrer Anmeldedaten geprüft hat, wird eine Datenbankverbindung für Sie eingerichtet. Diese Verbindung wird von der Anwendung gehalten, die sie angefordert hatte (in diesem Fall das mysql-Tool), bis entweder die Anwendung die Verbindung freigibt (wenn Sie quit eingeben) oder der Server selbst die Verbindung trennt (wenn er heruntergefahren wird). Jeder Verbindung mit dem MySQL-Server wird ein Identifier zugewiesen, den Sie beim Einloggen auch sehen: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 11 Server version: 6.0.3-alpha-community MySQL Community Server (GPL) Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
Max. Linie
Hier ist meine Verbindungs-ID die 11. Diese Information kann für den Datenbankadministrator bedeutungsvoll sein, wenn es Schwierigkeiten gibt, weil beispielsweise eine schlecht geformte Abfrage stundenlang läuft und man sie lieber abbrechen möchte. Sobald der Server den Benutzernamen und das Passwort geprüft und Ihnen eine Verbindung zugeteilt hat, können Sie Abfragen ausführen (und natürlich auch andere SQL-
| 41 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Anweisungen). Immer wenn eine Abfrage an den Server geschickt wird, prüft dieser vor der Ausführung Folgendes: • Haben Sie die Berechtigung, diese Anweisung auszuführen? • Haben Sie die Berechtigung, auf die gewünschten Daten zuzugreifen? • Ist die Syntax Ihrer Anweisung korrekt? Wenn Ihre Anweisung diese drei Prüfungen besteht, wird sie an die Abfrageoptimierung übergeben, die den effizientesten Ausführungspfad für sie ermittelt. Die Optimierung fragt beispielsweise, in welcher Reihenfolge die in Ihrer from-Klausel genannten Tabellen verbunden werden und welche Indizes zur Verfügung stehen. Dann sucht sie einen Ausführungsplan aus, der anschließend vom Server bei der Bearbeitung Ihrer Abfrage eingehalten wird. Viele interessieren sich für das faszinierende Thema, wie der Datenbankserver Ausführungspläne wählt und wie man diese Wahl beeinflussen kann. Wer MySQL benutzt, kann dies in High Performance MySQL (O’Reilly Verlag) von Baron Schwartz und anderen nachlesen. Darin lernen Sie unter anderem, wie man Indizes generiert, Ausführungspläne analysiert, die Optimierung durch Abfragehinweise beeinflusst und die Startparameter des Servers tunt. Wenn Sie Oracle Database oder SQL Server benutzen, haben Sie die Auswahl aus zig Büchern über DatenbankTuning.
Wenn der Server die Abfrage fertig ausgeführt hat, gibt er der aufrufenden Anwendung eine Ergebnismenge zurück (in diesem Fall wieder dem mysql-Tool). Wie in Kapitel 1 bereits gesagt, ist eine Ergebnismenge auch nur eine Tabelle mit Zeilen und Spalten. Scheitert Ihre Abfrage, zeigt Ihnen das mysql-Tool die gleiche Meldung wie am Ende des folgenden Beispiels: mysql> SELECT emp_id, fname, lname -> FROM employee -> WHERE lname = 'Bkadfl'; Empty set (0.00 sec)
Gibt die Abfrage eine oder mehrere Zeilen zurück, formatiert das mysql-Tool sie, indem es Spaltenüberschriften angibt und mit den Symbolen –, | und + Kästen um die Spalten zeichnet:
Max. Linie
mysql> SELECT fname, lname -> FROM employee; +----------+-----------+ | fname | lname | +----------+-----------+ | Michael | Smith | | Susan | Barker | | Robert | Tyler | | Susan | Hawthorne | | John | Gooding |
42 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | Helen | Fleming | | Chris | Tucker | | Sarah | Parker | | Jane | Grossman | | Paula | Roberts | | Thomas | Ziegler | | Samantha | Jameson | | John | Blake | | Cindy | Mason | | Frank | Portman | | Theresa | Markham | | Beth | Fowler | | Rick | Tulman | +----------+-----------+ 18 rows in set (0.00 sec)
Diese Abfrage gibt die Vor- und Nachnamen aller Mitarbeiter aus der employee-Tabelle zurück. Nachdem die letzte Datenzeile angezeigt wurde, zeigt das mysql-Tool an, wie viele Zeilen zurückgeliefert wurden. Hier sind es 18.
Abfrageklauseln Die select-Anweisung besteht aus mehreren Komponenten oder Klauseln. In MySQL ist nur eine von ihnen obligatorisch (nämlich die select-Klausel), aber normalerweise werden mindestens zwei oder drei der sechs möglichen Klauseln zusätzlich verwendet. Tabelle 3-1 zeigt die verschiedenen Klauseln und ihre Zwecke. Tabelle 3-1: Abfrageklauseln Name der Klausel
Zweck
Select
Legt fest, welche Spalten in die Ergebnismenge kommen.
From
Sagt, aus welchen Tabellen die Daten genommen und wie die Tabellen verbunden werden sollen.
Where
Filtert unerwünschte Daten heraus.
Group by
Fasst Zeilen nach gemeinsamen Spaltenwerten zusammen.
Having
Filtert unerwünschte Gruppen heraus.
Order by
Sortiert die Zeilen der Ergebnismenge nach einer oder mehr Spalten.
Alle Klauseln aus Tabelle 3-1 sind auch in der ANSI-Spezifikation erwähnt. Zusätzlich dazu kennt MySQL eine Reihe von eigenen Klauseln, die in Anhang B genauer beschrieben werden. Die folgenden Abschnitte beschreiben nur die sechs wichtigsten Klauseln.
Die Select-Klausel Max. Linie
Obwohl die select-Klausel die erste Klausel jeder select-Anweisung ist, wird sie vom Datenbankserver als eine der letzten ausgewertet. Denn ehe Sie bestimmen können, was die Ergebnismenge tatsächlich enthalten wird, müssen Sie herausfinden, was sie enthal-
Die Select-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 43
Max. Linie
Links ten könnte. Um die Rolle der select-Klausel genau zu verstehen, müssen Sie daher zunächst die from-Klausel besser kennenlernen. Hier ist zunächst einmal eine Abfrage: mysql> SELECT * -> FROM department; +---------+----------------+ | dept_id | name | +---------+----------------+ | 1 | Operations | | 2 | Loans | | 3 | Administration | +---------+----------------+ 3 rows in set (0.04 sec)
In dieser Abfrage listet die from-Klausel eine einzige Tabelle auf (department), und die select-Klausel besagt, dass alle Spalten (gekennzeichnet durch *) der department-Tabelle in die Ergebnismenge aufgenommen werden. Diese Abfrage könnte man in Worten wie folgt formulieren: Zeige mir alle Spalten der department-Tabelle.
Sie können nicht nur durch das Sternchensymbol alle Spalten auswählen, sondern auch explizit die Spalten aufführen, für die Sie sich interessieren: mysql> SELECT dept_id, name -> FROM department; +---------+----------------+ | dept_id | name | +---------+----------------+ | 1 | Operations | | 2 | Loans | | 3 | Administration | +---------+----------------+ 3 rows in set (0.01 sec)
Das Ergebnis ist dasselbe wie bei der ersten Abfrage, da alle Spalten der departmentTabelle (dept_id und name) in der select-Klausel benannt werden. Sie können jedoch auch nur eine Teilmenge der department-Tabellenspalten betrachten: mysql> SELECT name -> FROM department; +----------------+ | name | +----------------+ | Operations | | Loans | | Administration | +----------------+ 3 rows in set (0.00 sec)
Die Aufgabe der select-Klausel könnte man also folgendermaßen definieren:
Max. Linie
Die select-Klausel stellt fest, welche von allen möglichen Spalten in die Ergebnismenge der Abfrage aufgenommen werden.
44 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Wenn Sie nur Spalten von Tabellen auswählen könnten, die in der from-Klausel auftauchen, wäre das kein großes Kunststück. Doch interessant wird es, wenn Sie in die selectKlausel Dinge wie diese aufnehmen: • Literale, wie beispielsweise Zahlen oder Strings • Ausdrücke, wie transaction.amount * -1 • Aufrufe eingebauter Funktionen, wie etwa ROUND(transaction.amount, 2) • Aufrufe benutzerdefinierter Funktionen Die nächste Abfrage demonstriert, wie man eine Tabellenspalte, ein Literal, einen Ausdruck und eine eingebaute Funktion in eine einzige Abfrage der employee-Tabelle einfließen lässt: mysql> SELECT emp_id, -> 'ACTIVE', -> emp_id * 3.14159, -> UPPER(lname) -> FROM employee; +--------+--------+------------------+--------------+ | emp_id | ACTIVE | emp_id * 3.14159 | UPPER(lname) | +--------+--------+------------------+--------------+ | 1 | ACTIVE | 3.14159 | SMITH | | 2 | ACTIVE | 6.28318 | BARKER | | 3 | ACTIVE | 9.42477 | TYLER | | 4 | ACTIVE | 12.56636 | HAWTHORNE | | 5 | ACTIVE | 15.70795 | GOODING | | 6 | ACTIVE | 18.84954 | FLEMING | | 7 | ACTIVE | 21.99113 | TUCKER | | 8 | ACTIVE | 25.13272 | PARKER | | 9 | ACTIVE | 28.27431 | GROSSMAN | | 10 | ACTIVE | 31.41590 | ROBERTS | | 11 | ACTIVE | 34.55749 | ZIEGLER | | 12 | ACTIVE | 37.69908 | JAMESON | | 13 | ACTIVE | 40.84067 | BLAKE | | 14 | ACTIVE | 43.98226 | MASON | | 15 | ACTIVE | 47.12385 | PORTMAN | | 16 | ACTIVE | 50.26544 | MARKHAM | | 17 | ACTIVE | 53.40703 | FOWLER | | 18 | ACTIVE | 56.54862 | TULMAN | +--------+--------+------------------+--------------+ 18 rows in set (0.05 sec)
Ausdrücke und eingebaute Funktionen werden weiter unten genauer erklärt. Ich wollte Ihnen jedoch bereits jetzt ein Gefühl dafür vermitteln, was man in einer select-Klausel alles unterbringen kann. Wenn Sie nur eine eingebaute Funktion ausführen oder einen einfachen Ausdruck auswerten müssen, können Sie die from-Klausel ganz beiseite lassen. Ein Beispiel:
Max. Linie
mysql> SELECT VERSION( ), -> USER( ), -> DATABASE( );
Die Select-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 45
Links +-----------------------+-------------------+------------+ | version() | user() | database() | +-----------------------+-------------------+------------+ | 6.0.3-alpha-community | lrngsql@localhost | bank | +-----------------------+-------------------+------------+ 1 row in set (0.05 sec)
Da diese Abfrage nur drei eingebaute Funktionen aufruft und keine Daten aus irgendwelchen Tabellen abholt, ist keine from-Klausel erforderlich.
Spalten-Aliase Obwohl das mysql-Tool für die Rückgabespalten der Abfragen Überschriften generiert, können Sie diesen auch eigene Beschriftungen zuweisen. Es mag vielleicht seltener vorkommen, dass Sie einer Spalte, die einen unzutreffenden oder mehrdeutigen Bezeichner hat, einen eigenen Namen geben möchten, aber fast immer werden Sie dies tun, wenn Ihre Ergebnismenge Spalten enthält, die durch Ausdrücke oder eingebaute Funktionen generiert wurden. Einen neuen Namen vergeben Sie als Spalten-Alias hinter den einzelnen Elementen der select-Klausel. Hier sehen Sie die vorige Abfrage der employee-Tabelle mit Spalten-Aliasen für drei Spalten:
Max. Linie
mysql> SELECT emp_id, -> 'ACTIVE' status, -> emp_id * 3.14159 empid_x_pi, -> UPPER(lname) last_name_upper -> FROM employee; +--------+--------+------------+-----------------+ | emp_id | status | empid_x_pi | last_name_upper | +--------+--------+------------+-----------------+ | 1 | ACTIVE | 3.14159 | SMITH | | 2 | ACTIVE | 6.28318 | BARKER | | 3 | ACTIVE | 9.42477 | TYLER | | 4 | ACTIVE | 12.56636 | HAWTHORNE | | 5 | ACTIVE | 15.70795 | GOODING | | 6 | ACTIVE | 18.84954 | FLEMING | | 7 | ACTIVE | 21.99113 | TUCKER | | 8 | ACTIVE | 25.13272 | PARKER | | 9 | ACTIVE | 28.27431 | GROSSMAN | | 10 | ACTIVE | 31.41590 | ROBERTS | | 11 | ACTIVE | 34.55749 | ZIEGLER | | 12 | ACTIVE | 37.69908 | JAMESON | | 13 | ACTIVE | 40.84067 | BLAKE | | 14 | ACTIVE | 43.98226 | MASON | | 15 | ACTIVE | 47.12385 | PORTMAN | | 16 | ACTIVE | 50.26544 | MARKHAM | | 17 | ACTIVE | 53.40703 | FOWLER | | 18 | ACTIVE | 56.54862 | TULMAN | +--------+--------+------------+-----------------+ 18 rows in set (0.00 sec)
46 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Wenn Sie nun die Überschriften betrachten, erkennen Sie, dass die zweite, dritte und vierte Spalte jetzt vernünftige Namen haben, anstatt nur nach der Funktion oder dem Ausdruck zu heißen, durch den sie generiert wurden. Betrachten Sie einmal die select-Klausel, sehen Sie, dass die Aliase status, empid_x_pi und last_name_upper hinter der zweiten, dritten und vierten Spalte stehen. Wahrscheinlich stimmen Sie mir zu, dass die Ausgabe durch die Aliase leichter zu verstehen ist und dass man auch im Programm leichter mit ihr umgehen könnte, wenn man die Abfrage mit Java oder C# absetzte anstatt interaktiv mit dem mysql-Tool. Damit die Spalten-Aliase noch etwas besser erkennbar sind, können Sie vor dem Aliasnamen auch das Schlüsselwort AS angeben, wie Sie es in folgendem Beispiel sehen: mysql> SELECT emp_id, -> 'ACTIVE' AS status, -> emp_id * 3.14159 AS empid_x_pi, -> UPPER(lname) AS last_name_upper -> FROM employee;
Als Grund für die Verwendung des Schlüsselworts as wird häufig genannt, dass es die Lesbarkeit verbessert. Trotzdem habe ich mich dafür entschieden, es in den Beispielen in diesem Buch nicht zu verwenden.
Duplikate entfernen In manchen Fällen kann eine Abfrage doppelte Datenzeilen zurückliefern. Wenn Sie zum Beispiel die IDs aller Kunden abfragen wollten, die Konten besitzen, erhielten Sie Folgendes:
Max. Linie
mysql> SELECT cust_id -> FROM account; +---------+ | cust_id | +---------+ | 1 | | 1 | | 1 | | 2 | | 2 | | 3 | | 3 | | 4 | | 4 | | 4 | | 5 | | 6 | | 6 | | 7 | | 8 | | 8 | | 9 | | 9 |
Die Select-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 47
Links | 9 | | 10 | | 10 | | 11 | | 12 | | 13 | +---------+ 24 rows in set (0.00 sec)
Da manche Kunden mehrere Konten haben, wird jede Kunden-ID für jedes Konto des betreffenden Kunden extra aufgeführt. In diesem Fall möchten Sie jedoch vermutlich eine Menge unterschiedlicher Kunden, die Konten besitzen, anstatt die Kunden-ID für jede Zeile der account-Tabelle einmal anzeigen zu lassen. Dies erzielen Sie, indem Sie direkt hinter das select das Schlüsselwort distinct setzen: mysql> SELECT DISTINCT cust_id -> FROM account; +---------+ | cust_id | +---------+ | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 | | 9 | | 10 | | 11 | | 12 | | 13 | +---------+ 13 rows in set (0.01 sec)
Die neue Ergebnismenge enthält 13 Zeilen (eine pro Kunde) anstatt 24 (eine pro Konto). Wenn Sie nicht möchten, dass der Server doppelte Daten eliminiert, oder wenn Sie sicher sind, dass in der Ergebnismenge keine Doppelnennungen vorkommen, können Sie an Stelle von DISTINCT das Schlüsselwort ALL verwenden. Da dieses jedoch der Default ist und daher nicht extra erwähnt werden muss, schreibt kaum ein Programmierer ALL in seine Abfragen.
Max. Linie
Um eine Distinct-Ergebnismenge zu generieren, müssen die Daten sortiert werden, was bei großen Ergebnismengen viel Zeit in Anspruch nimmt. Bitte fallen Sie nicht darauf herein, DISTINCT zu benutzen, nur um Duplikate auszuschließen. Stattdessen nehmen Sie sich besser Zeit, die Daten, mit denen Sie arbeiten, genau zu verstehen. So können Sie erkennen, ob Duplikate möglich sind oder nicht.
48 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Die From-Klausel Bisher betrachteten wir Abfragen, deren from-Klauseln nur eine einzige Tabelle enthielten. Zwar definieren die meisten SQL-Bücher die from-Klausel einfach als Liste von einer oder mehreren Tabellen, aber ich würde diese Definition gern ausweiten: Die from-Klausel definiert die Tabellen, die von einer Abfrage benutzt werden, und besitzt zusätzlich Mittel, die Tabellen zu verknüpfen.
Diese Definition setzt sich aus zwei getrennten, aber zusammenhängenden Konzepten zusammen, die in den folgenden Abschnitten genauer erläutert werden.
Tabellen Bei dem Wort Tabelle denken die Meisten an verwandte Zeilen, die gemeinsam in einer Datenbank gespeichert sind. Das beschreibt aber nur eine Art von Tabellen. Ich verwende diesen Begriff gern in einem allgemeineren Sinn, in dem die Art und Weise, wie die Daten gespeichert werden, keine Rolle spielen. Stattdessen konzentriere ich mich nur auf die Menge verwandter Zeilen. Auf diese umfassendere Definition passen jedoch drei Arten von Tabellen: • permanente Tabellen (die mit create table angelegt wurden) • temporäre Tabellen (Zeilen, die von einer Unterabfrage zurückgegeben werden) • virtuelle Tabellen (die mit create view angelegt wurden) Jeden dieser Tabellentypen kann man in der from-Klausel einer Abfrage angeben. Da Sie inzwischen wissen, wie man eine permanente Tabelle in einer from-Klausel verwendet, werde ich jetzt auf die beiden anderen Tabellentypen näher eingehen.
Von Unterabfragen generierte Tabellen Eine Unterabfrage ist eine in einer anderen Abfrage enthaltene Abfrage. Unterabfragen stehen in runden Klammern und kommen in diversen Teilen einer select-Anweisung vor. Doch in der from-Klausel hat eine Unterabfrage die Rolle, eine temporäre Tabelle anzulegen, die für alle anderen Klauseln der Abfrage sichtbar ist und mit den anderen in der from-Klausel aufgeführten Tabellen interagieren kann. Ein einfaches Beispiel:
Max. Linie
mysql> SELECT e.emp_id, e.fname, e.lname -> FROM (SELECT emp_id, fname, lname, start_date, title -> FROM employee) e; +--------+----------+-----------+ | emp_id | fname | lname | +--------+----------+-----------+ | 1 | Michael | Smith | | 2 | Susan | Barker | | 3 | Robert | Tyler | | 4 | Susan | Hawthorne | | 5 | John | Gooding | | 6 | Helen | Fleming |
Die From-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 49
Links | 7 | Chris | Tucker | | 8 | Sarah | Parker | | 9 | Jane | Grossman | | 10 | Paula | Roberts | | 11 | Thomas | Ziegler | | 12 | Samantha | Jameson | | 13 | John | Blake | | 14 | Cindy | Mason | | 15 | Frank | Portman | | 16 | Theresa | Markham | | 17 | Beth | Fowler | | 18 | Rick | Tulman | +--------+----------+-----------+ 18 rows in set (0.00 sec)
In diesem Beispiel gibt eine Unterabfrage der employee-Tabelle fünf Spalten zurück, und die übergeordnete Abfrage referenziert drei der fünf vorhandenen Spalten. Die Unterabfrage wird von der übergeordneten Abfrage durch ihren Alias referenziert, in diesem Fall e. Dieses Beispiel ist grob vereinfacht und nicht sonderlich nützlich, aber in Kapitel 9 werden Unterabfragen eingehender behandelt.
Views Eine View ist eine im Data Dictionary gespeicherte Abfrage. Sie sieht aus und verhält sich wie eine Tabelle, aber mit einer View sind keine Daten assoziiert (daher bezeichne ich sie als virtuelle Tabelle). Wenn Sie eine Abfrage auf einer View ausführen, wird diese Abfrage mit der View-Definition verschmolzen, um die Abfrage anzulegen, die letztlich ausgeführt wird. Zur Veranschaulichung sehen Sie hier eine View-Definition, die die employee-Tabelle abfragt und einen Aufruf einer eingebauten Funktion enthält: mysql> CREATE VIEW employee_vw AS -> SELECT emp_id, fname, lname, -> YEAR(start_date) start_year -> FROM employee; Query OK, 0 rows affected (0.10 sec)
Nachdem die View erstellt wurde, werden keine weiteren Daten mehr generiert oder gespeichert: Die select-Anweisung wird einfach zur späteren Verwendung vom Server gespeichert. Da diese View nun angelegt ist, kann man sie abfragen:
Max. Linie
mysql> SELECT emp_id, start_year -> FROM employee_vw; +--------+------------+ | emp_id | start_year | +--------+------------+ | 1 | 2005 | | 2 | 2006 | | 3 | 2005 | | 4 | 2006 | | 5 | 2007 |
50 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 6 | 2008 | | 7 | 2008 | | 8 | 2006 | | 9 | 2006 | | 10 | 2006 | | 11 | 2004 | | 12 | 2007 | | 13 | 2004 | | 14 | 2006 | | 15 | 2007 | | 16 | 2005 | | 17 | 2006 | | 18 | 2006 | +--------+------------+ 18 rows in set (0.07 sec)
Views werden aus verschiedenen Gründen erzeugt, etwa um Spalten vor Benutzern zu verbergen oder um komplexe Datenbankentwürfe zu vereinfachen.
Tabellenverknüpfungen Die zweite Abweichung von der einfachen Definition der from-Klausel ist folgende Vorschrift: Wenn mehrere Tabellen in der from-Klausel auftauchen, müssen auch die Bedingungen für eine Verknüpfung dieser Tabellen mit angegeben werden. Dies ist kein Gebot von MySQL oder einem anderen Datenbankserver, sondern eine vom ANSI anerkannte Methode, mehrere Tabellen miteinander zu verbinden, und es ist die portabelste Methode auf den verschiedenen Datenbankservern. Tabellen-Joins werden in den Kapiteln 5 und 10 genauer erläutert. Hier sehen Sie vorab bereits ein kleines Beispiel, nur für den Fall, dass ich Ihre Neugier geweckt habe:
Max. Linie
mysql> SELECT employee.emp_id, employee.fname, -> employee.lname, department.name dept_name -> FROM employee INNER JOIN department -> ON employee.dept_id = department.dept_id; +--------+----------+-----------+----------------+ | emp_id | fname | lname | dept_name | +--------+----------+-----------+----------------+ | 1 | Michael | Smith | Administration | | 2 | Susan | Barker | Administration | | 3 | Robert | Tyler | Administration | | 4 | Susan | Hawthorne | Operations | | 5 | John | Gooding | Loans | | 6 | Helen | Fleming | Operations | | 7 | Chris | Tucker | Operations | | 8 | Sarah | Parker | Operations | | 9 | Jane | Grossman | Operations | | 10 | Paula | Roberts | Operations | | 11 | Thomas | Ziegler | Operations | | 12 | Samantha | Jameson | Operations | | 13 | John | Blake | Operations | | 14 | Cindy | Mason | Operations |
Die From-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 51
Links | 15 | Frank | Portman | Operations | | 16 | Theresa | Markham | Operations | | 17 | Beth | Fowler | Operations | | 18 | Rick | Tulman | Operations | +--------+----------+-----------+----------------+ 18 rows in set (0.05 sec)
Da diese Abfrage Daten aus den Tabellen employee (emp_id, fname, lname) und department (name) anzeigt, müssen beide Tabellen in der from-Klausel genannt werden. Der Mechanismus für eine Verknüpfung der beiden Tabellen (ein sogenannter Join) ist die Zugehörigkeit des Mitarbeiters zu einer Abteilung, die in der employee-Tabelle gespeichert ist. Der Datenbankserver wird angewiesen, den Wert der Spalte dept_id aus der Tabelle employee zu verwenden, um in der Tabelle department die betreffende Abteilung nachzuschlagen. Die Join-Bedingungen für die beiden Tabellen findet man in der on-Subklausel der from-Klausel. Hier lautet die Join-Bedingung ON employee.dept_id = department.dept_ id. Noch einmal: Kapitel 5 beschreibt gründlich, wie man mehrere Tabellen verbindet.
Tabellen-Aliase definieren Werden mehrere Tabellen in einer einzigen Abfrage verbunden, muss herauszufinden sein, welche Tabelle gemeint ist, wenn in den Klauseln select, where, group by, having und order by Spalten angesprochen werden. Wenn Sie eine Tabelle außerhalb der fromKlausel referenzieren, haben Sie zwei Möglichkeiten: • Sie verwenden den vollständigen Namen der Tabelle, wie in employee.emp_id. • Sie weisen den Tabellen Aliasnamen zu und verwenden in der Abfrage die Aliase. In der obigen Abfrage verwendete ich in der select- und on-Klausel den vollständigen Namen der Tabelle. Mit Aliasen würde die gleiche Abfrage folgendermaßen aussehen: SELECT e.emp_id, e.fname, e.lname, d.name dept_name FROM employee e INNER JOIN department d ON e.dept_id = d.dept_id;
Wenn Sie sich die from-Klausel genau anschauen, erkennen Sie, dass die employee-Tabelle den Alias e und die department-Tabelle den Alias d bekommen hat. Diese Aliase werden dann in der on-Klausel verwendet, um die Join-Bedingung zu definieren, und in der select-Klausel, um anzugeben, welche Spalten in die Ergebnismenge aufgenommen werden. Ich hoffe, Sie werden mir zustimmen, dass die Aliase die Anweisung kompakter machen, ohne Verwirrung zu stiften (solange die Aliasnamen sinnvoll gewählt werden). Zusätzlich können Sie bei Ihren Tabellen-Aliasen das Schlüsselwort AS verwenden, wie wir es zuvor bereits für Spalten-Aliase gezeigt haben:
Max. Linie
SELECT e.emp_id, e.fname, e.lname, d.name dept_name FROM employee AS e INNER JOIN department AS d ON e.dept_id = d.dept_id;
52 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Ich habe festgestellt, dass rund die Hälfte der Datenbankentwickler, mit denen ich zusammengearbeitet habe, das Schlüsselwort as für ihre Spalten- und Tabellenaliase nutzen, die andere Hälfte dagegen nicht.
Die where-Klausel Die bisher in diesem Kapitel gezeigten Abfragen wählten jede Zeile aus den Tabellen employee, department oder account aus (mit Ausnahme des distinct-Beispiels weiter oben in diesem Kapitel). Doch meist möchte man nicht jede Zeile abrufen, sondern uninteressante Zeilen beiseite lassen. Dies ist die Aufgabe der where-Klausel. Die where-Klausel ist der Mechanismus, mit dem unerwünschte Zeilen aus der Ergebnismenge herausgefiltert werden.
Wenn Sie beispielsweise aus der employee-Tabelle nur die Daten derjenigen Mitarbeiter abrufen möchten, die Chefkassierer (Head Teller) sind, können Sie dies mit der whereKlausel der folgenden Abfrage tun: mysql> SELECT emp_id, fname, lname, start_date, title -> FROM employee -> WHERE title = 'Head Teller'; +--------+---------+---------+------------+-------------+ | emp_id | fname | lname | start_date | title | +--------+---------+---------+------------+-------------+ | 6 | Helen | Fleming | 2008-03-17 | Head Teller | | 10 | Paula | Roberts | 2006-07-27 | Head Teller | | 13 | John | Blake | 2004-05-11 | Head Teller | | 16 | Theresa | Markham | 2005-03-15 | Head Teller | +--------+---------+---------+------------+-------------+ 4 rows in set (1.17 sec)
Hier wurden 14 der 18 Mitarbeiterdatensätze von der where-Klausel herausgefiltert. Diese where-Klausel enthält zwar nur eine einzige Filterbedingung, aber wenn Sie möchten, können Sie so viele Bedingungen wie nötig aufführen. Die Bedingungen werden durch Operatoren wie and, or und not getrennt (in Kapitel 4 werden die where-Klausel und ihre Filterbedingungen eingehend erläutert). Hier sehen Sie eine Erweiterung der obigen Abfrage mit einer zweiten Bedingung, die besagt, dass nur Mitarbeiter von Interesse sind, die nach dem 1. Januar 2006 ihre Arbeit angetreten haben:
Max. Linie
mysql> SELECT emp_id, fname, lname, start_date, title -> FROM employee -> WHERE title = 'Head Teller' -> AND start_date > '2006-01-01'; +--------+-------+---------+------------+-------------+ | emp_id | fname | lname | start_date | title | +--------+-------+---------+------------+-------------+ | 6 | Helen | Fleming | 2008-03-17 | Head Teller | | 10 | Paula | Roberts | 2006-07-27 | Head Teller | +--------+-------+---------+------------+-------------+ 2 rows in set (0.01 sec)
Die where-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 53
Links Die erste Bedingung (title = 'Head Teller') hatte 14 der 18 Mitarbeiterdatensätze herausgefiltert und die zweite (start_date > '2006-01-01') nun noch einmal zwei, sodass zwei Zeilen in der endgültigen Ergebnismenge verbleiben. Wir wollen sehen, was passiert, wenn wir den Operator zwischen den beiden Bedingungen von and auf or umstellen. mysql> SELECT emp_id, fname, lname, start_date, title -> FROM employee -> WHERE title = 'Head Teller' -> OR start_date > '2006-01-01'; +--------+----------+-----------+------------+--------------------+ | emp_id | fname | lname | start_date | title | +--------+----------+-----------+------------+--------------------+ | 2 | Susan | Barker | 2006-09-12 | Vice President | | 4 | Susan | Hawthorne | 2006-04-24 | Operations Manager | | 5 | John | Gooding | 2007-11-14 | Loan Manager | | 6 | Helen | Fleming | 2008-03-17 | Head Teller | | 7 | Chris | Tucker | 2008-09-15 | Teller | | 8 | Sarah | Parker | 2006-12-02 | Teller | | 9 | Jane | Grossman | 2006-05-03 | Teller | | 10 | Paula | Roberts | 2006-07-27 | Head Teller | | 12 | Samantha | Jameson | 2007-01-08 | Teller | | 13 | John | Blake | 2004-05-11 | Head Teller | | 14 | Cindy | Mason | 2006-08-09 | Teller | | 15 | Frank | Portman | 2007-04-01 | Teller | | 16 | Theresa | Markham | 2005-03-15 | Head Teller | | 17 | Beth | Fowler | 2006-06-29 | Teller | | 18 | Rick | Tulman | 2006-12-12 | Teller | +--------+----------+-----------+------------+--------------------+ 15 rows in set (0.00 sec)
Die Ausgabe zeigt, dass alle vier Chefkassierer in der Ergebnismenge aufgeführt werden, allerdings zusammen mit allen anderen Angestellten, die seit dem 1. Januar 2006 angefangen haben. Auf 15 der 18 Mitarbeiter in der employee-Tabelle trifft mindestens eine der beiden Bedingungen zu. Also: Wenn Sie Bedingungen mit dem and-Operator trennen, müssen alle diese Bedingungen true sein, damit die betreffende Zeile in die Ergebnismenge aufgenommen wird; wenn Sie hingegen or verwenden, genügt es schon, wenn nur eine der Bedingungen true ist. Was sollte man also tun, wenn man sowohl and als auch or in der where-Klausel verwenden muss? Gute Frage. In einem solchen Fall verwenden Sie runde Klammern, um die Bedingungen zu Gruppen zusammenzufassen. Die nächste Abfrage besagt, dass nur Chefkassierer, die nach dem 1. Januar 2006 ins Unternehmen kamen, oder Kassierer, die nach dem 1. Januar 2007 ihre Arbeit aufnahmen, in die Ergebnismenge kommen:
Max. Linie
mysql> SELECT emp_id, fname, lname, start_date, title -> FROM employee -> WHERE (title = 'Head Teller' AND start_date > '2006-01-01') -> OR (title = 'Teller' AND start_date > '2007-01-01');
54 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts +--------+----------+---------+------------+-------------+ | emp_id | fname | lname | start_date | title | +--------+----------+---------+------------+-------------+ | 6 | Helen | Fleming | 2008-03-17 | Head Teller | | 7 | Chris | Tucker | 2008-09-15 | Teller | | 10 | Paula | Roberts | 2006-07-27 | Head Teller | | 12 | Samantha | Jameson | 2007-01-08 | Teller | | 15 | Frank | Portman | 2007-04-01 | Teller | +--------+----------+---------+------------+-------------+ 5 rows in set (0.00 sec)
Verwenden Sie immer Klammern, um separate Gruppen von Bedingungen zusammenzufassen oder verschiedene Operatoren zu verwenden. So sind Sie, der Datenbankserver und andere Bearbeiter, die später Ihren Code modifizieren sollen, immer auf demselben Stand.
Die Klauseln group by und having Alle bisherigen Abfragen haben unbearbeitete Rohdaten geliefert. Doch gelegentlich möchte man auch Trends in den Daten ausfindig machen, und dazu muss der Datenbankserver die Daten ein wenig aufbereiten, ehe die Ergebnismenge abgeliefert werden kann. Ein möglicher Mechanismus hierfür ist die group by-Klausel, die Daten nach Spaltenwerten zusammenfasst. Vielleicht möchten Sie keine Liste von Mitarbeitern und zugehörigen Abteilungen, sondern eine Liste der Abteilungen mit der jeweiligen Anzahl der Mitarbeiter. Wenn Sie die group by-Klausel verwenden, können Sie auch eine havingKlausel einsetzen: Diese filtert die gruppierten Daten in der gleichen Weise, wie es die where-Klausel mit Rohdaten tut. Werfen Sie hier einen kurzen Blick auf eine Abfrage, die alle Angestellten in allen Abteilungen zählt und die Namen der Abteilungen zurückliefert, in denen mehr als zwei Personen arbeiten: mysql> SELECT d.name, count(e.emp_id) num_employees -> FROM department d INNER JOIN employee e -> ON d.dept_id = e.dept_id -> GROUP BY d.name -> HAVING count(e.emp_id) > 2; +----------------+---------------+ | name | num_employees | +----------------+---------------+ | Administration | 3 | | Operations | 14 | +----------------+---------------+ 2 rows in set (0.00 sec)
Max. Linie
Ich wollte diese beiden Klauseln nur kurz ansprechen, damit Sie später in diesem Buch bereits darauf vorbereitet sind. Die beiden Klauseln sind allerdings etwas fortgeschrittener als die beiden select-Klauseln. Daher bitte ich Sie um Verständnis, dass ich erst in Kapitel 8 genau sagen werde, wann und wie man group by und having verwendet.
Die Klauseln group by und having | 55 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Die order by-Klausel Im Allgemeinen haben die Zeilen der Ergebnismenge einer Abfrage keine besondere Reihenfolge. Wenn Sie eine geordnete Ergebnismenge wünschen, müssen Sie den Server mit der order by-Klausel veranlassen, die Ergebnisse zu sortieren. Die order by-Klausel ist der Mechanismus, mit dem die Ergebnismenge sortiert wird, und zwar entweder nach rohen Spaltendaten oder anhand von Ausdrücken, die auf Spaltendaten basieren.
Als Beispiel sehen Sie hier noch einmal eine frühere Abfrage der account-Tabelle: mysql> SELECT open_emp_id, product_cd -> FROM account; +-------------+------------+ | open_emp_id | product_cd | +-------------+------------+ | 10 | CHK | | 10 | SAV | | 10 | CD | | 10 | CHK | | 10 | SAV | | 13 | CHK | | 13 | MM | | 1 | CHK | | 1 | SAV | | 1 | MM | | 16 | CHK | | 1 | CHK | | 1 | CD | | 10 | CD | | 16 | CHK | | 16 | SAV | | 1 | CHK | | 1 | MM | | 1 | CD | | 16 | CHK | | 16 | BUS | | 10 | BUS | | 16 | CHK | | 13 | SBL | +-------------+------------+ 24 rows in set (0.00 sec)
Wenn Sie versuchen, die Daten für jeden Mitarbeiter zu analysieren, ist es hilfreich, die Ergebnisse anhand der Spalte open_emp_id zu sortieren. Dazu genügt es, diese Spalte in die order by-Klausel aufzunehmen:
Max. Linie
mysql> SELECT open_emp_id, product_cd -> FROM account -> ORDER BY open_emp_id; +-------------+------------+ | open_emp_id | product_cd | +-------------+------------+
56 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 1 | CHK | | 1 | SAV | | 1 | MM | | 1 | CHK | | 1 | CD | | 1 | CHK | | 1 | MM | | 1 | CD | | 10 | CHK | | 10 | SAV | | 10 | CD | | 10 | CHK | | 10 | SAV | | 10 | CD | | 10 | BUS | | 13 | CHK | | 13 | MM | | 13 | SBL | | 16 | CHK | | 16 | CHK | | 16 | SAV | | 16 | CHK | | 16 | BUS | | 16 | CHK | +-------------+------------+ 24 rows in set (0.00 sec)
Nun lässt sich einfacher erkennen, welche Konten von welchem Angestellten eröffnet wurden. Noch besser wäre es vielleicht, wenn man gewährleisten könnte, dass die Kontenarten für jeden Mitarbeiter in der gleichen Reihenfolge angezeigt werden. Dies erreichen Sie, indem Sie die Spalte product_cd hinter der Spalte open_emp_id in der order by-Klausel aufführen:
Max. Linie
mysql> SELECT open_emp_id, product_cd -> FROM account -> ORDER BY open_emp_id, product_cd; +-------------+------------+ | open_emp_id | product_cd | +-------------+------------+ | 1 | CD | | 1 | CD | | 1 | CHK | | 1 | CHK | | 1 | CHK | | 1 | MM | | 1 | MM | | 1 | SAV | | 10 | BUS | | 10 | CD | | 10 | CD | | 10 | CHK | | 10 | CHK | | 10 | SAV |
Die order by-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 57
Links | 10 | SAV | | 13 | CHK | | 13 | MM | | 13 | SBL | | 16 | BUS | | 16 | CHK | | 16 | CHK | | 16 | CHK | | 16 | CHK | | 16 | SAV | +-------------+------------+ 24 rows in set (0.00 sec)
Die Ergebnismenge wurde nun zuerst nach Mitarbeiter-ID und dann nach Kontenart sortiert. Dabei ist bedeutsam, in welcher Reihenfolge die Spalten in der order by-Klausel aufgeführt werden.
Auf- und absteigende Sortierung Beim Sortieren haben Sie die Möglichkeit, mit den Schlüsselwörtern asc und desc eine aufsteigende oder absteigende Reihenfolge festzulegen. Nach Voreinstellung wird ohnehin aufsteigend sortiert, sodass Sie eigentlich nur das Schlüsselwort desc einsetzen müssen, sofern Sie eine absteigende Sortierung wünschen. Die folgende Beispielabfrage listet alle Konten nach ihrem Saldo auf, beginnend mit dem höchsten Saldo:
Max. Linie
mysql> SELECT account_id, product_cd, open_date, avail_balance -> FROM account -> ORDER BY avail_balance DESC; +------------+------------+------------+---------------+ | account_id | product_cd | open_date | avail_balance | +------------+------------+------------+---------------+ | 29 | SBL | 2004-02-22 | 50000.00 | | 28 | CHK | 2003-07-30 | 38552.05 | | 24 | CHK | 2002-09-30 | 23575.12 | | 15 | CD | 2004-12-28 | 10000.00 | | 27 | BUS | 2004-03-22 | 9345.55 | | 22 | MM | 2004-10-28 | 9345.55 | | 12 | MM | 2004-09-30 | 5487.09 | | 17 | CD | 2004-01-12 | 5000.00 | | 18 | CHK | 2001-05-23 | 3487.19 | | 3 | CD | 2004-06-30 | 3000.00 | | 4 | CHK | 2001-03-12 | 2258.02 | | 13 | CHK | 2004-01-27 | 2237.97 | | 8 | MM | 2002-12-15 | 2212.50 | | 23 | CD | 2004-06-30 | 1500.00 | | 1 | CHK | 2000-01-15 | 1057.75 | | 7 | CHK | 2002-11-23 | 1057.75 | | 11 | SAV | 2000-01-15 | 767.77 | | 10 | CHK | 2003-09-12 | 534.12 | | 2 | SAV | 2000-01-15 | 500.00 | | 19 | SAV | 2001-05-23 | 387.99 | | 5 | SAV | 2001-03-12 | 200.00 |
58 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 21 | CHK | 2003-07-30 | 125.67 | | 14 | CHK | 2002-08-24 | 122.37 | | 25 | BUS | 2002-10-01 | 0.00 | +------------+------------+------------+---------------+ 24 rows in set (0.05 sec)
Absteigende Sortierreihenfolgen werden oft für Rankings verwendet, etwa in der Art »zeige mir die 5 höchsten Kontensalden«. MySQL kennt zudem eine limit-Klausel, mit der Sie Daten sortieren und dann alle außer den ersten x Zeilen wieder verwerfen können. In Anhang B wird die limit-Klausel zusammen mit anderen nicht dem ANSI-Standard entsprechenden Erweiterungen vorgestellt.
Sortieren mit Ausdrücken Resultate anhand von Spaltendaten zu sortieren ist ja schön und gut, aber manchmal möchte man doch gern ein anderes Sortierkriterium anwenden, eines, das nicht in der Datenbank gespeichert ist und möglicherweise auch nirgendwo sonst in der Abfrage erscheint. In solche Fällen können Sie in Ihre order by-Klausel auch einen Ausdruck schreiben. Vielleicht möchten Sie ja Ihre Kundendaten nach den letzten drei Ziffern der Steuernummer sortieren (in Amerika und in unserem Beispiel ist das die Federal ID): mysql> SELECT cust_id, cust_type_cd, city, state, fed_id -> FROM customer -> ORDER BY RIGHT(fed_id, 3); +---------+--------------+------------+-------+-------------+ | cust_id | cust_type_cd | city | state | fed_id | +---------+--------------+------------+-------+-------------+ | 1 | I | Lynnfield | MA | 111-11-1111 | | 10 | B | Salem | NH | 04-1111111 | | 2 | I | Woburn | MA | 222-22-2222 | | 11 | B | Wilmington | MA | 04-2222222 | | 3 | I | Quincy | MA | 333-33-3333 | | 12 | B | Salem | NH | 04-3333333 | | 13 | B | Quincy | MA | 04-4444444 | | 4 | I | Waltham | MA | 444-44-4444 | | 5 | I | Salem | NH | 555-55-5555 | | 6 | I | Waltham | MA | 666-66-6666 | | 7 | I | Wilmington | MA | 777-77-7777 | | 8 | I | Salem | NH | 888-88-8888 | | 9 | I | Newton | MA | 999-99-9999 | +---------+--------------+------------+-------+-------------+ 13 rows in set (0.24 sec)
Diese Abfrage nutzt die eingebaute Funktion right( ), um die letzten drei Ziffern der Spalte fed_id zu extrahieren, und sortiert dann die Zeilen anhand dieses Werts.
Sortieren nach numerischen Platzhaltern
Max. Linie
Wenn Sie in Ihrer select-Klausel die Daten nach Spalten sortieren, können Sie diese Spalten statt nach Namen auch nach ihrer Position in der select-Klausel referenzieren. Ein
Die order by-Klausel This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 59
Max. Linie
Links Beispiel: Angenommen, Sie möchten Ihre Daten anhand der zweiten und fünften Rückgabespalte einer Abfrage sortieren, dann könnten Sie wie folgt vorgehen: mysql> SELECT emp_id, title, start_date, fname, lname -> FROM employee -> ORDER BY 2, 5; +--------+--------------------+------------+----------+-----------+ | emp_id | title | start_date | fname | lname | +--------+--------------------+------------+----------+-----------+ | 13 | Head Teller | 2004-05-11 | John | Blake | | 6 | Head Teller | 2008-03-17 | Helen | Fleming | | 16 | Head Teller | 2005-03-15 | Theresa | Markham | | 10 | Head Teller | 2006-07-27 | Paula | Roberts | | 5 | Loan Manager | 2007-11-14 | John | Gooding | | 4 | Operations Manager | 2006-04-24 | Susan | Hawthorne | | 1 | President | 2005-06-22 | Michael | Smith | | 17 | Teller | 2006-06-29 | Beth | Fowler | | 9 | Teller | 2006-05-03 | Jane | Grossman | | 12 | Teller | 2007-01-08 | Samantha | Jameson | | 14 | Teller | 2006-08-09 | Cindy | Mason | | 8 | Teller | 2006-12-02 | Sarah | Parker | | 15 | Teller | 2007-04-01 | Frank | Portman | | 7 | Teller | 2008-09-15 | Chris | Tucker | | 18 | Teller | 2006-12-12 | Rick | Tulman | | 11 | Teller | 2004-10-23 | Thomas | Ziegler | | 3 | Treasurer | 2005-02-09 | Robert | Tyler | | 2 | Vice President | 2006-09-12 | Susan | Barker | +--------+--------------------+------------+----------+-----------+ 18 rows in set (0.00 sec)
Dieses Feature sollten Sie jedoch sparsam einsetzen, da manchmal seltsame Dinge passieren können, wenn man der select-Klausel eine Spalte hinzufügt, ohne die Nummern in der order by-Klausel entsprechend abzuändern. Wenn ich mal eben eine Abfrage schreibe, referenziere ich Spalten gelegentlich auch numerisch, aber wenn ich richtigen Code schreibe, referenziere ich Spalten grundsätzlich über den Namen.
Testen Sie Ihr Wissen Die folgenden Übungen sollen zum besseren Verständnis der select-Anweisung und ihrer Klauseln beitragen. Die Lösungen finden Sie in Anhang C.
Übung 3-1
Max. Linie
Fragen Sie die Personalnummer (Employee ID), den Vornamen und den Nachnamen aller Bankangestellten ab. Sortieren Sie die Daten zuerst nach Nachnamen und dann nach Vornamen.
60 | Kapitel 3: Datenbankabfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Übung 3-2 Fragen Sie die Kontonummer (Account ID), die Kundennummer (Customer ID) und den Saldo (Balance) für alle Konten ab, deren Status gleich 'ACTIVE' und deren Saldo größer als $2.500 ist.
Übung 3-3 Schreiben Sie für die account-Tabelle eine Abfrage, die die IDs der Mitarbeiter liefert, die Konten eröffnet haben (verwenden Sie dazu die Spalte account.open_emp_id). Für jeden Angestellten soll eine einzige Zeile zurückgeliefert werden.
Übung 3-4 Füllen Sie die (durch gekennzeichneten) Lücken für diese Abfrage mehrerer Datensätze aus, um die unten gezeigten Ergebnisse zu erhalten:
Max. Linie
mysql> SELECT p.product_cd, a.cust_id, a.avail_balance -> FROM product p INNER JOIN account -> ON p.product_cd = -> WHERE p. = 'ACCOUNT' -> ORDER BY , ; +------------+---------+---------------+ | product_cd | cust_id | avail_balance | +------------+---------+---------------+ | CD | 1 | 3000.00 | | CD | 6 | 10000.00 | | CD | 7 | 5000.00 | | CD | 9 | 1500.00 | | CHK | 1 | 1057.75 | | CHK | 2 | 2258.02 | | CHK | 3 | 1057.75 | | CHK | 4 | 534.12 | | CHK | 5 | 2237.97 | | CHK | 6 | 122.37 | | CHK | 8 | 3487.19 | | CHK | 9 | 125.67 | | CHK | 10 | 23575.12 | | CHK | 12 | 38552.05 | | MM | 3 | 2212.50 | | MM | 4 | 5487.09 | | MM | 9 | 9345.55 | | SAV | 1 | 500.00 | | SAV | 2 | 200.00 | | SAV | 4 | 767.77 | | SAV | 8 | 387.99 | +------------+---------+---------------+ 21 rows in set (0.09 sec)
Testen Sie Ihr Wissen | 61 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
First
Kapitel 4
KAPITEL 4
Filtern
In manchen Fällen wird mit allen Zeilen einer Tabelle gearbeitet, nämlich: • Wenn alle Daten aus einer Tabelle geleert werden sollen, um neue Data WarehouseFeeds zu präsentieren. • Wenn alle Zeilen einer Tabelle modifiziert werden sollen, nachdem eine neue Spalte hinzugefügt wurde. • Wenn alle Zeilen aus einer Message-Queue-Tabelle abgeholt werden sollen. In Fällen wie diesen benötigt eine SQL-Anweisung keine where-Klausel, da keine Zeilen von der Betrachtung ausgeschlossen werden müssen. Meist ist jedoch nur ein Teil der Tabellenzeilen von Interesse. Daher haben alle SQL-Datenanweisungen (außer insert) eine optionale where-Klausel mit Filterbedingungen, die die Zahl der von der SQLAnweisung betrachteten Zeilen eingrenzen. Zusätzlich enthält die select-Anweisung eine having-Klausel, um Filterbedingungen zu formulieren, die für gruppierte Daten gelten sollen. Dieses Kapitel untersucht die verschiedenen Filterbedingungen, die in den whereKlauseln der select-, update- und delete-Anweisungen stehen können; den Einsatz von Filterbedingungen in der having-Klausel einer select-Anweisung werden wir uns in Kapitel 8 ansehen.
Bedingungsauswertung Eine where-Klausel kann eine oder mehrere durch die Operatoren and und or getrennte Bedingungen enthalten. Wenn mehrere Bedingungen lediglich durch den and-Operator getrennt werden, müssen alle diese Bedingungen true sein, damit die betreffende Zeile in die Ergebnismenge aufgenommen wird. Betrachten Sie die folgende where-Klausel: WHERE title = 'Teller' AND start_date < '2007-01-01'
Max. Linie
Diese beiden Bedingungen führen dazu, dass nur Kassierer, die die Arbeit in der Bank vor 2007 aufnahmen, berücksichtigt werden (oder, wenn man die Sache aus einer anderen Perspektive betrachten will, alle Angestellten, die entweder kein Kassierer [Teller] sind
| 63 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links oder erst seit 2007 in der Bank arbeiten, ausgeschlossen werden). Dieses Beispiel verwendet zwar nur zwei Bedingungen, aber im Prinzip können Sie beliebig viele Bedingungen in die where-Klausel schreiben. Wenn alle durch den Operator and getrennt sind, müssen auch alle true sein, damit die Zeile in die Ergebnismenge kommt. Sind sämtliche Bedingungen der where-Klausel durch den Operator or getrennt, muss hingegen nur eine von ihnen true sein, damit die betreffende Zeile in der Ergebnismenge Aufnahme findet. Betrachten Sie die folgenden beiden Bedingungen: WHERE title = 'Teller' OR start_date < '2007-01-01'
Jetzt kann eine employee-Zeile auf mehreren Wegen in die Ergebnismenge gelangen: • Der Mitarbeiter ist Kassierer und wurde vor 2007 eingestellt. • Der Mitarbeiter ist Kassierer und wurde nach dem 1. Januar 2007 eingestellt. • Der Mitarbeiter ist zwar kein Kassierer, wurde aber vor 2007 eingestellt. Tabelle 4-1 zeigt die möglichen Resultate einer where-Klausel mit zwei durch or getrennten Bedingungen. Tabelle 4-1: Auswertung von zwei Bedingungen mit or-Operator Zwischenergebnis
Endergebnis
WHERE true OR true
True
WHERE true OR false
True
WHERE false OR true
True
WHERE false OR false
False
Im obigen Beispiel ist eine Zeile nur in einem Fall aus der Ergebnismenge ausgeschlossen: wenn der Mitarbeiter kein Kassierer ist und am 1. Januar 2003 oder später eingestellt wurde.
Verwendung von Klammern Wenn Ihre where-Klausel mindestens drei Bedingungen und sowohl and als auch or enthält, sollten Sie durch Klammern klarstellen, was Sie bezwecken, damit sowohl die Datenbank als auch nachfolgende Generationen Ihren Code noch lesen können. Die folgende where-Klausel erweitert das obige Beispiel um die Prüfung, ob der Mitarbeiter immer noch bei der Bank angestellt ist: WHERE end_date IS NULL AND (title = 'Teller' OR start_date < '2007-01-01')
Max. Linie
Jetzt gibt es drei Bedingungen: Damit eine Zeile in die Ergebnismenge gelangt, muss zuerst die erste Bedingung true und dann entweder die zweite oder die dritte (oder beide) ebenfalls true sein. Tabelle 4-2 zeigt die möglichen Resultate dieser where-Klausel.
64 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabelle 4-2: Auswertung von drei Bedingungen mit and und or Zwischenergebnis
Endergebnis
WHERE true AND (true OR true)
True
WHERE true AND (true OR false)
True
WHERE true AND (false OR true)
True
WHERE true AND (false OR false)
False
WHERE false AND (true OR true)
False
WHERE false AND (true OR false)
False
WHERE false AND (false OR true)
False
WHERE false AND (false OR false)
False
Man sieht: Je mehr Bedingungen eine where-Klausel aufweist, umso mehr Kombinationen muss der Server auswerten. In diesem Fall ergeben nur drei der acht möglichen Kombinationen das Endergebnis true.
Verwendung des Operators not Hoffentlich war das obige Beispiel mit den drei Bedingungen gut zu verstehen. Betrachten Sie gleichwohl folgende Abänderung: WHERE end_date IS NULL AND NOT (title = 'Teller' OR start_date < '2007-01-01')
Haben Sie die Änderung erkannt? Ich habe hinter dem and-Operator auf der zweiten Zeile einen not-Operator eingefügt. Nun suche ich nicht mehr nach Mitarbeitern, deren Arbeitsverträge noch laufen und die entweder Kassierer sind oder vor 2007 zur Bank kamen, sondern nach Mitarbeitern, deren Arbeitsverträge noch laufen und die sowohl keine Kassierer sind als auch 2007 oder später zur Bank kamen. Tabelle 4-3 zeigt die möglichen Resultate für dieses Beispiel. Tabelle 4-3: Auswertung von drei Bedingungen mit and, or und not
Max. Linie
Zwischenergebnis
Endergebnis
WHERE true AND NOT (true OR true)
False
WHERE true AND NOT (true OR false)
False
WHERE true AND NOT (false OR true)
False
WHERE true AND NOT (false OR false)
True
WHERE false AND NOT (true OR true)
False
WHERE false AND NOT (true OR false)
False
WHERE false AND NOT (false OR true)
False
WHERE false AND NOT (false OR false)
False
Bedingungsauswertung This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 65
Links Der not-Operator ist für einen Datenbankserver kein Problem, aber ein Mensch kann eine where-Klausel mit diesem Operator nicht mehr so leicht durchschauen. Daher trifft man ihn nur selten an. In diesem Fall könnte man die where-Klausel auch ohne not-Operator schreiben: WHERE end_date IS NULL AND title != 'Teller' AND start_date >= '2007-01-01'
Dem Server ist es sicherlich egal, aber Sie als Leser können diese Version der where-Klausel sicherlich einfacher verstehen.
Aufbau einer Bedingung Da Sie nun gesehen haben, wie mehrere Bedingungen vom Server ausgewertet werden, gehen wir wieder einen Schritt zurück und betrachten, was eigentlich eine einzelne Bedingung ausmacht. Eine Bedingung besteht aus einem oder mehreren Ausdrücken, die durch einen oder mehrere Operatoren gekoppelt sind. Ein Ausdruck kann Folgendes sein: • eine Zahl • eine Spalte einer Tabelle oder View • ein String-Literal wie beispielsweise 'Teller' • eine eingebaute Funktion wie beispielsweise CONCAT('Learning', ' ', 'SQL') • eine Unterabfrage • eine Liste von Ausdrücken wie beispielsweise ('Teller', 'Head Teller', 'Operations Manager')
Folgende Operatoren sind in Bedingungen zulässig: • Vergleichsoperatoren wie beispielsweise =, !=, , , LIKE, IN und BETWEEN • arithmetische Operatoren wie beispielsweise +, –, * und / Der folgende Abschnitt zeigt, wie diese Ausdrücke und Operatoren kombiniert werden können, um verschiedene Arten von Bedingungen zu konstruieren.
Bedingungstypen Es gibt viele verschiedene Wege, unerwünschte Daten herauszufiltern. Sie können bestimmte Werte, Wertemengen oder Wertebereiche ein- oder ausschließen oder mit diversen Mustererkennungstechniken Teilübereinstimmungen in String-Daten ausfindig machen. Im Folgenden werden diese Bedingungstypen genauer erklärt.
Max. Linie
Gleichheitsbedingungen Ein großer Prozentsatz der Filterbedingungen, die Sie schreiben oder über die Sie stolpern werden, hat die Form 'spalte = ausdruck':
66 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts title = 'Teller' fed_id = '111-11-1111' amount = 375.25 dept_id = (SELECT dept_id FROM department WHERE name = 'Loans')
Solche Bedingungen bezeichnet man als Gleichheitsbedingungen, da sie einen Ausdruck mit einem anderen gleichsetzen. Die ersten drei Beispiele setzen eine Spalte mit einem Literal gleich (zweimal ein String und einmal eine Zahl), und das vierte Beispiel setzt eine Spalte mit dem Rückgabewert einer Unterabfrage gleich. Die folgende Abfrage verwendet zwei Gleichheitsbedingungen: eine in der on-Klausel (eine Join-Bedingung) und die andere in der where-Klausel (eine Filterbedingung): mysql> SELECT pt.name product_type, p.name product -> FROM product p INNER JOIN product_type pt -> ON p.product_type_cd = pt.product_type_cd -> WHERE pt.name = 'Customer Accounts'; +-------------------+------------------------+ | product_type | product | +-------------------+------------------------+ | Customer Accounts | certificate of deposit | | Customer Accounts | checking account | | Customer Accounts | money market account | | Customer Accounts | savings account | +-------------------+------------------------+ 4 rows in set (0.08 sec)
Diese Abfrage zeigt alle Produkte, die eine Art Kundenkonto darstellen.
Ungleichheitsbedingungen Ebenfalls ziemlich gebräuchlich ist die Ungleichheitsbedingung, die fordert, dass zwei Ausdrücke nicht gleich sind. Hier habe ich die Filterbedingung aus der where-Klausel der obigen Abfrage in eine Ungleichheitsbedingung umgewandelt: mysql> SELECT pt.name product_type, p.name product -> FROM product p INNER JOIN product_type pt -> ON p.product_type_cd = pt.product_type_cd -> WHERE pt.name 'Customer Accounts'; +-------------------------------+-------------------------+ | product_type | product | +-------------------------------+-------------------------+ | Individual and Business Loans | auto loan | | Individual and Business Loans | business line of credit | | Individual and Business Loans | home mortgage | | Individual and Business Loans | small business loan | +-------------------------------+-------------------------+ 4 rows in set (0.00 sec)
Max. Linie
Diese Abfrage zeigt alle Produkte, die nicht Kundenkontenarten sind. Bei der Konstruktion von Ungleichheitsbedingungen sollten Sie entweder != oder als Operator verwenden.
Bedingungstypen | 67 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Daten mit Gleichheitsbedingungen modifizieren Gleichheits- und Ungleichheitsbedingungen werden gern auch zur Modifikation von Daten eingesetzt. Angenommen, die Bank wirft einmal jährlich alte Kontendatensätze über Bord. Ihre Aufgabe ist es, aus der account-Tabelle die Zeilen derjenigen Konten zu entfernen, die 1999 geschlossen wurden. Hier sehen Sie eine Möglichkeit, dies zu tun: DELETE FROM account WHERE status = 'CLOSED' AND YEAR(close_date) = 2002;
Diese Anweisung enthält zwei Gleichheitsbedingungen: eine, um geschlossene Konten zu finden, und eine, um herauszufinden, welche dieser Konten 2002 geschlossen wurden. Bei der Formulierung von delete- und update-Anweisungen werde ich versuchen, jede Anweisung so zu schreiben, dass keine Zeilen modifiziert werden. So bleiben die Daten bei der Ausführung der Anweisungen unverändert, und die Ausgabe Ihrer select-Anweisungen entspricht immer dem in diesem Buch Gezeigten. Da MySQL-Sessions standardmäßig im Auto-Commit-Modus ablaufen (siehe Kapitel 12), könnten Sie, falls meine Anweisungen die Beispieldaten ändern würden, diese Änderungen nicht wieder zurückrollen (ungeschehen machen). Sie selbst können natürlich mit Ihren Beispieldaten machen, was Sie wollen; meinetwegen können Sie sie auch löschen und meine Skripten erneut ausführen, aber ich für meinen Teil werde darauf achten, dass die Daten intakt bleiben.
Wertebereichsbedingungen Sie können in einer Bedingung nicht nur prüfen, ob ein Ausdruck gleich (oder ungleich) einem anderen Ausdruck ist, sondern auch, ob ein Ausdruck in einem bestimmten Wertebereich liegt. Diese Art von Bedingung findet man häufig im Umgang mit numerischen oder temporalen Daten. Betrachten Sie folgende Abfrage:
Max. Linie
mysql> SELECT emp_id, fname, lname, start_date -> FROM employee -> WHERE start_date < '2007-01-01'; +--------+---------+-----------+------------+ | emp_id | fname | lname | start_date | +--------+---------+-----------+------------+ | 1 | Michael | Smith | 2005-06-22 | | 2 | Susan | Barker | 2006-09-12 | | 3 | Robert | Tyler | 2005-02-09 | | 4 | Susan | Hawthorne | 2006-04-24 | | 8 | Sarah | Parker | 2006-12-02 | | 9 | Jane | Grossman | 2006-05-03 | | 10 | Paula | Roberts | 2006-07-27 | | 11 | Thomas | Ziegler | 2004-10-23 | | 13 | John | Blake | 2004-05-11 | | 14 | Cindy | Mason | 2006-08-09 | | 16 | Theresa | Markham | 2005-03-15 |
68 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 17 | Beth | Fowler | 2006-06-29 | | 18 | Rick | Tulman | 2006-12-12 | +--------+---------+-----------+------------+ 13 rows in set (0.15 sec)
Diese Abfrage findet alle vor 2007 engagierten Angestellten. Doch eventuell möchten Sie nicht nur eine Obergrenze für das Eintrittsdatum angeben, sondern auch eine Untergrenze: mysql> SELECT emp_id, fname, lname, start_date -> FROM employee -> WHERE start_date < '2007-01-01' -> AND start_date >= '2005-01-01'; +--------+---------+-----------+------------+ | emp_id | fname | lname | start_date | +--------+---------+-----------+------------+ | 1 | Michael | Smith | 2005-06-22 | | 2 | Susan | Barker | 2006-09-12 | | 3 | Robert | Tyler | 2005-02-09 | | 4 | Susan | Hawthorne | 2006-04-24 | | 8 | Sarah | Parker | 2006-12-02 | | 9 | Jane | Grossman | 2006-05-03 | | 10 | Paula | Roberts | 2006-07-27 | | 14 | Cindy | Mason | 2006-08-09 | | 16 | Theresa | Markham | 2005-03-15 | | 17 | Beth | Fowler | 2006-06-29 | | 18 | Rick | Tulman | 2006-12-12 | +--------+---------+-----------+------------+ 11 rows in set (0.00 sec)
Diese Version der Abfrage findet alle in den Jahren 2005 oder 2006 eingestellten Mitarbeiter.
Der between-Operator Wenn Sie sowohl eine obere als auch eine untere Grenze für Ihren Wertebereich haben, können Sie statt zwei separaten Bedingungen auch eine einzige Bedingung mit dem Operator between formulieren:
Max. Linie
mysql> SELECT emp_id, fname, lname, start_date -> FROM employee -> WHERE start_date BETWEEN '2005-01-01' AND '2007-01-01'; +--------+---------+-----------+------------+ | emp_id | fname | lname | start_date | +--------+---------+-----------+------------+ | 1 | Michael | Smith | 2005-06-22 | | 2 | Susan | Barker | 2006-09-12 | | 3 | Robert | Tyler | 2005-02-09 | | 4 | Susan | Hawthorne | 2006-04-24 | | 8 | Sarah | Parker | 2006-12-02 | | 9 | Jane | Grossman | 2006-05-03 | | 10 | Paula | Roberts | 2006-07-27 | | 14 | Cindy | Mason | 2006-08-09 |
Bedingungstypen | 69 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links | 16 | Theresa | Markham | 2005-03-15 | | 17 | Beth | Fowler | 2006-06-29 | | 18 | Rick | Tulman | 2006-12-12 | +--------+---------+-----------+------------+ 11 rows in set (0.03 sec)
Bei der Verwendung des between-Operators sind einige Dinge zu beachten: Geben Sie immer zuerst (hinter between) die Untergrenze und als Zweites (hinter and) die Obergrenze des Wertebereichs an. Hier sehen Sie, was passiert, wenn Sie versehentlich zuerst die Obergrenze angeben: mysql> SELECT emp_id, fname, lname, start_date -> FROM employee -> WHERE start_date BETWEEN '2007-01-01' AND '2005-01-01'; Empty set (0.00 sec)
Es werden keine Daten zurückgegeben, da der Server in Wirklichkeit aus Ihrer einen Bedingung zwei Bedingungen (mit den Operatoren =) macht, wie in: mysql> SELECT emp_id, fname, lname, start_date -> FROM employee -> WHERE start_date >= '2007-01-01' -> AND start_date SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE avail_balance BETWEEN 3000 AND 5000; +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 3 | CD | 1 | 3000.00 | | 17 | CD | 7 | 5000.00 | | 18 | CHK | 8 | 3487.19 | +------------+------------+---------+---------------+ 3 rows in set (0.10 sec)
Max. Linie
Alle Konten, deren Saldo zwischen $3.000 und $5.000 beträgt, werden gemeldet. Denken Sie auch hier daran, den kleineren Betrag als Erstes anzugeben.
70 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts String-Bereiche Während Datums- und Zahlenintervalle leicht zu verstehen sind, lassen sich Bedingungen, die nach String-Intervallen suchen, schon etwas schwieriger veranschaulichen. Angenommen, Sie suchen nach Kunden, deren Sozialversicherungsnummer in einen bestimmten Bereich fällt, und die Sozialversicherungsnummer hat das Format »XXX-XX-XXXX«, wobei X eine Ziffer zwischen 0 und 9 ist. Sie möchten jeden Kunden finden, dessen Sozialversicherungsnummer zwischen »500-00-0000« und »999-99-9999« liegt. Dann müssten Sie folgende Anweisung schreiben: mysql> SELECT cust_id, fed_id -> FROM customer -> WHERE cust_type_cd = 'I' -> AND fed_id BETWEEN '500-00-0000' AND '999-99-9999'; +---------+-------------+ | cust_id | fed_id | +---------+-------------+ | 5 | 555-55-5555 | | 6 | 666-66-6666 | | 7 | 777-77-7777 | | 8 | 888-88-8888 | | 9 | 999-99-9999 | +---------+-------------+ 5 rows in set (0.01 sec)
Um mit String-Bereichen arbeiten zu können, müssen Sie die Reihenfolge der Zeichen Ihres Zeichensatzes kennen (die Sortierreihenfolge der Zeichen eines Zeichensatzes nennt man auch Kollation).
Mitgliedschaftsbedingungen Es gibt Fälle, in denen man einen Ausdruck nicht auf einen Einzelwert oder Wertebereich eingrenzen möchte, sondern auf eine endliche Wertemenge. Wenn Sie beispielsweise alle Konten finden möchten, deren Produktkode 'CHK', 'SAV', 'CD' oder 'MM' ist, tun Sie Folgendes:
Max. Linie
mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE product_cd = 'CHK' OR product_cd = 'SAV' -> OR product_cd = 'CD' OR product_cd = 'MM'; +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 1 | CHK | 1 | 1057.75 | | 2 | SAV | 1 | 500.00 | | 3 | CD | 1 | 3000.00 | | 4 | CHK | 2 | 2258.02 | | 5 | SAV | 2 | 200.00 | | 7 | CHK | 3 | 1057.75 | | 8 | MM | 3 | 2212.50 |
Bedingungstypen | 71 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links | 10 | CHK | 4 | 534.12 | | 11 | SAV | 4 | 767.77 | | 12 | MM | 4 | 5487.09 | | 13 | CHK | 5 | 2237.97 | | 14 | CHK | 6 | 122.37 | | 15 | CD | 6 | 10000.00 | | 17 | CD | 7 | 5000.00 | | 18 | CHK | 8 | 3487.19 | | 19 | SAV | 8 | 387.99 | | 21 | CHK | 9 | 125.67 | | 22 | MM | 9 | 9345.55 | | 23 | CD | 9 | 1500.00 | | 24 | CHK | 10 | 23575.12 | | 28 | CHK | 12 | 38552.05 | +------------+------------+---------+---------------+ 21 rows in set (0.28 sec)
Diese where-Klausel (vier durch or verknüpfte Bedingungen) war zwar nicht schwer zu konstruieren, aber stellen Sie sich eine Menge von 10 oder 20 Ausdrücken vor! Für solche Situationen können Sie stattdessen den Operator in nutzen: SELECT account_id, product_cd, cust_id, avail_balance FROM account WHERE product_cd IN ('CHK','SAV','CD','MM');
Mit dem in-Operator können Sie eine Menge von beliebig vielen Ausdrücken in einer einzigen Bedingung unterbringen.
Unterabfragen Sie können nicht nur selbst eine Menge von Ausdrücken wie beispielsweise ('CHK','SAV', 'CD','MM') schreiben, sondern auch eine solche Menge vor Ort von einer Unterabfrage generieren lassen. Alle vier Produktarten der obigen Abfrage haben beispielsweise den product_type_cd-Wert 'ACCOUNT', was also spricht dagegen, eine Unterabfrage auf der product-Tabelle einzusetzen, um die vier Produktkodes abzurufen, anstatt sie explizit zu benennen:
Max. Linie
mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE product_cd IN (SELECT product_cd FROM product -> WHERE product_type_cd = 'ACCOUNT'); +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 3 | CD | 1 | 3000.00 | | 15 | CD | 6 | 10000.00 | | 17 | CD | 7 | 5000.00 | | 23 | CD | 9 | 1500.00 | | 1 | CHK | 1 | 1057.75 | | 4 | CHK | 2 | 2258.02 | | 7 | CHK | 3 | 1057.75 | | 10 | CHK | 4 | 534.12 | | 13 | CHK | 5 | 2237.97 |
72 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 14 | CHK | 6 | 122.37 | | 18 | CHK | 8 | 3487.19 | | 21 | CHK | 9 | 125.67 | | 24 | CHK | 10 | 23575.12 | | 28 | CHK | 12 | 38552.05 | | 8 | MM | 3 | 2212.50 | | 12 | MM | 4 | 5487.09 | | 22 | MM | 9 | 9345.55 | | 2 | SAV | 1 | 500.00 | | 5 | SAV | 2 | 200.00 | | 11 | SAV | 4 | 767.77 | | 19 | SAV | 8 | 387.99 | +------------+------------+---------+---------------+ 21 rows in set (0.11 sec)
Die Unterabfrage gibt eine Menge mit vier Werten zurück, und die Hauptabfrage schaut nach, ob die Spalte product_cd in dieser Rückgabemenge zu finden ist.
Verwendung von not in Manchmal möchte man wissen, ob ein bestimmter Ausdruck in einer Menge von Ausdrücken vorhanden ist, manchmal aber auch, ob er darin nicht vorhanden ist. In solchen Situationen leistet der Operator not in gute Dienste: mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE product_cd NOT IN ('CHK','SAV','CD','MM'); +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 25 | BUS | 10 | 0.00 | | 27 | BUS | 11 | 9345.55 | | 29 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 3 rows in set (0.09 sec)
Diese Abfrage findet alle Konten, die keine Scheck-, Spar-, Depot- oder Tagesgeldkonten sind.
Bedingungen abgleichen Bisher haben Sie Bedingungen zu sehen bekommen, die einen genauen String, einen String-Bereich oder eine String-Menge identifizieren. Es gibt jedoch noch einen letzten Bedingungstyp, der teilweise Übereinstimmungen von Strings erkennen kann. Sie können zum Beispiel alle Mitarbeiter ausfindig machen, deren Nachname mit »T« anfängt. Mit einer eingebauten Funktion können Sie den ersten Buchstaben der Spalte lname herausziehen, wie in folgender Anweisung:
Max. Linie
mysql> SELECT emp_id, fname, lname -> FROM employee -> WHERE LEFT(lname, 1) = 'T';
Bedingungstypen | 73 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links +--------+--------+--------+ | emp_id | fname | lname | +--------+--------+--------+ | 3 | Robert | Tyler | | 7 | Chris | Tucker | | 18 | Rick | Tulman | +--------+--------+--------+ 3 rows in set (0.01 sec)
Die eingebaute Funktion left( ) kann dies zwar tun, gibt Ihnen aber wenig Flexibilität. Stattdessen können Sie auch Suchausdrücke mit Wildcard-Zeichen formulieren, wie es im nächsten Abschnitt beschrieben wird.
Verwendung von Wildcards Wenn Sie sich für teilweise Übereinstimmung von Strings interessieren, suchen Sie wahrscheinlich eines der folgenden Phänomene: • Strings mit einem bestimmten Anfangs-/Endbuchstaben • Strings, die mit einem bestimmten Teilstring anfangen oder enden • Strings, in denen irgendwo ein bestimmtes Zeichen vorkommt • Strings, in denen irgendwo ein bestimmter Teilstring vorkommt • Strings, die unabhängig von den enthaltenen Zeichen ein bestimmtes Format aufweisen Mit bestimmten Suchausdrücken können Sie diese und andere Teilübereinstimmungen von Strings mithilfe der Wildcard-Zeichen aus Tabelle 4-4 ausfindig machen. Tabelle 4-4: Wildcard-Zeichen Wildcard-Zeichen
Passt auf
_
genau ein Zeichen
%
beliebig viele Zeichen (einschließlich null)
Der Unterstrich ist Platzhalter für ein einzelnes und das Prozentzeichen für beliebig viele Zeichen. Wenn Sie Bedingungen mit solchen Suchausdrücken formulieren, verwenden Sie den like-Operator:
Max. Linie
mysql> SELECT lname -> FROM employee -> WHERE lname LIKE '_a%e%'; +-----------+ | lname | +-----------+ | Barker | | Hawthorne | | Parker | | Jameson | +-----------+ 4 rows in set (0.00 sec)
74 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Der Suchausdruck im obigen Beispiel spezifiziert Strings, die an zweiter Stelle ein »a« und an irgendeiner nachfolgenden Position im String ein »e« aufweisen (und sei es an letzter Stelle des Strings). Tabelle 4-5 zeigt einige weitere Suchausdrücke zusammen mit ihren Interpretationen. Tabelle 4-5: Beispiele für Suchausdrücke Suchausdruck
Interpretation
F%
Strings, die mit »F« anfangen
%t
Strings, die mit »t« aufhören
%bas%
Strings, die den Teilstring »bas« enthalten
_ _t_
vier Zeichen lange Strings mit einem »t« an dritter Position
_ _ _-_ _-_ _ _ _
elf Zeichen lange Strings mit Bindestrichen an der vierten und siebten Position
Das letzte Beispiel aus Tabelle 4-5 könnte genutzt werden, um Kunden ausfindig zu machen, deren Federal ID dasselbe Format wie eine Sozialversicherungsnummer hat, wie hier: mysql> SELECT cust_id, fed_id -> FROM customer -> WHERE fed_id LIKE '_ _ _-_ _-_ _ _ _'; +---------+-------------+ | cust_id | fed_id | +---------+-------------+ | 1 | 111-11-1111 | | 2 | 222-22-2222 | | 3 | 333-33-3333 | | 4 | 444-44-4444 | | 5 | 555-55-5555 | | 6 | 666-66-6666 | | 7 | 777-77-7777 | | 8 | 888-88-8888 | | 9 | 999-99-9999 | +---------+-------------+ 9 rows in set (0.02 sec)
Die Wildcard-Zeichen funktionieren gut für einfache Suchausdrücke, aber wenn es etwas komplizierter wird, können Sie auch mehrere Suchausdrücke miteinander verbinden:
Max. Linie
mysql> SELECT emp_id, fname, lname -> FROM employee -> WHERE lname LIKE 'F%' OR lname LIKE 'G%'; +--------+-------+----------+ | emp_id | fname | lname | +--------+-------+----------+ | 5 | John | Gooding | | 6 | Helen | Fleming | | 9 | Jane | Grossman | | 17 | Beth | Fowler | +--------+-------+----------+ 4 rows in set (0.00 sec)
Bedingungstypen | 75 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Diese Abfrage findet alle Angestellten, deren Nachnamen mit »F« oder »G« anfangen.
Verwendung von regulären Ausdrücken Wenn Ihnen Wildcards nicht flexibel genug sind, können Sie Suchausdrücke mit regulären Ausdrücken konstruieren. Ein regulärer Ausdruck ist im Wesentlichen ein Suchausdruck auf der Grundlage von Steroiden. Wenn Sie SQL-Neuling sind, aber bereits mit Sprachen wie Perl programmiert haben, kennen Sie sich mit regulären Ausdrücken bestens aus. Sollten Sie jedoch noch nie mit ihnen zu tun gehabt haben, lesen Sie am besten Reguläre Ausdrücke von Jeffrey Friedl (O’Reilly Verlag), da dieses Thema viel zu umfangreich ist, um es in diesem Buch behandeln zu können. Die obige Abfrage (alle Mitarbeiter zu finden, deren Nachnamen mit »F« oder »G« anfangen) würde in der MySQL-Implementierung von regulären Ausdrücken folgendermaßen aussehen: mysql> SELECT emp_id, fname, lname -> FROM employee -> WHERE lname REGEXP '^[FG]'; +--------+-------+----------+ | emp_id | fname | lname | +--------+-------+----------+ | 5 | John | Gooding | | 6 | Helen | Fleming | | 9 | Jane | Grossman | | 17 | Beth | Fowler | +--------+-------+----------+ 4 rows in set (0.00 sec)
Der Operator regexp nimmt einen regulären Ausdruck (hier '^[FG]') und wendet ihn auf den Ausdruck auf der linken Seite der Bedingung an (die Spalte lname). Jetzt enthält die Abfrage eine einzelne Bedingung mit einem regulären Ausdruck an Stelle zweier mit Wildcards. Oracle Database und Microsoft SQL Server unterstützen ebenfalls reguläre Ausdrücke. Bei Oracle Database verwendet man die Funktion regexp_like an Stelle des Operators regexp und bei SQL Server den Operator like.
NULL: Ein böses Wort
Max. Linie
Ich habe mich darum gedrückt, solange ich konnte, aber jetzt lässt es sich nicht mehr umgehen, dieses Thema, das Unsicherheit, Angst und Entsetzen hervorruft: der nullWert. null ist die Abwesenheit eines Werts. Bevor ein Angestellter gefeuert wird, wird seine end_date-Spalte in der employee-Tabelle auf null gesetzt. Es gibt einfach keinen Wert, den man der Spalte end_date in dieser Situation vernünftigerweise zuweisen könnte. null ist jedoch eine wackelige Angelegenheit, da es mehrere Varianten davon gibt:
76 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Nicht zutreffend Zum Beispiel die Employee ID-Spalte für ein am Geldautomaten getätigtes Bankgeschäft. Wert noch nicht bekannt Zum Beispiel, wenn zu dem Zeitpunkt, da die Daten eines Kunden angelegt werden, dessen Federal ID noch unbekannt ist. Wert nicht definiert Zum Beispiel, wenn ein Konto für ein Produkt angelegt wird, das noch nicht in die Datenbank eingetragen wurde. Manche Theoretiker sind der Ansicht, es müsse für alle diese (und andere) Situationen jeweils unterschiedliche Ausdrücke geben, aber die meisten Praktiker sind sich einig, dass dies viel zu verwirrend wäre.
Bei der Arbeit mit null müssen Sie an Folgendes denken: • Ein Ausdruck kann null sein, aber nicht gleich null. • null und null sind nie gleich. Ob ein Ausdruck null ist, kann man mit dem Operator is null testen: mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee -> WHERE superior_emp_id IS NULL; +--------+---------+-------+-----------------+ | emp_id | fname | lname | superior_emp_id | +--------+---------+-------+-----------------+ | 1 | Michael | Smith | NULL | +--------+---------+-------+-----------------+ 1 row in set (0.00 sec)
Diese Abfrage gibt alle Firmenangehörigen zurück, die keinen Chef haben (wäre das nicht angenehm?). Hier sehen Sie die gleiche Abfrage mit = null an Stelle von is null: mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee -> WHERE superior_emp_id = NULL; Empty set (0.01 sec)
Die Abfrage wird korrekt geparst und ausgeführt, gibt aber keine Zeilen zurück. Unerfahrene SQL-Programmierer begehen häufig diese Sünde, und der Datenbankserver meldet keinen Fehler. Seien Sie also vorsichtig bei der Formulierung von Bedingungen, die null testen sollen. Wenn Sie sehen möchten, ob einer Spalte ein Wert zugewiesen wurde, verwenden Sie den Operator is not null:
Max. Linie
Max. Linie
mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee -> WHERE superior_emp_id IS NOT NULL;
NULL: Ein böses Wort This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 77
Links +--------+----------+-----------+-----------------+ | emp_id | fname | lname | superior_emp_id | +--------+----------+-----------+-----------------+ | 2 | Susan | Barker | 1 | | 3 | Robert | Tyler | 1 | | 4 | Susan | Hawthorne | 3 | | 5 | John | Gooding | 4 | | 6 | Helen | Fleming | 4 | | 7 | Chris | Tucker | 6 | | 8 | Sarah | Parker | 6 | | 9 | Jane | Grossman | 6 | | 10 | Paula | Roberts | 4 | | 11 | Thomas | Ziegler | 10 | | 12 | Samantha | Jameson | 10 | | 13 | John | Blake | 4 | | 14 | Cindy | Mason | 13 | | 15 | Frank | Portman | 13 | | 16 | Theresa | Markham | 4 | | 17 | Beth | Fowler | 16 | | 18 | Rick | Tulman | 16 | +--------+----------+-----------+-----------------+ 17 rows in set (0.00 sec)
Diese Version der Abfrage gibt die anderen 17 Mitarbeiter zurück, die im Gegensatz zu Michael Smith einen Chef haben. Bevor wir das Thema null wieder verlassen, möchte ich eine weitere mögliche Falle aufzeigen. Angenommen, Sie müssen alle Mitarbeiter finden, die nicht für Helen Fleming arbeiten, deren Employee ID die 6 ist. Instinktiv tun Sie zunächst Folgendes:
Max. Linie
mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee -> WHERE superior_emp_id != 6; +--------+----------+-----------+-----------------+ | emp_id | fname | lname | superior_emp_id | +--------+----------+-----------+-----------------+ | 2 | Susan | Barker | 1 | | 3 | Robert | Tyler | 1 | | 4 | Susan | Hawthorne | 3 | | 5 | John | Gooding | 4 | | 6 | Helen | Fleming | 4 | | 10 | Paula | Roberts | 4 | | 11 | Thomas | Ziegler | 10 | | 12 | Samantha | Jameson | 10 | | 13 | John | Blake | 4 | | 14 | Cindy | Mason | 13 | | 15 | Frank | Portman | 13 | | 16 | Theresa | Markham | 4 | | 17 | Beth | Fowler | 16 | | 18 | Rick | Tulman | 16 | +--------+----------+-----------+-----------------+ 14 rows in set (0.00 sec)
78 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Es stimmt zwar, dass diese 14 Mitarbeiter nicht für Helen Fleming arbeiten, aber wenn Sie die Daten genau anschauen, erkennen Sie, dass es noch jemanden gibt, der nicht für Helen arbeitet, aber hier nicht aufgeführt wird: Michael Smith. Seine Spalte superior_ emp_id hat den Wert null (weil er hier das hohe Tier ist). Um also die korrekte Antwort zu finden, müssen Sie auch die Möglichkeit einbeziehen, dass manche Zeilen in der Spalte superior_emp_id den Wert null aufweisen: mysql> SELECT emp_id, fname, lname, superior_emp_id -> FROM employee -> WHERE superior_emp_id != 6 OR superior_emp_id IS NULL; +--------+----------+-----------+-----------------+ | emp_id | fname | lname | superior_emp_id | +--------+----------+-----------+-----------------+ | 1 | Michael | Smith | NULL | | 2 | Susan | Barker | 1 | | 3 | Robert | Tyler | 1 | | 4 | Susan | Hawthorne | 3 | | 5 | John | Gooding | 4 | | 6 | Helen | Fleming | 4 | | 10 | Paula | Roberts | 4 | | 11 | Thomas | Ziegler | 10 | | 12 | Samantha | Jameson | 10 | | 13 | John | Blake | 4 | | 14 | Cindy | Mason | 13 | | 15 | Frank | Portman | 13 | | 16 | Theresa | Markham | 4 | | 17 | Beth | Fowler | 16 | | 18 | Rick | Tulman | 16 | +--------+----------+-----------+-----------------+ 15 rows in set (0.00 sec)
Nun enthält die Ergebnismenge alle 15 Mitarbeiter, die nicht für Helen arbeiten. Wenn Sie mit unbekannten Datenbanken arbeiten, sollten Sie immer zuerst herausfinden, welche Spalten einer Tabelle null-Werte erlauben. So können Sie in Ihren Filterbedingungen Vorkehrungen treffen, damit Ihnen keine Daten durchrutschen.
Testen Sie Ihr Wissen Die folgenden Übungen prüfen, ob Sie Filterbedingungen verstanden haben. Die Lösungen finden Sie in Anhang C. Für die ersten beiden Übungen werden folgende Transaktionsdaten verwendet:
Max. Linie
Txn_id
Txn_date
Account_id
Txn_type_cd
Amount
1
2005-02-22
101
CDT
1000.00
2
2005-02-23
102
CDT
525.75
3
2005-02-24
101
DBT
100.00
Testen Sie Ihr Wissen | 79 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Txn_id
Txn_date
Account_id
Txn_type_cd
Amount
4
2005-02-24
103
CDT
55
5
2005-02-25
101
DBT
50
6
2005-02-25
103
DBT
25
7
2005-02-25
102
CDT
125.37
8
2005-02-26
103
DBT
10
9
2005-02-27
101
CDT
75
Übung 4-1 Welche Transaction IDs würden durch folgende Filterbedingungen zurückgegeben? txn_date < '2005-02-26' AND (txn_type_cd = 'DBT' OR amount > 100)
Übung 4-2 Welche Transaction IDs würden durch diese Filterbedingungen zurückgegeben? account_id IN (101,103) AND NOT (txn_type_cd = 'DBT' OR amount > 100)
Übung 4-3 Schreiben Sie eine Abfrage, die alle im Jahr 2002 eröffneten Konten zurückliefert.
Übung 4-4 Schreiben Sie eine Abfrage, die alle Privatkunden ermittelt, deren Nachname an zweiter Stelle ein 'a' und danach irgendwo ein 'e' enthält.
Max. Linie
Max. Linie 80 | Kapitel 4: Filtern
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 5
KAPITEL 5
Mehrere Tabellen abfragen
In Kapitel 2 habe ich vorgeführt, wie man in einem als Normalisierung bezeichneten Vorgang aufeinander bezogene Konzepte in separate Teile zerlegt. Das Resultat jener Übung waren zwei Tabellen: person und favorite_food. Wenn Sie dann aber einen zusammenhängenden Bericht erstellen wollen, der den Namen der Person, ihre Adresse sowie ihre Leibspeisen aufführt, benötigen Sie einen Mechanismus, der die Daten aus diesen beiden Tabellen wieder zusammenbringt. Diesen Mechanismus bezeichnet man als Join, und dieses Kapitel wird sich auf die einfachste und verbreitetste Join-Form, den Inner Join, konzentrieren. Die unterschiedlichen Join-Typen wird dann Kapitel 10 behandeln.
Was ist ein Join? Abfragen auf einer einzigen Tabelle sind gewiss keine Seltenheit, aber die meisten Abfragen verwenden doch zwei, drei oder noch mehr Tabellen. Schauen wir uns die Definitionen der Tabellen employee und department an und definieren wir eine Abfrage, die Daten aus beiden Tabellen holt:
Max. Linie
mysql> DESC employee; +--------------------+----------------------+------+-----+------------+ | Field | Type | Null | Key | Default | +--------------------+----------------------+------+-----+------------+ | emp_id | smallint(5) unsigned | | PRI | NULL | | fname | varchar(20) | | | | | lname | varchar(20) | | | | | start_date | date | | | 0000-00-00 | | end_date | date | YES | | NULL | | superior_emp_id | smallint(5) unsigned | YES | MUL | NULL | | dept_id | smallint(5) unsigned | YES | MUL | NULL | | title | varchar(20) | YES | | NULL | | assigned_branch_id | smallint(5) unsigned | YES | MUL | NULL | +--------------------+----------------------+------+-----+------------+ 9 rows in set (0.11 sec)
Max. Linie | 81
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Links mysql> DESC department; +---------+----------------------+------+-----+---------+ | Field | Type | Null | Key | Default | +---------+----------------------+------+-----+---------+ | dept_id | smallint(5) unsigned | | PRI | NULL | | name | varchar(20) | | | | +---------+----------------------+------+-----+---------+ 2 rows in set (0.03 sec)
Angenommen, Sie möchten den Vor- und Nachnamen jedes Mitarbeiters zusammen mit dem Namen seiner Abteilung abfragen. Also müssen Sie die Spalten employee.fname, employee.lname und department.name anschauen. Doch wie kann man Daten aus beiden Tabellen mit einer einzigen Abfrage laden? Die Antwort liegt in der Spalte employee.dept_ id, in der für jeden Mitarbeiter die Kennnummer seiner Abteilung gespeichert ist (formaler ausgedrückt, ist die Spalte employee.dept_id der Fremdschlüssel zur department-Tabelle). Gleich werden Sie eine Abfrage sehen, die den Server anweist, die Spalte employee. dept_id als Brücke zwischen den Tabellen employee und department zu nutzen. Dadurch wird es möglich, Spalten aus beiden Tabellen in die Ergebnismenge der Abfrage zu laden. Diese Art von Operation bezeichnet man als Join.
Kartesisches Produkt Für den Anfang ist es am einfachsten, die Tabellen employee und department in die fromKlausel einer Abfrage zu packen und zu sehen, was passiert. Die folgende Abfrage liefert die Vor- und Nachnamen der Angestellten zusammen mit der zugehörigen Abteilung, wobei in der from-Klausel beide Tabellen, getrennt durch das Schlüsselwort join, aufgeführt sind:
Max. Linie
mysql> SELECT e.fname, e.lname, d.name -> FROM employee e JOIN department d; +----------+-----------+----------------+ | fname | lname | name | +----------+-----------+----------------+ | Michael | Smith | Operations | | Michael | Smith | Loans | | Michael | Smith | Administration | | Susan | Barker | Operations | | Susan | Barker | Loans | | Susan | Barker | Administration | | Robert | Tyler | Operations | | Robert | Tyler | Loans | | Robert | Tyler | Administration | | Susan | Hawthorne | Operations | | Susan | Hawthorne | Loans | | Susan | Hawthorne | Administration | | John | Gooding | Operations | | John | Gooding | Loans | | John | Gooding | Administration | | Helen | Fleming | Operations |
82 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | Helen | Fleming | Loans | | Helen | Fleming | Administration | | Chris | Tucker | Operations | | Chris | Tucker | Loans | | Chris | Tucker | Administration | | Sarah | Parker | Operations | | Sarah | Parker | Loans | | Sarah | Parker | Administration | | Jane | Grossman | Operations | | Jane | Grossman | Loans | | Jane | Grossman | Administration | | Paula | Roberts | Operations | | Paula | Roberts | Loans | | Paula | Roberts | Administration | | Thomas | Ziegler | Operations | | Thomas | Ziegler | Loans | | Thomas | Ziegler | Administration | | Samantha | Jameson | Operations | | Samantha | Jameson | Loans | | Samantha | Jameson | Administration | | John | Blake | Operations | | John | Blake | Loans | | John | Blake | Administration | | Cindy | Mason | Operations | | Cindy | Mason | Loans | | Cindy | Mason | Administration | | Frank | Portman | Operations | | Frank | Portman | Loans | | Frank | Portman | Administration | | Theresa | Markham | Operations | | Theresa | Markham | Loans | | Theresa | Markham | Administration | | Beth | Fowler | Operations | | Beth | Fowler | Loans | | Beth | Fowler | Administration | | Rick | Tulman | Operations | | Rick | Tulman | Loans | | Rick | Tulman | Administration | +----------+-----------+----------------+ 54 rows in set (0.23 sec)
Max. Linie
Hmmm … es gibt doch nur 18 Mitarbeiter und 3 verschiedene Abteilungen, warum hat die Ergebnismenge dann 54 Zeilen? Wenn Sie genauer hinschauen, werden Sie erkennen, dass die Menge der 18 Mitarbeiter drei Mal wiederholt wird, wobei alle Daten bis auf den Namen der Abteilung jedes Mal gleich sind. Da die Abfrage nichts darüber aussagte, wie die beiden Tabellen verbunden werden sollen, generierte der Datenbankserver das kartesische Produkt, das jede nur denkbare Permutation der beiden Tabellen einbezieht (18 Mitarbeiter mal 3 Abteilungen gleich 54 Permutationen). Diese Art von Join bezeichnet man als Cross Join, und sie wird mit Absicht kaum jemals verwendet. Cross Joins gehören zu den Join-Typen, die in Kapitel 10 genauer beschrieben werden.
Was ist ein Join? | 83 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Inner Joins Um die obige Abfrage so zu modifizieren, dass nur mehr 18 Zeilen in die Ergebnismenge gelangen (eine pro Mitarbeiter), müssen Sie beschreiben, wie die beiden Tabellen zusammenhängen. Weiter oben habe ich gezeigt, dass die employee.dept_id-Spalte als Verbindung zwischen diesen beiden Tabellen fungiert. Diese Information muss also der onSubklausel der from-Klausel hinzugefügt werden: mysql> SELECT e.fname, e.lname, d.name -> FROM employee e JOIN department d -> ON e.dept_id = d.dept_id; +----------+-----------+----------------+ | fname | lname | name | +----------+-----------+----------------+ | Michael | Smith | Administration | | Susan | Barker | Administration | | Robert | Tyler | Administration | | Susan | Hawthorne | Operations | | John | Gooding | Loans | | Helen | Fleming | Operations | | Chris | Tucker | Operations | | Sarah | Parker | Operations | | Jane | Grossman | Operations | | Paula | Roberts | Operations | | Thomas | Ziegler | Operations | | Samantha | Jameson | Operations | | John | Blake | Operations | | Cindy | Mason | Operations | | Frank | Portman | Operations | | Theresa | Markham | Operations | | Beth | Fowler | Operations | | Rick | Tulman | Operations | +----------+-----------+----------------+ 18 rows in set (0.00 sec)
Statt 54 hat die Ergebnismenge jetzt dank der on-Subklausel nur noch 18 Zeilen. Die Subklausel weist den Server an, die Tabellen employee und department auf der Spalte dept_id zu verbinden, um von einer Tabelle zur anderen zu gelangen. Die Zeile von Susan Hawthorne in der Tabelle employee hat beispielsweise die 1 als dept_id-Spaltenwert (im Beispiel nicht gezeigt). Anhand dieses Werts sucht der Server in der department-Tabelle wieder nach dem Wert 1 in der dept_id-Spalte und holt sich aus dieser Zeile dann den Wert 'Operations' aus der Spalte name.
Max. Linie
Wenn für die dept_id-Spalte in einer Tabelle ein Wert existiert und in der anderen nicht, scheitert der Join für die Zeilen, die den Wert enthalten. Sie werden aus der Ergebnismenge ausgeschlossen. Diesen gebräuchlichsten aller Join-Typen nennt man Inner Join. Zur Klarstellung: Wenn die department-Tabelle eine vierte Zeile für die Marketing-Abteilung enthält, dieser Abteilung aber keine Mitarbeiter zugeordnet wurden, dann wird die Marketing-Abteilung in der Ergebnismenge nicht erwähnt. Gleichermaßen gilt: Wenn Mitarbeiter einer Abteilung mit der ID 99 zugewiesen wurden, diese Abteilung aber in der
84 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts department-Tabelle nicht vorkommt, bleiben auch die betreffenden Angestellten aus der
Ergebnismenge ausgeschlossen. Möchten Sie also alle Zeilen einer Tabelle in die Ergebnismenge aufnehmen, egal ob eine dazu passende Zeile in einer anderen Tabelle existiert oder nicht, benötigen Sie einen Outer Join. Damit werden wir uns aber erst später im Buch befassen. In obigem Beispiel habe ich in der from-Klausel nicht angegeben, welcher Join-Typ verwendet werden soll. Wenn Sie jedoch zwei Tabellen mit einem Inner Join verbinden möchten, sollten Sie dies in der from-Klausel auch explizit sagen. Hier sehen Sie das Beispiel noch einmal, dieses Mal aber mit Nennung des Join-Typs (beachten Sie das Schlüsselwort INNER): mysql> SELECT e.fname, e.lname, d.name -> FROM employee e INNER JOIN department d -> ON e.dept_id = d.dept_id; +----------+-----------+----------------+ | fname | lname | name | +----------+-----------+----------------+ | Michael | Smith | Administration | | Susan | Barker | Administration | | Robert | Tyler | Administration | | Susan | Hawthorne | Operations | | John | Gooding | Loans | | Helen | Fleming | Operations | | Chris | Tucker | Operations | | Sarah | Parker | Operations | | Jane | Grossman | Operations | | Paula | Roberts | Operations | | Thomas | Ziegler | Operations | | Samantha | Jameson | Operations | | John | Blake | Operations | | Cindy | Mason | Operations | | Frank | Portman | Operations | | Theresa | Markham | Operations | | Beth | Fowler | Operations | | Rick | Tulman | Operations | +----------+-----------+----------------+ 18 rows in set (0.00 sec)
Wenn Sie keinen Join-Typ angeben, führt der Server nach Voreinstellung einen Inner Join aus. Doch wie Sie weiter unten im Buch sehen werden, gibt es auch noch andere JoinTypen. Machen Sie es sich zur Gewohnheit, immer genau den gewünschten Join-Typ anzugeben. Sind die Namen der beiden Spalten, auf denen die zwei Tabellen verbunden werden, identisch, wie es auch in der obigen Abfrage der Fall ist, können Sie die using-Subklausel an Stelle der on-Subklausel benutzen:
Max. Linie
mysql> SELECT e.fname, e.lname, d.name -> FROM employee e INNER JOIN department d -> USING (dept_id);
Was ist ein Join? | 85 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links +----------+-----------+----------------+ | fname | lname | name | +----------+-----------+----------------+ | Michael | Smith | Administration | | Susan | Barker | Administration | | Robert | Tyler | Administration | | Susan | Hawthorne | Operations | | John | Gooding | Loans | | Helen | Fleming | Operations | | Chris | Tucker | Operations | | Sarah | Parker | Operations | | Jane | Grossman | Operations | | Paula | Roberts | Operations | | Thomas | Ziegler | Operations | | Samantha | Jameson | Operations | | John | Blake | Operations | | Cindy | Mason | Operations | | Frank | Portman | Operations | | Theresa | Markham | Operations | | Beth | Fowler | Operations | | Rick | Tulman | Operations | +----------+-----------+----------------+ 18 rows in set (0.01 sec)
Da using eine Abkürzung ist, die nur in bestimmten Situationen verwendet werden soll, ziehe ich es allerdings vor, immer die on-Subklausel zu benutzen, um Verwirrung zu vermeiden.
Die Join-Syntax von ANSI Die Notation, die in diesem Buch für Tabellen-Joins verwendet wird, wurde in der SQL92-Version des ANSI-SQL-Standards eingeführt. Alle bedeutenden Datenbanksysteme (Oracle Database, Microsoft SQL Server, MySQL, IBM DB2 Universal Database und Sybase Adaptive Server) haben diese SQL92-Join-Syntax übernommen. Da die meisten dieser Server schon vor der Veröffentlichung der SQL92-Spezifikation auf dem Markt waren, kennen sie alle auch noch die ältere Join-Syntax. So würden beispielsweise alle diese Server auch folgende Abwandlung der obigen Abfrage verstehen:
Max. Linie
mysql> SELECT e.fname, e.lname, d.name -> FROM employee e, department d -> WHERE e.dept_id = d.dept_id; +----------+-----------+----------------+ | fname | lname | name | +----------+-----------+----------------+ | Michael | Smith | Administration | | Susan | Barker | Administration | | Robert | Tyler | Administration | | Susan | Hawthorne | Operations | | John | Gooding | Loans | | Helen | Fleming | Operations | | Chris | Tucker | Operations | | Sarah | Parker | Operations |
86 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | Jane | Grossman | Operations | | Paula | Roberts | Operations | | Thomas | Ziegler | Operations | | Samantha | Jameson | Operations | | John | Blake | Operations | | Cindy | Mason | Operations | | Frank | Portman | Operations | | Theresa | Markham | Operations | | Beth | Fowler | Operations | | Rick | Tulman | Operations | +----------+-----------+----------------+ 18 rows in set (0.01 sec)
Diese ältere Join-Methode kennt keine on-Subklausel, sondern nennt die Tabellen in der from-Klausel als kommaseparierte Liste und die Join-Bedingungen in der where-Klausel. Wenn Sie nun die SQL92-Syntax aufgeben und stattdessen die ältere Join-Syntax verwenden möchten, sollten Sie bedenken, dass die Join-Syntax des ANSI folgende Vorteile hat: • Join-Bedingungen und Filterbedingungen werden auf zwei verschiedene Klauseln verteilt (die on-Subklausel und die where-Klausel). Dadurch ist die Abfrage leichter zu verstehen. • Die Join-Bedingungen für jedes Tabellenpaar stehen jeweils in einer eigenen on-Klausel. Dadurch sinkt das Risiko, dass ein Teil eines Joins versehentlich weggelassen wird. • Abfragen mit der Join-Syntax von SQL92 sind über verschiedene Datenbankserver portierbar, während die ältere Syntax auf den Servern immer ein wenig unterschiedlich gehandhabt wird. Die Vorteile der SQL92-Join-Syntax werden bei komplexen Abfragen offenbar, die sowohl Join- als auch Filterbedingungen enthalten. Folgende Abfrage gibt alle Konten zurück, die von erfahrenen (vor 2007 eingestellten) Schalterangestellten der Zweigstelle Woburn eröffnet wurden:
Max. Linie
mysql> SELECT a.account_id, a.cust_id, a.open_date, a.product_cd -> FROM account a, branch b, employee e -> WHERE a.open_emp_id = e.emp_id -> AND e.start_date < '2007-01-01' -> AND e.assigned_branch_id = b.branch_id -> AND (e.title = 'Teller' OR e.title = 'Head Teller') -> AND b.name = 'Woburn Branch'; +------------+---------+------------+------------+ | account_id | cust_id | open_date | product_cd | +------------+---------+------------+------------+ | 1 | 1 | 2000-01-15 | CHK | | 2 | 1 | 2000-01-15 | SAV | | 3 | 1 | 2004-06-30 | CD | | 4 | 2 | 2001-03-12 | CHK | | 5 | 2 | 2001-03-12 | SAV | | 17 | 7 | 2004-01-12 | CD | | 27 | 11 | 2004-03-22 | BUS | +------------+---------+------------+------------+ 7 rows in set (0.00 sec)
Was ist ein Join? | 87 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links Bei dieser Abfrage lässt sich nicht so einfach feststellen, welche Bedingungen der whereKlausel Join-Bedingungen und welche Filterbedingungen sind. Auch bleibt es unklar, welcher Join-Typ eigentlich verlangt wird (um den Join-Typ zu erkennen, müssen Sie die Join-Bedingungen in der where-Klausel auf Sonderzeichen untersuchen), und es ist kaum zu erkennen, ob irgendwelche Join-Bedingungen versehentlich weggelassen wurden. Hier sehen Sie die gleiche Abfrage mit der SQL92-Join-Syntax: mysql> SELECT a.account_id, a.cust_id, a.open_date, a.product_cd -> FROM account a INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> INNER JOIN branch b -> ON e.assigned_branch_id = b.branch_id -> WHERE e.start_date < '2007-01-01' -> AND (e.title = 'Teller' OR e.title = 'Head Teller') -> AND b.name = 'Woburn Branch'; +------------+---------+------------+------------+ | account_id | cust_id | open_date | product_cd | +------------+---------+------------+------------+ | 1 | 1 | 2000-01-15 | CHK | | 2 | 1 | 2000-01-15 | SAV | | 3 | 1 | 2004-06-30 | CD | | 4 | 2 | 2001-03-12 | CHK | | 5 | 2 | 2001-03-12 | SAV | | 17 | 7 | 2004-01-12 | CD | | 27 | 11 | 2004-03-22 | BUS | +------------+---------+------------+------------+ 7 rows in set (0.05 sec)
Hoffentlich stimmen Sie mir zu, dass die Version mit der SQL92-Join-Syntax verständlicher ist.
Joins mit drei oder mehr Tabellen Mit einem Join lassen sich nicht nur zwei, sondern auch drei Tabellen verbinden, allerdings mit einem kleinen Kniff. Bei zwei Tabellen haben Sie eben diese beiden Tabellen, einen Join-Typ in der from-Klausel und eine einzige on-Subklausel, um zu definieren, wie die Tabellen verbunden werden. Bei drei Tabellen haben Sie die drei Tabellen, zwei JoinTypen in der from-Klausel und zwei on-Subklauseln. Hier ist ein weiteres Beispiel mit einem Zwei-Tabellen-Join:
Max. Linie
mysql> SELECT a.account_id, c.fed_id -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id -> WHERE c.cust_type_cd = 'B'; +------------+------------+ | account_id | fed_id | +------------+------------+ | 24 | 04-1111111 | | 25 | 04-1111111 | | 27 | 04-2222222 |
88 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 28 | 04-3333333 | | 29 | 04-4444444 | +------------+------------+ 5 rows in set (0.15 sec)
Diese Abfrage, die für jedes Firmenkonto eine Kontonummer und eine Steuernummer zurückgibt, müsste mittlerweile für Sie kein Problem sein. Wenn Sie jedoch die employeeTabelle hinzufügen, um herauszufinden, welcher Kassierer das jeweilige Konto eröffnet hat, sieht die Abfrage folgendermaßen aus: mysql> SELECT a.account_id, c.fed_id, e.fname, e.lname -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id -> INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> WHERE c.cust_type_cd = 'B'; +------------+------------+---------+---------+ | account_id | fed_id | fname | lname | +------------+------------+---------+---------+ | 24 | 04-1111111 | Theresa | Markham | | 25 | 04-1111111 | Theresa | Markham | | 27 | 04-2222222 | Paula | Roberts | | 28 | 04-3333333 | Theresa | Markham | | 29 | 04-4444444 | John | Blake | +------------+------------+---------+---------+ 5 rows in set (0.00 sec)
Da jetzt drei Tabellen, zwei Join-Typen und zwei on-Subklauseln in der from-Klausel stehen, stellen sich die Dinge etwas komplizierter dar. Auf den ersten Blick könnte man aus der Reihenfolge der Tabellen schließen, dass die employee-Tabelle mit der customerTabelle verbunden wird, da zuerst die account-Tabelle, dann die customer-Tabelle und zum Schluss die employee-Tabelle erwähnt wird. Wenn Sie die Reihenfolge der ersten beiden Tabellen umstellen, bekommen Sie jedoch genau die gleichen Ergebnisse: mysql> SELECT a.account_id, c.fed_id, e.fname, e.lname -> FROM customer c INNER JOIN account a -> ON a.cust_id = c.cust_id -> INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> WHERE c.cust_type_cd = 'B'; +------------+------------+---------+---------+ | account_id | fed_id | fname | lname | +------------+------------+---------+---------+ | 24 | 04-1111111 | Theresa | Markham | | 25 | 04-1111111 | Theresa | Markham | | 27 | 04-2222222 | Paula | Roberts | | 28 | 04-3333333 | Theresa | Markham | | 29 | 04-4444444 | John | Blake | +------------+------------+---------+---------+ 5 rows in set (0.09 sec)
Max. Linie
Nun steht die customer-Tabelle an erster Stelle, gefolgt von der account- und der employeeTabelle. Da die on-Subklauseln gleich geblieben sind, ist auch das Ergebnis unverändert.
Joins mit drei oder mehr Tabellen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 89
Max. Linie
Links Der Vollständigkeit halber sehen Sie hier die gleiche Abfrage noch ein letztes Mal, aber diesmal mit einer vollkommen umgekehrten Abfolge der Tabellen (employee zu account zu customer): mysql> SELECT a.account_id, c.fed_id, e.fname, e.lname -> FROM employee e INNER JOIN account a -> ON e.emp_id = a.open_emp_id -> INNER JOIN customer c -> ON a.cust_id = c.cust_id -> WHERE c.cust_type_cd = 'B'; +------------+------------+---------+---------+ | account_id | fed_id | fname | lname | +------------+------------+---------+---------+ | 24 | 04-1111111 | Theresa | Markham | | 25 | 04-1111111 | Theresa | Markham | | 27 | 04-2222222 | Paula | Roberts | | 28 | 04-3333333 | Theresa | Markham | | 29 | 04-4444444 | John | Blake | +------------+------------+---------+---------+ 5 rows in set (0.00 sec)
Ist die Join-Abfolge relevant? Wenn Sie sich fragen, warum alle drei Versionen der account/employee/customer-Abfrage das gleiche Ergebnis liefern, sollten Sie daran denken, dass SQL keine prozedurale Programmiersprache ist. Das bedeutet, dass Sie zwar beschreiben, was Sie haben möchten und welche Datenbank beteiligt werden sollte, aber der Datenbankserver entscheidet, wie Ihre Abfragen am besten ausgeführt werden. Auf Basis von Statistiken, die von Ihren Datenbankobjekten gesammelt wurden, muss der Server eine der drei Tabellen als Startpunkt nehmen (die von ihm gewählte Tabelle wird daher als treibende Tabelle bezeichnet) und entscheiden, in welcher Reihenfolge die verbleibenden Tabellen verknüpft werden sollen. Deswegen ist die Reihenfolge, in der die Tabellen in Ihrer from-Klausel erscheinen, irrelevant. Sollten Sie allerdings der Meinung sein, dass die Tabellen in Ihrer Abfrage immer in einer bestimmten Abfolge verknüpft werden sollten, können Sie die Tabellen in der gewünschten Abfolge angeben und dann unter MySQL das Schlüsselwort STRAIGHT_JOIN angeben, unter SQL Server die Option FORCE ORDER verlangen oder unter Oracle einen der Optimizer-Hints ORDERED oder LEADING verwenden. Um MySQL zu sagen, dass es die customerTabelle als treibende Tabelle einsetzen und dann die Tabellen account und employee anknüpfen soll, könnten Sie beispielsweise Folgendes machen:
Max. Linie
mysql> SELECT STRAIGHT_JOIN a.account_id, c.fed_id, e.fname, e.lname -> FROM customer c INNER JOIN account a -> ON a.cust_id = c.cust_id -> INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> WHERE c.cust_type_cd = 'B';
90 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Eine Abfrage mit drei oder mehr Tabellen ist wie ein Schneeball, der einen Hügel herunterkullert. Die ersten beiden Tabellen bringen den Ball ins Rollen, und jede weitere Tabelle bleibt an dem Schneeball haften, wenn er seinen Weg ins Tal nimmt. Den Schneeball kann man sich als das Zwischenergebnis vorstellen, das in dem Maße, wie weitere Tabellen hinzukommen, mehr und mehr Spalten aufnimmt. Somit wird die employeeTabelle nicht wirklich mit der account-Tabelle verbunden, sondern mit der Zwischenergebnismenge, die beim Join der Tabellen customer und account entstand. (Falls Sie sich fragen, wie ich auf die Schneeball-Analogie gekommen bin: Ich schrieb dieses Kapitel mitten im New England-Winter: Fast drei Meter Schnee sind schon gefallen, und morgen soll es noch mehr werden. Oh joy.)
Unterabfragen als Tabellen Jetzt kennen Sie bereits mehrere Beispiele für Abfragen mit drei Tabellen, aber eine weitere Variante möchte ich nicht unerwähnt lassen: Was tun, wenn einige der Tabellen von Unterabfragen generiert werden? Eigentlich sind Unterabfragen das Thema von Kapitel 9, aber was eine Unterabfrage in der from-Klausel ist, habe ich im vorigen Kapitel bereits angesprochen. Hier ist eine neue Version der früheren Abfrage (finde alle Konten, die von erfahrenen Kassierern der Zweigstelle Woburn eröffnet wurden): Dieses Mal wird die account-Tabelle mit Unterabfragen der Tabellen branch und employee verbunden: 1 SELECT a.account_id, a.cust_id, a.open_date, a.product_cd 2 FROM account a INNER JOIN 3 (SELECT emp_id, assigned_branch_id 4 FROM employee 5 WHERE start_date < '2007-01-01' 6 AND (title = 'Teller' OR title = 'Head Teller')) e 7 ON a.open_emp_id = e.emp_id 8 INNER JOIN 9 (SELECT branch_id 10 FROM branch 11 WHERE name = 'Woburn Branch') b 12 ON e.assigned_branch_id = b.branch_id;
Die erste Unterabfrage, die auf Zeile 3 anfängt und den Alias e bekommt, findet alle erfahrenen Kassierer heraus. Die zweite, die auf Zeile 9 beginnt und b heißt, findet die ID der Zweigstelle Woburn. Zuerst wird die account-Tabelle unter Verwendung der Personalnummer (Employee ID) mit der Unterabfrage nach den unerfahrenen Kassierern verbunden, und dann wird die resultierende Tabelle unter Verwendung der Zweigstellennummer (Branch ID) mit der Unterabfrage nach der Zweigstelle Woburn verbunden. Die Ergebnisse sind die gleichen wie bei der vorherigen Version der Abfrage (sehen Sie selbst), aber dennoch sehen die Abfragen völlig verschieden aus.
Max. Linie
Zwar ist hier nichts Beängstigendes zu sehen, aber nehmen Sie sich etwas Zeit, um zu verstehen, was abläuft. Beispielsweise steht in der Hauptabfrage keine where-Klausel: Da alle Filterbedingungen nur die Tabellen employee und branch betreffen, befinden sie sich in den Unterabfragen. In der Hauptabfrage gibt es keinen Bedarf für Filterbedingungen. Um
Joins mit drei oder mehr Tabellen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
| 91
Max. Linie
Links zu veranschaulichen, was hier geschieht, kann man die Unterabfragen separat ausführen und die Ergebnismengen anschauen. Hier sehen Sie die Ergebnisse der ersten Unterabfrage auf der employee-Tabelle: mysql> SELECT emp_id, assigned_branch_id -> FROM employee -> WHERE start_date < '2007-01-01' -> AND (title = 'Teller' OR title = 'Head Teller'); +--------+--------------------+ | emp_id | assigned_branch_id | +--------+--------------------+ | 8 | 1 | | 9 | 1 | | 10 | 2 | | 11 | 2 | | 13 | 3 | | 14 | 3 | | 16 | 4 | | 17 | 4 | | 18 | 4 | +--------+--------------------+ 9 rows in set (0.03 sec)
Diese Ergebnismenge besteht also aus einer Reihe von Personalnummern (Employee IDs) und den zugehörigen Zweigstellennummern (Branch IDs). Wenn man diese auf der Spalte emp_id mit der account-Tabelle verbindet, erhält man als Zwischenergebnis alle Zeilen der account-Tabelle mit einer zusätzlichen Spalte, in der die Zweigstellennummer des Mitarbeiters erscheint, der das Konto eröffnet hat. Die Ergebnisse der zweiten Unterabfrage auf der branch-Tabelle sind: mysql> SELECT branch_id -> FROM branch -> WHERE name = 'Woburn Branch'; +-----------+ | branch_id | +-----------+ | 2 | +-----------+ 1 row in set (0.02 sec)
Diese Abfrage liefert eine einzige Zeile mit einer einzigen Spalte: der Kennnummer der Zweigstelle Woburn. Diese Tabelle wird mit der Spalte assigned_branch_id der Zwischenergebnismenge verbunden, sodass alle Konten, die nicht von Mitarbeitern der Zweigstelle Woburn eröffnet wurden, aus der endgültigen Ergebnismenge herausgefiltert werden.
Zweimal dieselbe Tabelle verwenden
Max. Linie
Wenn Sie mehrere Tabellen mit einem Join verbinden, kann es geschehen, dass ein- und dieselbe Tabelle mehrmals benötigt wird. In der Musterdatenbank gibt es beispielsweise Fremdschlüssel zur branch-Tabelle sowohl in der Tabelle account (die Zweigstelle, in der
92 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts das Konto eröffnet wurde) als auch in der Tabelle employee (die Zweigstelle, in der der Mitarbeiter arbeitet). Wenn Sie beide Zweigstellen in Ihrer Ergebnismenge haben möchten, können Sie die branch-Tabelle zweimal in die from-Klausel schreiben: einmal in einem Join mit der employee-Tabelle und einmal in einem Join mit der account-Tabelle. Damit das funktioniert, müssen Sie aber jeder Instanz der branch-Tabelle einen anderen Alias geben, damit der Server weiß, welche in den verschiedenen Klauseln jeweils gemeint ist: mysql> SELECT a.account_id, e.emp_id, -> b_a.name open_branch, b_e.name emp_branch -> FROM account a INNER JOIN branch b_a -> ON a.open_branch_id = b_a.branch_id -> INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> INNER JOIN branch b_e -> ON e.assigned_branch_id = b_e.branch_id -> WHERE a.product_cd = 'CHK'; +------------+--------+---------------+---------------+ | account_id | emp_id | open_branch | emp_branch | +------------+--------+---------------+---------------+ | 10 | 1 | Headquarters | Headquarters | | 14 | 1 | Headquarters | Headquarters | | 21 | 1 | Headquarters | Headquarters | | 1 | 10 | Woburn Branch | Woburn Branch | | 4 | 10 | Woburn Branch | Woburn Branch | | 7 | 13 | Quincy Branch | Quincy Branch | | 13 | 16 | So. NH Branch | So. NH Branch | | 18 | 16 | So. NH Branch | So. NH Branch | | 24 | 16 | So. NH Branch | So. NH Branch | | 28 | 16 | So. NH Branch | So. NH Branch | +------------+--------+---------------+---------------+ 10 rows in set (0.16 sec)
Diese Abfrage zeigt an, wer wo welches Girokonto eröffnet. Die branch-Tabelle wird zweimal verwendet, und zwar mit den Aliasen b_a und b_e. Durch die unterschiedlichen Aliase der branch-Tabelle kann der Server verstehen, welche Instanz der Tabelle jeweils gemeint ist: diejenige, die mit der account-Tabelle verbunden wurde, oder die, die mit der employee-Tabelle verbunden wurde. Somit ist dies ein Beispiel für eine Abfrage, in der Tabellen-Aliase sogar obligatorisch sind.
Self-Joins
Max. Linie
Eine Tabelle kann nicht nur zweimal in derselben Abfrage verwendet, sondern sogar mit sich selbst verbunden werden. Zunächst mag das seltsam klingen, aber es gibt gute Gründe, dies gelegentlich zu tun. Die employee-Tabelle enthält beispielsweise einen selbstreferenzierenden Fremdschlüssel: eine Spalte (superior_emp_id), die auf den Primärschlüssel derselben Tabelle verweist. Diese Spalte referenziert den jeweiligen Vorgesetzten eines Mitarbeiters (wenn der Mitarbeiter nicht selbst der Chef des Ganzen ist; dann ist
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Self-Joins | 93
Max. Linie
Links die Spalte natürlich null). Mit einem Self-Join können Sie eine Abfrage schreiben, die die Namen der Angestellten zusammen mit ihren jeweiligen Vorgesetzten aufführt: mysql> SELECT e.fname, e.lname, -> e_mgr.fname mgr_fname, e_mgr.lname mgr_lname -> FROM employee e INNER JOIN employee e_mgr -> ON e.superior_emp_id = e_mgr.emp_id; +----------+-----------+-----------+-----------+ | fname | lname | mgr_fname | mgr_lname | +----------+-----------+-----------+-----------+ | Susan | Barker | Michael | Smith | | Robert | Tyler | Michael | Smith | | Susan | Hawthorne | Robert | Tyler | | John | Gooding | Susan | Hawthorne | | Helen | Fleming | Susan | Hawthorne | | Chris | Tucker | Helen | Fleming | | Sarah | Parker | Helen | Fleming | | Jane | Grossman | Helen | Fleming | | Paula | Roberts | Susan | Hawthorne | | Thomas | Ziegler | Paula | Roberts | | Samantha | Jameson | Paula | Roberts | | John | Blake | Susan | Hawthorne | | Cindy | Mason | John | Blake | | Frank | Portman | John | Blake | | Theresa | Markham | Susan | Hawthorne | | Beth | Fowler | Theresa | Markham | | Rick | Tulman | Theresa | Markham | +----------+-----------+-----------+-----------+ 17 rows in set (0.00 sec)
Diese Abfrage enthält zwei Instanzen der employee-Tabelle: Eine liefert die Namen der Angestellten (und hat den Tabellen-Alias e), und die andere liefert die Namen der vorgesetzten Manager (und hat den Tabellen-Alias e_mgr). Die on-Subklausel nutzt diese Aliase, um die employee-Tabelle über den Fremdschlüssel superior_emp_id mit sich selbst zu verbinden. Dies ist ein anderes Beispiel für eine Abfrage, in der man auf Tabellen-Aliase nicht verzichten kann; andernfalls wüsste der Server nicht, ob Sie sich auf einen Angestellten oder seinen Vorgesetzten beziehen. Die employee-Tabelle enthält zwar 18 Zeilen, von denen aber nur 17 zurückgegeben werden; da der Vorstand der Bank, Michael Smith, keinen Vorgesetzten hat (seine superior_ emp_id-Spalte hat den Wert null), schlug der Join auf seiner Zeile fehl. Wollte man auch Michael Smith in die Ergebnismenge aufnehmen, müsste man einen Outer Join verwenden. Dieser wird aber erst in Kapitel 10 behandelt.
Equi-Joins und Non-Equi-Joins Max. Linie
Alle bisher gezeigten Abfragen auf mehreren Tabellen verwendeten Equi-Joins, bei denen die Werte der beiden Tabellen übereinstimmen müssen, damit der Join Erfolg hat. Ein Equi-Join verwendet immer ein Gleichheitszeichen, wie hier zu sehen: ON e.assigned_branch_id = b.branch_id
94 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Nun werden zwar auch Sie überwiegend mit Equi-Joins arbeiten, aber gelegentlich vielleicht Ihre Tabellen auf Wertebereichen verbinden. Dann spricht man von Non-EquiJoins. Hier sehen Sie ein Beispiel einer Abfrage, die so verfährt: SELECT e.emp_id, e.fname, e.lname, e.start_date FROM employee e INNER JOIN product p ON e.start_date >= p.date_offered AND e.start_date SELECT e1.fname, e1.lname, 'VS' vs, e2.fname, e2.lname -> FROM employee e1 INNER JOIN employee e2 -> ON e1.emp_id != e2.emp_id -> WHERE e1.title = 'Teller' AND e2.title = 'Teller'; +----------+----------+----+----------+----------+ | fname | lname | vs | fname | lname | +----------+----------+----+----------+----------+ | Sarah | Parker | VS | Chris | Tucker | | Jane | Grossman | VS | Chris | Tucker | | Thomas | Ziegler | VS | Chris | Tucker | | Samantha | Jameson | VS | Chris | Tucker | | Cindy | Mason | VS | Chris | Tucker | | Frank | Portman | VS | Chris | Tucker | | Beth | Fowler | VS | Chris | Tucker | | Rick | Tulman | VS | Chris | Tucker | | Chris | Tucker | VS | Sarah | Parker | | Jane | Grossman | VS | Sarah | Parker | | Thomas | Ziegler | VS | Sarah | Parker | | Samantha | Jameson | VS | Sarah | Parker | | Cindy | Mason | VS | Sarah | Parker | | Frank | Portman | VS | Sarah | Parker | | Beth | Fowler | VS | Sarah | Parker | | Rick | Tulman | VS | Sarah | Parker | ... | Chris | Tucker | VS | Rick | Tulman | | Sarah | Parker | VS | Rick | Tulman | | Jane | Grossman | VS | Rick | Tulman | | Thomas | Ziegler | VS | Rick | Tulman |
Equi-Joins und Non-Equi-Joins | 95 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Links | Samantha | Jameson | VS | Rick | Tulman | | Cindy | Mason | VS | Rick | Tulman | | Frank | Portman | VS | Rick | Tulman | | Beth | Fowler | VS | Rick | Tulman | +----------+----------+----+----------+----------+ 72 rows in set (0.01 sec)
Damit sind Sie bereits auf dem richtigen Weg, aber das Problem besteht hier darin, dass für jedes Spielerpaar (beispielsweise Sarah Parker gegen Chris Tucker) auch ein Paar mit umgekehrt angeordneten Namen existiert (d.h. Chris Tucker gegen Sarah Parker). Man könnte die gewünschten Ergebnisse auch mit der Join-Bedingung e1.emp_id < e2.emp_id bekommen, sodass jeder Kassierer nur gegen Kassierer spielt, die eine höhere Personalnummer haben (wenn Sie wollen, können Sie natürlich auch e1.emp_id > e2.emp_id sagen):
Max. Linie
mysql> SELECT e1.fname, e1.lname, 'VS' vs, e2.fname, e2.lname -> FROM employee e1 INNER JOIN employee e2 -> ON e1.emp_id < e2.emp_id -> WHERE e1.title = 'Teller' AND e2.title = 'Teller'; +----------+----------+----+----------+----------+ | fname | lname | vs | fname | lname | +----------+----------+----+----------+----------+ | Chris | Tucker | VS | Sarah | Parker | | Chris | Tucker | VS | Jane | Grossman | | Sarah | Parker | VS | Jane | Grossman | | Chris | Tucker | VS | Thomas | Ziegler | | Sarah | Parker | VS | Thomas | Ziegler | | Jane | Grossman | VS | Thomas | Ziegler | | Chris | Tucker | VS | Samantha | Jameson | | Sarah | Parker | VS | Samantha | Jameson | | Jane | Grossman | VS | Samantha | Jameson | | Thomas | Ziegler | VS | Samantha | Jameson | | Chris | Tucker | VS | Cindy | Mason | | Sarah | Parker | VS | Cindy | Mason | | Jane | Grossman | VS | Cindy | Mason | | Thomas | Ziegler | VS | Cindy | Mason | | Samantha | Jameson | VS | Cindy | Mason | | Chris | Tucker | VS | Frank | Portman | | Sarah | Parker | VS | Frank | Portman | | Jane | Grossman | VS | Frank | Portman | | Thomas | Ziegler | VS | Frank | Portman | | Samantha | Jameson | VS | Frank | Portman | | Cindy | Mason | VS | Frank | Portman | | Chris | Tucker | VS | Beth | Fowler | | Sarah | Parker | VS | Beth | Fowler | | Jane | Grossman | VS | Beth | Fowler | | Thomas | Ziegler | VS | Beth | Fowler | | Samantha | Jameson | VS | Beth | Fowler | | Cindy | Mason | VS | Beth | Fowler | | Frank | Portman | VS | Beth | Fowler | | Chris | Tucker | VS | Rick | Tulman | | Sarah | Parker | VS | Rick | Tulman | | Jane | Grossman | VS | Rick | Tulman |
96 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | Thomas | Ziegler | VS | Rick | Tulman | | Samantha | Jameson | VS | Rick | Tulman | | Cindy | Mason | VS | Rick | Tulman | | Frank | Portman | VS | Rick | Tulman | | Beth | Fowler | VS | Rick | Tulman | +----------+----------+----+----------+----------+ 36 rows in set (0.00 sec)
Jetzt haben Sie eine Liste von 36 Spielerpaaren, also genau die richtige Anzahl für Fälle, in denen Paare von neun verschiedenen Entitäten gesucht werden.
Join-Bedingungen und Filterbedingungen Sie wissen nun, dass Join-Bedingungen in die on-Subklausel und Filterbedingungen in die where-Klausel gehören. Da SQL jedoch flexibel ist, was die Platzierung der Bedingungen angeht, müssen Sie beim Aufbau Ihrer Abfragen gut aufpassen. Die folgende Abfrage verbindet beispielsweise zwei Tabellen mit einem einfachen Join und hat nur eine einzige Filterbedingung in der where-Klausel: mysql> SELECT a.account_id, a.product_cd, c.fed_id -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id -> WHERE c.cust_type_cd = 'B'; +------------+------------+------------+ | account_id | product_cd | fed_id | +------------+------------+------------+ | 24 | CHK | 04-1111111 | | 25 | BUS | 04-1111111 | | 27 | BUS | 04-2222222 | | 28 | CHK | 04-3333333 | | 29 | SBL | 04-4444444 | +------------+------------+------------+ 5 rows in set (0.01 sec)
Das ist einfach, aber was geschieht, wenn Sie versehentlich die Filterbedingung in die onSubklausel statt in die where-Klausel schreiben?
Max. Linie
mysql> SELECT a.account_id, a.product_cd, c.fed_id -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id -> AND c.cust_type_cd = 'B'; +------------+------------+------------+ | account_id | product_cd | fed_id | +------------+------------+------------+ | 24 | CHK | 04-1111111 | | 25 | BUS | 04-1111111 | | 27 | BUS | 04-2222222 | | 28 | CHK | 04-3333333 | | 29 | SBL | 04-4444444 | +------------+------------+------------+ 5 rows in set (0.01 sec)
Join-Bedingungen und Filterbedingungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie | 97
Links Diese zweite Version, in der beide Bedingungen in der on-Subklausel stehen und die where-Klausel fehlt, generiert die gleichen Resultate. Was passiert, wenn man beide Bedingungen in die where-Klausel setzt, aber die from-Klausel nach wie vor die ANSI-JoinSyntax verwendet? mysql> SELECT a.account_id, a.product_cd, c.fed_id -> FROM account a INNER JOIN customer c -> WHERE a.cust_id = c.cust_id -> AND c.cust_type_cd = 'B'; +------------+------------+------------+ | account_id | product_cd | fed_id | +------------+------------+------------+ | 24 | CHK | 04-1111111 | | 25 | BUS | 04-1111111 | | 27 | BUS | 04-2222222 | | 28 | CHK | 04-3333333 | | 29 | SBL | 04-4444444 | +------------+------------+------------+ 5 rows in set (0.01 sec)
Der MySQL-Server hat schon wieder die gleiche Ergebnismenge generiert. Es ist also an Ihnen, die Bedingungen an den richtigen Platz zu setzen, damit Ihre Abfragen verständlich und leicht zu pflegen sind.
Testen Sie Ihr Wissen Die folgenden Übungen sollen Ihr Wissen über Inner Joins festigen. Die Lösungen finden Sie in Anhang C.
Übung 5-1 Füllen Sie die (durch gekennzeichneten) Lücken der folgenden Abfrage aus, um die gezeigten Resultate zu erhalten:
Max. Linie
mysql> SELECT e.emp_id, e.fname, e.lname, b.name -> FROM employee e INNER JOIN b -> ON e.assigned_branch_id = b.; +--------+----------+-----------+---------------+ | emp_id | fname | lname | name | +--------+----------+-----------+---------------+ | 1 | Michael | Smith | Headquarters | | 2 | Susan | Barker | Headquarters | | 3 | Robert | Tyler | Headquarters | | 4 | Susan | Hawthorne | Headquarters | | 5 | John | Gooding | Headquarters | | 6 | Helen | Fleming | Headquarters | | 7 | Chris | Tucker | Headquarters | | 8 | Sarah | Parker | Headquarters |
98 | Kapitel 5: Mehrere Tabellen abfragen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 9 | Jane | Grossman | Headquarters | | 10 | Paula | Roberts | Woburn Branch | | 11 | Thomas | Ziegler | Woburn Branch | | 12 | Samantha | Jameson | Woburn Branch | | 13 | John | Blake | Quincy Branch | | 14 | Cindy | Mason | Quincy Branch | | 15 | Frank | Portman | Quincy Branch | | 16 | Theresa | Markham | So. NH Branch | | 17 | Beth | Fowler | So. NH Branch | | 18 | Rick | Tulman | So. NH Branch | +--------+----------+-----------+---------------+ 18 rows in set (0.03 sec)
Übung 5-2 Schreiben Sie eine Abfrage, die die Kontonummer (Account ID) jedes Privatkunden (customer.cust_type_cd = 'I') zusammen mit seiner Federal ID (customer.fed_id) und dem Namen des Produkts ausgibt, auf dem sein Konto basiert (product.name).
Übung 5-3 Schreiben Sie eine Abfrage, die alle Mitarbeiter findet, deren Vorgesetzter einer anderen Abteilung angehört. Fragen Sie die Employee IDs, den Vornamen (First Name) und den Nachnamen (Last Name) des Mitarbeiters ab.
Max. Linie
Max. Linie Testen Sie Ihr Wissen | 99 This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 6
KAPITEL 6
Umgang mit Mengen
Die Daten einer Datenbank kann man zwar ganz gut zeilenweise betrachten, aber eigentlich dreht sich in relationalen Datenbanken ja alles um Mengen. Sie wissen bereits, wie man Tabellen durch Abfragen und Unterabfragen erstellt, durch insert-Anweisungen permanent anlegt und durch Joins zusammenbringt. In diesem Kapitel erfahren Sie nun, wie mehrere Tabellen mithilfe der diversen Mengenoperatoren kombiniert werden.
Grundlagen der Mengenlehre In vielen Teilen der Erde steht die Mengenlehre bereits in der Grundschule auf dem Lehrplan. Vielleicht erinnern Sie sich noch an Bilder wie das in Abbildung 6-1. A
B
= A union B Abbildung 6-1: Die Vereinigungsoperation (union)
Max. Linie
Der schattierte Bereich in Abbildung 6-1 ist die Vereinigung (union) der Mengen A und B: die Kombination der beiden Mengen, wobei aber die sich überschneidenden Bereiche nur ein einziges Mal einbezogen werden. Na, kommt es Ihnen so langsam bekannt vor? Wenn ja, haben Sie jetzt endlich Gelegenheit, Ihr Wissen zu nutzen; wenn nein, ist das auch
| This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
101
Max. Linie
Links nicht so tragisch, denn die Mengenlehre lässt sich mit ein paar Grafiken ganz einfach veranschaulichen. Angenommen, die Kreise seien zwei Datenmengen (A und B): Stellen Sie sich vor, dass eine Teilmenge dieser Daten von beiden Mengen geteilt wird, und dass diese gemeinsamen Daten durch den überlappenden Bereich in Abbildung 6-1 dargestellt werden. Da Mengentheorie ohne überschneidende Datenmengen langweilig ist, werde ich für jede Mengenoperation dieselbe Grafik verwenden. Es gibt noch eine andere Mengenoperation, die sich ausschließlich mit der Überschneidung der beiden Datenmengen befasst: die sogenannte Schnittmenge (intersect), die Sie in Abbildung 6-2 sehen. Die Datenmenge, die durch die Überschneidung von A und B gegeben ist, ist genau der Bereich, in dem sich die beiden Mengen überlappen. Überlappen sich zwei Mengen nicht, ist ihre Schnittmenge die leere Menge. A
B
= A intersect B Abbildung 6-2: Die Schnittmengenoperation (intersect)
A
Max. Linie
B
= A except B Abbildung 6-3: Die Differenzoperation (except)
102 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Die dritte und letzte Mengenoperation, die in Abbildung 6-3 dargestellt wird, ist die Differenzoperation (except). Abbildung 6-3 zeigt das Ergebnis von A except B: Es ist die ganze Menge A minus der Teilmenge, die sich mit der Menge B überschneidet. Gibt es zwischen den beiden Mengen keine Überschneidung, kommt bei der Operation A except B die ganze Menge A heraus. Mit diesen drei Operationen oder einer Kombination daraus können Sie jedes gewünschte Resultat erzielen. Stellen Sie sich zum Beispiel vor, Sie möchten eine Menge wie die in Abbildung 6-4 erstellen. A
B
= ???? Abbildung 6-4: Unbekannte Datenmenge
Die gesuchte Datenmenge enthält alles aus den Mengen A und B, aber ohne den überschneidenden Bereich. Das können Sie nicht mit einer einzigen der drei oben gezeigten Operationen schaffen; Sie müssen zuerst eine Datenmenge erstellen, die die Mengen A und B komplett einbezieht und dann mit einer zweiten Operation den überschneidenden Bereich eliminieren. Wenn die kombinierte Menge als A union B beschrieben wird und der überschneidende Bereich als A intersect B, generiert folgende Operation die in Abbildung 6-4 gezeigte Datenmenge: (A union B) except (A intersect B)
Natürlich führen viele Wege nach Rom; man hätte dasselbe auch mit folgender Operation erreichen können: (A except B) union (B except A)
Diese Konzepte sind anhand von Grafiken noch recht leicht zu veranschaulichen. In den folgenden Abschnitten werden Sie jedoch erfahren, wie man sie mithilfe der SQL-Mengenoperatoren auf eine relationale Datenbank überträgt.
Max. Linie
Max. Linie Grundlagen der Mengenlehre | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
103
Links Mengenlehre in der Praxis Die Kreise, mit denen in den Grafiken im vorigen Abschnitt Datenmengen dargestellt wurden, sagen nichts darüber aus, was in diesen Datenmengen enthalten sein mag. Wenn Sie es jedoch mit echten Daten zu tun haben, müssen Sie unbedingt wissen, wie zwei zu kombinierende Datenmengen zusammengesetzt sind. Stellen Sie sich beispielsweise vor, was geschieht, wenn Sie versuchen, die Vereinigungsmenge der Tabellen product und customer zu erstellen, deren Tabellen-Definitionen wie folgt lauten: mysql> DESC product; +-----------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------------+-------------+------+-----+---------+-------+ | product_cd | varchar(10) | | PRI | | | | name | varchar(50) | | | | | | product_type_cd | varchar(10) | | MUL | | | | date_offered | date | YES | | NULL | | | date_retired | date | YES | | NULL | | +-----------------+-------------+------+-----+---------+-------+ 5 rows in set (0.23 sec) mysql> DESC customer; +--------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+----------------+ | cust_id | int(10) unsigned | | PRI | NULL | auto_increment | | fed_id | varchar(12) | | | | | | cust_type_cd | enum('I','B') | | | I | | | address | varchar(30) | YES | | NULL | | | city | varchar(20) | YES | | NULL | | | state | varchar(20) | YES | | NULL | | | postal_code | varchar(10) | YES | | NULL | | +--------------+------------------+------+-----+---------+----------------+ 7 rows in set (0.04 sec)
Wenn man das kombiniert, würde die erste Spalte der Ergebnistabelle die Kombination der Spalten product.product_cd und customer.cust_id sein, die zweite die Kombination der Spalten product.name und customer.fed_id usw. Zwar sind einige der Spaltenpaare recht einfach zu kombinieren (z.B. zwei numerische Spalten), aber wenn in anderen Fällen Spaltenpaare aus einer numerischen und einer String-Spalte oder einer String-Spalte und einer Datumsspalte zusammengestellt werden sollen, wird es problematisch. Außerdem hätten die kombinierten Tabellen in der sechsten und siebten Spalte nur Daten aus der Tabelle customer, da die product-Tabelle nur fünf Spalten aufweist. Klar ist: Wenn zwei Tabellen kombiniert werden sollen, müssen sie ein gewisses Maß an Gemeinsamkeit aufweisen.
Max. Linie
Bei Mengenoperationen auf zwei Datenmengen müssen also folgende Richtlinien beachtet werden:
104 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts • Beide Datenmengen müssen gleich viele Spalten haben. • Die Datentypen der zu kombinierenden Spalten beider Datenmengen müssen gleich sein (oder sich ineinander konvertieren lassen). Wenn man diese Regeln kennt, kann man sich leichter vorstellen, was in der Praxis mit »überlappenden Daten« gemeint ist: Jedes Spaltenpaar aus den beiden zu kombinierenden Tabellen muss in einer Zeile den gleichen String, die gleiche Zahl oder das gleiche Datum aufweisen, damit diese Zeile für beide Tabellen als gleich betrachtet wird. Mengenoperationen führt man aus, indem man einen Mengenoperator zwischen zwei select-Anweisungen setzt: mysql> SELECT 1 num, 'abc' str -> UNION -> SELECT 9 num, 'xyz' str; +-----+-----+ | num | str | +-----+-----+ | 1 | abc | | 9 | xyz | +-----+-----+ 2 rows in set (0.02 sec)
Jede der beiden Abfragen ergibt eine Datenmenge mit einer einzigen Zeile, die aus einer numerischen und einer String-Spalte besteht. Der Mengenoperator, in diesem Fall union, lässt den Datenbankserver alle Zeilen der beiden Tabellen kombinieren. Dadurch enthält die Ergebnismenge zwei Zeilen mit je zwei Spalten. Diese Abfrage bezeichnet man als zusammengesetzte Abfrage, da sie mehrere ansonsten selbstständige Abfragen enthält. Wie Sie später noch sehen werden, können zusammengesetzte Abfragen auch mehr als zwei Abfragen umfassen, wenn mehrere Mengenoperationen erforderlich sind, um das Endresultat zu erzielen.
Mengenoperatoren SQL kennt drei Mengenoperatoren, mit denen die oben beschriebenen Mengenoperationen ausgeführt werden können. Zusätzlich hat jedoch jeder Mengenoperator zwei Varianten: eine, die Duplikate einbezieht, und eine, die sie verwirft (allerdings nicht unbedingt sämtliche Duplikate). Im Folgenden werde ich jeden einzelnen Operator definieren und seine Verwendung beschreiben.
Der union-Operator
Max. Linie
Die Operatoren union und union all können mehrere Datenmengen kombinieren. Der Unterschied zwischen den beiden ist, dass union die kombinierte Ergebnismenge sortiert und Duplikate entfernt, union all hingegen nicht. Mit union all ist die Zeilenzahl der Ergebnismenge immer gleich der Summe der Zeilen in den beiden verknüpften Mengen.
Mengenoperatoren | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
105
Max. Linie
Links Diese Operation ist die einfachste Mengenoperation, die man machen kann (zumindest aus der Sicht des Servers), da keine Prüfung auf überlappende Daten erforderlich ist. Das folgende Beispiel zeigt, wie der union all-Operator aus den beiden Tabellen der verschiedenen Kundenarten (Privat- und Firmenkunden) eine Menge aller Kundendaten generiert: mysql> SELECT cust_id, lname name -> FROM individual -> UNION ALL -> SELECT cust_id, name -> FROM business; +---------+------------------------+ | cust_id | name | +---------+------------------------+ | 1 | Hadley | | 2 | Tingley | | 3 | Tucker | | 4 | Hayward | | 5 | Frasier | | 6 | Spencer | | 7 | Young | | 8 | Blake | | 9 | Farley | | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. | | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | +---------+------------------------+ 13 rows in set (0.04 sec)
Die Abfrage gibt alle 13 Kunden zurück, wobei 9 Zeilen aus der Privatkundentabelle individual und die restlichen 4 aus der Firmenkundentabelle business stammen. Während die business-Tabelle eine einzige Spalte enthält, um den Firmennamen zu speichern, enthält die individual-Tabelle allerdings zwei Spalten, um den Vor- und Nachnamen zu speichern. In diesem Fall habe ich nur den Nachnamen aus der individual-Tabelle einbezogen. Um Ihnen noch klarer zu machen, dass der union all-Operator Doppelnennungen nicht über Bord wirft, sehen Sie hier die gleiche Abfrage wie zuvor, allerdings mit einer zusätzlichen Abfrage der business-Tabelle:
Max. Linie
mysql> SELECT cust_id, lname name -> FROM individual -> UNION ALL -> SELECT cust_id, name -> FROM business -> UNION ALL -> SELECT cust_id, name -> FROM business; +---------+------------------------+ | cust_id | name | +---------+------------------------+ | 1 | Hadley | | 2 | Tingley | | 3 | Tucker |
106 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 4 | Hayward | | 5 | Frasier | | 6 | Spencer | | 7 | Young | | 8 | Blake | | 9 | Farley | | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. | | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. | | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | +---------+------------------------+ 17 rows in set (0.01 sec)
Diese zusammengesetzte Abfrage enthält drei select-Anweisungen, von denen zwei identisch sind. Wie die Ergebnisse zeigen, werden die vier Zeilen der business-Tabelle zweimal genannt (es handelt sich um die Kundennummern 10, 11, 12 und 13). Es ist unwahrscheinlich, dass Sie je dieselbe Abfrage in einer zusammengesetzten Abfrage zweimal wiederholen. Hier sehen Sie eine andere zusammengesetzte Abfrage, die Doppeldaten zurückliefert: mysql> SELECT emp_id -> FROM employee -> WHERE assigned_branch_id = 2 -> AND (title = 'Teller' OR title = 'Head Teller') -> UNION ALL -> SELECT DISTINCT open_emp_id -> FROM account -> WHERE open_branch_id = 2; +--------+ | emp_id | +--------+ | 10 | | 11 | | 12 | | 10 | +--------+ 4 rows in set (0.01 sec)
Die erste Abfrage dieser zusammengesetzten Anweisung ruft alle Kassierer der Zweigstelle Woburn ab, während die zweite eine andere Menge mit Kassierern liefert, die in Woburn Konten eröffnet haben. Von den vier Zeilen der Ergebnismenge ist eine ein Duplikat (Employee ID 10). Wenn aus der kombinierten Tabelle doppelte Zeilen ausgeschlossen werden sollen, müssen Sie den union-Operator an Stelle von union all verwenden:
Max. Linie
mysql> SELECT emp_id -> FROM employee -> WHERE assigned_branch_id = 2
Mengenoperatoren | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 107
Links -> AND (title = 'Teller' OR title = 'Head Teller') -> UNION -> SELECT DISTINCT open_emp_id -> FROM account -> WHERE open_branch_id = 2; +--------+ | emp_id | +--------+ | 10 | | 11 | | 12 | +--------+ 3 rows in set (0.01 sec)
In dieser Version der Abfrage werden nur unterschiedliche Zeilen in die Ergebnismenge aufgenommen. Statt vier Zeilen (drei verschiedene, ein Duplikat), die der union all-Operator zurückliefert, gibt sie nur drei zurück.
Der intersect-Operator Die ANSI SQL-Spezifikation enthält den intersect-Operator für Schnittmengenoperationen. Doch leider versäumt es die Version 6.0 von MySQL, den intersect-Operator zu implementieren. Wenn Sie Oracle oder SQL Server 2008 benutzen, können Sie intersect nutzen; da ich jedoch für alle Beispiele in diesem Buch MySQL einsetze, sind die Ergebnismengen der Beispielabfragen in diesem Abschnitt nachgestellt und können mit keiner der MySQL-Versionen bis und einschließlich 6.0 ausgeführt werden. Ich zeige daher auch keinen MySQL-Prompt (mysql>), da die Abfragen schließlich nicht auf einem MySQLServer ausführbar sind. Wenn die beiden Abfragen in einer zusammengesetzten Abfrage Datenmengen zurückliefern, deren Daten sich nicht überschneiden, ist ihre Schnittmenge die leere Menge. Betrachten Sie folgende Abfrage: SELECT emp_id, fname, lname FROM employee INTERSECT SELECT cust_id, fname, lname FROM individual; Empty set (0.04 sec)
Die erste Abfrage gibt die ID und den Namen jedes Angestellten und die zweite die ID und den Namen jedes Kunden zurück. Da sich die Mengen nicht überschneiden, ist ihre Schnittmenge die leere Menge.
Max. Linie
Der nächste Schritt besteht darin, Abfragen zu finden, bei denen es sehr wohl überlappende Daten gibt, und dann den intersect-Operator einzusetzen. Für diesen Zweck verwende ich die gleiche Abfrage wie für die Veranschaulichung des Unterschieds zwischen union und union all, nur dass dieses Mal der Operator intersect eingesetzt wird:
108 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts SELECT emp_id FROM employee WHERE assigned_branch_id = 2 AND (title = 'Teller' OR title = 'Head Teller') INTERSECT SELECT DISTINCT open_emp_id FROM account WHERE open_branch_id = 2; +--------+ | emp_id | +--------+ | 10 | +--------+ 1 row in set (0.01 sec)
Die Schnittmenge dieser beiden Abfragen ergibt die Employee ID 10, also den einzigen Wert, der in beiden Ergebnismengen auftaucht. Zusammen mit dem intersect-Operator, der alle Doppelzeilen aus dem überlappenden Bereich über Bord wirft, soll es laut ANSI SQL-Spezifikation auch einen intersect allOperator geben, der keine Duplikate entfernt. Doch der einzige Datenbankserver, der bislang intersect all implementiert, ist der DB2 Universal Server von IBM.
Der except-Operator Die ANSI SQL-Spezifikation sieht für Differenzoperationen den except-Operator vor. Auch dieser wird leider in der Version 6.0 von MySQL nicht implementiert, sodass für diesen Abschnitt dasselbe gilt wie für den vorigen. Mit Oracle Database wird für solche Operationen der nicht dem ANSIStandard entsprechende minus-Operator verwendet.
Der except-Operator liefert die erste Tabelle minus irgendwelchen Überschneidungen mit der zweiten Tabelle zurück. Hier sehen Sie noch einmal das Beispiel aus dem vorigen Abschnitt, aber dieses Mal mit except an Stelle von intersect:
Max. Linie
SELECT emp_id FROM employee WHERE assigned_branch_id = 2 AND (title = 'Teller' OR title = 'Head Teller') EXCEPT SELECT DISTINCT open_emp_id FROM account WHERE open_branch_id = 2; +--------+ | emp_id | +--------+ | 11 |
Mengenoperatoren | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 109
Links | 12 | +--------+ 2 rows in set (0.01 sec)
In dieser Version der Abfrage besteht die Ergebnismenge aus den drei Zeilen der ersten Abfrage minus der Employee ID 10, da diese in den Ergebnismengen beider Abfragen auftaucht. Es gibt auch einen except all-Operator in der ANSI SQL-Spezifikation, doch dieser wird ebenfalls nur von DB2 Universal Server von IBM implementiert. Da der except all-Operator ein bisschen knifflig ist, gebe ich hier ein Beispiel, um die Handhabung von Duplikaten zu veranschaulichen. Angenommen, Sie haben zwei Datenmengen, die wie folgt aussehen: Menge A +--------+ | emp_id | +--------+ | 10 | | 11 | | 12 | | 10 | | 10 | +--------+
Menge B +--------+ | emp_id | +--------+ | 10 | | 10 | +--------+
Die Operation A except B ergibt Folgendes: +--------+ | emp_id | +--------+ | 11 | | 12 | +--------+
Wenn Sie die Operation in A except all B umändern, erscheint dieses: +--------+ | emp_id | +--------+ | 10 | | 11 | | 12 | +--------+
Max. Linie
Der Unterschied zwischen den beiden Operationen besteht also darin, dass except alle Duplikate aus der Menge A entfernt, während except all nur ein Duplikat aus Menge A für jedes Auftreten in Menge B über Bord wirft.
110 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Regeln für Mengenoperationen In den folgenden Abschnitten werden einige Regeln skizziert, die bei der Arbeit mit zusammengesetzten Abfragen zu beachten sind.
Ergebnisse zusammengesetzter Abfragen sortieren Wenn die Ergebnisse Ihrer zusammengesetzten Abfrage sortiert werden sollen, können Sie hinter die letzte Abfrage eine order by-Klausel setzen. Die Spaltennamen in der order by-Klausel müssen Sie allerdings aus den Spaltennamen der ersten Abfrage dieser zusammengesetzten Abfrage wählen. Häufig sind die Spaltennamen in beiden Abfragen einer zusammengesetzten Abfrage gleich, aber das muss nicht in jedem Fall so sein, wie die folgende Abfrage zeigt: mysql> SELECT emp_id, assigned_branch_id -> FROM employee -> WHERE title = 'Teller' -> UNION -> SELECT open_emp_id, open_branch_id -> FROM account -> WHERE product_cd = 'SAV' -> ORDER BY emp_id; +--------+--------------------+ | emp_id | assigned_branch_id | +--------+--------------------+ | 1 | 1 | | 7 | 1 | | 8 | 1 | | 9 | 1 | | 10 | 2 | | 11 | 2 | | 12 | 2 | | 14 | 3 | | 15 | 3 | | 16 | 4 | | 17 | 4 | | 18 | 4 | +--------+--------------------+ 12 rows in set (0.04 sec)
In diesem Beispiel stehen in beiden Abfragen unterschiedliche Spaltennamen. Wenn Sie einen Spaltennamen aus der zweiten Abfrage in Ihrer order by-Klausel angeben, wird folgender Fehler gemeldet:
Max. Linie
mysql> -> -> -> -> ->
SELECT emp_id, assigned_branch_id FROM employee WHERE title = 'Teller' UNION SELECT open_emp_id, open_branch_id FROM account
Regeln für Mengenoperationen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 111
Links -> WHERE product_cd = 'SAV' -> ORDER BY open_emp_id; ERROR 1054 (42S22): Unknown Spalte 'open_emp_id' in 'order clause'
Ich empfehle Ihnen, den Spalten in beiden Abfragen identische Spalten-Aliase zu geben, damit dieses Problem gar nicht erst auftritt.
Präzedenz von Mengenoperationen Wenn Ihre zusammengesetzte Abfrage mehr als zwei Abfragen enthält und verschiedene Mengenoperatoren verwendet, müssen Sie sich Gedanken über die Reihenfolge machen, um die angestrebten Ergebnisse zu erzielen. Betrachten Sie folgende aus drei Abfragen zusammengesetzte Anweisung: mysql> SELECT cust_id -> FROM account -> WHERE product_cd IN ('SAV', 'MM') -> UNION ALL -> SELECT a.cust_id -> FROM account a INNER JOIN branch b -> ON a.open_branch_id = b.branch_id -> WHERE b.name = 'Woburn Branch' -> UNION -> SELECT cust_id -> FROM account -> WHERE avail_balance BETWEEN 500 AND 2500; +---------+ | cust_id | +---------+ | 1 | | 2 | | 3 | | 4 | | 8 | | 9 | | 7 | | 11 | | 5 | +---------+ 9 rows in set (0.00 sec)
Diese zusammengesetzte Abfrage enthält drei Abfragen, die Mengen nicht-eindeutiger Kundennummern zurückliefern; zwischen den ersten beiden Abfragen steht der union all-Operator und zwischen der zweiten und dritten der union-Operator. Auf den ersten Blick könnte man meinen, die Reihenfolge von union und union all spiele keine Rolle, aber in Wirklichkeit macht sie einen großen Unterschied. Hier ist die gleiche zusammengesetzte Abfrage mit umgekehrter Reihenfolge der Mengenoperatoren:
Max. Linie
mysql> -> -> ->
SELECT cust_id FROM account WHERE product_cd IN ('SAV', 'MM') UNION
112 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts -> SELECT a.cust_id -> FROM account a INNER JOIN branch b -> ON a.open_branch_id = b.branch_id -> WHERE b.name = 'Woburn Branch' -> UNION ALL -> SELECT cust_id -> FROM account -> WHERE avail_balance BETWEEN 500 AND 2500; +---------+ | cust_id | +---------+ | 1 | | 2 | | 3 | | 4 | | 8 | | 9 | | 7 | | 11 | | 1 | | 1 | | 2 | | 3 | | 3 | | 4 | | 4 | | 5 | | 9 | +---------+ 17 rows in set (0.00 sec)
Ein Blick auf die Ergebnisse beweist ganz klar, welchen Unterschied die Reihenfolge von verschiedenen Mengenoperatoren in einer zusammengesetzten Abfrage ausmacht. Im Allgemeinen werden zusammengesetzte Abfragen mit drei oder mehr Abfragen der Reihe nach von oben nach unten ausgewertet; allerdings gibt es folgende Fallgruben: • Laut ANSI SQL-Spezifikation muss der intersect-Operator Vorrang vor den anderen Mengenoperatoren haben. • Sie können die Reihenfolge, in der die Abfragen kombiniert werden, durch Klammern steuern. Da jedoch MySQL weder intersect implementiert noch Klammern in zusammengesetzten Abfragen erlaubt, müssen Sie die einzelnen Elemente Ihrer zusammengesetzten Abfrage sorgfältig arrangieren, um das gewünschte Ergebnis zu erhalten. Wenn Sie einen anderen Datenbankserver verwenden, können Sie zusammengehörige Abfragen in Klammern setzen, um die standardmäßige Top-to-bottom-Verarbeitung zusammengesetzter Abfragen auszuhebeln:
Max. Linie
(SELECT cust_id FROM account WHERE product_cd IN ('SAV', 'MM') UNION ALL
Regeln für Mengenoperationen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 113
Links SELECT a.cust_id FROM account a INNER JOIN branch b ON a.open_branch_id = b.branch_id WHERE b.name = 'Woburn Branch') INTERSECT (SELECT cust_id FROM account WHERE avail_balance BETWEEN 500 AND 2500 EXCEPT SELECT cust_id FROM account WHERE product_cd = 'CD' AND avail_balance < 1000);
In dieser zusammengesetzten Abfrage werden die erste und zweite Abfrage mit dem union all-Operator und die dritte und vierte mit dem except-Operator kombiniert, um zum Schluss die Ergebnisse dieser beiden Operationen mit dem intersect-Operator zu kombinieren und das Endergebnis zu erhalten.
Testen Sie Ihr Wissen Die folgenden Übungen sollen überprüfen, ob Sie Mengenoperationen richtig verstanden haben. In Anhang C finden Sie die Lösungen.
Übung 6-1 Es seien Menge A = {L M N O P} und Menge B = {P Q R S T}. Welche Mengen werden von folgenden Operationen generiert? • A union B • A union all B • A intersect B • A except B
Übung 6-2 Schreiben Sie eine zusammengesetzte Abfrage, die die Vor- und Nachnamen aller Privatkunden sowie die Vor- und Nachnamen aller Mitarbeiter findet.
Übung 6-3
Max. Linie
Sortieren Sie die Ergebnisse aus 6-2 nach der Spalte lname.
114 | Kapitel 6: Umgang mit Mengen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
First
Kapitel 7
KAPITEL 7
Daten generieren, konvertieren und manipulieren
Wie bereits im Vorwort gesagt, soll dieses Buch allgemein gültige SQL-Techniken vermitteln, die sich auf unterschiedlichen Datenbankservern einsetzen lassen. Dieses Kapitel befasst sich jedoch mit dem Generieren, Konvertieren und Manipulieren von StringDaten, numerischen Daten und temporalen Daten, und hierzu kennt SQL keine Befehle. Stattdessen gibt es eingebaute Funktionen zum Generieren, Konvertieren und Manipulieren von Daten. Während der SQL-Standard einige Funktionen spezifiziert, weichen die Datenbankhersteller nicht selten von diesen Spezifikationen ab. Daher habe ich mir folgende Herangehensweise überlegt: Zuerst zeige ich Ihnen einige gebräuchliche Methoden, um Daten in SQL-Anweisungen zu manipulieren, und dann einige der eingebauten Funktionen, die Microsoft SQL Server, Oracle Database und MySQL implementieren. Ich empfehle Ihnen dringend, parallel zu diesem Kapitel eine Referenz anzuschaffen, in der alle von Ihrem Server implementierten Funktionen beschrieben werden. Wenn Sie mit mehreren Datenbankservern arbeiten, können Sie auch Referenzen finden, in denen diverse Server beschrieben werden, wie beispielsweise SQL in a Nutshell von Kevin Kline oder SQL – kurz & gut von Jonathan Gennick, beide von O’Reilly.
Der Umgang mit String-Daten Wenn Sie mit String-Daten arbeiten, verwenden Sie einen der folgenden Zeichendatentypen: char
Speichert Strings fester Länge, die mit Leerzeichen ausgepolstert werden. In MySQL dürfen char-Werte bis zu 255 Zeichen lang sein, in Oracle Database bis zu 2.000 Zeichen und in SQL Server bis zu 8.000 Zeichen. varchar
Max. Linie
Speichert Strings variabler Länge. In MySQL darf eine varchar-Spalte bis zu 65.535 Zeichen lang sein, in Oracle Database (im Typ varchar2) bis zu 4.000 Zeichen und in SQL Server bis zu 8.000 Zeichen.
| This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
115
Max. Linie
Links text (MySQL und SQL Server) oder CLOB (Character Large Object; Oracle Database)
Speichert sehr lange Strings variabler Länge (in diesem Zusammenhang als »Dokumente« bezeichnet). MySQL kennt mehrere Texttypen (tinytext, text, mediumtext und longtext) für Dokumente bis zu 4 GByte. SQL Server kennt einen einzigen textTyp für Dokumente bis 2 GByte, und Oracle Database hat den Datentyp CLOB für Dokumente bis zu sagenhaften 128 TByte. SQL Server 2005 bietet außerdem den Datentyp varchar(max) und empfiehlt, statt des Typs text diesen einzusetzen, da text in zukünftigen Versionen des Datenbanksystems entfernt werden wird. Um diese verschiedenen Typen in Aktion zu zeigen, werde ich in den Beispielen dieses Abschnitts folgende Tabelle verwenden: CREATE TABLE string_tbl (char_fld CHAR(30), vchar_fld VARCHAR(30), text_fld TEXT );
In den nächsten beiden Unterabschnitten erfahren Sie, wie String-Daten generiert und manipuliert werden.
String-Daten generieren Der einfachste Weg, eine Zeichenspalte mit Daten zu füllen, besteht darin, einfach einen String in Anführungszeichen zu übergeben: mysql> INSERT INTO string_tbl (char_fld, vchar_fld, text_fld) -> VALUES ('This is char data', -> 'This is varchar data', -> 'This is text data'); Query OK, 1 row affected (0.00 sec)
Wenn Sie String-Daten in eine Tabelle einfügen, denken Sie bitte daran: Übersteigt die Länge des Strings die maximale Größe der Zeichenspalte (sei es das angegebene Maximum oder der für den Datentyp zulässige Höchstwert), löst der Server eine Exception aus. Obwohl das das Standardverhalten aller drei Server ist, können Sie MySQL und SQL Server so konfigurieren, dass der String stillschweigend abgeschnitten und keine Exception ausgelöst wird. Um zu zeigen, wie MySQL mit dieser Situation umgeht, versuche ich mit der folgenden update-Anweisung, in eine auf 30 Zeichen beschränkte vchar_fldSpalte einen 46 Zeichen langen String zu schreiben: mysql> UPDATE string_tbl -> SET vchar_fld = 'This is a piece of extremely long varchar data'; ERROR 1406 (22001): Data too long for column 'vchar_fld' at row 1
Max. Linie
Dies ist bei vielen neueren Installationen beispielsweise unter Windows das Standardverhalten. Das bedeutet, dass Exceptions ausgelöst werden, wenn ein Problem aufkommt, bei dem der Server in älteren Versionen den String einfach beschnitten und eine Warnung ausgegeben hätte. Wenn Sie es vorziehen, dass der Server den String beschneidet, können
116 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Sie stattdessen einen Modus wie den ANSI-Modus verwenden, der dieses Verhalten nicht einschließt. Das folgende Beispiel zeigt, wie man prüft, in welchem Modus der Server arbeitet, und wie man ihn mit dem SET-Befehl ändert: mysql> SELECT @@session.sql_mode; +----------------------------------------------------------------+ | @@session.sql_mode | +----------------------------------------------------------------+ | STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | +----------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> SET sql_mode='ansi'; Query OK, 0 rows affected (0.08 sec) mysql> SELECT @@session.sql_mode; +-------------------------------------------------------------+ | @@session.sql_mode | +-------------------------------------------------------------+ | REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI | +-------------------------------------------------------------+ 1 row in set (0.00 sec)
Wenn Sie die UPDATE-Anweisung von oben erneut ausführen, werden Sie sehen, dass die Spalte geändert und die folgende Warnung ausgegeben wird: mysql> SHOW WARNINGS; +---------+------+------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------+ | Warning | 1265 | Data truncated for column 'vchar_fld' at row 1 | +---------+------+------------------------------------------------+ 1 row in set (0.00 sec)
Wenn Sie nun die vchar_fld-Spalte abfragen, sehen Sie Folgendes: mysql> SELECT vchar_fld -> FROM string_tbl; +--------------------------------+ | vchar_fld | +--------------------------------+ | This is a piece of extremely l | +--------------------------------+ 1 row in set (0.05 sec)
Max. Linie
Es wurden also nur die ersten 30 Zeichen des 46-Zeichen-Strings in die vchar_fld-Spalte geladen. Das beste Mittel, um das Abschneiden von Strings (oder Exceptions im Fall der Oracle Database oder MySQL im strengen Modus) im Umgang mit varchar-Spalten zu vermeiden, besteht darin, die Obergrenze der Spalte auf einen so großen Wert zu setzen, dass auch die längsten Strings, die in dieser Spalte vorkommen könnten, problemlos gespeichert werden. (Wenn man bedenkt, dass der Server nur gerade genug Platz für den String reserviert, geht auch bei einer hohen Obergrenze für varchar-Spalten kein Platz verloren.)
Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
117
Max. Linie
Links Einfache Anführungszeichen Da Strings durch einfache Anführungszeichen abgegrenzt werden, müssen Sie auf solche Anführungszeichen oder auch Apostrophe innerhalb der Strings achten. Sie können zum Beispiel folgenden String nicht einfügen, da der Server denkt, der Apostroph in dem Wort »doesn’t« markiere das Ende des Strings: UPDATE string_tbl SET text_fld = 'This string doesn't work';
Damit der Server den Apostroph in »doesn't« ignoriert, müssen Sie ein Escape-Zeichen davor setzen. Dann behandelt der Server den Apostroph wie jedes andere Zeichen im String. Auf allen drei Datenbankplattformen ist dieses Zeichen ein zweites einzelnes Anführungszeichen, das direkt vor den Apostroph gesetzt wird: mysql> UPDATE string_tbl -> SET text_fld = 'This string didn''t work, but it does now'; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
Benutzer von Oracle Database und MySQL können ein einzelnes Anführungszeichen auch mit einem Backslash als Escape-Zeichen versehen: UPDATE string_tbl SET text_fld = 'This string didn\'t work, but it does now'
Wenn Sie einen String abrufen, um ihn auf einem Bildschirm oder in einem Report-Feld anzuzeigen, brauchen Sie mit den eingebetteten Anführungszeichen nichts weiter zu tun: mysql> SELECT text_fld -> FROM string_tbl; +------------------------------------------+ | text_fld | +------------------------------------------+ | This string didn't work, but it does now | +------------------------------------------+ 1 row in set (0.00 sec)
Falls Sie den String jedoch in eine Datei einfügen möchten, die von einem anderen Programm gelesen wird, sollten Sie das Escape-Zeichen in den Abfrage-String einbinden. Mit MySQL können Sie auch die eingebaute Funktion quote( ) nutzen, die den gesamten String in Anführungszeichen setzt und eventuell vorhandene Binnen-Anführungszeichen und -Apostrophe mit Escape-Zeichen unschädlich macht. Mit der Funktion quote( ) abgerufen, sieht unser String folgendermaßen aus:
Max. Linie
mysql> SELECT QUOTE(text_fld) -> FROM string_tbl; +---------------------------------------------+ | QUOTE(text_fld) | +---------------------------------------------+ | 'This string didn\'t work, but it does now' | +---------------------------------------------+ 1 row in set (0.04 sec)
118 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Wenn Sie Daten abfragen, um sie zu exportieren, sollten Sie die quote( )-Funktion für alle nicht vom System generierten Zeichenspalten wie etwa customer_notes verwenden.
Sonderzeichen einbinden Wenn Ihre Anwendung multinational ist, werden manche Ihrer Strings Zeichen enthalten, die nicht auf Ihrer Tastatur zu finden sind. In Französisch müssen Sie beispielsweise Zeichen mit Akzent verwenden, wie etwa é. SQL Server und MySQL-Server kennen die eingebaute Funktion char( ), mit der man Strings aus allen möglichen der 255 ASCII-Zeichen erstellen kann (Oracle Database verwendet hierfür die Funktion chr( )). Die folgenden Beispiele zeigen, wie ein String und seine aus einzelnen Zeichen aufgebaute Entsprechung abgerufen wird: mysql> SELECT 'abcdefg', CHAR(97,98,99,100,101,102,103); +---------+--------------------------------+ | abcdefg | CHAR(97,98,99,100,101,102,103) | +---------+--------------------------------+ | abcdefg | abcdefg | +---------+--------------------------------+ 1 row in set (0.01 sec)
Das 97. Zeichen des ASCII-Zeichensatzes ist also der Buchstabe a. Während die oben angezeigten Zeichen keine Sonderzeichen sind, werden Sie in den folgenden Beispielen sehen, an welcher Stelle im ASCII-Zeichensatz die Buchstaben mit Akzent und andere Sonderzeichen, wie etwa Währungssymbole, zu finden sind: mysql> SELECT CHAR(128,129,130,131,132,133,134,135,136,137); +-----------------------------------------------+ | CHAR(128,129,130,131,132,133,134,135,136,137) | +-----------------------------------------------+ | Çüéâäàåçêë | +-----------------------------------------------+ 1 row in set (0.01 sec) mysql> SELECT CHAR(138,139,140,141,142,143,144,145,146,147); +-----------------------------------------------+ | CHAR(138,139,140,141,142,143,144,145,146,147) | +-----------------------------------------------+ | èïîìÄÅÉæÆô | +-----------------------------------------------+ 1 row in set (0.01 sec)
Max. Linie
mysql> SELECT CHAR(148,149,150,151,152,153,154,155,156,157); +-----------------------------------------------+ | CHAR(148,149,150,151,152,153,154,155,156,157) | +-----------------------------------------------+ | öòûùÿ...Ü¢£¥ | +-----------------------------------------------+ 1 row in set (0.00 sec)
Max. Linie
mysql> SELECT CHAR(158,159,160,161,162,163,164,165); +---------------------------------------+
Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
119
Links | CHAR(158,159,160,161,162,163,164,165) | +---------------------------------------+ | fáíóúñÑ | +---------------------------------------+ 1 row in set (0.01 sec)
Für diese Beispiele habe ich den Zeichensatz latin1 verwendet. Wenn Ihre Session für einen anderen Zeichensatz konfiguriert ist, werden Sie andere als die hier gezeigten Zeichen zu Gesicht bekommen. Die Grundregeln sind dieselben; Sie müssen sich lediglich mit der Anordnung Ihres Zeichensatzes vertraut machen, um Sonderzeichen finden zu können.
Einen String zeichenweise zu erstellen kann ziemlich öde sein, vor allem, wenn nur wenige der Zeichen in dem String Sonderzeichen sind. Zum Glück gibt es die concat( )Funktion, um einzelne Strings zu verketten, von denen einige getippt und andere mit der char( )-Funktion generiert sein können. Das folgende Beispiel zeigt, wie man mit den Funktionen concat( ) und char( ) danke schön sagt: mysql> SELECT CONCAT('danke sch', CHAR(148), 'n'); +-------------------------------------+ | CONCAT('danke sch', CHAR(148), 'n') | +-------------------------------------+ | danke schön | +-------------------------------------+ 1 row in set (0.00 sec)
Benutzer der Oracle Database können statt der Funktion concat( ) den Verkettungsoperator (||) verwenden: SELECT 'danke sch' || CHR(148) || 'n' FROM dual;
Da SQL Server keine concat( )-Funktion kennt, müssen Sie hier den Verkettungsoperator (+) einsetzen: SELECT 'danke sch' + CHAR(148) + 'n'
Wenn Sie für ein Zeichen das ASCII-Äquivalent herausfinden möchten, können Sie die ascii( )-Funktion verwenden, die das am weitesten links stehende Zeichen im String nimmt und eine Zahl zurückgibt: mysql> SELECT ASCII('ö'); +------------+ | ASCII('ö') | +------------+ | 148 | +------------+ 1 row in set (0.00 sec)
Max. Linie
Mit den Funktionen char( ), ascii( ) und concat( ) (oder den Verkettungsoperatoren) müssten Sie mit jeder romanischen Sprache zurechtkommen können, auch wenn Ihre Tastatur keine Akzente und Sonderzeichen vorsieht.
120 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts String-Bearbeitung Jeder Datenbankserver kennt eine Reihe von eingebauten Funktionen für die String-Bearbeitung. In diesem Abschnitt geht es um zwei Arten solcher Funktionen: Die einen liefern Zahlen zurück und die anderen Strings. Doch bevor ich beginne, möchte ich die Daten der Tabelle string_tbl wie folgt umändern: mysql> DELETE FROM string_tbl; Query OK, 1 row affected (0.02 sec) mysql> INSERT INTO string_tbl (char_fld, vchar_fld, text_fld) -> VALUES ('This string is 28 characters', -> 'This string is 28 characters', -> 'This string is 28 characters'); Query OK, 1 row affected (0.00 sec)
String-Funktionen, die Zahlen zurückgeben Von den String-Funktionen, die Zahlen zurückliefern, ist die length( )-Funktion eine der gebräuchlichsten, sie gibt die Anzahl der Zeichen eines Strings zurück (SQL Server-Nutzer verwenden stattdessen die len( )-Funktion). Die folgende Abfrage wendet die length() -Funktion auf die Spalten der Tabelle string_tbl an: mysql> SELECT LENGTH(char_fld) char_length, -> LENGTH(vchar_fld) varchar_length, -> LENGTH(text_fld) text_length -> FROM string_tbl; +-------------+----------------+-------------+ | char_length | varchar_length | text_length | +-------------+----------------+-------------+ | 28 | 28 | 28 | +-------------+----------------+-------------+ 1 row in set (0.00 sec)
Während die Länge der Spalten varchar und text den Erwartungen entspricht, hätte man eigentlich erwarten können, dass die char-Spalte 30 Zeichen lang wäre. Schließlich habe ich Ihnen gesagt, dass Strings in char-Spalten nach rechts hin mit Leerzeichen ausgepolstert werden. Da jedoch der MySQL-Server angehängte Leerzeichen von char-Daten, die er abruft, wieder entfernt, liefern alle String-Funktionen dasselbe Ergebnis, unabhängig vom Spaltentyp. Doch vielleicht interessieren Sie sich weniger für die Länge des Strings als vielmehr für die Position eines Teilstrings in einem String. Wenn Sie beispielsweise wissen möchten, an welcher Position der String »characters« in der vchar_fld-Spalte auftritt, können Sie die position( )-Funktion gebrauchen:
Max. Linie
mysql> SELECT POSITION('characters' IN vchar_fld) -> FROM string_tbl; +-------------------------------------+ | POSITION('characters' IN vchar_fld) | +-------------------------------------+
Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 121
Links | 19 | +-------------------------------------+ 1 row in set (0.12 sec)
Ist der Teilstring nicht vorhanden, gibt die position( )-Funktion 0 zurück. Wer mit Sprachen wie C oder C++ programmiert, in denen das erste Element eines Arrays an der Position 0 liegt, sollte sich merken, dass in Datenbanken das erste Zeichen eines Strings an Position 1 liegt. Wenn position( ) den Wert 0 zurückgibt, bedeutet dies, dass der Teilstring nicht gefunden wurde, und nicht etwa, dass er an erster Position im String vorkommt.
Wenn Sie die Suche nicht beim ersten Zeichen im String, sondern an einer anderen Stelle starten möchten, verwenden Sie die locate( )-Funktion. Sie ähnelt der position( )-Funktion, nimmt aber einen optionalen dritten Parameter, der angibt, an welcher Position die Suche beginnt. Die locate( )-Funktion ist allerdings proprietär, während position( ) zum SQL:2003-Standard gehört. Das folgende Beispiel sucht ab dem fünften Zeichen der vchar_fld-Spalte nach dem String 'is': mysql> SELECT LOCATE('is', vchar_fld, 5) -> FROM string_tbl; +----------------------------+ | LOCATE('is', vchar_fld, 5) | +----------------------------+ | 13 | +----------------------------+ 1 row in set (0.02 sec)
Oracle Database kennt an Stelle der Funktionen position( ) oder locate( ) die instr( )-Funktion, die mit zwei Argumenten position( ) und mit drei Argumenten locate( ) entspricht. SQL Server hat ebenfalls weder position( ) noch locate( ), aber stattdessen die Funktion charindx( ), die wie Oracles instr( )-Funktion entweder zwei oder drei Argumente entgegennimmt.
Eine andere Funktion, die Strings als Argumente nimmt und Zahlen zurückliefert, ist die String-Vergleichsfunktion strcmp( ). Die Funktion strcmp( ) wird nur von MySQL implementiert und hat keine Entsprechung in Oracle Database oder SQL Server. Sie nimmt zwei Strings als Argumente und gibt zurück: –1 wenn der erste String in der Sortierreihenfolge vor dem zweiten kommt,
Max. Linie
0
wenn die Strings identisch sind, oder
1
wenn der erste String in der Sortierreihenfolge hinter dem zweiten kommt.
Zur Veranschaulichung zeige ich zuerst mithilfe einer Abfrage die Sortierreihenfolge von fünf Strings und dann, wie strcmp( ) diese Strings miteinander vergleicht. Hier sind die fünf Strings, die ich in die Tabelle string_tbl einfügen werde:
122 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> DELETE FROM string_tbl; Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('abcd'); Query OK, 1 row affected (0.03 sec) mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('xyz'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('QRSTUV'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('qrstuv'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO string_tbl(vchar_fld) VALUES ('12345'); Query OK, 1 row affected (0.00 sec)
Und hier sind die Strings in ihrer Sortierreihenfolge: mysql> SELECT vchar_fld -> FROM string_tbl -> ORDER BY vchar_fld; +-----------+ | vchar_fld | +-----------+ | 12345 | | abcd | | QRSTUV | | qrstuv | | xyz | +-----------+ 5 rows in set (0.00 sec)
Die nächste Abfrage stellt sechs Vergleiche zwischen diesen Strings an: mysql> SELECT STRCMP('12345','12345') 12345_12345, -> STRCMP('abcd','xyz') abcd_xyz, -> STRCMP('abcd','QRSTUV') abcd_QRSTUV, -> STRCMP('qrstuv','QRSTUV') qrstuv_QRSTUV, -> STRCMP('12345','xyz') 12345_xyz, -> STRCMP('xyz','qrstuv') xyz_qrstuv; +-------------+----------+-------------+---------------+-----------+------------+ | 12345_12345 | abcd_xyz | abcd_QRSTUV | qrstuv_QRSTUV | 12345_xyz | xyz_qrstuv | +-------------+----------+-------------+---------------+-----------+------------+ | 0 | -1 | -1 | 0 | -1 | 1 | +-------------+----------+-------------+---------------+-----------+------------+ 1 row in set (0.00 sec)
Max. Linie
Der erste Vergleich ergibt 0. Das entspricht den Erwartungen, da ich den String mit sich selbst verglichen habe. Der vierte Vergleich ergibt ebenfalls 0, dieses Mal etwas überraschend, da die Strings zwar aus den gleichen Buchstaben bestehen, aber einmal in Großund einmal in Kleinschrift. Der Grund ist der, dass die strcmp( )-Funktion von MySQL
Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
123
Max. Linie
Links Groß- und Kleinschreibung nicht unterscheiden kann. Das muss man sich im Umgang mit dieser Funktion unbedingt merken. Die übrigen vier Vergleiche ergeben entweder –1 oder 1, je nachdem, ob der erste String in der Sortierreihenfolge vor oder hinter dem zweiten liegt. So ergibt beispielsweise strcmp('abcd','xyz') den Wert –1, da 'abcd' vor 'xyz' kommt In MySQL können Sie mit der Funktion strcmp( ) auch die Operatoren like und regexp einsetzen, um Strings in der select-Klausel zu vergleichen. Solche Vergleiche ergeben 1 (für true) oder 0 (für false). Daher können Sie mit diesen Operatoren Ausdrücke erstellen, die eine Zahl zurückgeben, ähnlich wie die in diesem Abschnitt beschriebenen Funktionen. Hier sehen Sie ein Beispiel mit like: mysql> SELECT name, name LIKE '%ns' ends_in_ns -> FROM department; +----------------+------------+ | name | ends_in_ns | +----------------+------------+ | Operations | 1 | | Loans | 1 | | Administration | 0 | +----------------+------------+ 3 rows in set (0.25 sec)
Dieses Beispiel ruft die Namen aller Abteilungen ab, zusammen mit einem Ausdruck, der 1 zurückgibt, wenn der Name der Abteilung auf »ns« endet, und 0, wenn nicht. Für kompliziertere Mustervergleiche gibt es den Operator regexp: mysql> SELECT cust_id, cust_Typ_cd, fed_id, -> fed_id REGEXP '.{3}-.{2}-.{4}' is_ss_no_format -> FROM customer; +---------+--------------+-------------+-----------------+ | cust_id | cust_Typ_cd | fed_id | is_ss_no_format | +---------+--------------+-------------+-----------------+ | 1 | I | 111-11-1111 | 1 | | 2 | I | 222-22-2222 | 1 | | 3 | I | 333-33-3333 | 1 | | 4 | I | 444-44-4444 | 1 | | 5 | I | 555-55-5555 | 1 | | 6 | I | 666-66-6666 | 1 | | 7 | I | 777-77-7777 | 1 | | 8 | I | 888-88-8888 | 1 | | 9 | I | 999-99-9999 | 1 | | 10 | B | 04-1111111 | 0 | | 11 | B | 04-2222222 | 0 | | 12 | B | 04-3333333 | 0 | | 13 | B | 04-4444444 | 0 | +---------+--------------+-------------+-----------------+ 13 rows in set (0.00 sec)
Max. Linie
Die vierte Spalte dieser Abfrage ist 1, wenn der Wert der fed_id-Spalte das Format einer amerikanischen Sozialversicherungsnummer hat.
124 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts SQL Server und Oracle Database können ähnliche Ergebnisse mit caseAusdrücken erzielen. Diese werden in Kapitel 11 genauer beschrieben.
String-Funktionen, die Strings zurückgeben Gelegentlich werden Sie vorhandene Strings modifizieren müssen, sei es, dass Sie einen Teil des Strings herausziehen, sei es, dass Sie zusätzlichen Text einfügen. Jeder Datenbankserver kennt auch für diese Anwendungen mehrere Funktionen. Doch zunächst werde ich die Daten der Tabelle string_tbl wieder in ihren Ausgangszustand zurücksetzen: mysql> DELETE FROM string_tbl; Query OK, 5 rows affected (0.00 sec) mysql> INSERT INTO string_tbl (text_fld) -> VALUES ('This string was 29 characters'); Query OK, 1 row affected (0.01 sec)
Weiter oben in diesem Kapitel habe ich bereits beschrieben, wie man mit der Funktion concat( ) Wörter erstellen kann, die Zeichen mit Akzenten enthalten. Die concat( )Funktion ist jedoch auch in vielen anderen Lebenslagen nützlich, etwa wenn man an einen gespeicherten String weitere Zeichen anfügen möchte. Das folgende Beispiel hängt an den in der text_fld-Spalte gespeicherten String eine zusätzliche Phrase an: mysql> UPDATE string_tbl -> SET text_fld = CONCAT(text_fld, ', but now it is longer'); Query OK, 1 row affected (0.03 sec) Rows matched: 1 Changed: 1 Warnings: 0
Nun hat die text_fld-Spalte folgenden Inhalt: mysql> SELECT text_fld -> FROM string_tbl; +-----------------------------------------------------+ | text_fld | +-----------------------------------------------------+ | This string was 29 characters, but now it is longer | +-----------------------------------------------------+ 1 row in set (0.00 sec)
Wie alle Funktionen, die einen String zurückgeben, kann concat( ) die in einer Zeichenspalte gespeicherten Daten auch ersetzen. Häufig wird die concat( )-Funktion ebenfalls dazu genutzt, einen String aus Einzeldaten zu konstruieren. Die folgende Abfrage generiert beispielsweise für jeden Bankkassierer einen Beschreibungsstring:
Max. Linie
Max. Linie Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
125
Links mysql> SELECT CONCAT(fname, ' ', lname, ' has been a ', -> title, ' since ', start_date) emp_narrative -> FROM employee -> WHERE title = 'Teller' OR title = 'Head Teller'; +---------------------------------------------------------+ | emp_narrative | +---------------------------------------------------------+ | Helen Fleming has been a Head Teller since 2008-03-17 | | Chris Tucker has been a Teller since 2008-09-15 | | Sarah Parker has been a Teller since 2006-12-02 | | Jane Grossman has been a Teller since 2006-05-03 | | Paula Roberts has been a Head Teller since 2006-07-27 | | Thomas Ziegler has been a Teller since 2004-10-23 | | Samantha Jameson has been a Teller since 2007-01-08 | | John Blake has been a Head Teller since 2004-05-11 | | Cindy Mason has been a Teller since 2006-08-09 | | Frank Portman has been a Teller since 2007-04-01 | | Theresa Markham has been a Head Teller since 2005-03-15 | | Beth Fowler has been a Teller since 2006-06-29 | | Rick Tulman has been a Teller since 2006-12-12 | +---------------------------------------------------------+ 13 rows in set (0.30 sec)
Die Funktion concat( ) kann mit jedem Ausdruck umgehen, der einen String liefert, und sie konvertiert sogar Zahlen und Datumswerte in das String-Format. Das wird erkennbar, wenn man die Datumsspalte (start_date) als Argument verwendet. Oracle Database hingegen kennt zwar auch eine concat( )-Funktion, doch diese nimmt nur zwei String-Argumente entgegen. Die oben gezeigte Abfrage wird also auf Oracle nicht funktionieren. Hier muss stattdessen der Verkettungsoperator (||) eingesetzt werden: SELECT fname || ' ' || lname || ' has been a ' || title || ' since ' || start_date emp_narrative FROM employee WHERE title = 'Teller' OR title = 'Head Teller';
SQL Server kennt die concat( )-Funktion überhaupt nicht. Hier muss man also genau wie in der letzten Abfrage vorgehen, allerdings mit dem Verkettungsoperator von SQL Server (+) an Stelle des || von Oracle. Nun ist concat( ) zwar nützlich, um am Anfang oder Ende eines Strings Zeichen hinzuzufügen, aber vielleicht möchten Sie ja auch einmal Zeichen in der Mitte eines Strings einsetzen. Alle drei Datenbankserver kennen für diesen Zweck unterschiedliche Funktionen. Daher werde ich zuerst die Funktion von MySQL und dann die der anderen beiden Server vorstellen.
Max. Linie
MySQL verwendet die Funktion insert( ), die vier Argumente entgegennimmt: Den Originalstring, die Startposition, die Anzahl der zu ersetzenden Zeichen und den Ersetzungsstring. Je nach dem Wert des dritten Arguments kann die Funktion Zeichen in einem String entweder einfügen oder ersetzen. Hat das dritte Argument den Wert null, wird der Ersetzungsstring eingefügt, und alle nachfolgenden Zeichen werden nach rechts verschoben:
126 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT INSERT('goodbye world', 9, 0, 'cruel ') string; +---------------------+ | string | +---------------------+ | goodbye cruel world | +---------------------+ 1 row in set (0.00 sec)
In diesem Beispiel wurden ab Position 9 alle Zeichen des Strings nach rechts verschoben und der string 'cruel ' eingefügt. Wenn das dritte Argument jedoch größer als null ist, wird die angegebene Anzahl Zeichen durch den Ersetzungsstring überschrieben: mysql> SELECT INSERT('goodbye world', 1, 7, 'hello') string; +-------------+ | string | +-------------+ | hello world | +-------------+ 1 row in set (0.00 sec)
In diesem Beispiel werden die ersten sieben Zeichen durch den String 'hello' ersetzt. Oracle Database hat zwar keine einzelne Funktion mit der Flexibilität der insert( )Funktion von MySQL, stellt jedoch die replace( )-Funktion zur Verfügung, die einen Teilstring durch einen anderen ersetzen kann. Hier habe ich das obige Beispiel auf replace( ) umgearbeitet: SELECT REPLACE('goodbye world', 'goodbye', 'hello') FROM dual;
Alle Instanzen des Strings 'goodbye' werden jetzt durch 'hello' ersetzt, was den String 'hello world' ergibt. Da die replace( )-Funktion wirklich jede Instanz des Suchstrings durch den Ersetzungsstring ersetzt, müssen Sie gut aufpassen, um nicht mehr Ersetzungen zu bekommen, als Sie geplant hatten. SQL Server kennt ebenfalls eine replace( )-Funktion mit der gleichen Funktionalität wie die von Oracle, aber zusätzlich stellt SQL Server auch eine Funktion namens stuff( ) zur Verfügung, die ähnlich der MySQL-Funktion insert( ) arbeitet. Hier sehen Sie ein Beispiel: SELECT STUFF('hello world', 1, 5, 'goodbye cruel')
Wenn Sie dies ausführen, werden fünf Zeichen ab Position 1 ersetzt, und dafür wird der String 'goodbye cruel' eingefügt. So ergibt sich 'goodbye cruel world'. Doch Teilstrings kann man nicht nur einfügen, sondern auch extrahieren. Zu diesem Zweck stellen alle drei Server die Funktion substring( ) zur Verfügung (wobei Oracles Version substr( ) heißt). Diese Funktion holt eine festgelegte Anzahl Zeichen an einer festgelegten Position aus einem String heraus. Das folgende Beispiel extrahiert fünf Zeichen ab der neunten Position aus einem String:
Max. Linie
Max. Linie
mysql> SELECT SUBSTRING('goodbye cruel world', 9, 5); +----------------------------------------+ | SUBSTRING('goodbye cruel world', 9, 5) |
Der Umgang mit String-Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
127
Links +----------------------------------------+ | cruel | +----------------------------------------+ 1 row in set (0.00 sec)
Zusammen mit den hier vorgestellten Funktionen bieten alle drei Server noch viele weitere eingebaute Funktionen zur Bearbeitung von String-Daten. Während viele dieser Funktionen sehr spezielle Aufgaben erfüllen (zum Beispiel die Erstellung des String-Äquivalents von Oktal- oder Hexadezimalzahlen), sind andere Funktionen in vielen Lebenslagen interessant, wie beispielsweise die, die am Ende eines Strings Leerzeichen entfernen oder anfügen. Weitere Informationen finden Sie in der SQL-Referenz Ihres Servers oder in einer allgemeinen SQL-Referenz wie SQL in a Nutshell (O’Reilly).
Der Umgang mit numerischen Daten Im Gegensatz zu Strings (und temporalen Daten, wie Sie gleich noch sehen werden) lassen sich numerische Daten ganz leicht generieren. Sie können eine Zahl eintippen, aus einer Spalte abrufen oder durch eine Berechnung erzeugen. Alle arithmetischen Operatoren (+, -, *, /) stehen für Berechnungen zur Verfügung, und zur Festlegung der Präzedenz dienen die runden Klammern: mysql> SELECT (37 * 59) / (78 - (8 * 6)); +----------------------------+ | (37 * 59) / (78 - (8 * 6)) | +----------------------------+ | 72.77 | +----------------------------+ 1 row in set (0.00 sec)
Wie bereits in Kapitel 2 gesagt, ist das Hauptproblem bei der Speicherung numerischer Daten die Rundung, die stattfindet, wenn die vorgegebene Größe einer Spalte überschritten wird. So wird zum Beispiel die Zahl 999,99 auf 99,9 gerundet, wenn man sie in einer Spalte vom Typ float(3,1) speichert.
Arithmetische Funktionen Die meisten eingebauten numerischen Funktionen werden für ganz bestimmte arithmetische Zwecke verwendet, etwa um die Quadratwurzel einer Zahl zu ermitteln. Tabelle 7-1 zeigt einige der gebräuchlicheren Funktionen, die ein einzelnes numerisches Argument nehmen und eine Zahl zurückgeben. Tabelle 7-1: Numerische Funktionen mit einem Argument
Max. Linie
Funktionsname
Beschreibung
Acos(x)
Berechnet den Arkuskosinus von x.
Asin(x)
Berechnet den Arkussinus von x.
128 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabelle 7-1: Numerische Funktionen mit einem Argument Funktionsname
Beschreibung
Atan(x)
Berechnet den Arkustangens von x.
Cos(x)
Berechnet den Kosinus von x.
Cot(x)
Berechnet den Kotangens von x.
Exp(x)
Berechnet ex.
Ln(x)
Berechnet den natürlichen Logarithmus von x.
Sin(x)
Berechnet den Sinus von x.
Sqrt(x)
Berechnet die Quadratwurzeln von x.
Tan(x)
Berechnet den Tangens von x.
Diese Funktionen haben ganz bestimmte Aufgaben, und ich werde mich bremsen, Ihnen jeweils Beispiele zu zeigen (wenn Sie eine Funktion nicht am Namen und an der Beschreibung erkennen, benötigen Sie sie auch nicht). Doch andere numerische Funktionen, die für Berechnungen eingesetzt werden, sind flexibler und lohnen eine Erklärung. Da gibt es zum Beispiel den modulo-Operator, der einen Divisionsrest berechnet und in MySQL und Oracle Database durch die mod( )-Funktion implementiert wird. Das folgende Beispiel berechnet den Rest von 4 geteilt durch 10: mysql> SELECT MOD(10,4); +-----------+ | MOD(10,4) | +-----------+ | 2 | +-----------+ 1 row in set (0.02 sec)
Die mod( )-Funktion wird zwar normalerweise mit ganzzahligen Argumenten verwendet, kann jedoch in MySQL 4.1.7 und höher auch mit reellen Zahlen kombiniert werden: mysql> SELECT MOD(22.75, 5); +---------------+ | MOD(22.75, 5) | +---------------+ | 2.75 | +---------------+ 1 row in set (0.02 sec)
SQL Server hat keine mod( )-Funktion. Stattdessen wird der Operator % für Divisionsreste verwendet. Der Ausdruck 10 % 4 ergibt somit den Wert 2.
Max. Linie
Eine weitere numerische Funktion mit zwei numerischen Argumenten ist pow( ) (oder power( ), wenn Sie mit Oracle Database oder SQL Server arbeiten). Sie gibt eine Zahl hoch einer anderen Zahl zurück:
Der Umgang mit numerischen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
129
Max. Linie
Links mysql> SELECT POW(2,8); +----------+ | POW(2,8) | +----------+ | 256 | +----------+ 1 row in set (0.03 sec)
Also ist pow(2,8) in MySQL äquivalent zu 28. Da Speicherplatz in Computern in Stücken von 2x Bytes zugewiesen wird, kann man mit der pow( )-Funktion ganz gut ermitteln, wie viele Bytes ein zugewiesenes Stück Speicher tatsächlich hat: mysql> SELECT POW(2,10) kilobyte, POW(2,20) megabyte, -> POW(2,30) gigabyte, POW(2,40) terabyte; +----------+----------+------------+---------------+ | kilobyte | megabyte | gigabyte | terabyte | +----------+----------+------------+---------------+ | 1024 | 1048576 | 1073741824 | 1099511627776 | +----------+----------+------------+---------------+ 1 row in set (0.00 sec)
Vielleicht geht’s Ihnen ja anders, aber ich persönlich kann mir ein Gigabyte besser als 230 Bytes merken, als mir die Zahl 1.073.741.824 einzuprägen.
Die Genauigkeit von Zahlen steuern Im Umgang mit Fließkommazahlen möchte man nicht immer sämtliche Dezimalstellen einer Zahl einbeziehen oder anzeigen. Die Daten von Geldtransaktionen kann man zum Beispiel bis zur sechsten Nachkommastelle genau speichern, aber für die Bildschirmanzeige auf zwei Nachkommastellen runden. Vier Funktionen sind praktisch, wenn es darum geht, die Genauigkeit von Fließkommazahlen im Zaum zu halten: ceil( ), floor( ), round( ) und truncate( ). Alle drei Server kennen diese Funktionen, auch wenn Oracle Database trunc( ) an Stelle von truncate( ) und SQL Server ceiling( ) an Stelle von ceil( ) verwendet. Die Funktionen ceil( ) und floor( ) runden auf den nächstliegenden Integer auf oder ab, wie hier zu sehen: mysql> SELECT CEIL(72.445), FLOOR(72.445); +--------------+---------------+ | CEIL(72.445) | FLOOR(72.445) | +--------------+---------------+ | 73 | 72 | +--------------+---------------+ 1 row in set (0.06 sec)
Max. Linie
Hier wird also jede Zahl zwischen 72 und 73 von ceil( ) als 73 und von floor( ) als 72 ausgewertet. Beachten Sie, dass ceil( ) auch dann aufrundet, wenn der Dezimalanteil einer Zahl sehr klein ist, und floor( ) auch dann abrundet, wenn der Dezimalanteil sehr groß ist:
130 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT CEIL(72.000000001), FLOOR(72.999999999); +--------------------+---------------------+ | CEIL(72.000000001) | FLOOR(72.999999999) | +--------------------+---------------------+ | 73 | 72 | +--------------------+---------------------+ 1 row in set (0.00 sec)
Wenn das für Ihre Anwendung etwas zu extrem ist, können Sie auch die round( )-Funktion einsetzen, um vom Mittelpunkt zwischen zwei Integern auf- oder abzurunden: mysql> SELECT ROUND(72.49999), ROUND(72.5), ROUND(72.50001); +-----------------+-------------+-----------------+ | ROUND(72.49999) | ROUND(72.5) | ROUND(72.50001) | +-----------------+-------------+-----------------+ | 72 | 72 | 73 | +-----------------+-------------+-----------------+ 1 row in set (0.00 sec)
Die Funktion round( ) rundet jede Zahl auf, deren Dezimalanteil über dem Mittelpunkt zwischen den beiden Integern liegt, und sie rundet jede Zahl ab, deren Dezimalanteil kleiner oder gleich dem Mittelpunkt zwischen den beiden Integern ist. Da man oft zumindest einen Teil der Dezimalstellen beibehalten möchte, anstatt gleich auf den nächsten Integer zu runden, nimmt die Funktion round( ) als optionales zweites Argument eine Anzahl von Dezimalstellen entgegen, um anzugeben, auf wie viele Stellen hinter dem Komma eine Zahl gerundet werden soll. Das folgende Beispiel zeigt, wie das zweite Argument verwendet wird, um die Zahl 72,0909 auf eine, zwei oder drei Nachkommastellen zu runden: mysql> SELECT ROUND(72.0909, 1), ROUND(72.0909, 2), ROUND(72.0909, 3); +-------------------+-------------------+-------------------+ | ROUND(72.0909, 1) | ROUND(72.0909, 2) | ROUND(72.0909, 3) | +-------------------+-------------------+-------------------+ | 72.1 | 72.09 | 72.091 | +-------------------+-------------------+-------------------+ 1 row in set (0.00 sec)
Wie round( ) nimmt auch die Funktion truncate( ) ein optionales zweites Argument entgegen, das eine Anzahl von Dezimalstellen angibt. Doch truncate( ) schneidet die überzähligen Stellen einfach ohne jede Rundung ab. Das folgende Beispiel zeigt, wie die Zahl 72,0909 auf eine, zwei oder drei Dezimalstellen gekappt wird:
Max. Linie
mysql> SELECT TRUNCATE(72.0909, 1), TRUNCATE(72.0909, 2), -> TRUNCATE(72.0909, 3); +----------------------+----------------------+----------------------+ | TRUNCATE(72.0909, 1) | TRUNCATE(72.0909, 2) | TRUNCATE(72.0909, 3) | +----------------------+----------------------+----------------------+ | 72.0 | 72.09 | 72.090 | +----------------------+----------------------+----------------------+ 1 row in set (0.00 sec)
Der Umgang mit numerischen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 131
Links SQL Server verwendet an Stelle von truncate( ) die Funktion round( ) mit einem optionalen dritten Argument. Wenn dieses angegeben und nicht null ist, wird die Zahl gekappt statt gerundet.
Sowohl truncate( ) als auch round( ) erlauben als zweites Argument auch einen negativen Wert. In diesem Fall werden die Ziffern links vom Dezimalzeichen gekappt oder gerundet. Das mag Ihnen vielleicht komisch vorkommen, hat aber in manchen Fällen seine Berechtigung. Vielleicht möchten Sie ja ein Produkt an den Mann bringen, das nur in Einheiten von zehn verkauft werden kann. Wenn ein Kunde 17 davon bestellt, können Sie die Bestellmenge mit einer der folgenden Methoden passend machen: mysql> SELECT ROUND(17, -1), TRUNCATE(17, -1); +---------------+------------------+ | ROUND(17, -1) | TRUNCATE(17, -1) | +---------------+------------------+ | 20 | 10 | +---------------+------------------+ 1 row in set (0.00 sec)
Wenn es sich bei dem fraglichen Produkt um Heftzwecken handelt, wird der Unterschied sich wohl nicht stark in der Bilanz niederschlagen, aber wenn Sie Rolex-Uhren verkaufen, kann die richtige Rundungsregel dem Umsatz doch recht förderlich sein.
Vorzeichenbehaftete Daten Wenn Sie mit numerischen Spalten umgehen, die negative Werte erlauben (in Kapitel 2 sagte ich Ihnen, dass man eine numerische Spalte auch als unsigned deklarieren kann, um nur positive Zahlen darin zuzulassen), können Ihnen mehrere numerische Funktionen von Nutzen sein. Angenommen, Sie sollen einen Report mit den aktuellen Salden aller Bankkonten generieren. Die folgende Abfrage liefert drei Spalten, die für diesen Report nützlich sein können:
Max. Linie
mysql> SELECT account_id, SIGN(avail_balance), ABS(avail_balance) -> FROM account; +------------+---------------------+--------------------+ | account_id | SIGN(avail_balance) | ABS(avail_balance) | +------------+---------------------+--------------------+ | 1 | 1 | 1057.75 | | 2 | 1 | 500.00 | | 3 | 1 | 3000.00 | | 4 | 1 | 2258.02 | | 5 | 1 | 200.00 | | ... | | 19 | 1 | 1500.00 | | 20 | 1 | 23575.12 | | 21 | 0 | 0.00 | | 22 | 1 | 9345.55 |
132 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 23 | 1 | 38552.05 | | 24 | 1 | 50000.00 | +------------+---------------------+--------------------+ 24 rows in set (0.00 sec)
Die zweite Spalte nutzt die sign( )-Funktion, um –1 für debitorische Konten, 0 für Nullsalden und 1 für kreditorische Konten zurückzugeben. Die dritte Spalte gibt die absoluten Werte der Kontensalden mithilfe der Funktion abs( ) zurück.
Der Umgang mit temporalen Daten Von den drei in diesem Kapitel behandelten Datentypen (Zeichendaten, numerische Daten und temporale Daten) sind Letztere am verzwicktesten zu generieren und zu manipulieren. Kompliziert wird es unter anderem deshalb, weil ein Datum und eine Uhrzeit auf mannigfaltige Weise beschrieben werden können. Das Datum, an dem ich diesen Absatz geschrieben habe, ließe sich zum Beispiel auf folgende Weisen darstellen: • Mittwoch, 17. September 2008 • 9/17/2008 2:14:56 P.M. EST • 17/9/2008 19:14:56 GMT • 2612008 (julianisches Format) • Sternzeit [–4] 85712.03 14:14:56 (Star Trek-Format) Während manche dieser Unterschiede lediglich eine Sache der Formatierung sind, ist die Komplexität des Ganzen vor allem eine Sache des Bezugsrahmens. Was damit gemeint ist, erkläre ich im nächsten Abschnitt.
Zeitzonen Da überall in der Welt die Menschen Wert darauf legen, dass der Mittag ungefähr mit dem höchsten Stand der Sonne zusammenfällt, wurde nie ernsthaft versucht, allen eine gemeinsame Uhrzeit aufzuzwingen. Stattdessen wurde die Welt in 24 imaginäre Teile eingeteilt, die sogenannten Zeitzonen. Innerhalb einer Zeitzone gilt für alle dieselbe Uhrzeit, aber zeitzonenübergreifend gelten verschiedene Uhrzeiten. Das mag noch einfach sein, aber da in einigen Gegenden die Uhrzeit zweimal jährlich um eine Stunde verschoben wird (man bezeichnet dies als Sommerzeit), während in anderen Regionen der Welt keine Sommerzeit gilt, kann die Uhrzeit an zwei Punkten der Welt mal um vier und mal um fünf Stunden abweichen. Selbst in derselben Zeitzone können manche Gegenden Sommerzeit haben und andere nicht, sodass verschiedene Uhren in derselben Zeitzone mal synchron laufen und mal um eine Stunde abweichen.
Max. Linie
Auch wenn dieses Thema erst im Computerzeitalter so richtig anfing, Verwirrung zu stiften, gibt es unterschiedliche Zeitzonen bereits seit den frühen Tagen der großen Entdecker. Um einen gemeinsamen Bezugspunkt für die Zeitfeststellung einzurichten, stellten die Seefahrer des 15. Jahrhunderts ihre Uhren auf die Uhrzeit der Stadt Greenwich in
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
133
Max. Linie
Links England ein. Diese Zeit nennt man Greenwich Mean Time oder GMT. Alle anderen Zeitzonen lassen sich als Abweichung einer gewissen Anzahl Stunden von der GMT beschreiben. So ist zum Beispiel die Uhrzeit in der Zeitzone der östlichen Staaten der USA, die sogenannte Eastern Standard Time, gleich GMT – 5:00, also fünf Stunden früher als GMT. Heutzutage verwendet man eine Variante der GMT namens »koordinierte Weltzeit» (Coordinated Universal Time oder UTC), die auf einer Atomuhr basiert oder, genauer gesagt, der Uhrzeit, die den Durchschnitt von 200 Atomuhren an 50 Standorten in aller Welt wiedergibt. Dies ist die sogenannte »Weltzeit« (Universal Time). SQL Server und MySQL kennen Funktionen, die den aktuellen UTC-Zeitstempel zurückgeben (getutcdate( ) für SQL Server und utc_timestamp( ) für MySQL). Die meisten Datenbankserver haben standardmäßig die Zeitzoneneinstellung des Servers, auf dem sie laufen, und stellen Tools zur Verfügung, um diese Zeitzone nach Bedarf zu ändern. So würde zum Beispiel eine Datenbank zur Speicherung von Börsengeschäften auf der ganzen Welt generell für UTC-Zeit konfiguriert sein, während eine Datenbank, die nur die Geschäfte eines bestimmten Einzelhandels registriert, die Zeitzone des Servers verwendet. MySQL kennt zwei verschiedene Zeitzoneneinstellungen: eine globale und eine SessionZeitzone, die für jeden in die Datenbank eingeloggten Benutzer eine andere sein kann. Beide Einstellungen sehen Sie in der folgenden Abfrage: mysql> SELECT @@global.time_zone, @@session.time_zone; +--------------------+---------------------+ | @@global.time_zone | @@session.time_zone | +--------------------+---------------------+ | SYSTEM | SYSTEM | +--------------------+---------------------+ 1 row in set (0.00 sec)
Der Wert system sagt Ihnen, dass der Server die Zeitzoneneinstellung des Servers verwendet, auf dem die Datenbank läuft. Wenn Sie an einem Computer in Zürich sitzen und über ein Netzwerk eine Session mit einem MySQL-Server in New York eröffnen, sollten Sie die Zeitzone für Ihre Session wie folgt ändern: mysql> SET time_zone = 'Europe/Zurich'; Query OK, 0 rows affected (0.18 sec)
Danach sehen die Zeitzoneneinstellungen folgendermaßen aus:
Max. Linie
mysql> SELECT @@global.time_zone, @@session.time_zone; +--------------------+---------------------+ | @@global.time_zone | @@session.time_zone | +--------------------+---------------------+ | SYSTEM | Europe/Zurich | +--------------------+---------------------+ 1 row in set (0.00 sec)
134 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Alle Daten, die in Ihrer Session nun angezeigt werden, richten sich nach der Uhrzeit von Zürich. Benutzer der Oracle Database können die Zeitzoneneinstellung für eine Session mit folgendem Befehl ändern: ALTER SESSION TIMEZONE = 'Europe/Zurich'
MySQL-Zeitzonendaten laden Wenn Sie den MySQL-Server auf Windows ausführen, müssen Sie die Zeitzonendaten manuell laden, ehe Sie globale oder Session-Zeitzonen einstellen können: 1. Laden Sie die Zeitzonendaten von http://dev.mysql.com/downloads/timezones.html herunter. 2. Fahren Sie den MySQL-Server herunter. 3. Extrahieren Sie die Dateien aus dem heruntergeladenen Zip-Archiv (in meinem Fall hieß die Datei timezone-2006p.zip) und legen Sie sie in Ihrem MySQL-Installationsverzeichnis unter /data/mysql ab (der vollständige Pfad meiner Installation war /Program Files/MySQL/MySQL Server 6.0/data/mysql). 4. Starten Sie den MySQL-Server neu. Um die Zeitzonendaten anzuschauen, gehen Sie mit dem Befehl use mysql in die mysqlDatenbank und führen folgende Abfrage aus: mysql> SELECT name FROM time_zone_name; +----------------------------------+ | name | +----------------------------------+ | Africa/Abidjan | | Africa/Accra | | Africa/Addis_Ababa | | Africa/Algiers | | Africa/Asmera | | Africa/Bamako | | Africa/Bangui | | Africa/Banjul | | Africa/Bissau | | Africa/Blantyre | | Africa/Brazzaville | | Africa/Bujumbura | ... | US/Alaska | | US/Aleutian | | US/Arizona | | US/Central |
Max. Linie
– Fortsetzung –
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 135
Links | US/East-Indiana | | US/Eastern | | US/Hawaii | | US/Indiana-Starke | | US/Michigan | | US/Mountain | | US/Pacific | | US/Samoa | | UTC | | W-SU | | WET | | Zulu | +----------------------------------+ 545 rows in set (0.01 sec)
Um Ihre Zeitzone zu ändern, wählen Sie aus diesen Ländern das aus, das Ihrem Standort am ehesten entspricht.
Temporale Daten generieren Temporale Daten können wie folgt generiert werden: • Man kopiert Daten aus einer vorhandenen Datums-, Datums-/Uhrzeit- oder Uhrzeitspalte. • Man führt eine eingebaute Funktion aus, die ein Datum, ein Datum und eine Uhrzeit oder eine Uhrzeit zurückgibt. • Man konstruiert eine String-Darstellung der temporalen Daten, um sie vom Server auswerten zu lassen. Für das zuletzt genannte Verfahren müssen Sie wissen, welche Bestandteile in der Datumsformatierung eingesetzt werden.
String-Darstellungen von temporalen Daten In Tabelle 2-6 in Kapitel 2 wurden die bekanntesten Datumsbestandteile bereits genannt. Um Ihr Gedächtnis aufzufrischen, werden diese Komponenten in Tabelle 7-2 noch einmal wiederholt. Tabelle 7-2: Datumsformatbestandteile
Max. Linie
Bestandteil
Definition
Wertebereich
YYYY
Jahr und Jahrhundert
1000 bis 9999
MM
Monat
01 (January) bis 12 (December)
DD
Tag
01 bis 31
HH
Stunde
00 bis 23
136 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabelle 7-2: Datumsformatbestandteile (Fortsetzung) Bestandteil
Definition
Wertebereich
HHH
Stunden (verstrichen)
–838 bis 838
MI
Minute
00 bis 59
SS
Sekunde
00 bis 59
Um einen String zu erstellen, den der Server als date, datetime oder time interpretieren kann, müssen Sie diese Bestandteile in der Reihenfolge zusammensetzen, die in Tabelle 7-3 gezeigt wird. Tabelle 7-3: Erforderliche Datumsbestandteile Typ
Standardformat
Date
YYYY-MM-DD
Datetime
YYYY-MM-DD HH:MI:SS
Timestamp
YYYY-MM-DD HH:MI:SS
Time
HHH:MI:SS
Um also eine datetime-Spalte auf 15.30 Uhr am 17. September 2008 zu setzen, müssen Sie folgenden String konstruieren: '2008-09-17 15:30:00'
Wenn der Server einen datetime-Wert erwartet, wie es beispielsweise bei der Aktualisierung einer datetime-Spalte der Fall ist, oder wenn eine eingebaute Funktion mit einem datetime-Argument aufgerufen wird, können Sie einen korrekt formatierten String mit den erforderlichen Datumsbestandteilen übergeben. Der Server erledigt die Konvertierung für Sie. Die folgende Anweisung modifiziert zum Beispiel das Datum eines Bankgeschäfts: UPDATE transaction SET txn_date = '2008-09-17 15:30:00' WHERE txn_id = 99999;
Der Server stellt fest, dass der String in der SET-Klausel ein datetime-Wert sein muss, da er als Inhalt für eine datetime-Spalte dient. Daher versucht der Server den String zu konvertieren, indem er ihn in seine sechs Bestandteile parst (Jahr, Monat, Tag, Stunde, Minute, Sekunde), die im Standardformat von Datums-/Uhrzeitangaben enthalten sind.
Strings in Datumswerte konvertieren
Max. Linie
Erwartet der Server keinen datetime-Wert, müssen Sie ihn anweisen, den String in ein datetime zu konvertieren. Die folgende einfache Beispielabfrage liefert einen datetimeWert mithilfe der cast( )-Funktion:
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
137
Max. Linie
Links mysql> SELECT CAST('2008-09-17 15:30:00' AS DATETIME); +-----------------------------------------+ | CAST('2008-09-17 15:30:00' AS DATETIME) | +-----------------------------------------+ | 2008-09-17 15:30:00 | +-----------------------------------------+ 1 row in set (0.00 sec)
Die cast( )-Funktion wird am Ende dieses Kapitels erläutert. Dieses Beispiel zeigt zwar nur die Erstellung von datetime-Werten, aber die gleiche Logik gilt auch für die Typen date und time. Die folgende Abfrage nutzt die cast( )-Funktion, um einen date-Wert und einen time-Wert zu generieren: mysql> SELECT CAST('2008-09-17' AS DATE) date_field, -> CAST('108:17:57' AS TIME) time_field; +------------+------------+ | date_field | time_field | +------------+------------+ | 2008-09-17 | 108:17:57 | +------------+------------+ 1 row in set (0.00 sec)
Anstatt eine implizite Konvertierung vornehmen zu lassen, können Sie Ihre Strings natürlich auch explizit konvertieren, und zwar selbst dann, wenn der Server einen date-, datetime- oder time-Wert erwartet. Wenn Strings in temporale Werte (implizit oder explizit) konvertiert werden, müssen alle Datumsbestandteile in der erforderlichen Reihenfolge übergeben werden. Während andere Server das Datumsformat ziemlich streng handhaben, ist der MySQL-Server locker, was die Trennzeichen zwischen den Datumsbestandteilen betrifft. MySQL akzeptiert alle folgenden Strings als gültige Darstellungen von 15:30 Uhr des 17. Spetember 2008: '2008-09-17 15:30:00' '2008/09/17 15:30:00' '2008,09,17,15,30,00' '20080917153000'
Das gibt Ihnen zwar Flexibilität, aber gelegentlich möchten Sie vielleicht auch einen temporalen Wert ohne die Standard-Datumsbestandteile generieren. Der nächste Abschnitt stellt eine eingebaute Funktion vor, die viel flexibler als cast( ) ist.
Funktionen zur Datumserzeugung
Max. Linie
Wenn Sie temporale Daten aus einem String erzeugen müssen und dieser String keine für cast( ) geeignete Form hat, können Sie eine eingebaute Funktion nutzen, die zusammen mit einem Datums-String einen Format-String liefert. MySQL stellt zu diesem Zweck die Funktion str_to_date( ) zur Verfügung. Nehmen wir an, Sie möchten den String »September 17, 2008« aus einer Datei herausholen und in eine date-Spalte setzen. Da der String nicht in dem erforderlichen Format YYYY-MM-DD vorliegt, können Sie die Funk-
138 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts tion str_to_date( ) verwenden, anstatt ihn neu zu formatieren. Danach können Sie die cast( )-Funktion wieder benutzen: UPDATE individual SET birth_date = STR_TO_DATE('September 17, 2008', '%M %d, %Y') WHERE cust_id = 9999;
Das zweite Argument im Aufruf von str_to_date( ) definiert das Format des DatumsStrings, in diesem Fall einen Monatsnamen (%M), eine numerische Tagesangabe (%d) und eine numerische, vierziffrige Jahresangabe (%Y). Es gibt zwar mehr als 30 anerkannte Formatbestandteile, aber Tabelle 7-4 zeigt nur ein rundes Dutzend davon, nämlich die meistgenutzten. Tabelle 7-4: Datumsformatbestandteile Formatbestandteile
Beschreibung
%M
Monatsname (January bis December)
%m
Monat (numerisch) (01 bis 12)
%d
Tag (numerisch) (01 bis 31)
%j
Tag des Jahres (001 bis 366)
%W
Wochentag (Sunday bis Saturday)
%Y
Jahr, vier Ziffern (numerisch)
%y
Jahr, zwei Ziffern (numerisch)
%H
Stunde (00 bis 23)
%h
Stunde (01 bis 12)
%i
Minuten (00 bis 59)
%s
Sekunden (00 bis 59)
%f
Mikrosekunden (000000 bis 999999)
%p
A.M. oder P.M.
Die Funktion str_to_date( ) gibt je nach Inhalt des Format-Strings einen datetime-, dateoder time-Wert zurück. Wenn der Format-String zum Beispiel nur %H, %i und %s enthält, wird ein time-Wert zurückgegeben. Nutzer von Oracle Database können die to_date( )-Funktion genau wie die MySQL-Funktion str_to_date( ) einsetzen. SQL Server bietet eine convert()-Funktion, die nicht ganz so flexibel ist wie die von MySQL und Oracle Database. Sie können keinen eigenen Format-String angeben, sondern Ihr String muss einem von 21 vordefinierten Formaten entsprechen.
Max. Linie
Wenn Sie das aktuelle Datum und die aktuelle Uhrzeit generieren möchten, müssen Sie keinen String konstruieren, da die folgenden eingebauten Funktionen auf die Systemzeit schauen und einen entsprechenden String zurückliefern:
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
139
Max. Linie
Links mysql> SELECT CURRENT_DATE(), CURRENT_TIME(), CURRENT_TIMESTAMP(); +----------------+----------------+---------------------+ | CURRENT_DATE() | CURRENT_TIME() | CURRENT_TIMESTAMP() | +----------------+----------------+---------------------+ | 2008-09-18 | 19:53:12 | 2008-09-18 19:53:12 | +----------------+----------------+---------------------+ 1 row in set (0.12 sec)
Die Rückgabewerte dieser Funktionen haben das Standardformat für den temporalen Typ, der jeweils zurückgeliefert wird. Oracle Database kennt die Funktionen current_ date( ) und current_timestamp( ), aber nicht current_time( ). SQL Server kennt nur die current_timestamp( )-Funktion.
Temporale Daten bearbeiten Dieser Abschnitt stellt die eingebauten Funktionen vor, die Datumsargumente nehmen und Datumswerte, Strings oder Zahlen zurückgeben.
Temporale Funktionen, die Datumswerte zurückgeben Viele der eingebauten temporalen Funktionen nehmen ein einzelnes Datum als Argument entgegen und geben ein anderes Datum zurück. Die MySQL-Funktion date_add( ) zum Beispiel ermöglicht es, zu einem angegebenen Datum einen Zeitraum (Tage, Monate oder Jahre) zu addieren, um ein anderes Datum zu generieren. Das folgende Beispiel addiert fünf Tage zum aktuellen Datum hinzu: mysql> SELECT DATE_ADD(CURRENT_DATE(), INTERVAL 5 DAY); +------------------------------------------+ | DATE_ADD(CURRENT_DATE(), INTERVAL 5 DAY) | +------------------------------------------+ | 2008-09-22 | +------------------------------------------+ 1 row in set (0.06 sec)
Das zweite Argument hat drei Elemente: das Schlüsselwort interval, die gewünschte Menge und die Art des Intervalls. Tabelle 7-5 zeigt einige gebräuchliche Intervalltypen. Tabelle 7-5: Gebräuchliche Intervalltypen Intervall
Max. Linie
Beschreibung
Second
Anzahl der Sekunden
Minute
Anzahl der Minuten
Hour
Anzahl der Stunden
Day
Anzahl der Tage
Month
Anzahl der Monate
Year
Anzahl der Jahre
Minute_second
Anzahl der Minuten und Sekunden, getrennt durch »:«
140 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Tabelle 7-5: Gebräuchliche Intervalltypen (Fortsetzung) Intervall
Beschreibung
Hour_second
Anzahl der Stunden, Minuten und Sekunden, getrennt durch »:«
Year_month
Anzahl der Jahre und Monate, getrennt durch »-«
Die ersten sechs Intervalltypen aus Tabelle 7-5 sind einfach, doch die letzten drei müssen etwas näher erläutert werden, da sie mehrere Elemente enthalten. Wenn man Ihnen beispielsweise sagt, dass die Transaktions-ID 9999 in Wirklichkeit 3 Stunden, 27 Minuten und 11 Sekunden später auftrat, als an die transaction-Tabelle gemeldet, können Sie dies mit folgender Anweisung beheben: UPDATE transaction SET txn_date = DATE_ADD(txn_date, INTERVAL '3:27:11' HOUR_SECOND) WHERE txn_id = 9999;
In diesem Beispiel nimmt die Funktion date_add( ) den Wert der Spalte txn_date, addiert 3 Stunden, 27 Minuten und 11 Sekunden dazu und schreibt das Ergebnis in die txn_dateSpalte. Ein anderes Beispiel. Sie arbeiten in der Personalabteilung (HR) und stellen fest, dass der Mitarbeiter mit der Personalnummer 4789 sich jünger gemacht hat, als er in Wirklichkeit ist. Dann könnten Sie folgendermaßen 9 Jahre und 11 Monate zu seinem Geburtsdatum hinzuaddieren: UPDATE employee SET birth_date = DATE_ADD(birth_date, INTERVAL '9-11' YEAR_MONTH) WHERE emp_id = 4789;
Benutzer von SQL Server können das obige Beispiel mit der Funktion dateadd( ) nachspielen: UPDATE employee SET birth_date = DATEADD(MONTH, 119, birth_date) WHERE emp_id = 4789
Da SQL Server keine kombinierten Intervalle kennt (d.h. year_month), habe ich 9 Jahre und 11 Monate in 119 Monate umgerechnet. Oracle Database-Nutzer lösen diese Aufgabe mit der Funktion add_months( ): UPDATE employee SET birth_date = ADD_MONTHS(birth_date, 119) WHERE emp_id = 4789;
Max. Linie
In manchen Fällen möchte man ein Intervall zu einem Datum addieren und weiß zwar, wo man ankommen möchte, aber nicht, wie viele Tage man benötigt, um dorthin zu kommen. Angenommen, ein Bankkunde meldet sich beim Onlinebanking an und plant eine Überweisung für das Ende des Monats. Anstatt Code zu schreiben, der den aktuellen Monat ermittelt und dann nachschaut, wie viele Tage dieser Monat hat, können Sie die last_day( )-Funktion aufrufen, die Ihnen diese Arbeit abnimmt. (MySQL und Oracle
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
141
Max. Linie
Links Database kennen die last_day( )-Funktion; SQL Server hat nichts Vergleichbares). Wenn der Kunde am 17. September 2008 das Geld anweisen möchte, wird der letzte Tag des Monats September folgendermaßen ermittelt: mysql> SELECT LAST_DAY('2008-09-17'); +------------------------+ | LAST_DAY('2008-09-17') | +------------------------+ | 2008-09-30 | +------------------------+ 1 row in set (0.10 sec)
Egal ob Sie einen date- oder datetime-Wert liefern, die last_day( )-Funktion gibt Ihnen immer ein date zurück. Sie mag zwar auf den ersten Blick keine große Zeitersparnis bringen, aber ihre Logik kann knifflig sein, wenn man den letzten Tag des Monats Februar ermittelt und herausfinden muss, ob das Jahr ein Schaltjahr ist oder nicht. Eine andere temporale Funktion, die ein Datum liefert, konvertiert einen datetime-Wert von einer Zeitzone in eine andere. Hierzu kennt MySQL die Funktion convert_tz( ) und Oracle Database die Funktion new_time( ). Wenn ich meine Ortszeit in UTC konvertieren möchte, könnte ich Folgendes tun: mysql> SELECT CURRENT_TIMESTAMP() current_est, -> CONVERT_TZ(CURRENT_TIMESTAMP(), 'US/Eastern', 'UTC') current_utc; +---------------------+---------------------+ | current_est | current_utc | +---------------------+---------------------+ | 2008-09-18 20:01:25 | 2008-09-19 00:01:25 | +---------------------+---------------------+ 1 row in set (0.76 sec)
Diese Funktion ist praktisch, wenn Sie Datumswerte in einer anderen Zeitzone als der Ihrer Datenbank empfangen.
Temporale Funktionen, die Strings zurückgeben Die meisten temporalen Funktionen, die String-Werte zurückliefern, werden verwendet, um einen Teil eines Datums oder einer Uhrzeit zu extrahieren. MySQL hat zum Beispiel die Funktion dayname( ), um zu ermitteln, auf welchen Wochentag ein bestimmtes Datum fällt: mysql> SELECT DAYNAME('2008-09-18'); +-----------------------+ | DAYNAME('2008-09-18') | +-----------------------+ | Thursday | +-----------------------+ 1 row in set (0.08 sec)
Max. Linie
MySQL enthält viele solcher Funktionen, mit denen sich Teile aus Datumswerten herausziehen lassen, aber ich empfehle Ihnen, stattdessen die extract( )-Funktion zu verwenden, da man sich leichter ein paar Variationen derselben Funktion als ein Dutzend
142 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts verschiedene Funktionen merken kann. Außerdem gehört extract( ) zum SQL:2003Standard und wurde auch von Oracle Database implementiert. Die extract( )-Funktion verwendet die gleichen Intervalltypen wie date_add( ) (siehe Tabelle 7-5), um anzugeben, welcher Teil des Datums von Interesse ist. Wenn Sie zum Beispiel nur das Jahr aus einem datetime-Wert herausholen möchten, tun Sie Folgendes: mysql> SELECT EXTRACT(YEAR FROM '2008-09-18 22:19:05'); +------------------------------------------+ | EXTRACT(YEAR FROM '2008-09-18 22:19:05') | +------------------------------------------+ | 2008 | +------------------------------------------+ 1 row in set (0.00 sec)
SQL Server hat zwar nicht extract( ) implementiert, aber er kennt die datepart( )-Funktion. Folgendes Beispiel extrahiert das Jahr mit datepart( ) aus einem datetime-Wert: SELECT DATEPART(YEAR, GETDATE( ))
Temporale Funktionen, die Zahlen zurückgeben Weiter oben in diesem Kapitel habe ich Ihnen eine Funktion gezeigt, die ein Intervall zu einem Datumswert addieren und dadurch ein neues Datum generieren kann. Eine andere häufige Aktivität bei der Arbeit mit Datumswerten ist die, dass man zwei Werte nimmt und die Anzahl der Intervalle (Tage, Wochen, Jahre) zwischen diesen beiden Daten ermittelt. Für diesen Zweck gibt es bei MySQL die Funktion datediff( ), die die Anzahl der vollen Tage zwischen zwei Daten zurückgibt. Wenn ich zum Beispiel wissen möchte, wie viele Tage meine Kinder in diesem Sommer Schulferien haben, sage ich: mysql> SELECT DATEDIFF('2009-09-03', '2009-06-24'); +--------------------------------------+ | DATEDIFF('2009-09-03', '2009-06-24') | +--------------------------------------+ | 71 | +--------------------------------------+ 1 row in set (0.05 sec)
Ich muss mich also auf Nervenkitzel, Mückenstiche und aufgeschlagene Knie gefasst machen, ehe die Racker endlich wieder sicher in der Schule untergebracht sind. Die datediff( )-Funktion ignoriert Uhrzeiten in ihren Argumenten. Selbst wenn ich die Uhrzeit des einen Datums auf eine Sekunde vor und die des zweiten auf eine Sekunde nach Mitternacht einstellen würde, hätte dies keinen Einfluss auf die Berechnung:
Max. Linie
mysql> SELECT DATEDIFF('2009-09-03 23:59:59', '2009-06-24 00:00:01'); +--------------------------------------------------------+ | DATEDIFF('2009-09-03 23:59:59', '2009-06-24 00:00:01') | +--------------------------------------------------------+ | 71 | +--------------------------------------------------------+ 1 row in set (0.00 sec)
Der Umgang mit temporalen Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 143
Links Wenn ich die Argumente vertausche und das frühere Datum zuerst nenne, gibt datediff( ) eine negative Zahl zurück: mysql> SELECT DATEDIFF('2009-06-24', '2009-09-03'); +--------------------------------------+ | DATEDIFF('2009-06-24', '2009-09-03') | +--------------------------------------+ | -71 | +--------------------------------------+ 1 row in set (0.01 sec)
SQL Server kennt die datediff( )-Funktion ebenfalls, aber in einer flexibleren Implementierung als MySQL: Sie können den Intervalltyp angeben (Jahr, Monat, Tag oder Stunde), anstatt nur die Tage zwischen zwei Daten zu zählen. Hier sehen Sie, wie SQL Server das obige Beispiel lösen würde: SELECT DATEDIFF(DAY, '2009-06-24', '2009-09-03')
Bei Oracle Database können Sie die Anzahl der Tage zwischen zwei Daten angeben, indem Sie einfach das eine Datum von dem anderen subtrahieren.
Konvertierungsfunktionen Weiter oben in diesem Kapitel zeigte ich Ihnen bereits, wie man mit der cast( )-Funktion einen String in einen datetime-Wert konvertiert. Zwar hat jeder Datenbankserver eine Reihe von proprietären Funktionen, die Typkonvertierungen vornehmen können, aber ich rate Ihnen grundsätzlich zur cast( )-Funktion, da diese im SQL:2003-Standard enthalten ist und von MySQL, Oracle Database und Microsoft SQL Server gleichermaßen implementiert wird. Um cast( ) zu verwenden, liefern Sie einen Wert oder Ausdruck, das Schlüsselwort as und den Typ, in den der Wert konvertiert werden soll. Das folgende Beispiel konvertiert einen String in einen Integer: mysql> SELECT CAST('1456328' AS SIGNED INTEGER); +-----------------------------------+ | CAST('1456328' AS SIGNED INTEGER) | +-----------------------------------+ | 1456328 | +-----------------------------------+ 1 row in set (0.01 sec)
Wenn sie einen String in eine Zahl konvertiert, versucht die cast( )-Funktion, den gesamten String von links nach rechts umzuwandeln. Findet sie darin irgendwelche nichtnumerischen Zeichen, endet die Konvertierung ohne Fehlermeldung. Betrachten Sie folgendes Beispiel:
Max. Linie
Max. Linie 144 | Kapitel 7: Daten generieren, konvertieren und manipulieren This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Rechts mysql> SELECT CAST('999ABC111' AS UNSIGNED INTEGER); +---------------------------------------+ | CAST('999ABC111' AS UNSIGNED INTEGER) | +---------------------------------------+ | 999 | +---------------------------------------+ 1 row in set, 1 warning (0.08 sec) mysql> show warnings; +---------+------+------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------+ | Warning | 1292 | Truncated incorrect INTEGER value: '999ABC111' | +---------+------+------------------------------------------------+ 1 row in set (0.07 sec)
Hier werden die ersten drei Ziffern des Strings konvertiert, aber der Rest wird verworfen, sodass sich der Wert 999 ergibt. Der Server meldet allerdings eine Warnung, um Ihnen mitzuteilen, dass nicht der gesamte String umgewandelt wurde. Wenn Sie einen String in einen date-, time- oder datetime-Wert konvertieren, müssen Sie sich an die Standardformate jedes Typs halten, da man der cast( )-Funktion keinen Format-String übergeben kann. Liegt Ihr Datum nicht im Standardformat vor (d.h. YYYYMM-DD HH:MI:SS für datetime-Typen), müssen Sie zu einer anderen Funktion greifen. Dafür empfiehlt sich beispielsweise die MySQL-Funktion str_to_date( ), die weiter oben in diesem Kapitel beschrieben wird.
Testen Sie Ihr Wissen Diese Übungen sollen Ihre Kenntnisse über die in diesem Kapitel vorgestellten eingebauten Funktionen festigen. In Anhang C finden Sie die Antworten.
Übung 7-1 Schreiben Sie eine Abfrage, die das 17. bis 25. Zeichen des Strings »Please find the substring in this string« findet.
Übung 7-2 Schreiben Sie eine Abfrage, die den absoluten Wert und das Vorzeichen (–1, 0 oder 1) der Zahl –25.76823 findet. Geben Sie die Zahl außerdem auf das nächste Hundertstel gerundet zurück.
Max. Übung 7-3 Linie Schreiben Sie eine Abfrage, die nur den Monat des aktuellen Datums zurückliefert. Testen Sie Ihr Wissen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 145
First
Kapitel 8
KAPITEL 8
Gruppieren und Aggregieren von Daten
Daten werden im Allgemeinen so feinkörnig gespeichert, wie es den Anforderungen der Datenbanknutzer entspricht. Wenn Martin im Rechnungswesen einzelne Kundentransaktionen aufrufen muss, ist in der Datenbank eine Tabelle vonnöten, die genau diese Einzeltransaktionen speichert. Das heißt aber nicht, dass alle Benutzer die Daten genau so verwenden müssen, wie sie in der Datenbank gespeichert sind. Dieses Kapitel betrachtet, wie man Daten zusammenfassen und ansammeln kann, damit die Benutzer mit ihnen auf einer grobkörnigeren Ebene umgehen können, als in der Datenbank gespeichert.
Gruppieren von Daten Gelegentlich möchten Sie vielleicht einen Trend in Ihren Daten ausfindig machen. Dazu muss der Server diese Daten ein wenig aufbereiten, ehe die gesuchten Resultate zu Tage treten. Angenommen, Sie sind in der Bank der Leiter des operativen Geschäfts und möchten herausfinden, welcher Kassierer wie viele Konten eröffnet hat. Eine einfache Abfrage liefert Ihnen folgende Rohdaten:
Max. Linie
mysql> SELECT open_emp_id -> FROM account; +-------------+ | open_emp_id | +-------------+ | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | | 10 | | 10 | | 10 |
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
147
Links | 10 | | 10 | | 10 | | 10 | | 13 | | 13 | | 13 | | 16 | | 16 | | 16 | | 16 | | 16 | | 16 | +-------------+ 24 rows in set (0.01 sec)
Bei nur 24 Zeilen in der account-Tabelle ist es relativ einfach zu sehen, dass vier verschiedene Mitarbeiter die Konten eröffnet haben und dass der Kassierer mit der Personalnummer 16 sogar sechs Konten eröffnet hat. Doch bei einer Bank mit zig Angestellten und Tausenden von Konten wäre ein solches Vorgehen öde und fehleranfällig. Stattdessen können Sie den Datenbankserver mit einer group by-Klausel veranlassen, die Daten zusammenzufassen. In der folgenden, ähnlichen Abfrage gruppiert die group byKlausel die Kontendaten nach Personalnummern: mysql> SELECT open_emp_id -> FROM account -> GROUP BY open_emp_id; +-------------+ | open_emp_id | +-------------+ | 1 | | 10 | | 13 | | 16 | +-------------+ 4 rows in set (0.00 sec)
Die Ergebnismenge enthält für jeden unterschiedlichen Wert in der open_emp_id-Spalte eine Zeile, sodass statt der vollen 24 Zeilen nur vier Ergebniszeilen vorhanden sind. Der Grund für diese verkleinerte Ergebnismenge besteht darin, dass jeder der vier Angestellten mehrere Konten eröffnet hat. Um die genaue Anzahl in Erfahrung zu bringen, können Sie eine Aggregatfunktion in die select-Klausel schreiben. Diese zählt, wie viele Zeilen in jeder Gruppe vorhanden sind:
Max. Linie
mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 |
148 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 10 | 7 | | 13 | 3 | | 16 | 6 | +-------------+----------+ 4 rows in set (0.00 sec)
Die Aggregatfunktion count( ) zählt die Zeilen in den einzelnen Gruppen, und der Stern lässt den Server alle Elemente der Gruppe zählen. Mit einer Kombination aus group byKlausel und count( )-Aggregatfunktion können Sie genau die Daten generieren, die Sie benötigen, um Fragen im Geschäftsprozess zu beantworten. Sie müssen sich nicht mehr mit Rohdaten abplagen. Beim Gruppieren von Daten müssen manchmal unerwünschte gruppierte Daten (und nicht Rohdaten) aus der Ergebnismenge herausgefiltert werden. Da die group by-Klausel erst läuft, nachdem die where-Klausel ausgewertet wurde, können Sie der where-Klausel keine Filterbedingungen für diesen Zweck hinzufügen. Im Folgenden versuche ich zum Beispiel, alle Fälle herauszufiltern, in denen Angestellte weniger als fünf Konten eröffnet haben: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> WHERE COUNT(*) > 4 -> GROUP BY open_emp_id; ERROR 1111 (HY000): Invalid use of group function
In einer where-Klausel darf nicht auf die Aggregatfunktion count(*) verwiesen werden, da die Gruppen zu dem Zeitpunkt der Auswertung der where-Klausel noch gar nicht ausgewertet worden sind. Stattdessen können Sie Ihre Gruppen-Filterbedingungen in die having-Klausel schreiben und folgende Variante der Abfrage formulieren: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> HAVING COUNT(*) > 4; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 | | 10 | 7 | | 16 | 6 | +-------------+----------+ 3 rows in set (0.00 sec)
Da Gruppen, die weniger als fünf Elemente haben, per having-Klausel herausgefiltert worden sind, enthält die Ergebnismenge jetzt nur noch diejenigen Angestellten, die fünf oder mehr Konten eröffnet haben. Der Angestellte mit der ID 13 wird im Ergebnis also nicht mehr berücksichtigt.
Max. Linie
Max. Linie Gruppieren von Daten | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
149
Links Aggregatfunktionen Aggregatfunktionen führen eine bestimmte Operation mit allen Zeilen einer Gruppe aus. Zwar hat jeder Datenbankserver seine eigenen, ganz speziellen Aggregatfunktionen, aber es gibt auch ganz gebräuchliche, die von allen bedeutenden Servern implementiert werden: Max( )
Gibt den größten Wert in einer Menge zurück. Min( )
Gibt den kleinsten Wert in einer Menge zurück. Avg( )
Gibt den Durchschnittswert einer Menge zurück. Sum( )
Gibt die Summe der Werte einer Menge zurück. Count( )
Gibt die Anzahl der Werte in einer Menge zurück. Die folgende Abfrage verwendet alle diese gebräuchlichen Aggregatfunktionen, um die Salden aller Girokonten zu analysieren: mysql> SELECT MAX(avail_balance) max_balance, -> MIN(avail_balance) min_balance, -> AVG(avail_balance) avg_balance, -> SUM(avail_balance) tot_balance, -> COUNT(*) num_accounts -> FROM account -> WHERE product_cd = 'CHK'; +-------------+-------------+-------------+-------------+--------------+ | max_balance | min_balance | avg_balance | tot_balance | num_accounts | +-------------+-------------+-------------+-------------+--------------+ | 38552.05 | 122.37 | 7300.800985 | 73008.01 | 10 | +-------------+-------------+-------------+-------------+--------------+ 1 row in set (0.09 sec)
Die Ergebnisse dieser Abfrage verraten, dass bei den zehn Girokonten in der accountTabelle der höchste Saldo $38.552,05 und der niedrigste $122,37 beträgt. Der Durchschnittssaldo ist $7.300,80 und die Summe aller Salden $73.008,01. Ich hoffe, dass Ihnen dies eine erste Vorstellung von der Rolle der Aggregatfunktionen vermittelt hat. In den nächsten Abschnitten wird noch klarer werden, wie man diese Funktionen am besten einsetzt.
Implizite und explizite Gruppen
Max. Linie
Im obigen Beispiel wird jeder Rückgabewert der Abfrage von einer Aggregatfunktion generiert, und die Aggregatfunktionen werden auf die Zeilengruppe angewendet, die durch die Filterbedingung product_cd = 'CHK' definiert ist. Da es keine group by-Klausel
150 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts gibt, ist eine einzige, implizite Gruppe vorhanden (alle von der Abfrage zurückgegebenen Zeilen). In den meisten Fällen möchte man jedoch zusätzlich zu den von Aggregatfunktionen generierten noch weitere Spalten abrufen. Was wäre zum Beispiel, wenn Sie die obige Abfrage erweiterten, um dieselben fünf Aggregatfunktionen für jeden Produkttyp anstatt nur für Girokonten auszuführen? Für eine solche Abfrage müsste man die Spalte product_ cd zusammen mit den fünf Aggregatfunktionen verwenden: SELECT product_cd, MAX(avail_balance) max_balance, MIN(avail_balance) min_balance, AVG(avail_balance) avg_balance, SUM(avail_balance) tot_balance, COUNT(*) num_accounts FROM account;
Doch wenn Sie diese Abfrage ausführen, wird folgender Fehler gemeldet: ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT( ),...) with no GROUP columns is illegal if there is no GROUP BY-Klausel
Ihnen selbst mag es vielleicht klar sein, dass Sie die Aggregatfunktionen auf jede Produktmenge anwenden möchten, die in der account-Tabelle zu finden ist. Doch die Abfrage scheitert, da Sie nicht explizit gesagt haben, wie die Daten gruppiert werden sollen. Daher müssen Sie eine group by-Klausel hinzufügen, in der steht, auf welche Zeilengruppen die Aggregatfunktionen angewendet werden sollen: mysql> SELECT product_cd, -> MAX(avail_balance) max_balance, -> MIN(avail_balance) min_balance, -> AVG(avail_balance) avg_balance, -> SUM(avail_balance) tot_balance, -> COUNT(*) num_accts -> FROM account -> GROUP BY product_cd; +------------+-------------+-------------+--------------+-------------+-----------+ | product_cd | max_balance | min_balance | avg_balance | tot_balance | num_accts | +------------+-------------+-------------+--------------+-------------+-----------+ | BUS | 9345.55 | 0.00 | 4672.774902 | 9345.55 | 2 | | CD | 10000.00 | 1500.00 | 4875.000000 | 19500.00 | 4 | | CHK | 38552.05 | 122.37 | 7300.800985 | 73008.01 | 10 | | MM | 9345.55 | 2212.50 | 5681.713216 | 17045.14 | 3 | | SAV | 767.77 | 200.00 | 463.940002 | 1855.76 | 4 | | SBL | 50000.00 | 50000.00 | 50000.000000 | 50000.00 | 1 | +------------+-------------+-------------+--------------+-------------+-----------+ 6 rows in set (0.00 sec)
Max. Linie
Durch die neue group by-Klausel weiß der Server nun, dass er zuerst Zeilen mit dem gleichen Wert in der Spalte product_cd zusammengruppieren und dann die fünf Aggregatfunktionen auf jede der so entstandenen sechs Gruppen anwenden soll.
Aggregatfunktionen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
151
Max. Linie
Links Unterschiedliche Werte zählen Wenn Sie mithilfe der count( )-Funktion die Anzahl der Elemente in jeder Gruppe ermitteln, haben Sie die Wahl: Sie können entweder alle Elemente zählen oder nur die unterschiedlichen Werte einer Spalte quer durch alle Mitglieder einer Gruppe. Betrachten Sie zum Beispiel die folgenden Daten, die zeigen, welcher Mitarbeiter welches Konto eröffnet hat: mysql> SELECT account_id, open_emp_id -> FROM account -> ORDER BY open_emp_id; +------------+-------------+ | account_id | open_emp_id | +------------+-------------+ | 8 | 1 | | 9 | 1 | | 10 | 1 | | 12 | 1 | | 13 | 1 | | 17 | 1 | | 18 | 1 | | 19 | 1 | | 1 | 10 | | 2 | 10 | | 3 | 10 | | 4 | 10 | | 5 | 10 | | 14 | 10 | | 22 | 10 | | 6 | 13 | | 7 | 13 | | 24 | 13 | | 11 | 16 | | 15 | 16 | | 16 | 16 | | 20 | 16 | | 21 | 16 | | 23 | 16 | +------------+-------------+ 24 rows in set (0.00 sec)
Wie man sieht, haben vier verschiedene Mitarbeiter (nämlich die mit den Personalnummern 1, 10, 13 und 16) jeweils mehrere Konten eröffnet. Angenommen, Sie möchten diese nicht manuell zählen, sondern eine Abfrage schreiben, um zu zählen, wie viele Angestellte Konten eröffnet haben. Wenn Sie die count( )-Funktion auf die Spalte open_ emp_id anwenden, erhalten Sie folgende Ergebnisse:
Max. Linie
mysql> SELECT COUNT(open_emp_id) -> FROM account; +--------------------+ | COUNT(open_emp_id) | +--------------------+
152 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 24 | +--------------------+ 1 row in set (0.00 sec)
Geben Sie also open_emp_id als zu zählende Spalte an, sind die Ergebnisse genau die gleichen, wie wenn Sie count(*) sagen. Möchten Sie jedoch unterschiedliche Werte in der Gruppe zählen, anstatt einfach nur die Zeilenzahl in der Gruppe festzustellen, müssen Sie das Schlüsselwort distinct verwenden: mysql> SELECT COUNT(DISTINCT open_emp_id) -> FROM account; +-----------------------------+ | COUNT(DISTINCT open_emp_id) | +-----------------------------+ | 4 | +-----------------------------+ 1 row in set (0.00 sec)
Mit distinct betrachtet die count( )-Funktion bei jedem Mitglied der Gruppe die einzelnen Spaltenwerte, um doppelte Werte auszumachen und zu entfernen, anstatt einfach nur die Werte in der Gruppe zusammenzuzählen.
Ausdrücke Als Argumente für Aggregatfunktionen können Sie nicht nur Spalten, sondern auch Ausdrücke verwenden. Angenommen, Sie möchten den Höchstbetrag der noch nicht gebuchten Einlagen aller Konten berechnen. Um ihn zu berechnen, muss man das verfügbare Guthaben von dem ausstehenden Guthaben subtrahieren: mysql> SELECT MAX(pending_balance - avail_balance) max_uncleared -> FROM account; +---------------+ | max_uncleared | +---------------+ | 660.00 | +---------------+ 1 row in set (0.00 sec)
Dieses Beispiel verwendet zwar einen ganz einfachen Ausdruck, aber man kann Ausdrücke, die als Argumente für Aggregatfunktionen dienen, auch beliebig komplex gestalten, solange sie nur eine Zahl, einen String oder ein Datum zurückliefern. In Kapitel 11 zeige ich Ihnen, wie man case-Ausdrücke mit Aggregatfunktionen kombiniert, um festzustellen, ob eine bestimmte Zeile in eine Aggregation aufgenommen werden soll oder nicht.
Umgang mit Null-Werten
Max. Linie
Bei Aggregationen oder eigentlich bei allen numerischen Berechnungen müssen Sie immer berücksichtigen, wie das Berechnungsergebnis durch null-Werte beeinflusst werden könnte. Zur Veranschaulichung erstelle ich eine einfache Tabelle zur Speicherung numerischer Daten und bevölkere sie mit der Menge {1, 3, 5}:
Aggregatfunktionen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
153
Max. Linie
Links mysql> CREATE TABLE number_tbl -> (val SMALLINT); Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO number_tbl VALUES (1); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO number_tbl VALUES (3); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO number_tbl VALUES (5); Query OK, 1 row affected (0.00 sec)
Betrachten Sie nun die folgende Abfrage, die fünf Aggregatfunktionen auf einer Zahlenmenge ausführt: mysql> SELECT COUNT(*) num_rows, -> COUNT(val) num_vals, -> SUM(val) total, -> MAX(val) max_val, -> AVG(val) avg_val -> FROM number_tbl; +----------+----------+-------+---------+---------+ | num_rows | num_vals | total | max_val | avg_val | +----------+----------+-------+---------+---------+ | 3 | 3 | 9 | 5 | 3.0000 | +----------+----------+-------+---------+---------+ 1 row in set (0.08 sec)
Das Ergebnis ist keine Überraschung: count(*) und count(val) geben beide den Wert 3 zurück, sum(val) den Wert 9, max(val) den Wert 5 und avg(val) den Wert 3. Doch nun werde ich einen null-Wert in die Tabelle number_tbl einfügen und dieselbe Abfrage erneut ausführen: mysql> INSERT INTO number_tbl VALUES (NULL); Query OK, 1 row affected (0.01 sec) mysql> SELECT COUNT(*) num_rows, -> COUNT(val) num_vals, -> SUM(val) total, -> MAX(val) max_val, -> AVG(val) avg_val -> FROM number_tbl; +----------+----------+-------+---------+---------+ | num_rows | num_vals | total | max_val | avg_val | +----------+----------+-------+---------+---------+ | 4 | 3 | 9 | 5 | 3.0000 | +----------+----------+-------+---------+---------+ 1 row in set (0.00 sec)
Max. Linie
Auch nach Einfügung des null-Werts in die Tabelle geben die Funktionen sum( ), max( ) und avg( ) alle die gleichen Werte zurück, ein Zeichen dafür, dass sie alle null-Werte, auf die sie stoßen, schlicht ignorieren. Die count(*)-Funktion gibt jetzt den Wert 4 zurück.
154 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Das ist zulässig, da die number_tbl-Tabelle vier Zeilen enthält. Doch die count(val)-Funktion gibt immer noch 3 zurück. Der Unterschied besteht darin, dass count(*) die Zeilen zählt, während count(val) die Anzahl der Werte zählt, die in der val-Spalte stehen, und dabei null-Werte übergeht.
Gruppen generieren Nur selten kommt es vor, dass sich jemand für Rohdaten interessiert. Die meisten Nutzer, die mit Datenanalyse zu tun haben, benötigen bearbeitete Daten, die eher ihren Anforderungen entsprechen. Häufig kommen folgende Datenmanipulationen vor: • Gesamtbeträge für eine geografische Region generieren, z.B. alle Umsätze in Europa • Überflieger ausfindig machen, z.B. den erfolgreichsten Verkäufer 2005 • Häufigkeiten ermitteln, z.B. die Anzahl der Kontoeröffnungen pro Zweigstelle Um solche Fragen zu beantworten, muss der Datenbankserver Zeilen nach Spalten oder Ausdrücken zusammenfassen. Wie bereits in mehreren Beispielen gezeigt wurde, ist die group by-Klausel der Mechanismus zum Gruppieren von Daten innerhalb einer Abfrage. In diesem Abschnitt werden Sie sehen, wie man Daten nach einer oder mehreren Spalten gruppiert, wie man Daten durch Ausdrücke gruppiert und wie man Zusammenfassungen innerhalb von Gruppen generiert.
Gruppieren auf einer einzelnen Spalte Gruppierungen auf einer einzigen Spalte sind am einfachsten und am häufigsten. Wenn Sie sich beispielsweise für den Gesamtsaldo jedes Produkts interessieren, müssen Sie lediglich auf der Spalte account.product_cd gruppieren: mysql> SELECT product_cd, SUM(avail_balance) prod_balance -> FROM account -> GROUP BY product_cd; +------------+--------------+ | product_cd | prod_balance | +------------+--------------+ | BUS | 9345.55 | | CD | 19500.00 | | CHK | 73008.01 | | MM | 17045.14 | | SAV | 1855.76 | | SBL | 50000.00 | +------------+--------------+ 6 rows in set (0.00 sec)
Diese Abfrage generiert sechs Gruppen, nämlich für jedes Produkt eine, und bildet dann die Summe aller Zeilen in jeder Gruppe.
Max. Linie
Max. Linie Gruppen generieren | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
155
Links Gruppieren auf mehreren Spalten In manchen Fällen möchte man Gruppen generieren, die mehrere Spalten umfassen. Als Erweiterung des vorstehenden Beispiels könnte man nicht nur für jedes Produkt den Gesamtsaldo herausfinden, sondern für Produkte und Zweigstellen (in der Art: Welchen Gesamtsaldo haben alle Girokonten der Zweigstelle Woburn?). Das folgende Beispiel zeigt, wie das geht: mysql> SELECT product_cd, open_branch_id, -> SUM(avail_balance) tot_balance -> FROM account -> GROUP BY product_cd, open_branch_id; +------------+----------------+-------------+ | product_cd | open_branch_id | tot_balance | +------------+----------------+-------------+ | BUS | 2 | 9345.55 | | BUS | 4 | 0.00 | | CD | 1 | 11500.00 | | CD | 2 | 8000.00 | | CHK | 1 | 782.16 | | CHK | 2 | 3315.77 | | CHK | 3 | 1057.75 | | CHK | 4 | 67852.33 | | MM | 1 | 14832.64 | | MM | 3 | 2212.50 | | SAV | 1 | 767.77 | | SAV | 2 | 700.00 | | SAV | 4 | 387.99 | | SBL | 3 | 50000.00 | +------------+----------------+-------------+ 14 rows in set (0.00 sec)
Diese Version der Abfrage generiert 14 Gruppen, eine für jede Produkt/Zweigstelle-Kombination in der account-Tabelle. Ich habe die open_branch_id-Spalte nicht nur der selectKlausel, sondern auch der group by-Klausel hinzugefügt, da open_branch_id von einer Tabelle abgefragt und nicht mit einer Aggregatfunktion generiert wird.
Gruppieren mit Ausdrücken Sie können Daten jedoch nicht nur auf Spalten gruppieren, sondern auch anhand von Werten, die durch Ausdrücke geliefert werden. Die folgende Abfrage gruppiert Mitarbeiter nach dem Jahr ihres Eintritts in die Bank:
Max. Linie
mysql> SELECT EXTRACT(YEAR FROM start_date) year, -> COUNT(*) how_many -> FROM employee -> GROUP BY EXTRACT(YEAR FROM start_date); +------+----------+ | year | how_many | +------+----------+ | 2004 | 2 |
156 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 2005 | 3 | | 2006 | 8 | | 2007 | 3 | | 2008 | 2 | +------+----------+ 5 rows in set (0.15 sec)
Die Abfrage verwendet einen recht einfachen Ausdruck, der mithilfe der extract( )-Funktion nur den Jahresbestandteil eines Datums zurückliefert, um die Zeilen der employeeTabelle zu gruppieren.
Rollups generieren Im Abschnitt »Gruppieren auf mehreren Spalten« weiter oben in diesem Kapitel zeigte ich ein Beispiel, in dem Kontensalden pro Produkt und Zweigstelle aufsummiert wurden. Nun wollen wir jedoch zusammen mit den Gesamtsalden für jede Produkt/ZweigstelleKombination auch die Gesamtsalden für jedes unterschiedliche Produkt ermitteln. Dazu könnte man eine zusätzliche Abfrage ausführen und die Ergebnisse zusammenführen, man könnte die Abfrageergebnisse in eine Tabellenkalkulation übertragen oder ein PerlSkript, ein Java-Programm oder einen anderen Mechanismus ersinnen, um an die Daten zu kommen und die zusätzlichen Berechnungen auszuführen. Doch die beste Lösung ist die Option with rollup, die dafür sorgt, dass der Datenbankserver Ihnen die ganze Arbeit abnimmt. Hier sehen Sie die überarbeitete Abfrage mit der with rollup-Option in der group by-Klausel:
Max. Linie
mysql> SELECT product_cd, open_branch_id, -> SUM(avail_balance) tot_balance -> FROM account -> GROUP BY product_cd, open_branch_id WITH ROLLUP; +------------+----------------+-------------+ | product_cd | open_branch_id | tot_balance | +------------+----------------+-------------+ | BUS | 2 | 9345.55 | | BUS | 4 | 0.00 | | BUS | NULL | 9345.55 | | CD | 1 | 11500.00 | | CD | 2 | 8000.00 | | CD | NULL | 19500.00 | | CHK | 1 | 782.16 | | CHK | 2 | 3315.77 | | CHK | 3 | 1057.75 | | CHK | 4 | 67852.33 | | CHK | NULL | 73008.01 | | MM | 1 | 14832.64 | | MM | 3 | 2212.50 | | MM | NULL | 17045.14 | | SAV | 1 | 767.77 | | SAV | 2 | 700.00 | | SAV | 4 | 387.99 | | SAV | NULL | 1855.76 |
Gruppen generieren | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 157
Links | SBL | 3 | 50000.00 | | SBL | NULL | 50000.00 | | NULL | NULL | 170754.46 | +------------+----------------+-------------+ 21 rows in set (0.02 sec)
Nun gibt es noch sieben zusätzliche Ergebnismengen, je eine für die sechs unterschiedlichen Produkte und eine weitere für das große Ganze (die Kombination aller Produkte). Bei den sechs Produkt-Rollups wird für die Spalte open_branch_id ein null-Wert übergeben, da der Rollup alle Zweigstellen berücksichtigen soll. Wenn Sie Zeile 3 der Ausgabe betrachten, erkennen Sie, dass bezogen auf alle Zweigstellen insgesamt $9.345,55 auf BUS-Konten lagen. In der Zeile der Gesamtbeträge wird für die Spalten product_cd und open_branch_id jeweils ein null-Wert eingesetzt. Die letzte Zeile der Ausgabe zeigt einen Gesamtbetrag von $170.754,46 für alle Produkte und Zweigstellen an. Wenn Sie Oracle Database verwenden, müssen Sie eine etwas abweichende Syntax für Rollups verwenden. Die group by-Klausel für die obige Abfrage sähe bei Oracle folgendermaßen aus: GROUP BY ROLLUP(product_cd, open_branch_id)
Diese Syntax hat den Vorteil, dass sie Rollups auf einer Teilmenge der Spalten in der group by-Klausel ermöglicht. Wenn Sie beispielsweise nach den Spalten a, b, und c gruppieren, können Sie den Server mit folgender Klausel veranlassen, Rollups nur auf b und c vorzunehmen: GROUP BY a, ROLLUP(b, c)
Wenn Sie Gesamtbeträge nicht nur pro Produkt, sondern auch pro Zweigstelle berechnen möchten, können Sie die Option with cube einsetzen, die Zusammenfassungszeilen für alle möglichen Kombinationen der Gruppierungsspalten erstellt. Leider steht with cube in der Version 4.1 von MySQL nicht zur Verfügung, wohl aber für SQL Server und Oracle Database. Hier zeige ich ein Beispiel mit with cube, wobei ich jedoch den mysql>-Prompt weglasse, um klarzustellen, dass eine solche Abfrage mit MySQL noch nicht durchführbar ist:
Max. Linie
SELECT product_cd, open_branch_id, SUM(avail_balance) tot_balance FROM account GROUP BY product_cd, open_branch_id WITH CUBE; +------------+----------------+-------------+ | product_cd | open_branch_id | tot_balance | +------------+----------------+-------------+ | NULL | NULL | 170754.46 | | NULL | 1 | 27882.57 | | NULL | 2 | 21361.32 | | NULL | 3 | 53270.25 | | NULL | 4 | 68240.32 | | BUS | 2 | 9345.55 | | BUS | 4 | 0.00 | | BUS | NULL | 9345.55 | | CD | 1 | 11500.00 |
158 | Kapitel 8: Gruppieren und Aggregieren von Daten This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | CD | 2 | 8000.00 | | CD | NULL | 19500.00 | | CHK | 1 | 782.16 | | CHK | 2 | 3315.77 | | CHK | 3 | 1057.75 | | CHK | 4 | 67852.33 | | CHK | NULL | 73008.01 | | MM | 1 | 14832.64 | | MM | 3 | 2212.50 | | MM | NULL | 17045.14 | | SAV | 1 | 767.77 | | SAV | 2 | 700.00 | | SAV | 4 | 387.99 | | SAV | NULL | 1855.76 | | SBL | 3 | 50000.00 | | SBL | NULL | 50000.00 | +------------+----------------+-------------+ 25 rows in set (0.02 sec)
with cube generiert vier Zeilen mehr als with rollup, nämlich eine für jede der vier Zweigstellennummern. Wie bei with rollup werden auch hier null-Werte in die Spalte product_ cd eingesetzt, um anzuzeigen, dass hier eine Zweigstellenzusammenfassung ausgeführt
wurde. Auch hier gilt für Oracle Database eine andere Syntax. Die group by-Klausel für die obige Abfrage müsste in Oracle wie folgt formuliert werden: GROUP BY CUBE(product_cd, open_branch_id)
Gruppen-Filterbedingungen In Kapitel 4 haben Sie die verschiedenen Arten von Filterbedingungen kennengelernt und gesehen, wie man diese in der where-Klausel einsetzt. Beim Gruppieren von Daten können Sie auch nach dem Generieren der Gruppen Filterbedingungen auf die Daten anwenden. Diese Art von Filterbedingungen platziert man in die having-Klausel. Betrachten Sie folgendes Beispiel:
Max. Linie
mysql> SELECT product_cd, SUM(avail_balance) prod_balance -> FROM account -> WHERE status = 'ACTIVE' -> GROUP BY product_cd -> HAVING SUM(avail_balance) >= 10000; +------------+--------------+ | product_cd | prod_balance | +------------+--------------+ | CD | 19500.00 | | CHK | 73008.01 | | MM | 17045.14 | | SBL | 50000.00 | +------------+--------------+ 4 rows in set (0.00 sec)
Gruppen-Filterbedingungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
159
Links Diese Abfrage hat zwei Filterbedingungen: eine in der where-Klausel, um inaktive Konten herauszufiltern, und eine in der having-Klausel, um jedes Produkt herauszufiltern, das einen Gesamtsaldo von weniger als $10.000 aufweist. Also wird einer der Filter angewendet, bevor die Daten gruppiert werden, und der andere danach. Wenn Sie versehentlich beide Filter in die where-Klausel schreiben, wird folgender Fehler gemeldet: mysql> SELECT product_cd, SUM(avail_balance) prod_balance -> FROM account -> WHERE status = 'ACTIVE' -> AND SUM(avail_balance) > 10000 -> GROUP BY product_cd; ERROR 1111 (HY000): Invalid use of group-Funktion
Diese Abfrage scheitert, weil man keine Aggregatfunktion in die where-Klausel einer Abfrage schreiben darf. Der Grund: Da Filter in der where-Klausel ausgewertet werden, bevor die Gruppierung eintritt, kann der Server noch keine Funktionen auf Gruppen ausführen. Wenn Sie einer Abfrage mit einer group by-Klausel Filterbedingungen geben, müssen Sie genau überlegen, ob der Filter auf Rohdaten wirkt (dann gehört er in die where-Klausel) oder auf gruppierte Daten (denn gehört er in die having-Klausel).
Sie können allerdings sehr wohl in die having-Klausel Aggregatfunktionen einbinden, die nicht in der select-Klausel auftreten: mysql> SELECT product_cd, SUM(avail_balance) prod_balance -> FROM account -> WHERE status = 'ACTIVE' -> GROUP BY product_cd -> HAVING MIN(avail_balance) >= 1000 -> AND MAX(avail_balance) SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE account_id = (SELECT MAX(account_id) FROM account);
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
163
Links +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 29 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 1 row in set (0.65 sec)
In diesem Beispiel gibt die Unterabfrage den größten Wert zurück, der in der account_idSpalte der account-Tabelle auftritt, und die übergeordnete Anweisung liefert dann die Daten über dieses Konto. Wenn Sie einmal unsicher sind, was eine Unterabfrage tut, können Sie sie auch isoliert ausführen (ohne die Klammern), um zu schauen, was sie zurückgibt. Hier sehen Sie die Unterabfrage aus obigem Beispiel: mysql> SELECT MAX(account_id) FROM account; +------------+-----+ | MAX(account_id) | +--------+---------+ | 29 | +-----------------+ 1 row in set (0.00 sec)
Da diese Unterabfrage eine einzige Zeile mit einer einzigen Spalte zurückgibt, kann man sie als Ausdruck in einer Gleichheitsbedingung verwenden. (Würde sie mehrere Zeilen zurückliefern, könnte man sie zwar mit etwas vergleichen, aber nicht gleichsetzen. Doch dazu später mehr.) Im vorliegenden Beispiel können Sie den Rückgabewert der Unterabfrage nehmen und als rechten Ausdruck der Filterbedingung in der übergeordneten Abfrage einsetzen: mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE account_id = 29; +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 29 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 1 row in set (0.02 sec)
In diesem Fall ist die Unterabfrage nützlich, da sie es ermöglicht, Daten über das Konto mit der höchsten Kontonummer in einer einzigen Abfrage zu liefern, anstatt zuerst die höchste account_id mit einer und dann die gewünschten Daten der account-Tabelle mit einer anderen Abfrage zu ermitteln. Sie werden noch sehen: Unterabfragen sind in vielen Lebenslagen praktisch und könnten vielleicht Ihr Lieblingstool im Werkzeugkasten von SQL werden.
Typen von Unterabfragen Max. Linie
Zuvor wurde bereits gesagt, welch unterschiedliche Arten von Ergebnismengen eine Unterabfrage zurückliefern kann (eine Zeile/eine Spalte, eine Zeile/mehrere Spalten oder mehrere Zeilen/mehrere Spalten). Doch es gibt noch einen anderen Faktor, nach dem
164 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts sich Unterabfragen differenzieren lassen: Manche Unterabfragen sind vollständig in sich geschlossen (sogenannte nicht-korrelierte Unterabfragen), während andere Spalten aus der übergeordneten Anweisung referenzieren (sogenannte korrelierte Unterabfragen). Die nächsten Abschnitte werden diese beiden Arten von Unterabfragen vorstellen und zeigen, welche Operatoren man mit ihnen verwenden kann.
Nicht-korrelierte Unterabfragen Das obige Beispiel zeigt eine nicht-korrelierte Unterabfrage, die für sich allein ausgeführt werden kann und keine Spalten der übergeordneten Anweisung benötigt. Solche Unterabfragen werden Ihnen am häufigsten begegnen, sofern Sie keine update- oder deleteAnweisungen schreiben: In diesen beiden Anweisungen kommen korrelierte Unterabfragen häufig vor (darüber später mehr). Die Beispiel-Unterabfrage ist aber nicht nur nichtkorreliert, sondern gibt auch eine Tabelle zurück, die aus einer einzigen Zeile und einer einzigen Spalte besteht. Dieser Typ von Unterabfrage wird als skalare Unterabfrage bezeichnet und kann auf beiden Seiten einer Bedingung mit den üblichen Operatoren (=, , , =) auftreten. Das nächste Beispiel zeigt, wie man eine skalare Unterabfrage in einer Ungleichheitsbedingung einsetzen kann:
Max. Linie
mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE open_emp_id (SELECT e.emp_id -> FROM employee e INNER JOIN branch b -> ON e.assigned_branch_id = b.branch_id -> WHERE e.title = 'Head Teller' AND b.city = 'Woburn'); +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 7 | CHK | 3 | 1057.75 | | 8 | MM | 3 | 2212.50 | | 10 | CHK | 4 | 534.12 | | 11 | SAV | 4 | 767.77 | | 12 | MM | 4 | 5487.09 | | 13 | CHK | 5 | 2237.97 | | 14 | CHK | 6 | 122.37 | | 15 | CD | 6 | 10000.00 | | 18 | CHK | 8 | 3487.19 | | 19 | SAV | 8 | 387.99 | | 21 | CHK | 9 | 125.67 | | 22 | MM | 9 | 9345.55 | | 23 | CD | 9 | 1500.00 | | 24 | CHK | 10 | 23575.12 | | 25 | BUS | 10 | 0.00 | | 28 | CHK | 12 | 38552.05 | | 29 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 17 rows in set (0.86 sec)
Nicht-korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 165
Links Diese Abfrage gibt alle Konten zurück, die nicht vom Chefkassierer der Zweigstelle Woburn eröffnet worden sind. (Für die Unterabfrage gilt die Annahme, dass jede Zweigstelle nur einen Chefkassierer hat.) Die Unterabfrage in diesem Beispiel ist insofern etwas komplexer als die im vorigen, als sie zwei Tabellen verbindet und auch zwei Filterbedingungen enthält. Unterabfragen können so einfach oder so komplex wie nötig sein und dürfen beliebige Abfrageklauseln verwenden (select, from, where, group by, having und order by). Wenn Sie in einer Gleichheitsbedingung eine Unterabfrage nutzen, die mehrere Zeilen zurückgibt, wird ein Fehler gemeldet. Ändern wir beispielsweise die vorige Abfrage so ab, dass die Unterabfrage alle Kassierer von Woburn anstatt nur eines einzigen Chefkassierers zurückliefert, erhalten wir folgenden Fehler: mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE open_emp_id (SELECT e.emp_id -> FROM employee e INNER JOIN branch b -> ON e.assigned_branch_id = b.branch_id -> WHERE e.title = 'Teller' AND b.city = 'Woburn'); ERROR 1242 (21000): Subquery returns more than 1 row
Wenn Sie die Unterabfrage einzeln ausführen, erhalten Sie folgendes Ergebnis: mysql> SELECT e.emp_id -> FROM employee e INNER JOIN branch b -> ON e.assigned_branch_id = b.branch_id -> WHERE e.title = 'Teller' AND b.city = 'Woburn'; +--------+ | emp_id | +--------+ | 11 | | 12 | +--------+ 2 rows in set (0.02 sec)
Die übergeordnete Abfrage scheitert, weil ein einzelner Ausdruck (open_emp_id) nicht mit mehreren Ausdrücken (emp_ids 11 und 12) gleichgesetzt werden kann. Mit anderen Worten: Ein Ding kann nicht gleich einer Menge von Dingen sein. Im nächsten Abschnitt werden wir dieses Problem lösen, indem wir einen anderen Operator einsetzen.
Unterabfragen, die eine Spalte und mehrere Zeilen liefern Wenn Ihre Unterabfrage mehrere Zeilen zurückgibt, kann sie nicht auf einer der Seiten einer Gleichheitsbedingung eingesetzt werden; das hat das vorige Beispiel gezeigt. Es gibt jedoch noch vier weitere Operatoren, um Bedingungen mit solchen Unterabfragen zu formulieren.
Max. Linie
Max. Linie 166 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Rechts Die Operatoren in und not in Ein einzelner Wert lässt sich zwar nicht mit mehreren Werten gleichsetzen, aber man kann prüfen, ob er in einer Menge von Werten enthalten ist. Das nächste Beispiel verwendet zwar keine Unterabfrage, aber es zeigt, wie man eine Bedingung konstruiert, die mit dem in-Operator einen Wert in einer Wertemenge ausfindig macht: mysql> SELECT branch_id, name, city -> FROM branch -> WHERE name IN ('Headquarters', 'Quincy Branch'); +-----------+---------------+---------+ | branch_id | name | city | +-----------+---------------+---------+ | 1 | Headquarters | Waltham | | 3 | Quincy Branch | Quincy | +-----------+---------------+---------+ 2 rows in set (0.03 sec)
Der Ausdruck auf der linken Seite der Bedingung ist die Spalte name und der auf der rechten Seite eine String-Menge. Der in-Operator schaut nach, ob einer der Strings in der Namensspalte zu finden ist. Wenn ja, ist die Bedingung erfüllt, und die Zeile wird der Ergebnismenge hinzugefügt. Dieselben Resultate ließen sich auch mit zwei Gleichheitsbedingungen erzielen: mysql> SELECT branch_id, name, city -> FROM branch -> WHERE name = 'Headquarters' OR name = 'Quincy Branch'; +-----------+---------------+---------+ | branch_id | name | city | +-----------+---------------+---------+ | 1 | Headquarters | Waltham | | 3 | Quincy Branch | Quincy | +-----------+---------------+---------+ 2 rows in set (0.01 sec)
Das erscheint zwar vernünftig, wenn die Menge nur zwei Ausdrücke enthält, aber man erkennt sofort die Vorteile einer einzigen Bedingung mit in-Operator, wenn man sich vorstellt, dass die Menge dutzende (oder hunderte oder tausende) von Werten enthielte. Nun mag es zwar gelegentlich vorkommen, dass Sie eine Menge von Strings, Datumswerten oder Zahlen erstellen, um sie auf einer Seite einer Bedingung einzusetzen, aber der wahrscheinlichere Fall ist der, dass die Menge mittels einer Unterabfrage, die eine oder mehrere Zeilen zurückgibt, erst bei der Ausführung der Abfrage angelegt wird. Die folgende Abfrage verwendet den in-Operator mit einer Unterabfrage auf der rechten Seite der Filterbedingung, um zu schauen, welche Mitarbeiter die Vorgesetzten von anderen sind:
Max. Linie
mysql> SELECT emp_id, fname, lname, title -> FROM employee -> WHERE emp_id IN (SELECT superior_emp_id -> FROM employee);
Nicht-korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 167
Links +--------+---------+-----------+--------------------+ | emp_id | fname | lname | title | +--------+---------+-----------+--------------------+ | 1 | Michael | Smith | President | | 3 | Robert | Tyler | Treasurer | | 4 | Susan | Hawthorne | Operations Manager | | 6 | Helen | Fleming | Head Teller | | 10 | Paula | Roberts | Head Teller | | 13 | John | Blake | Head Teller | | 16 | Theresa | Markham | Head Teller | +--------+---------+-----------+--------------------+ 7 rows in set (0.01 sec)
Die Unterabfrage gibt die Personalnummern aller Vorgesetzten zurück, und die übergeordnete Abfrage ruft aus der employee-Tabelle vier Spalten für diese Mitarbeiter ab. Hier sind die Ergebnisse der Unterabfrage: mysql> SELECT superior_emp_id -> FROM employee; +-----------------+ | superior_emp_id | +-----------------+ | NULL | | 1 | | 1 | | 3 | | 4 | | 4 | | 4 | | 4 | | 4 | | 6 | | 6 | | 6 | | 10 | | 10 | | 13 | | 13 | | 16 | | 16 | +-----------------+ 18 rows in set (0.00 sec)
Max. Linie
Wie man sieht, tauchen einige Personalnummern mehrmals auf, da manche Mitarbeiter mehrere unter sich haben. Das beeinträchtigt jedoch nicht die Ergebnisse der übergeordneten Abfrage, da es egal ist, ob eine Personalnummer in der Ergebnismenge der Unterabfrage einmal oder mehrmals auftaucht. Wenn die Doppelnennungen in der Ergebnistabelle der Unterabfrage Sie stören, können Sie natürlich das Schlüsselwort distinct in der select-Klausel der Unterabfrage verwenden, aber an dem Ergebnis der übergeordneten Abfrage ändert sich dadurch nichts.
168 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Man kann nicht nur prüfen, ob ein Wert in einer Menge existiert, sondern auch, ob er darin nicht existiert: und zwar mit dem not in-Operator. Die folgende Version der Abfrage verwendet not in anstelle von in: mysql> SELECT emp_id, fname, lname, title -> FROM employee -> WHERE emp_id NOT IN (SELECT superior_emp_id -> FROM employee -> WHERE superior_emp_id IS NOT NULL); +--------+----------+----------+----------------+ | emp_id | fname | lname | title | +--------+----------+----------+----------------+ | 2 | Susan | Barker | Vice President | | 5 | John | Gooding | Loan Manager | | 7 | Chris | Tucker | Teller | | 8 | Sarah | Parker | Teller | | 9 | Jane | Grossman | Teller | | 11 | Thomas | Ziegler | Teller | | 12 | Samantha | Jameson | Teller | | 14 | Cindy | Mason | Teller | | 15 | Frank | Portman | Teller | | 17 | Beth | Fowler | Teller | | 18 | Rick | Tulman | Teller | +--------+----------+----------+----------------+ 11 rows in set (0.00 sec)
Diese Abfrage findet alle Angestellten, die keine Vorgesetzten sind. Hierfür musste ich der Unterabfrage eine Filterbedingung hinzufügen, um zu gewährleisten, dass in der Rückgabetabelle der Unterabfrage keine null-Werte auftreten. Warum das nötig ist, erfahren Sie im nächsten Abschnitt.
Der all-Operator Während der in-Operator nachschaut, ob ein Ausdruck in einer Menge von Ausdrücken auftritt, stellt der all-Operator Vergleiche zwischen einem einzelnen Wert und jedem Wert in einer Menge an. Um eine solche Bedingung zu konstruieren, benötigen Sie einen der Vergleichsoperatoren (=, , usw.) in Verbindung mit dem all-Operator. Die nächste Abfrage sucht zum Beispiel alle Mitarbeiter, deren Personalnummern nicht mit irgendeiner Personalnummer einer Führungskraft übereinstimmen:
Max. Linie
mysql> SELECT emp_id, fname, lname, title -> FROM employee -> WHERE emp_id ALL (SELECT superior_emp_id -> FROM employee -> WHERE superior_emp_id IS NOT NULL); +--------+----------+----------+----------------+ | emp_id | fname | lname | title | +--------+----------+----------+----------------+ | 2 | Susan | Barker | Vice President | | 5 | John | Gooding | Loan Manager | | 7 | Chris | Tucker | Teller |
Nicht-korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 169
Links | 8 | Sarah | Parker | Teller | | 9 | Jane | Grossman | Teller | | 11 | Thomas | Ziegler | Teller | | 12 | Samantha | Jameson | Teller | | 14 | Cindy | Mason | Teller | | 15 | Frank | Portman | Teller | | 17 | Beth | Fowler | Teller | | 18 | Rick | Tulman | Teller | +--------+----------+----------+----------------+ 11 rows in set (0.05 sec)
Wieder gibt die Unterabfrage die Personalnummern der leitenden Mitarbeiter zurück, und die übergeordnete Abfrage zeigt die Daten aller Angestellten an, deren Personalnummern ungleich denen aller von der Unterabfrage zurückgegebenen Personalnummern sind. Anders ausgedrückt: Die Abfrage findet alle Mitarbeiter, die keine Vorgesetzten sind. Wenn Sie dieses Vorgehen umständlich finden, sind Sie in guter Gesellschaft: Die meisten würden diese Abfrage anders, ohne den all-Operator, formulieren. Beispielsweise liefert diese Abfrage dieselben Resultate wie das letzte Beispiel des vorigen Abschnitts, das den not in-Operator verwendete. Letztlich ist es Geschmackssache, doch ich glaube, dass die Version mit not in allgemein verständlicher ist. Wenn Sie not in oder all verwenden, um einen Wert mit einer Menge von Werten zu vergleichen, müssen Sie dafür sorgen, dass die Wertemenge keinen null-Wert enthält. Da der Server den Wert auf der linken Seite des Ausdrucks mit jedem Element der Menge gleichsetzt, aber jeder Versuch, irgendetwas mit null gleichzusetzen unknown ergibt, kommt bei der folgenden Abfrage die leere Menge heraus: mysql> SELECT emp_id, fname, lname, title -> FROM employee -> WHERE emp_id NOT IN (1, 2, NULL); Empty set (0.00 sec)
Es gibt allerdings Fälle, in denen der all-Operator natürlicher ist. Im nächsten Beispiel suche ich mit all Konten, deren Saldo kleiner als der aller Konten von Frank Tucker ist:
Max. Linie
mysql> SELECT account_id, cust_id, product_cd, avail_balance -> FROM account -> WHERE avail_balance < ALL (SELECT a.avail_balance -> FROM account a INNER JOIN individual i -> ON a.cust_id = i.cust_id -> WHERE i.fname = 'Frank' AND i.lname = 'Tucker'); +------------+---------+------------+---------------+ | account_id | cust_id | product_cd | avail_balance | +------------+---------+------------+---------------+ | 2 | 1 | SAV | 500.00 | | 5 | 2 | SAV | 200.00 | | 10 | 4 | CHK | 534.12 | | 11 | 4 | SAV | 767.77 | | 14 | 6 | CHK | 122.37 | | 19 | 8 | SAV | 387.99 |
170 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 21 | 9 | CHK | 125.67 | | 25 | 10 | BUS | 0.00 | +------------+---------+------------+---------------+ 8 rows in set (0.17 sec)
Hier sehen Sie die Rückgabe der Unterabfrage: Die Tabelle besteht aus den verschiedenen Kontensalden von Frank Tucker: mysql> SELECT a.avail_balance -> FROM account a INNER JOIN individual i -> ON a.cust_id = i.cust_id -> WHERE i.fname = 'Frank' AND i.lname = 'Tucker'; +---------------+ | avail_balance | +---------------+ | 1057.75 | | 2212.50 | +---------------+ 2 rows in set (0.01 sec)
Tucker hat zwei Konten, und der niedrigere der beiden Salden beträgt $1.057,75. Da die übergeordnete Abfrage alle Konten findet, deren Saldo kleiner als die Salden von Tucker sind, umfasst die Ergebnismenge alle Konten mit einem Saldo kleiner als $1.057,75.
Der any-Operator Wie all vergleicht auch any einen Wert mit den Elementen einer Wertemenge; doch im Gegensatz zu all wird eine Bedingung, die den Operator any verwendet, als true ausgewertet, sobald auch nur ein einziger der Vergleiche positiv ausfällt. Dagegen ist eine Bedingung, die den Operator all verwendet, nur dann true, wenn Vergleiche mit allen Elementen der Menge positiv ausfallen; das war im vorigen Beispiel zu sehen. Angenommen, Sie möchten alle Konten finden, deren Saldo größer ist als irgendein Kontensaldo von Tucker:
Max. Linie
mysql> SELECT account_id, cust_id, product_cd, avail_balance -> FROM account -> WHERE avail_balance > ANY (SELECT a.avail_balance -> FROM account a INNER JOIN individual i -> ON a.cust_id = i.cust_id -> WHERE i.fname = 'Frank' AND i.lname = 'Tucker'); +------------+---------+------------+---------------+ | account_id | cust_id | product_cd | avail_balance | +------------+---------+------------+---------------+ | 3 | 1 | CD | 3000.00 | | 4 | 2 | CHK | 2258.02 | | 8 | 3 | MM | 2212.50 | | 12 | 4 | MM | 5487.09 | | 13 | 5 | CHK | 2237.97 | | 15 | 6 | CD | 10000.00 | | 17 | 7 | CD | 5000.00 | | 18 | 8 | CHK | 3487.19 | | 22 | 9 | MM | 9345.55 | | 23 | 9 | CD | 1500.00 |
Nicht-korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 171
Links | 24 | 10 | CHK | 23575.12 | | 27 | 11 | BUS | 9345.55 | | 28 | 12 | CHK | 38552.05 | | 29 | 13 | SBL | 50000.00 | +------------+---------+------------+---------------+ 14 rows in set (0.00 sec)
Da Tucker zwei Konten mit den Salden $1.057,75 und $2.212,50 hat, muss ein Konto, um irgendeinen dieser Werte zu schlagen, mindestens $1.057,75 als Saldo aufweisen. Die meisten bevorzugen zwar in, aber = any ist äquivalent zum in-Operator.
Unterabfragen, die mehrere Spalten liefern Bisher gaben alle Beispiel-Unterabfragen dieses Kapitels nur eine einzige Spalte und eine oder mehrere Zeilen zurück. In bestimmten Situationen sind jedoch Unterabfragen praktisch, die mehrere Spalten zurückgeben. Um dies zu veranschaulichen, betrachten wir zunächst ein Beispiel mit mehreren Einzelspalten-Unterabfragen: mysql> SELECT account_id, product_cd, cust_id -> FROM account -> WHERE open_branch_id = (SELECT branch_id -> FROM branch -> WHERE name = 'Woburn Branch') -> AND open_emp_id IN (SELECT emp_id -> FROM employee -> WHERE title = 'Teller' OR title = 'Head Teller'); +------------+------------+---------+ | account_id | product_cd | cust_id | +------------+------------+---------+ | 1 | CHK | 1 | | 2 | SAV | 1 | | 3 | CD | 1 | | 4 | CHK | 2 | | 5 | SAV | 2 | | 17 | CD | 7 | | 27 | BUS | 11 | +------------+------------+---------+ 7 rows in set (0.09 sec)
Max. Linie
Diese Abfrage verwendet zwei Unterabfragen, um die Nummer der Zweigstelle Woburn und die Personalnummern aller Bankkassierer zu ermitteln. Die übergeordnete Abfrage nutzt sodann diese Informationen, um alle Girokonten zu finden, die von einem Chefkassierer der Zweigstelle Woburn eröffnet wurden. Da jedoch die employee-Tabelle auch angibt, in welcher Zweigstelle der betreffende Mitarbeiter arbeitet, können Sie dieselben Ergebnisse auch erzielen, indem Sie die Spalten account.open_branch_id und account. open_emp_id beide mit einer einzigen Unterabfrage auf den Tabellen employee und branch vergleichen. Um das zu schaffen, müssen in Ihrer Filterbedingung beide Spalten aus der
172 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts account-Tabelle in Klammern und in der gleichen Reihenfolge, wie sie von der Unterab-
frage kommen, angegeben werden: mysql> SELECT account_id, product_cd, cust_id -> FROM account -> WHERE (open_branch_id, open_emp_id) IN -> (SELECT b.branch_id, e.emp_id -> FROM branch b INNER JOIN employee e -> ON b.branch_id = e.assigned_branch_id -> WHERE b.name = 'Woburn Branch' -> AND (e.title = 'Teller' OR e.title = 'Head Teller')); +------------+------------+---------+ | account_id | product_cd | cust_id | +------------+------------+---------+ | 1 | CHK | 1 | | 2 | SAV | 1 | | 3 | CD | 1 | | 4 | CHK | 2 | | 5 | SAV | 2 | | 14 | CD | 7 | | 22 | BUS | 11 | +------------+------------+---------+ 7 rows in set (0.00 sec)
Diese Version funktioniert wie die vorige, allerdings mit einer einzigen Unterabfrage, die zwei Spalten zurückgibt, anstatt mit zwei Unterabfragen, die jeweils nur eine Spalte zurückgeben. Sie könnten natürlich das obige Beispiel einfach so abändern, dass es einen Join der drei Tabellen anstelle einer Unterabfrage verwendet. Doch der Lerneffekt ist besser, wenn man sieht, dass sich in SQL dieselben Ergebnisse auf verschiedene Arten erzielen lassen. Hier sehen Sie ein anderes Beispiel, dieses Mal jedoch eines, in dem die Unterabfrage unverzichtbar ist. Angenommen, es haben sich Kunden über unrichtige Werte in den Saldenspalten der account-Tabelle beschwert (available balance und pending balance). Ihre Aufgabe ist es, alle Konten zu finden, deren Salden nicht mit der Summe der auf diesem Konto gebuchten Beträge übereinstimmen. Hier ist schon einmal eine Teillösung des Problems: SELECT 'ALERT! : Account #1 Has Incorrect Balance!' FROM account WHERE (avail_balance, pending_balance) (SELECT SUM(), SUM() FROM transaction WHERE account_id = 1) AND account_id = 1;
Max. Linie
Wie Sie sehen, habe ich die Ausdrücke, mit denen die Transaktionsbeträge für die Saldenberechnungen aufsummiert werden, hier weggelassen, aber ich verspreche Ihnen, dies in Kapitel 11 nachzuholen, wenn Sie gelernt haben, wie man case-Ausdrücke konstruiert. Dennoch ist die Abfrage vollständig genug, um zu sehen, dass die Unterabfrage zwei
Nicht-korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
173
Max. Linie
Links Summen aus der transaction-Tabelle generiert und dann mit den Spalten avail_balance und pending_balance der account-Tabelle vergleicht. Die Unterabfrage und die übergeordnete Abfrage enthalten beide die Filterbedingung account_id = 1. Daher wird die Abfrage in ihrer gegenwärtigen Form immer nur ein einziges Konto zugleich prüfen. Im nächsten Abschnitt werden Sie eine allgemeinere Form dieser Abfrage sehen, die alle Konten mit einer einzigen Ausführung prüft.
Korrelierte Unterabfragen Alle bisher gezeigten Unterabfragen waren von ihren übergeordneten Anweisungen unabhängig, konnten also auch selbstständig ausgeführt werden und Ergebnisse liefern. Eine korrelierte Unterabfrage ist dagegen abhängig von ihrer übergeordneten Anweisung, da sie eine oder mehrere Spalten von ihr benötigt. Anders als eine nicht-korrelierte Unterabfrage wird eine korrelierte Unterabfrage nicht einmalig vor der übergeordneten Anweisung ausgeführt, sondern für jede in Frage kommende Zeile (also für jede Zeile, die für die endgültige Ergebnismenge in Betracht kommt) ein Mal. Die folgende Abfrage nutzt beispielsweise eine korrelierte Unterabfrage, um die Anzahl der Konten pro Kunde zu zählen, und die übergeordnete Abfrage liefert die Kunden, die genau zwei Konten besitzen: mysql> SELECT c.cust_id, c.cust_type_cd, c.city -> FROM customer c -> WHERE 2 = (SELECT COUNT(*) -> FROM account a -> WHERE a.cust_id = c.cust_id); +---------+--------------+---------+ | cust_id | cust_type_cd | city | +---------+--------------+---------+ | 2 | I | Woburn | | 3 | I | Quincy | | 6 | I | Waltham | | 8 | I | Salem | | 10 | B | Salem | +---------+--------------+---------+ 5 rows in set (0.01 sec)
Die Referenz auf c.cust_id ganz am Ende der Unterabfrage macht sie zu einer korrelierten: Damit die Unterabfrage ausgeführt werden kann, muss die übergeordnete Abfrage Werte für c.cust_id liefern. In diesem Fall ruft die übergeordnete Abfrage alle 13 Zeilen der customer-Tabelle ab und führt die Unterabfrage für jeden Kunden ein Mal aus, indem sie jeweils die passende Kundennummer für jede Ausführung übergibt. Wenn die Unterabfrage den Wert 2 zurückgibt, ist die Filterbedingung wahr, und die Zeile wird der Ergebnismenge hinzugefügt.
Max. Linie
Zusammen mit Gleichheitsbedingungen können Sie korrelierte Unterabfragen auch in anderen Typen von Bedingungen verwenden, so zum Beispiel in der hier gezeigten Wertebereichsbedingung:
174 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT c.cust_id, c.cust_type_cd, c.city -> FROM customer c -> WHERE (SELECT SUM(a.avail_balance) -> FROM account a -> WHERE a.cust_id = c.cust_id) -> BETWEEN 5000 AND 10000; +---------+--------------+------------+ | cust_id | cust_type_cd | city | +---------+--------------+------------+ | 4 | I | Waltham | | 7 | I | Wilmington | | 11 | B | Wilmington | +---------+--------------+------------+ 3 rows in set (0.02 sec)
Diese Variante der obigen Abfrage findet alle Kunden, deren verfügbares Guthaben bezogen auf alle ihre Konten zwischen $5.000 und $10.000 beträgt. Auch hier wird die korrelierte Unterabfrage 13 Mal ausgeführt (einmal pro Kundendatensatz), und jede Ausführung gibt den Gesamtsaldo über alle Konten eines bestimmten Kunden zurück. Ein weiterer kleiner, aber feiner Unterschied in der vorangegangenen Abfrage ist, dass die Unterabfrage jetzt auf der linken Seite der Bedingung steht, was vielleicht etwas seltsam aussieht, aber vollkommen zulässig ist.
Am Ende des vorigen Abschnitts zeigte ich Ihnen, wie man den verfügbaren (available) und den ausstehenden (pending) Saldo eines Kontos aus den Transaktionen ermittelt, die für das Konto protokolliert wurden. Dabei versprach ich Ihnen auch ein modifiziertes Beispiel, das alle Konten mit einer einzigen Ausführung erschlägt. Hier sehen Sie das alte Beispiel noch einmal: SELECT 'ALERT! : Account #1 Has Incorrect Balance!' FROM account WHERE (avail_balance, pending_balance) (SELECT SUM(), SUM() FROM transaction WHERE account_id = 1) AND account_id = 1;
Wenn Sie eine korrelierte anstatt einer nicht-korrelierten Unterabfrage verwenden, wird die übergeordnete Abfrage ein einziges Mal, die Unterabfrage hingegen ein Mal für jedes Konto ausgeführt. Dies ist die überarbeitete Fassung:
Max. Linie
SELECT CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!') FROM account a WHERE (a.avail_balance, a.pending_balance) (SELECT SUM(), SUM() FROM transaction t WHERE t.account_id = a.account_id);
Korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 175
Links Nun enthält die Unterabfrage eine Filterbedingung, die die Kontonummer aus der Transaktion und die Kontonummer aus der übergeordneten Abfrage zueinander in Beziehung setzt. Die select-Klausel wurde ebenfalls so abgeändert, dass sie nun eine Warnmeldung mit der zugehörigen Kontonummer verkettet, anstatt nur den hartcodierten Wert 1 zu melden.
Der exists-Operator Korrelierte Unterabfragen werden zwar oft in Gleichheits- und Wertebereichsbedingungen gesichtet, aber der Operator, der am häufigsten für Bedingungen mit korrelierten Unterabfragen eingesetzt wird, ist exists. Der exists-Operator wird verwendet, um herauszufinden, ob eine Beziehung, unabhängig von Quantitäten, überhaupt existiert. Die folgende Abfrage findet beispielsweise alle Konten, auf denen an einem bestimmten Tag eine Transaktion stattfand, ohne danach zu fragen, wie viele Transaktionen es in Wirklichkeit waren: SELECT a.account_id, FROM account a WHERE EXISTS (SELECT FROM transaction t WHERE t.account_id AND t.txn_date =
a.product_cd, a.cust_id, a.avail_balance 1 = a.account_id '2008-09-22');
Dem exists-Operator ist es egal, ob Ihre Unterabfrage null, eine oder viele Zeilen zurückgibt; die Bedingung interessiert sich lediglich dafür, ob überhaupt Zeilen von der Unterabfrage zurückkamen. Ein Blick auf die select-Klausel der Unterabfrage zeigt, dass diese nur aus einem einzigen Literal (1) besteht: Da die Bedingung in der übergeordneten Abfrage lediglich wissen muss, wie viele Zeilen zurückgegeben wurden, sind die eigentlichen Daten, die die Unterabfrage liefert, irrelevant. Die Unterabfrage kann zurückliefern, was immer Sie wünschen: SELECT a.account_id, FROM account a WHERE EXISTS (SELECT FROM transaction t WHERE t.account_id AND t.txn_date =
a.product_cd, a.cust_id, a.avail_balance t.txn_id, 'hello', 3.1415927 = a.account_id '2008-09-22');
Die Konvention verlangt allerdings, bei Verwendung von exists entweder select 1 oder select * anzugeben. Sie können auch not exists sagen, um Unterabfragen zu finden, die keine Zeilen zurückliefern:
Max. Linie
mysql> SELECT a.account_id, a.product_cd, a.cust_id -> FROM account a -> WHERE NOT EXISTS (SELECT 1 -> FROM business b -> WHERE b.cust_id = a.cust_id);
176 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts +------------+------------+---------+ | account_id | product_cd | cust_id | +------------+------------+---------+ | 1 | CHK | 1 | | 2 | SAV | 1 | | 3 | CD | 1 | | 4 | CHK | 2 | | 5 | SAV | 2 | | 7 | CHK | 3 | | 8 | MM | 3 | | 10 | CHK | 4 | | 11 | SAV | 4 | | 12 | MM | 4 | | 13 | CHK | 5 | | 14 | CHK | 6 | | 15 | CD | 6 | | 17 | CD | 7 | | 18 | CHK | 8 | | 19 | SAV | 8 | | 21 | CHK | 9 | | 22 | MM | 9 | | 23 | CD | 9 | +------------+------------+---------+ 19 rows in set (0.99 sec)
Diese Abfrage findet alle Kunden, deren Kundennummer nicht in der business-Tabelle vorkommt, also im Grunde alle Privatkunden.
Datenmanipulation mit korrelierten Unterabfragen Alle bisherigen Beispiele dieses Kapitels waren select-Anweisungen, doch das bedeutet nicht, dass Unterabfragen nicht auch in anderen SQL-Anweisungen nützlich wären. Unterabfragen werden in update-, delete- und insert-Anweisungen ebenfalls viel genutzt, wobei korrelierte Unterabfragen in update- und delete-Anweisungen häufig sind. Hier sehen Sie ein Beispiel einer korrelierten Unterabfrage, die die last_activity_date-Spalte der accountTabelle modifiziert: UPDATE account a SET a.last_activity_date = (SELECT MAX(t.txn_date) FROM transaction t WHERE t.account_id = a.account_id);
Max. Linie
Diese Anweisung ändert jede Zeile der account-Tabelle (da sie keine where-Klausel hat), indem sie das letzte Transaktionsdatum für jedes Konto ausfindig macht. Nun scheint es zwar normal, anzunehmen, dass für jedes Konto zumindest eine Transaktion stattgefunden hat, doch besser wäre es, dies vor einer Änderung der last_activity_date-Spalte zu prüfen. Sonst wird die Spalte eines Kontos, auf dem noch keine Kontobewegungen stattgefunden haben, auf null gesetzt, da die Unterabfrage keine Zeilen zurückgibt. Die fol-
Korrelierte Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
177
Max. Linie
Links gende, überarbeitete Fassung der update-Anweisung hat eine where-Klausel mit einer zweiten korrelierten Unterabfrage: UPDATE account a SET a.last_activity_date = (SELECT MAX(t.txn_date) FROM transaction t WHERE t.account_id = a.account_id) WHERE EXISTS (SELECT 1 FROM transaction t WHERE t.account_id = a.account_id);
Die beiden korrelierten Unterabfragen sind bis auf die select-Klauseln identisch. Die Unterabfrage in der set-Klausel wird allerdings nur ausgeführt, wenn die Bedingung in der where-Klausel der update-Anweisung true ist (das hieße, dass zumindest eine Transaktion für das Konto gefunden wurde), sodass die Daten in der last_activity_dateSpalte nicht mit einem null-Wert überschrieben werden können. Korrelierte Unterabfragen sind auch in delete-Anweisungen häufig anzutreffen. Angenommen, Sie möchten an jedem Monatsultimo ein Skript für die Datenpflege ausführen, das überflüssige Daten löscht. Dieses Skript könnte folgende Anweisung enthalten, um aus der department-Tabelle Daten zu entfernen, denen keine Zeilen der employee-Tabelle zugeordnet sind: DELETE FROM department WHERE NOT EXISTS (SELECT 1 FROM employee WHERE employee.dept_id = department.dept_id);
Wenn Sie in MySQL korrelierte Unterabfragen mit delete-Anweisungen verwenden, müssen Sie daran denken, dass Tabellen-Aliase aus unerfindlichen Gründen mit delete nicht erlaubt sind. Daher musste ich in der Unterabfrage den vollen Namen der Tabelle einsetzen. Die meisten anderen Datenbankserver gestatten hingegen Aliase für die Tabellen department und employee: DELETE FROM department d WHERE NOT EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id);
Einsatz von Unterabfragen
Max. Linie
Nun, da Sie die verschiedenen Arten von Unterabfragen kennengelernt haben und wissen, welche Operatoren für den Umgang mit den von diesen Unterabfragen zurückgelieferten Daten eingesetzt werden, ist es an der Zeit, die vielen Anwendungsmöglichkeiten für Unterabfragen auszuloten, um mächtige SQL-Anweisungen zu schreiben. Die folgenden drei Abschnitte zeigen auf, wie man mit Unterabfragen selbst definierte Tabellen erstellt, Bedingungen konstruiert und Spaltenwerte in Ergebnismengen generiert.
178 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Unterabfragen als Datenquellen In Kapitel 3 hatte ich gesagt, dass die from-Klausel einer select-Anweisung die Tabellen aufführt, die in der Abfrage benutzt werden. Da eine Unterabfrage auch eine Ergebnismenge mit Zeilen und Spalten voller Daten generiert, ist es vollkommen zulässig, Unterabfragen in einer from-Klausel gemeinsam mit Tabellen aufzuführen. Das mag zwar auf den ersten Blick trotz allen Interesses nicht so praxisrelevant erscheinen, aber bei genauerer Betrachtung ist der gemeinsame Einsatz von Unterabfragen und Tabellen eines der mächtigsten Werkzeuge, das Sie beim Schreiben von Anweisungen zur Verfügung haben. Ein einfaches Beispiel: mysql> SELECT d.dept_id, d.name, e_cnt.how_many num_employees -> FROM department d INNER JOIN -> (SELECT dept_id, COUNT(*) how_many -> FROM employee -> GROUP BY dept_id) e_cnt -> ON d.dept_id = e_cnt.dept_id; +---------+----------------+---------------+ | dept_id | name | num_employees | +---------+----------------+---------------+ | 1 | Operations | 14 | | 2 | Loans | 1 | | 3 | Administration | 3 | +---------+----------------+---------------+ 3 rows in set (0.04 sec)
Hier generiert eine Unterabfrage eine Liste von Abteilungsnummern (Department IDs) zusammen mit der Anzahl der Mitarbeiter der betreffenden Abteilung. Im Folgenden sehen Sie die Ergebnistabelle der Unterabfrage: mysql> SELECT dept_id, COUNT(*) how_many -> FROM employee -> GROUP BY dept_id; +---------+----------+ | dept_id | how_many | +---------+----------+ | 1 | 14 | | 2 | 1 | | 3 | 3 | +---------+----------+ 3 rows in set (0.00 sec)
Die Unterabfrage bekommt den Namen e_cnt und wird mit der department-Tabelle auf der dept_id-Spalte verbunden. Die übergeordnete Abfrage ruft dann die Nummer und den Namen der Abteilung aus der department-Tabelle und die Anzahl der Mitarbeiter aus der e_cnt-Unterabfrage ab.
Max. Linie
Unterabfragen in der from-Klausel dürfen nicht korreliert sein, da sie zuerst ausgeführt werden. Ihre Daten verbleiben dann im Arbeitsspeicher, bis die übergeordnete Abfrage ihre Ausführung beendet. Durch Unterabfragen werden enorm flexible Abfragen möglich, da sie weit über das vorhandene Tabellenmaterial hinausgehen, buchstäblich jede
Einsatz von Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
179
Max. Linie
Links nur erdenkliche View Ihrer Daten erstellen und dann diese Ergebnisse mit anderen Tabellen oder Unterabfragen durch Joins verbinden können. Wenn Sie Reports schreiben oder Data Feeds für externe Systeme generieren, können Sie auf diese Weise mit einer einzigen Abfrage Dinge tun, für die ansonsten mehrere Abfragen oder eine prozedurale Sprache erforderlich wären.
Daten erzeugen Unterabfragen können nicht nur vorhandene Daten zusammenfassen, sondern auch neue Daten generieren, die in dieser Form in der Datenbank noch gar nicht vorliegen. Angenommen, Sie möchten Ihre Kunden danach einteilen, wie viel Geld sie auf ihren Sparkonten liegen haben, aber dabei Gruppen-Definitionen verwenden, die nicht in Ihrer Datenbank gespeichert sind. Sagen wir einmal, Sie möchten Ihre Kunden in folgende Gruppen einteilen: Gruppenname
Untergrenze
Obergrenze
Small Fry
0
$4,999.99
Average Joes
$5,000
$9,999.99
Heavy Hitters
$10,000
$9,999,999.99
Um diese Gruppen mit einer einzigen Abfrage zu generieren, müssen Sie sie auf irgendeine Weise definieren. Als Erstes benötigen Sie also eine Abfrage, die die Gruppen-Definitionen anlegt: mysql> SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit -> UNION ALL -> SELECT 'Average Joes' name, 5000 low_limit, 9999.99 high_limit -> UNION ALL -> SELECT 'Heavy Hitters' name, 10000 low_limit, 9999999.99 high_limit; +---------------+-----------+------------+ | name | low_limit | high_limit | +---------------+-----------+------------+ | Small Fry | 0 | 4999.99 | | Average Joes | 5000 | 9999.99 | | Heavy Hitters | 10000 | 9999999.99 | +---------------+-----------+------------+ 3 rows in set (0.00 sec)
Ich habe den Mengenoperator union all verwendet, um die Ergebnisse von drei getrennten Abfragen zu einer einzigen Ergebnismenge zu verschmelzen. Jede Abfrage ruft drei Literale ab, und die Ergebnisse werden zu einer Ergebnismenge mit drei Zeilen und drei Spalten zusammengesetzt. Nun haben wir eine Abfrage, um die gewünschten Gruppen anzulegen, und können sie in die from-Klausel einer anderen Abfrage einsetzen, um die Kundengruppen zu generieren:
Max. Linie
mysql> SELECT groups.name, COUNT(*) num_customers -> FROM -> (SELECT SUM(a.avail_balance) cust_balance
180 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts -> FROM account a INNER JOIN product p -> ON a.product_cd = p.product_cd -> WHERE p.product_type_cd = 'ACCOUNT' -> GROUP BY a.cust_id) cust_rollup INNER JOIN -> (SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit -> UNION ALL -> SELECT 'Average Joes' name, 5000 low_limit, -> 9999.99 high_limit -> UNION ALL -> SELECT 'Heavy Hitters' name, 10000 low_limit, -> 9999999.99 high_limit) groups -> ON cust_rollup.cust_balance -> BETWEEN groups.low_limit AND groups.high_limit -> GROUP BY groups.name; +---------------+---------------+ | name | num_customers | +---------------+---------------+ | Average Joes | 2 | | Heavy Hitters | 4 | | Small Fry | 5 | +---------------+---------------+ 3 rows in set (0.01 sec)
In der from-Klausel gibt es jetzt zwei Unterabfragen: Die erste heißt cust_rollup und gibt für jeden Kunden den Gesamtsaldo aller Einlagekonten zurück, die zweite heißt groups und generiert die Tabelle mit den drei Kundengruppen. Hier sehen Sie die von cust_ rollup generierten Daten: mysql> SELECT SUM(a.avail_balance) cust_balance -> FROM account a INNER JOIN product p -> ON a.product_cd = p.product_cd -> WHERE p.product_type_cd = 'ACCOUNT' -> GROUP BY a.cust_id; +--------------+ | cust_balance | +--------------+ | 4557.75 | | 2458.02 | | 3270.25 | | 6788.98 | | 2237.97 | | 10122.37 | | 5000.00 | | 3875.18 | | 10971.22 | | 23575.12 | | 38552.05 | +--------------+ 11 rows in set (0.05 sec)
Max. Linie
Die von cust_rollup generierten Daten werden dann über eine Wertebereichsbedingung (cust_rollup.cust_balance BETWEEN groups.low_limit AND groups.high_limit) mit der
Einsatz von Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
181
Max. Linie
Links groups-Tabelle verbunden. Zum Schluss werden diese verbundenen Daten gruppiert und
die Kunden in jeder Gruppe gezählt, um die endgültige Ergebnismenge anzulegen. Sie könnten natürlich auch einfach beschließen, eine permanente Tabelle für die Gruppen-Definitionen anzulegen, anstatt eine Unterabfrage zu verwenden. Damit würde Ihre Datenbank jedoch nach einiger Zeit durch Massen von kleinen, sehr speziellen Tabellen zugemüllt, und bei den meisten davon würden Sie gar nicht mehr wissen, zu welchem Zweck sie ursprünglich erstellt wurden. Ich habe schon in Umgebungen gearbeitet, in denen die Datenbanknutzer ihre eigenen Tabellen für spezielle Zwecke anlegen durften, und die Ergebnisse waren katastrophal (Tabellen, die in Backups nicht vorkamen, Tabellen, die bei Server-Upgrades verloren gingen, Server-Abstürze auf Grund von Problemen mit der Speicherplatzzuweisung usw.). Mit Unterabfragen können Sie eine Strategie verfolgen, bei der in einer Datenbank nur dann neue Tabellen angelegt werden, wenn es eine klare betriebliche Notwendigkeit gibt, neue Daten zu speichern.
Task-orientierte Unterabfragen In Systemen, die für Reporting oder Data Feed-Erstellung genutzt werden, trifft man häufig auf Abfragen wie diese:
Max. Linie
mysql> SELECT p.name product, b.name branch, -> CONCAT(e.fname, ' ', e.lname) name, -> SUM(a.avail_balance) tot_deposits -> FROM account a INNER JOIN employee e -> ON a.open_emp_id = e.emp_id -> INNER JOIN branch b -> ON a.open_branch_id = b.branch_id -> INNER JOIN product p -> ON a.product_cd = p.product_cd -> WHERE p.product_type_cd = 'ACCOUNT' -> GROUP BY p.name, b.name, e.fname, e.lname -> ORDER BY 1,2; +------------------------+---------------+-----------------+--------------+ | product | branch | name | tot_deposits | +------------------------+---------------+-----------------+--------------+ | certificate of deposit | Headquarters | Michael Smith | 11500.00 | | certificate of deposit | Woburn Branch | Paula Roberts | 8000.00 | | checking account | Headquarters | Michael Smith | 782.16 | | checking account | Quincy Branch | John Blake | 1057.75 | | checking account | So. NH Branch | Theresa Markham | 67852.33 | | checking account | Woburn Branch | Paula Roberts | 3315.77 | | money market account | Headquarters | Michael Smith | 14832.64 | | money market account | Quincy Branch | John Blake | 2212.50 | | savings account | Headquarters | Michael Smith | 767.77 | | savings account | So. NH Branch | Theresa Markham | 387.99 | | savings account | Woburn Branch | Paula Roberts | 700.00 | +------------------------+---------------+-----------------+--------------+ 11 rows in set (0.00 sec)
Diese Abfrage summiert die Salden aller Einlagenkonten nach dem Kontotyp, nach dem Angestellten, der das Konto eröffnete, und nach der Zweigstelle, in der es eröffnet wurde.
182 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Bei genauer Betrachtung erkennt man, dass die Tabellen product, branch und employee nur zur Anzeige benötigt werden und dass die account-Tabelle alles hat, was nötig ist, um die Gruppierungen anzulegen (product_cd, open_branch_id, open_emp_id und avail_ balance). Daher könnte man die Aufgabe, die Gruppen zu generieren, in eine Unterabfrage verlagern und dann die drei anderen Tabellen mit der Ergebnistabelle dieser Unterabfrage verbinden, um das gewünschte Endergebnis zu erhalten. Hier sehen Sie die Gruppierungs-Unterabfrage: mysql> SELECT product_cd, open_branch_id branch_id, open_emp_id emp_id, -> SUM(avail_balance) tot_deposits -> FROM account -> GROUP BY product_cd, open_branch_id, open_emp_id; +------------+-----------+--------+--------------+ | product_cd | branch_id | emp_id | tot_deposits | +------------+-----------+--------+--------------+ | BUS | 2 | 10 | 9345.55 | | BUS | 4 | 16 | 0.00 | | CD | 1 | 1 | 11500.00 | | CD | 2 | 10 | 8000.00 | | CHK | 1 | 1 | 782.16 | | CHK | 2 | 10 | 3315.77 | | CHK | 3 | 13 | 1057.75 | | CHK | 4 | 16 | 67852.33 | | MM | 1 | 1 | 14832.64 | | MM | 3 | 13 | 2212.50 | | SAV | 1 | 1 | 767.77 | | SAV | 2 | 10 | 700.00 | | SAV | 4 | 16 | 387.99 | | SBL | 3 | 13 | 50000.00 | +------------+-----------+--------+--------------+ 14 rows in set (0.02 sec)
Dies ist der Kern der Abfrage; die restlichen Tabellen werden lediglich benötigt, um sinnvolle Strings an die Stelle der Fremdschlüsselspalten product_cd, open_branch_id und open_emp_id zu setzen. Die nächste Anweisung verpackt diese Abfrage in einer Unterabfrage einer Abfrage der account-Tabelle und verbindet die Ergebnistabelle mit den drei anderen Tabellen:
Max. Linie
mysql> -> -> -> -> -> -> -> -> -> -> -> ->
SELECT p.name product, b.name branch, CONCAT(e.fname, ' ', e.lname) name, account_groups.tot_deposits FROM (SELECT product_cd, open_branch_id branch_id, open_emp_id emp_id, SUM(avail_balance) tot_deposits FROM account GROUP BY product_cd, open_branch_id, open_emp_id) account_groups INNER JOIN employee e ON e.emp_id = account_groups.emp_id INNER JOIN branch b ON b.branch_id = account_groups.branch_id INNER JOIN product p ON p.product_cd = account_groups.product_cd WHERE p.product_type_cd = 'ACCOUNT';
Einsatz von Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 183
Links +------------------------+---------------+-----------------+--------------+ | product | branch | name | tot_deposits | +------------------------+---------------+-----------------+--------------+ | certificate of deposit | Headquarters | Michael Smith | 11500.00 | | certificate of deposit | Woburn Branch | Paula Roberts | 8000.00 | | checking account | Headquarters | Michael Smith | 782.16 | | checking account | Quincy Branch | John Blake | 1057.75 | | checking account | So. NH Branch | Theresa Markham | 67852.33 | | checking account | Woburn Branch | Paula Roberts | 3315.77 | | money market account | Headquarters | Michael Smith | 14832.64 | | money market account | Quincy Branch | John Blake | 2212.50 | | savings account | Headquarters | Michael Smith | 767.77 | | savings account | So. NH Branch | Theresa Markham | 387.99 | | savings account | Woburn Branch | Paula Roberts | 700.00 | +------------------------+---------------+-----------------+--------------+ 11 rows in set (0.01 sec)
Ich gebe zu: Schönheit liegt im Auge des Betrachters. Doch ich persönlich finde diese Version der Abfrage viel befriedigender als die große, einfachere Version. Außerdem wird sie wahrscheinlich schneller ausgeführt, da die Gruppierung nur auf kleinen, numerischen Fremdschlüsselspalten vorgenommen wird (product_cd, open_branch_id, open_emp_id) anstatt auf String-Spalten, die sehr lang sein können (branch.name, product.name, employee.fname, employee.lname).
U n te ra b fra g e n in Filte rb e d in g u n g e n Viele der Beispiele in diesem Kapitel verwendeten Unterabfragen als Ausdrücke in Filterbedingungen. Es dürfte Sie also nicht überraschen, dass dieses ein Hauptanwendungsbereich für Unterabfragen ist. Doch Filterbedingungen mit Unterabfragen gibt es nicht nur in der where-Klausel. Die nächste Abfrage verwendet eine Unterabfrage in der havingKlausel, um zu ermitteln, welche Mitarbeiter die meisten Konten eröffnet haben: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> HAVING COUNT(*) = (SELECT MAX(emp_cnt.how_many) -> FROM (SELECT COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id) emp_cnt); +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 | +-------------+----------+ 1 row in set (0.01 sec)
Max. Linie
Die Unterabfrage in der having-Klausel findet die größte Zahl von Konten, die überhaupt von irgendeinem Mitarbeiter eröffnet wurden, und die übergeordnete Abfrage findet heraus, welcher Angestellter das ist. Wenn mehrere Angestellte diesen Höchstwert erreichen, gibt die Abfrage mehrere Zeilen zurück.
184 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Unterabfragen zum Generieren von Ausdrücken In diesem letzten Abschnitt des Kapitels schlage ich noch einmal einen Bogen zum Anfang: mit einer skalaren Unterabfrage, die eine Spalte mit einer Zeile zurückgibt. Denn diese kann man nicht nur in Filterbedingungen einsetzen, sondern überall dort, wo ein Ausdruck auftreten kann, also auch in der select- und order by-Klausel einer Abfrage und in der values-Klausel einer insert-Anweisung. In »Task-orientierte Unterabfragen« auf Seite 182 zeigte ich Ihnen, wie man den Gruppierungsmechanismus vom Rest einer Abfrage getrennt in eine Unterabfrage auslagern kann. Hier sehen Sie die gleiche Abfrage in einer neuen Variante, die Unterabfragen für denselben Zweck, aber anders einsetzt: mysql> SELECT -> (SELECT p.name FROM product p -> WHERE p.product_cd = a.product_cd -> AND p.product_type_cd = 'ACCOUNT') product, -> (SELECT b.name FROM branch b -> WHERE b.branch_id = a.open_branch_id) branch, -> (SELECT CONCAT(e.fname, ' ', e.lname) FROM employee e -> WHERE e.emp_id = a.open_emp_id) name, -> SUM(a.avail_balance) tot_deposits -> FROM account a -> GROUP BY a.product_cd, a.open_branch_id, a.open_emp_id -> ORDER BY 1,2; +------------------------+---------------+-----------------+--------------+ | product | branch | name | tot_deposits | +------------------------+---------------+-----------------+--------------+ | NULL | Quincy Branch | John Blake | 50000.00 | | NULL | So. NH Branch | Theresa Markham | 0.00 | | NULL | Woburn Branch | Paula Roberts | 9345.55 | | certificate of deposit | Headquarters | Michael Smith | 11500.00 | | certificate of deposit | Woburn Branch | Paula Roberts | 8000.00 | | checking account | Headquarters | Michael Smith | 782.16 | | checking account | Quincy Branch | John Blake | 1057.75 | | checking account | So. NH Branch | Theresa Markham | 67852.33 | | checking account | Woburn Branch | Paula Roberts | 3315.77 | | money market account | Headquarters | Michael Smith | 14832.64 | | money market account | Quincy Branch | John Blake | 2212.50 | | savings account | Headquarters | Michael Smith | 767.77 | | savings account | So. NH Branch | Theresa Markham | 387.99 | | savings account | Woburn Branch | Paula Roberts | 700.00 | +------------------------+---------------+-----------------+--------------+ 14 rows in set (0.01 sec)
Zwischen der alten und der neuen Version dieser Abfrage, die eine Unterabfrage in der from-Klausel verwendet, gibt es zwei wichtige Unterschiede:
Max. Linie
• Anstatt die Tabellen product, branch und employee mit den Kontendaten in einem Join zu verbinden, schlagen korrelierte skalare Unterabfragen in der select-Klausel die Namen von Produkt, Zweigstelle und Mitarbeiter nach.
Einsatz von Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
185
Max. Linie
Links • Die Ergebnismenge hat 14 anstelle von 11 Zeilen, und drei der Produktnamen sind null. Die Ergebnismenge ist drei Zeilen länger, weil die frühere Version der Abfrage die Filterbedingung p.product_type_cd = 'ACCOUNT' enthielt. Diese eliminierte Zeilen mit den Produkttypen »INSURANCE« und »LOAN«, also beispielsweise Darlehen an kleine Firmen. Da diese Version der Abfrage keinen Join mit der product-Tabelle enthält, gibt es keine Möglichkeit, die Filterbedingung in die Hauptabfrage einzubinden. Die korrelierte Unterabfrage der product-Tabelle hat diesen Filter, aber der einzige Effekt ist der, dass der Produktname null in der Tabelle verbleibt. Um die zusätzlichen drei Zeilen loszuwerden, könnte man die product-Tabelle mit der account-Tabelle verbinden und die Filterbedingung einbinden oder einfach Folgendes tun: mysql> SELECT all_prods.product, all_prods.branch, -> all_prods.name, all_prods.tot_deposits -> FROM -> (SELECT -> (SELECT p.name FROM product p -> WHERE p.product_cd = a.product_cd -> AND p.product_type_cd = 'ACCOUNT') product, -> (SELECT b.name FROM branch b -> WHERE b.branch_id = a.open_branch_id) branch, -> (SELECT CONCAT(e.fname, ' ', e.lname) FROM employee e -> WHERE e.emp_id = a.open_emp_id) name, -> SUM(a.avail_balance) tot_deposits -> FROM account a -> GROUP BY a.product_cd, a.open_branch_id, a.open_emp_id -> ) all_prods -> WHERE all_prods.product IS NOT NULL -> ORDER BY 1,2; +------------------------+---------------+-----------------+--------------+ | product | branch | name | tot_deposits | +------------------------+---------------+-----------------+--------------+ | certificate of deposit | Headquarters | Michael Smith | 11500.00 | | certificate of deposit | Woburn Branch | Paula Roberts | 8000.00 | | checking account | Headquarters | Michael Smith | 782.16 | | checking account | Quincy Branch | John Blake | 1057.75 | | checking account | So. NH Branch | Theresa Markham | 67852.33 | | checking account | Woburn Branch | Paula Roberts | 3315.77 | | money market account | Headquarters | Michael Smith | 14832.64 | | money market account | Quincy Branch | John Blake | 2212.50 | | savings account | Headquarters | Michael Smith | 767.77 | | savings account | So. NH Branch | Theresa Markham | 387.99 | | savings account | Woburn Branch | Paula Roberts | 700.00 | +------------------------+---------------+-----------------+--------------+ 11 rows in set (0.01 sec)
Max. Linie
Dadurch, dass wir einfach die vorige Abfrage in einer Unterabfrage (namens all_prods) verpackten und eine Filterbedingung zum Ausschluss von null-Werten aus der productSpalte hinzufügten, gibt die Abfrage nun die gewünschten elf Zeilen zurück. Das Ergebnis
186 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts ist eine Abfrage, die alle Gruppierungen mit Rohdaten aus der account-Tabelle vornimmt und dann die Ausgabe mit Daten der drei anderen Tabelle garniert, und das ohne irgendwelche Joins. Wie bereits gesagt, können Unterabfragen auch in der order by-Klausel auftreten. Die folgende Abfrage liefert Mitarbeiterdaten, sortiert zuerst nach dem Nachnamen des jeweiligen Vorgesetzten und dann nach dem Nachnamen des Mitarbeiters: mysql> SELECT emp.emp_id, CONCAT(emp.fname, ' ', emp.lname) emp_name, -> (SELECT CONCAT(boss.fname, ' ', boss.lname) -> FROM employee boss -> WHERE boss.emp_id = emp.superior_emp_id) boss_name -> FROM employee emp -> WHERE emp.superior_emp_id IS NOT NULL -> ORDER BY (SELECT boss.lname FROM employee boss -> WHERE boss.emp_id = emp.superior_emp_id), emp.lname; +--------+------------------+-----------------+ | emp_id | emp_name | boss_name | +--------+------------------+-----------------+ | 14 | Cindy Mason | John Blake | | 15 | Frank Portman | John Blake | | 9 | Jane Grossman | Helen Fleming | | 8 | Sarah Parker | Helen Fleming | | 7 | Chris Tucker | Helen Fleming | | 13 | John Blake | Susan Hawthorne | | 6 | Helen Fleming | Susan Hawthorne | | 5 | John Gooding | Susan Hawthorne | | 16 | Theresa Markham | Susan Hawthorne | | 10 | Paula Roberts | Susan Hawthorne | | 17 | Beth Fowler | Theresa Markham | | 18 | Rick Tulman | Theresa Markham | | 12 | Samantha Jameson | Paula Roberts | | 11 | Thomas Ziegler | Paula Roberts | | 2 | Susan Barker | Michael Smith | | 3 | Robert Tyler | Michael Smith | | 4 | Susan Hawthorne | Robert Tyler | +--------+------------------+-----------------+ 17 rows in set (0.01 sec)
Die Abfrage verwendet zwei korrelierte skalare Unterabfragen: eine in der select-Klausel, um den vollen Namen jedes Vorgesetzten abzufragen, und eine andere in der order byKlausel, um zu Sortierzwecken nur den Nachnamen jedes Vorgesetzten abzufragen. Zusammen mit korrelierten skalaren Unterabfragen in select-Anweisungen können Sie nicht-korrelierte skalare Unterabfragen einsetzen, um Werte für eine insert-Anweisung zu generieren. Angenommen, Sie möchten eine neue Zeile für die Kontentabellen anlegen und haben folgende Daten:
Max. Linie
• den Produktnnamen (»savings account«),
Max. Linie
• die Federal ID des Kunden (»555-55-5555«),
Einsatz von Unterabfragen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
187
Links • den Namen der Zweigstelle, in der das Konto eröffnet wurde (»Quincy Branch«), sowie • den Vor- und Nachnamen des Kassierers, der das Konto eröffnete (»Frank Portman«). Bevor Sie eine Zeile in der account-Tabelle anlegen können, müssen Sie die Schlüsselwerte aller dieser Einzeldaten betrachten, um auch die Fremdschlüsselspalten in der account-Tabelle ausfüllen zu können. Dazu gibt es zwei Möglichkeiten: Sie können vier Abfragen ausführen, um die Primärschlüsselwerte zu finden, und diese Werte in eine insert-Anweisung setzen, oder Sie können die vier Schlüsselwerte mit Unterabfragen innerhalb einer insert-Anweisung ermitteln. Hier sehen Sie den zweiten Ansatz: INSERT INTO account (account_id, product_cd, cust_id, open_date, last_activity_date, status, open_branch_id, open_emp_id, avail_balance, pending_balance) VALUES (NULL, (SELECT product_cd FROM product WHERE name = 'savings account'), (SELECT cust_id FROM customer WHERE fed_id = '555-55-5555'), '2005-01-25', '2005-01-25', 'ACTIVE', (SELECT branch_id FROM branch WHERE name = 'Quincy Branch'), (SELECT emp_id FROM employee WHERE lname = 'Portman' AND fname = 'Frank'), 0, 0);
Mit einer einzigen SQL-Anweisung können Sie eine Zeile in der account-Tabelle anlegen und gleichzeitig vier Fremdschlüsselspaltenwerte ermitteln. Dieses Vorgehen hat allerdings einen Nachteil: Wenn Sie Daten für Spalten, die null-Werte erlauben, mit Unterabfragen generieren, gelingt Ihre insert-Anweisung auch dann, wenn eine Ihrer Unterabfragen eben keinen Wert zurückgibt. Hätten Sie beispielsweise Frank Portmans Namen in der vierten Unterabfrage falsch geschrieben, würde dennoch eine Zeile in account angelegt, bei der allerdings die open_emp_id auf null gesetzt wäre.
Zusammenfassung zu Unterabfragen Da in diesem Kapitel viel Stoff verarbeitet wurde, ist eine kleine Wiederholung sicher sinnvoll. In den Beispielen dieses Kapitels sahen Sie Unterabfragen, die: • eine einzelne Spalte und Zeile, eine Spalte mit mehreren Zeilen oder auch mehrere Spalten und Zeilen zurückgaben, • von der übergeordneten Anweisung unabhängig waren (nicht-korrelierte Unterabfragen), • eine oder mehrere Spalten der übergeordneten Anweisung referenzierten (korrelierte Unterabfragen),
Max. Linie
• in Bedingungen mit Vergleichsoperatoren und mit den speziellen Operatoren in, not in, exists und not exists vorkamen, • in select-, update-, delete- und insert-Anweisungen auftraten,
188 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts • Ergebnismengen generierten, die mit anderen Tabellen (oder Unterabfragen) in einer Abfrage verbunden werden können, • verwendet wurden, um Werte zum Ausfüllen einer Tabelle oder zum Bevölkern von Spalten in der Ergebnismenge einer Abfrage zu generieren, • in den select-, from-, where-, having- und order by-Klauseln von Abfragen vorkamen. Da Unterabfragen so vielseitig sind, müssen Sie sich keine Gedanken machen, wenn sich bei Ihnen nach der ersten Lektüre dieses Kapitels noch nicht alle diese Konzepte gefestigt haben. Experimentieren Sie weiterhin mit den verschiedenen Einsatzmöglichkeiten von Unterabfragen herum; auf diese Weise werden Sie bald immer dann, wenn Sie eine kompliziertere SQL-Anweisung schreiben müssen, ganz von selbst auf Unterabfragen kommen.
Testen Sie Ihr Wissen Diese Übungen sollen testen, ob Sie Unterabfragen verstanden haben. Die Lösungen finden Sie in Anhang C.
Übung 9-1 Schreiben Sie für die account-Tabelle eine Abfrage, die eine Filterbedingung mit einer nicht-korrelierten Unterabfrage der product-Tabelle verwendet, um alle Darlehenskonten zu finden (product.product_type_cd = 'LOAN'). Rufen Sie die Kontonummer (Account ID), den Produktcode, die Kundennummer (Customer ID) und den verfügbaren Saldo (available balance) ab.
Übung 9-2 Überarbeiten Sie die Abfrage aus 9-1, indem Sie eine korrelierte Unterabfrage der productTabelle verwenden, um dieselben Ergebnisse zu erhalten.
Übung 9-3 Verbinden Sie folgende Abfrage per Join mit der employee-Tabelle, um zu zeigen, wie erfahren die einzelnen Mitarbeiter sind:
Max. Linie
SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt UNION ALL SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt UNION ALL SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt
Testen Sie Ihr Wissen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 189
Links Geben Sie der Unterabfrage den Alias »levels« und fragen Sie nach der Personalnummer (Employee ID), dem Vornamen, dem Nachnamen und der Erfahrung (levels.name). (Hinweis: Schreiben Sie eine Join-Bedingung, die mithilfe einer Ungleichheitsbedingung feststellt, welcher Level für die Spalte employee.start_date gilt.)
Übung 9-4 Schreiben Sie eine Abfrage der employee-Tabelle, die die Personalnummer (Employee ID), den Vornamen, den Nachnamen sowie die Namen der Abteilung (department) und der Zweigstelle (branch) liefert, in der der betreffende Mitarbeiter arbeitet. Verwenden Sie keine Tabellen-Joins.
Max. Linie
Max. Linie 190 | Kapitel 9: Unterabfragen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 10
KAPITEL 10
Weitere Joins
Das in Kapitel 5 eingeführte Konzept eines Inner Joins dürfte Ihnen inzwischen hinlänglich bekannt sein. Im vorliegenden Kapitel geht es daher um andere Möglichkeiten, Tabellen zu verbinden: um den Outer Join und den Cross Join.
Outer Joins In allen bisherigen Beispielen, die mehrere Tabellen verwendeten, haben wir uns nicht dafür interessiert, ob die Join-Bedingungen eventuell nicht für alle Tabellenzeilen eine Übereinstimmung finden würden. So wurde beispielsweise bei einem Join der Tabellen account und customer kein Gedanke daran verschwendet, dass zu einem Wert der Spalte cust_id der account-Tabelle vielleicht kein passender Wert in der Spalte cust_id der customer-Tabelle gefunden werden könnte. Wenn es so wäre, würden einige Zeilen der einen oder der anderen Tabelle eben nicht in die Ergebnismenge geladen. Um sicherzugehen, wollen wir die Daten der beiden Tabellen noch einmal überprüfen. Hier sind die Spalten account_id und cust_id der account-Tabelle:
Max. Linie
mysql> SELECT account_id, cust_id -> FROM account; +------------+---------+ | account_id | cust_id | +------------+---------+ | 1 | 1 | | 2 | 1 | | 3 | 1 | | 4 | 2 | | 5 | 2 | | 7 | 3 | | 8 | 3 | | 10 | 4 | | 11 | 4 | | 12 | 4 | | 13 | 5 | | 14 | 6 |
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
191
Links | 15 | 6 | | 17 | 7 | | 18 | 8 | | 19 | 8 | | 21 | 9 | | 22 | 9 | | 23 | 9 | | 24 | 10 | | 25 | 10 | | 27 | 11 | | 28 | 12 | | 29 | 13 | +------------+---------+ 24 rows in set (1.50 sec)
Es gibt 24 Konten, wobei 13 verschiedene Kunden mit den Kundennummern 1 bis 13 jeweils mindestens ein Konto besitzen. Hier sehen Sie die Kundennummern aus der customer-Tabelle: mysql> SELECT cust_id -> FROM customer; +---------+ | cust_id | +---------+ | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 | | 9 | | 10 | | 11 | | 12 | | 13 | +---------+ 13 rows in set (0.02 sec)
In der customer-Tabelle gibt es 13 Kunden mit den Kundennummern 1 bis 13. Also wird jede Kundennummer mindestens einmal in der account-Tabelle aufgeführt. Wenn die beiden Tabellen auf der Spalte cust_id verbunden werden, kann man daher davon ausgehen, dass 24 Zeilen in die Ergebnismenge geschrieben werden (vorbehaltlich zusätzlicher Filterbedingungen):
Max. Linie
mysql> SELECT a.account_id, c.cust_id -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id; +------------+---------+ | account_id | cust_id | +------------+---------+ | 1 | 1 |
192 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 2 | 1 | | 3 | 1 | | 4 | 2 | | 5 | 2 | | 7 | 3 | | 8 | 3 | | 10 | 4 | | 11 | 4 | | 12 | 4 | | 13 | 5 | | 14 | 6 | | 15 | 6 | | 17 | 7 | | 18 | 8 | | 19 | 8 | | 21 | 9 | | 22 | 9 | | 23 | 9 | | 24 | 10 | | 25 | 10 | | 27 | 11 | | 28 | 12 | | 29 | 13 | +------------+---------+ 24 rows in set (0.06 sec)
Wie erwartet, führt die Ergebnismenge alle 24 Konten auf. Doch was geschieht, wenn Sie einen Join der account-Tabelle mit einer der spezialisierten Kundentabellen vornehmen, beispielsweise mit der Firmenkundentabelle namens business? mysql> SELECT a.account_id, b.cust_id, b.name -> FROM account a INNER JOIN business b -> ON a.cust_id = b.cust_id; +------------+---------+------------------------+ | account_id | cust_id | name | +------------+---------+------------------------+ | 24 | 10 | Chilton Engineering | | 25 | 10 | Chilton Engineering | | 27 | 11 | Northeast Cooling Inc. | | 28 | 12 | Superior Auto Body | | 29 | 13 | AAA Insurance Inc. | +------------+---------+------------------------+ 5 rows in set (0.10 sec)
Statt 24 befinden sich jetzt nur noch fünf Zeilen in der Ergebnismenge. Schauen wir einmal in der business-Tabelle nach, warum das so ist:
Max. Linie
mysql> SELECT cust_id, name -> FROM business; +---------+------------------------+ | cust_id | name | +---------+------------------------+ | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Outer Joins |
193
Links | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | +---------+------------------------+ 4 rows in set (0.01 sec)
Von den 13 Zeilen der Kundentabelle betreffen nur vier davon Firmenkunden, und da einer der Firmenkunden zwei Konten hat, werden insgesamt fünf Zeilen der accountTabelle mit den Firmenkunden in Verbindung gebracht. Doch was tun Sie, wenn Ihre Abfrage alle Konten zurückgeben, aber den Firmennamen nur nennen soll, wenn das Konto einem Firmenkunden gehört? Dies ist ein Beispiel für eine Situation, die einen Outer Join zwischen den Tabellen account und business erfordert: mysql> SELECT a.account_id, a.cust_id, b.name -> FROM account a LEFT OUTER JOIN business b -> ON a.cust_id = b.cust_id; +------------+---------+------------------------+ | account_id | cust_id | name | +------------+---------+------------------------+ | 1 | 1 | NULL | | 2 | 1 | NULL | | 3 | 1 | NULL | | 4 | 2 | NULL | | 5 | 2 | NULL | | 7 | 3 | NULL | | 8 | 3 | NULL | | 10 | 4 | NULL | | 11 | 4 | NULL | | 12 | 4 | NULL | | 13 | 5 | NULL | | 14 | 6 | NULL | | 15 | 6 | NULL | | 17 | 7 | NULL | | 18 | 8 | NULL | | 19 | 8 | NULL | | 21 | 9 | NULL | | 22 | 9 | NULL | | 23 | 9 | NULL | | 24 | 10 | Chilton Engineering | | 25 | 10 | Chilton Engineering | | 27 | 11 | Northeast Cooling Inc. | | 28 | 12 | Superior Auto Body | | 29 | 13 | AAA Insurance Inc. | +------------+---------+------------------------+ 24 rows in set (0.04 sec)
Max. Linie
Ein Outer Join bezieht von einer Tabelle alle Zeilen und von der zweiten nur die dazu passenden ein. In diesem Fall werden alle Zeilen der account-Tabelle eingebunden, da ich einen left outer join angegeben habe und die account-Tabelle auf der linken Seite der Join-Definition steht. Die Spalte name ist null für alle Zeilen außer für die vier Firmenkun-
194 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts den (cust_id 10, 11, 12 und 13). Nachfolgend sehen Sie eine ähnliche Abfrage mit einem Outer Join auf der Privatkundentabelle individual anstatt auf der business-Tabelle: mysql> SELECT a.account_id, a.cust_id, i.fname, i.lname -> FROM account a LEFT OUTER JOIN individual i -> ON a.cust_id = i.cust_id; +------------+---------+----------+---------+ | account_id | cust_id | fname | lname | +------------+---------+----------+---------+ | 1 | 1 | James | Hadley | | 2 | 1 | James | Hadley | | 3 | 1 | James | Hadley | | 4 | 2 | Susan | Tingley | | 5 | 2 | Susan | Tingley | | 7 | 3 | Frank | Tucker | | 8 | 3 | Frank | Tucker | | 10 | 4 | John | Hayward | | 11 | 4 | John | Hayward | | 12 | 4 | John | Hayward | | 13 | 5 | Charles | Frasier | | 14 | 6 | John | Spencer | | 15 | 6 | John | Spencer | | 17 | 7 | Margaret | Young | | 18 | 8 | George | Blake | | 19 | 8 | George | Blake | | 21 | 9 | Richard | Farley | | 22 | 9 | Richard | Farley | | 23 | 9 | Richard | Farley | | 24 | 10 | NULL | NULL | | 25 | 10 | NULL | NULL | | 27 | 11 | NULL | NULL | | 28 | 12 | NULL | NULL | | 29 | 13 | NULL | NULL | +------------+---------+----------+---------+ 24 rows in set (0.09 sec)
Diese Abfrage ist eigentlich das Gegenstück zur vorigen: Privatkunden werden mit Vorund Nachnamen aufgeführt, während die Firmenkundenspalten null sind.
Left und Right Outer Joins In den Beispielen für Outer Joins im vorigen Abschnitt verwendete ich einen left outer join. Das Schlüsselwort left bedeutet, dass die Tabelle auf der linken Seite der fromKlausel über die Anzahl der Zeilen der Ergebnismenge entscheidet, während die Tabelle auf der rechten Seite nur dort Spaltenwerte liefert, wo eine Übereinstimmung gefunden wurde. Betrachten Sie folgende Abfrage:
Max. Linie
mysql> SELECT c.cust_id, b.name -> FROM customer c LEFT OUTER JOIN business b -> ON c.cust_id = b.cust_id;
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Outer Joins |
195
Links +---------+------------------------+ | cust_id | name | +---------+------------------------+ | 1 | NULL | | 2 | NULL | | 3 | NULL | | 4 | NULL | | 5 | NULL | | 6 | NULL | | 7 | NULL | | 8 | NULL | | 9 | NULL | | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. | | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | +---------+------------------------+ 13 rows in set (0.00 sec)
Da die from-Klausel einen Left Outer Join enthält, werden alle 13 Zeilen der customerTabelle in die Ergebnismenge aufgenommen, während die business-Tabelle nur für Geschäftskunden die Werte der zweiten Spalte der Ergebnismenge beisteuert. Wenn Sie die gleiche Abfrage mit einem Right Outer Join ausführten, bekämen Sie folgende Ergebnisse zu sehen: mysql> SELECT c.cust_id, b.name -> FROM customer c RIGHT OUTER JOIN business b -> ON c.cust_id = b.cust_id; +---------+------------------------+ | cust_id | name | +---------+------------------------+ | 10 | Chilton Engineering | | 11 | Northeast Cooling Inc. | | 12 | Superior Auto Body | | 13 | AAA Insurance Inc. | +---------+------------------------+ 4 rows in set (0.00 sec)
Über die Anzahl der Zeilen in der Ergebnismenge entscheidet nunmehr die businessTabelle. Daher hat die Ergebnismenge jetzt nur noch vier Zeilen. Bedenken Sie, dass beide Abfragen Outer Joins durchführen. Die Schlüsselwörter left und right sind nur dazu da, dem Server mitzuteilen, welche Tabelle Lücken in ihren Daten aufweisen darf. Wenn Sie einen Outer Join mit den Tabellen A und B durchführen und dabei von A mit zusätzlichen Spalten von B nur die mit passenden Daten abfragen möchten, können Sie sagen: A left outer join B oder B right outer join A.
Outer Joins mit drei Tabellen
Max. Linie
In manchen Fällen möchte man einen Outer Join einer Tabelle mit zwei anderen Tabellen ausführen. Angenommen, Sie benötigen eine Liste aller Konten, in der für Privatkunden der Vor- und Nachname und für Firmenkunden der Firmenname aufgeführt werden soll:
196 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT a.account_id, a.product_cd, -> CONCAT(i.fname, ' ', i.lname) person_name, -> b.name business_name -> FROM account a LEFT OUTER JOIN individual i -> ON a.cust_id = i.cust_id -> LEFT OUTER JOIN business b -> ON a.cust_id = b.cust_id; +------------+------------+-----------------+------------------------+ | account_id | product_cd | person_name | business_name | +------------+------------+-----------------+------------------------+ | 1 | CHK | James Hadley | NULL | | 2 | SAV | James Hadley | NULL | | 3 | CD | James Hadley | NULL | | 4 | CHK | Susan Tingley | NULL | | 5 | SAV | Susan Tingley | NULL | | 7 | CHK | Frank Tucker | NULL | | 8 | MM | Frank Tucker | NULL | | 10 | CHK | John Hayward | NULL | | 11 | SAV | John Hayward | NULL | | 12 | MM | John Hayward | NULL | | 13 | CHK | Charles Frasier | NULL | | 14 | CHK | John Spencer | NULL | | 15 | CD | John Spencer | NULL | | 17 | CD | Margaret Young | NULL | | 18 | CHK | George Blake | NULL | | 19 | SAV | George Blake | NULL | | 21 | CHK | Richard Farley | NULL | | 22 | MM | Richard Farley | NULL | | 23 | CD | Richard Farley | NULL | | 24 | CHK | NULL | Chilton Engineering | | 25 | BUS | NULL | Chilton Engineering | | 27 | BUS | NULL | Northeast Cooling Inc. | | 28 | CHK | NULL | Superior Auto Body | | 29 | SBL | NULL | AAA Insurance Inc. | +------------+------------+-----------------+------------------------+ 24 rows in set (0.08 sec)
Die Ergebnisse enthalten alle 24 Zeilen der account-Tabelle, jeweils entweder mit dem Namen einer Privatperson oder mit dem Namen einer Firma. Diese Namen stammen aus den beiden durch Outer Joins verbundenen Tabellen. Meines Wissens kann eine Tabelle in MySQL per Outer Join mit beliebig vielen anderen Tabellen verbunden werden, aber natürlich können Sie immer Unterabfragen verwenden, um die Anzahl der Joins in Ihrer Abfrage zu beschränken. Das obige Beispiel kann man auch wie folgt umschreiben:
Max. Linie
mysql> SELECT account_ind.account_id, account_ind.product_cd, -> account_ind.person_name, -> b.name business_name -> FROM -> (SELECT a.account_id, a.product_cd, a.cust_id, -> CONCAT(i.fname, ' ', i.lname) person_name -> FROM account a LEFT OUTER JOIN individual i
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Outer Joins |
197
Links -> ON a.cust_id = i.cust_id) account_ind -> LEFT OUTER JOIN business b -> ON account_ind.cust_id = b.cust_id; +------------+------------+-----------------+------------------------+ | account_id | product_cd | person_name | business_name | +------------+------------+-----------------+------------------------+ | 1 | CHK | James Hadley | NULL | | 2 | SAV | James Hadley | NULL | | 3 | CD | James Hadley | NULL | | 4 | CHK | Susan Tingley | NULL | | 5 | SAV | Susan Tingley | NULL | | 7 | CHK | Frank Tucker | NULL | | 8 | MM | Frank Tucker | NULL | | 10 | CHK | John Hayward | NULL | | 11 | SAV | John Hayward | NULL | | 12 | MM | John Hayward | NULL | | 13 | CHK | Charles Frasier | NULL | | 14 | CHK | John Spencer | NULL | | 15 | CD | John Spencer | NULL | | 17 | CD | Margaret Young | NULL | | 18 | CHK | George Blake | NULL | | 19 | SAV | George Blake | NULL | | 21 | CHK | Richard Farley | NULL | | 22 | MM | Richard Farley | NULL | | 23 | CD | Richard Farley | NULL | | 24 | CHK | NULL | Chilton Engineering | | 25 | BUS | NULL | Chilton Engineering | | 27 | BUS | NULL | Northeast Cooling Inc. | | 28 | CHK | NULL | Superior Auto Body | | 29 | SBL | NULL | AAA Insurance Inc. | +------------+------------+-----------------+------------------------+ 24 rows in set (0.08 sec)
In dieser Version der Abfrage wird die individual-Tabelle innerhalb einer Unterabfrage namens account_ind per Outer Join mit der account-Tabelle und das Ergebnis wiederum mit einem Outer Join mit der business-Tabelle verbunden. So gibt es in jeder Abfrage (der Unterabfrage und der übergeordneten Abfrage) immer nur einen einzigen Outer Join. Wenn Sie eine andere Datenbank als MySQL verwenden, benötigen Sie eventuell diese Strategie, wenn Sie einen Outer Join mit mehreren Tabellen ausführen möchten.
Self Outer Joins In Kapitel 5 haben Sie bereits das Konzept eines Self Joins kennengelernt, in dem eine Tabelle mit sich selbst verbunden wird. Hier sehen Sie noch einmal ein Beispiel eines Self Joins aus Kapitel 5, in dem die employee-Tabelle mit sich selbst verbunden wird, um eine Liste der Angestellten und ihrer Vorgesetzten zu generieren:
Max. Linie
mysql> SELECT e.fname, e.lname, -> e_mgr.fname mgr_fname, e_mgr.lname mgr_lname -> FROM employee e INNER JOIN employee e_mgr -> ON e.superior_emp_id = e_mgr.emp_id;
198 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts +----------+-----------+-----------+-----------+ | fname | lname | mgr_fname | mgr_lname | +----------+-----------+-----------+-----------+ | Susan | Barker | Michael | Smith | | Robert | Tyler | Michael | Smith | | Susan | Hawthorne | Robert | Tyler | | John | Gooding | Susan | Hawthorne | | Helen | Fleming | Susan | Hawthorne | | Chris | Tucker | Helen | Fleming | | Sarah | Parker | Helen | Fleming | | Jane | Grossman | Helen | Fleming | | Paula | Roberts | Susan | Hawthorne | | Thomas | Ziegler | Paula | Roberts | | Samantha | Jameson | Paula | Roberts | | John | Blake | Susan | Hawthorne | | Cindy | Mason | John | Blake | | Frank | Portman | John | Blake | | Theresa | Markham | Susan | Hawthorne | | Beth | Fowler | Theresa | Markham | | Rick | Tulman | Theresa | Markham | +----------+-----------+-----------+-----------+ 17 rows in set (0.02 sec)
Diese Abfrage funktioniert prima, allerdings mit einer kleinen Einschränkung: Mitarbeiter, die keinen Vorgesetzten haben, fallen aus der Ergebnismenge heraus. Doch wenn Sie den Join von einem Inner Join in einen Outer Join umwandeln, enthält die Ergebnismenge sämtliche Mitarbeiter, also auch die ohne Vorgesetzte:
Max. Linie
mysql> SELECT e.fname, e.lname, -> e_mgr.fname mgr_fname, e_mgr.lname mgr_lname -> FROM employee e LEFT OUTER JOIN employee e_mgr -> ON e.superior_emp_id = e_mgr.emp_id; +----------+-----------+-----------+-----------+ | fname | lname | mgr_fname | mgr_lname | +----------+-----------+-----------+-----------+ | Michael | Smith | NULL | NULL | | Susan | Barker | Michael | Smith | | Robert | Tyler | Michael | Smith | | Susan | Hawthorne | Robert | Tyler | | John | Gooding | Susan | Hawthorne | | Helen | Fleming | Susan | Hawthorne | | Chris | Tucker | Helen | Fleming | | Sarah | Parker | Helen | Fleming | | Jane | Grossman | Helen | Fleming | | Paula | Roberts | Susan | Hawthorne | | Thomas | Ziegler | Paula | Roberts | | Samantha | Jameson | Paula | Roberts | | John | Blake | Susan | Hawthorne | | Cindy | Mason | John | Blake | | Frank | Portman | John | Blake | | Theresa | Markham | Susan | Hawthorne | | Beth | Fowler | Theresa | Markham | | Rick | Tulman | Theresa | Markham | +----------+-----------+-----------+-----------+ 18 rows in set (0.00 sec)
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Outer Joins |
199
Links Im Ergebnis finden wir nun auch Michael Smith, der als Präsident der Bank niemanden mehr über sich hat. Die Abfrage generiert mit einem Left Outer Join eine Liste aller Mitarbeiter und, soweit vorhanden, ihrer Vorgesetzten. Wenn Sie aus dem Join einen Right Outer Join machten, sähen die Ergebnisse folgendermaßen aus: mysql> SELECT e.fname, e.lname, -> e_mgr.fname mgr_fname, e_mgr.lname mgr_lname -> FROM employee e RIGHT OUTER JOIN employee e_mgr -> ON e.superior_emp_id = e_mgr.emp_id; +----------+-----------+-----------+-----------+ | fname | lname | mgr_fname | mgr_lname | +----------+-----------+-----------+-----------+ | Susan | Barker | Michael | Smith | | Robert | Tyler | Michael | Smith | | NULL | NULL | Susan | Barker | | Susan | Hawthorne | Robert | Tyler | | John | Gooding | Susan | Hawthorne | | Helen | Fleming | Susan | Hawthorne | | Paula | Roberts | Susan | Hawthorne | | John | Blake | Susan | Hawthorne | | Theresa | Markham | Susan | Hawthorne | | NULL | NULL | John | Gooding | | Chris | Tucker | Helen | Fleming | | Sarah | Parker | Helen | Fleming | | Jane | Grossman | Helen | Fleming | | NULL | NULL | Chris | Tucker | | NULL | NULL | Sarah | Parker | | NULL | NULL | Jane | Grossman | | Thomas | Ziegler | Paula | Roberts | | Samantha | Jameson | Paula | Roberts | | NULL | NULL | Thomas | Ziegler | | NULL | NULL | Samantha | Jameson | | Cindy | Mason | John | Blake | | Frank | Portman | John | Blake | | NULL | NULL | Cindy | Mason | | NULL | NULL | Frank | Portman | | Beth | Fowler | Theresa | Markham | | Rick | Tulman | Theresa | Markham | | NULL | NULL | Beth | Fowler | | NULL | NULL | Rick | Tulman | +----------+-----------+-----------+-----------+ 28 rows in set (0.00 sec)
Max. Linie
Diese Abfrage zeigt jeden Vorgesetzten (immer noch die dritte und vierte Spalte) zusammen mit seinen Untergebenen. Michael Smith erscheint also zweimal, nämlich als Vorgesetzter von Susan Barker und Robert Tyler; Susan Barker erscheint einmal als Vorgesetzte von niemandem (null-Werte in der ersten und zweiten Spalte). Alle 18 Mitarbeiter tauchen mindestens einmal in der dritten und vierten Spalte auf, wobei diejenigen, die mehr als einen Kollegen unter sich haben, mehrmals auftauchen. So kommt es, dass die Ergebnismenge 28 Zeilen hat. Dieses ganz andere Ergebnis als das der vorherigen Abfrage wurde durch die Änderung eines einzigen Schlüsselworts erzielt (left wurde zu right).
200 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Also: Wenn Sie mit Outer Joins arbeiten, überlegen Sie sich vorher am besten genau, ob Sie einen Left oder einen Right Outer Join benötigen.
Cross Joins In Kapitel 5 sprach ich bereits das Konzept eines kartesischen Produkts an: Im Grunde ist es das Ergebnis, das ein Join mehrerer Tabellen ergibt, wenn keine Join-Bedingung vorhanden ist. Kartesische Produkte entstehen oft aus Versehen (d.h., jemand vergisst, in die from-Klausel eine Join-Bedingung zu schreiben), werden aber nur selten absichtsvoll erzeugt. Wenn Sie tatsächlich einmal das kartesische Produkt zweier Tabellen benötigen, formulieren Sie einen Cross Join: mysql> SELECT pt.name, p.product_cd, p.name -> FROM product p CROSS JOIN product_type pt; +-------------------------------+------------+-------------------------+ | name | product_cd | name | +-------------------------------+------------+-------------------------+ | Customer Accounts | AUT | auto loan | | Customer Accounts | BUS | business line of credit | | Customer Accounts | CD | certificate of deposit | | Customer Accounts | CHK | checking account | | Customer Accounts | MM | money market account | | Customer Accounts | MRT | home mortgage | | Customer Accounts | SAV | savings account | | Customer Accounts | SBL | small business loan | | Insurance Offerings | AUT | auto loan | | Insurance Offerings | BUS | business line of credit | | Insurance Offerings | CD | certificate of deposit | | Insurance Offerings | CHK | checking account | | Insurance Offerings | MM | money market account | | Insurance Offerings | MRT | home mortgage | | Insurance Offerings | SAV | savings account | | Insurance Offerings | SBL | small business loan | | Individual und Business Loans | AUT | auto loan | | Individual und Business Loans | BUS | business line of credit | | Individual und Business Loans | CD | certificate of deposit | | Individual und Business Loans | CHK | checking account | | Individual und Business Loans | MM | money market account | | Individual und Business Loans | MRT | home mortgage | | Individual und Business Loans | SAV | savings account | | Individual und Business Loans | SBL | small business loan | +-------------------------------+------------+-------------------------+ 24 rows in set (0.00 sec)
Max. Linie
Diese Abfrage generiert das kartesische Produkt der Tabellen product und product_type, woraus sich 24 Zeilen ergeben (die 8 Zeilen von product mal die 3 Zeilen von product_ type). Da Sie nun wissen, was ein Cross Join ist und wie man ihn formuliert, stellt sich die Frage: Wozu das Ganze? Die meisten SQL-Bücher beschreiben Cross Joins, sagen Ihnen aber im gleichen Atemzug, dass sie meist zu nichts nütze sind. Doch ich möchte Ihnen
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Cross Joins |
201
Max. Linie
Links zumindest eine Situation aufzeigen, in der ein Cross Join meiner Meinung nach eben doch zu etwas gut ist. In Kapitel 9 beschrieb ich, wie Tabellen mithilfe von Unterabfragen angelegt werden. Ich verwendete dazu ein Beispiel, in dem eine dreizeilige Tabelle aufgebaut wurde, die mit anderen Tabellen per Join verbunden werden kann. Hier sehen Sie noch einmal die Tabelle, die in dem Beispiel erstellt wurde: mysql> SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit -> UNION ALL -> SELECT 'Average Joes' name, 5000 low_limit, 9999.99 high_limit -> UNION ALL -> SELECT 'Heavy Hitters' name, 10000 low_limit, 9999999.99 high_limit; +---------------+-----------+------------+ | name | low_limit | high_limit | +---------------+-----------+------------+ | Small Fry | 0 | 4999.99 | | Average Joes | 5000 | 9999.99 | | Heavy Hitters | 10000 | 9999999.99 | +---------------+-----------+------------+ 3 rows in set (0.00 sec)
Nun war diese Tabelle zwar ideal, um Kunden nach dem Gesamtsaldo ihrer Konten in drei Gruppen einzuteilen, doch die Strategie, einzeilige Tabellen mit dem Mengenoperator union all zusammenzuführen, funktioniert weniger gut, wenn man eine große Tabelle anlegen möchte. Angenommen, Sie benötigen eine Abfrage, die für jeden Tag des Jahres 2008 eine Zeile erzeugt, aber Sie haben in Ihrer Datenbank keine Tabelle, die bereits für jeden Tag eine Zeile enthält. Mit der Strategie aus dem Beispiel in Kapitel 9 könnten Sie so etwas wie das Folgende erreichen: SELECT '2008-01-01' UNION ALL SELECT '2008-01-02' UNION ALL SELECT '2008-01-03' UNION ALL ... ... ... SELECT '2008-12-29' UNION ALL SELECT '2008-12-30' UNION ALL SELECT '2008-12-31'
Max. Linie
dt dt dt
dt dt dt
Eigentlich ist es aber ziemlich öde, eine Abfrage zu konstruieren, die Ergebnisse aus 366 Abfragen zusammenführt. Daher wollen wir die Strategie wechseln: Wie wäre es, wenn wir eine Tabelle mit 366 Zeilen anlegen könnten (2004 war ein Schaltjahr), die eine einzige Spalte mit den Zahlen 0 bis 366 enthält, und dann jeweils diese Anzahl Tage zum
202 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts 1. Januar 2008 hinzuaddierten? Hier sehen Sie eine Möglichkeit, wie man eine solche Tabelle anlegen könnte:
Max. Linie
mysql> SELECT ones.num + tens.num + hundreds.num -> FROM -> (SELECT 0 num UNION ALL -> SELECT 1 num UNION ALL -> SELECT 2 num UNION ALL -> SELECT 3 num UNION ALL -> SELECT 4 num UNION ALL -> SELECT 5 num UNION ALL -> SELECT 6 num UNION ALL -> SELECT 7 num UNION ALL -> SELECT 8 num UNION ALL -> SELECT 9 num) ones -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 10 num UNION ALL -> SELECT 20 num UNION ALL -> SELECT 30 num UNION ALL -> SELECT 40 num UNION ALL -> SELECT 50 num UNION ALL -> SELECT 60 num UNION ALL -> SELECT 70 num UNION ALL -> SELECT 80 num UNION ALL -> SELECT 90 num) tens -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 100 num UNION ALL -> SELECT 200 num UNION ALL -> SELECT 300 num) hundreds; +------------------------------------+ | ones.num + tens.num + hundreds.num | +------------------------------------+ | 0 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 | | 9 | | 10 | | 11 | | 12 | ... ... ... | 391 | | 392 | | 393 |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Cross Joins |
203
Links | 394 | | 395 | | 396 | | 397 | | 398 | | 399 | +------------------------------------+ 400 rows in set (0.00 sec)
Wenn Sie das kartesische Produkt der drei Mengen {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 10, 20, 30, 40, 50, 60, 70, 80, 90} und {0, 100, 200, 300} nehmen und die Werte in den drei Spalten addieren, erhalten Sie eine 400 Zeilen lange Ergebnismenge mit allen Zahlen von 0 bis 399. Das mag zwar mehr sein als die 366 Zeilen, die benötigt werden, um die Tage des Jahres 2004 zu generieren, doch die überzähligen Zeilen lassen sich leicht loswerden, wie Sie in Kürze sehen werden. Der nächste Schritt besteht darin, diese Zahlenmenge in eine Menge von Datumsangaben zu konvertieren. Dazu verwende ich die date_add( )-Funktion, die jede Zahl der Ergebnismenge jeweils zum 1. Januar 2008 hinzuaddiert. Dann schreibe ich eine Filterbedingung, die Daten, die in das Jahr 2009 fallen, wieder über Bord wirft:
Max. Linie
mysql> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> ->
SELECT DATE_ADD('2008-01-01', INTERVAL (ones.num + tens.num + hundreds.num) DAY) dt FROM (SELECT 0 num UNION ALL SELECT 1 num UNION ALL SELECT 2 num UNION ALL SELECT 3 num UNION ALL SELECT 4 num UNION ALL SELECT 5 num UNION ALL SELECT 6 num UNION ALL SELECT 7 num UNION ALL SELECT 8 num UNION ALL SELECT 9 num) ones CROSS JOIN (SELECT 0 num UNION ALL SELECT 10 num UNION ALL SELECT 20 num UNION ALL SELECT 30 num UNION ALL SELECT 40 num UNION ALL SELECT 50 num UNION ALL SELECT 60 num UNION ALL SELECT 70 num UNION ALL SELECT 80 num UNION ALL SELECT 90 num) tens CROSS JOIN (SELECT 0 num UNION ALL SELECT 100 num UNION ALL SELECT 200 num UNION ALL SELECT 300 num) hundreds WHERE DATE_ADD('2008-01-01', INTERVAL (ones.num + tens.num + hundreds.num) DAY) < '2009-01-01' ORDER BY 1;
204 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts +------------+ | dt | +------------+ | 2008-01-01 | | 2008-01-02 | | 2008-01-03 | | 2008-01-04 | | 2008-01-05 | | 2008-01-06 | | 2008-01-07 | | 2008-01-08 | | 2008-01-09 | | 2008-01-10 | ... ... ... | 2008-02-20 | | 2008-02-21 | | 2008-02-22 | | 2008-02-23 | | 2008-02-24 | | 2008-02-25 | | 2008-02-26 | | 2008-02-27 | | 2008-02-28 | | 2008-02-29 | | 2008-03-01 | ... ... ... | 2008-12-20 | | 2008-12-21 | | 2008-12-22 | | 2008-12-23 | | 2008-12-24 | | 2008-12-25 | | 2008-12-26 | | 2008-12-27 | | 2008-12-28 | | 2008-12-29 | | 2008-12-30 | | 2008-12-31 | +------------+ 366 rows in set (0.01 sec)
Das Schöne an diesem Vorgehen ist, dass die Ergebnismenge automatisch den zusätzlichen Schalttag (29. Februar) enthält, ohne dass Sie etwas dafür tun müssten, da der Datenbankserver schon von selbst darauf kommt, wenn er 59 Tage zum 1. Januar 2008 addiert.
Max. Linie
Nun, da Sie einen Mechanismus haben, um alle Tage des Jahres 2008 zu fabrizieren, stellt sich wieder die Frage: Wie setzt man ihn ein? Vielleicht müssen Sie einmal eine Abfrage konstruieren, die jeden Tag des Jahres 2008 zusammen mit der Anzahl der an diesem Tag
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Cross Joins |
205
Max. Linie
Links durchgeführten Banktransaktionen, der Anzahl an diesem Tag eröffneter Konten und so weiter zeigt. Die erste Aufgabe löst man folgendermaßen:
Max. Linie
mysql> SELECT days.dt, COUNT(t.txn_id) -> FROM transaction t RIGHT OUTER JOIN -> (SELECT DATE_ADD('2008-01-01', -> INTERVAL (ones.num + tens.num + hundreds.num) DAY) dt -> FROM -> (SELECT 0 num UNION ALL -> SELECT 1 num UNION ALL -> SELECT 2 num UNION ALL -> SELECT 3 num UNION ALL -> SELECT 4 num UNION ALL -> SELECT 5 num UNION ALL -> SELECT 6 num UNION ALL -> SELECT 7 num UNION ALL -> SELECT 8 num UNION ALL -> SELECT 9 num) ones -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 10 num UNION ALL -> SELECT 20 num UNION ALL -> SELECT 30 num UNION ALL -> SELECT 40 num UNION ALL -> SELECT 50 num UNION ALL -> SELECT 60 num UNION ALL -> SELECT 70 num UNION ALL -> SELECT 80 num UNION ALL -> SELECT 90 num) tens -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 100 num UNION ALL -> SELECT 200 num UNION ALL -> SELECT 300 num) hundreds -> WHERE DATE_ADD('2008-01-01', -> INTERVAL (ones.num + tens.num + hundreds.num) DAY) < -> '2009-01-01') days -> ON days.dt = t.txn_date -> GROUP BY days.dt -> ORDER BY 1; +------------+-----------------+ | dt | COUNT(t.txn_id) | +------------+-----------------+ | 2008-01-01 | 0 | | 2008-01-02 | 0 | | 2008-01-03 | 0 | | 2008-01-04 | 0 | | 2008-01-05 | 21 | | 2008-01-06 | 0 | | 2008-01-07 | 0 | | 2008-01-08 | 0 | | 2008-01-09 | 0 | | 2008-01-10 | 0 | | 2008-01-11 | 0 |
206 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 2008-01-12 | 0 | | 2008-01-13 | 0 | | 2008-01-14 | 0 | | 2008-01-15 | 0 | ... | 2008-12-31 | 0 | +------------+-----------------+ 366 rows in set (0.03 sec)
Diese Abfrage ist eine der interessantesten, die uns bisher in diesem Buch begegnet ist: Sie enthält Cross Joins, Outer Joins, eine Datumsfunktion, eine Gruppierung, Mengenoperationen (union all) und eine Aggregatfunktion (count( )). Sie mag zwar nicht die eleganteste Lösung der vorgegebenen Aufgabe sein, aber schließlich soll sie ja als Beispiel dafür dienen, wie man mit ein wenig Kreativität und einer soliden Grundlage in SQL sogar ein selten genutztes Feature wie Cross Joins zu einem brauchbaren Werkzeug im SQL-Werkzeugkasten umfunktionieren kann.
Natural Joins Wenn Sie etwas faul sind (sind wir ja schließlich alle), können Sie auch einen Join-Typ wählen, in dem Sie nur die Tabellen angeben, während die Join-Bedingung vom Datenbankserver ermittelt wird. Dieser sogenannte Natural Join erschließt die Join-Bedingungen aus gleichen Spaltennamen in unterschiedlichen Tabellen. So hat zum Beispiel die account-Tabelle eine Spalte namens cust_id, die der Fremdschlüssel zur customer-Tabelle ist, deren Primärschlüssel ebenfalls cust_id heißt. Sie können also eine Abfrage schreiben, die beide Tabellen mit einem natural join verbindet:
Max. Linie
mysql> SELECT a.account_id, a.cust_id, c.cust_type_cd, c.fed_id -> FROM account a NATURAL JOIN customer c; +------------+---------+--------------+-------------+ | account_id | cust_id | cust_type_cd | fed_id | +------------+---------+--------------+-------------+ | 1 | 1 | I | 111-11-1111 | | 2 | 1 | I | 111-11-1111 | | 3 | 1 | I | 111-11-1111 | | 4 | 2 | I | 222-22-2222 | | 5 | 2 | I | 222-22-2222 | | 6 | 3 | I | 333-33-3333 | | 7 | 3 | I | 333-33-3333 | | 8 | 4 | I | 444-44-4444 | | 9 | 4 | I | 444-44-4444 | | 10 | 4 | I | 444-44-4444 | | 11 | 5 | I | 555-55-5555 | | 12 | 6 | I | 666-66-6666 | | 13 | 6 | I | 666-66-6666 | | 14 | 7 | I | 777-77-7777 | | 15 | 8 | I | 888-88-8888 | | 16 | 8 | I | 888-88-8888 | | 17 | 9 | I | 999-99-9999 | | 18 | 9 | I | 999-99-9999 |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie Natural Joins |
207
Links | 19 | 9 | I | 999-99-9999 | | 20 | 10 | B | 04-1111111 | | 21 | 10 | B | 04-1111111 | | 22 | 11 | B | 04-2222222 | | 23 | 12 | B | 04-3333333 | | 24 | 13 | B | 04-4444444 | +------------+---------+--------------+-------------+ 24 rows in set (0.02 sec)
Da Sie einen Natural Join verlangt haben, hat sich der Server die Tabellen-Definitionen angeschaut und die Join-Bedingung a.cust_id = c.cust_id selbst hinzugefügt, um die beiden Tabellen zu verbinden. Das alles mag ja gut und schön sein, doch was ist, wenn in den Tabellen keine gleichnamigen Spalten zu finden sind? So hat zum Beispiel die Tabelle account auch einen Fremdschlüssel zur branch-Tabelle, doch die Spalte in der account-Tabelle heißt open_branch_id anstatt einfach branch_id. Mal sehen, was passiert, wenn ich einen natural join zwischen den Tabellen account und branch versuche:
Max. Linie
mysql> SELECT a.account_id, a.cust_id, a.open_branch_id, b.name -> FROM account a NATURAL JOIN branch b; +------------+---------+----------------+---------------+ | account_id | cust_id | open_branch_id | name | +------------+---------+----------------+---------------+ | 1 | 1 | 2 | Headquarters | | 1 | 1 | 2 | Woburn Branch | | 1 | 1 | 2 | Quincy Branch | | 1 | 1 | 2 | So. NH Branch | | 2 | 1 | 2 | Headquarters | | 2 | 1 | 2 | Woburn Branch | | 2 | 1 | 2 | Quincy Branch | | 2 | 1 | 2 | So. NH Branch | | 3 | 1 | 2 | Headquarters | | 3 | 1 | 2 | Woburn Branch | | 3 | 1 | 2 | Quincy Branch | | 3 | 1 | 2 | So. NH Branch | | 4 | 2 | 2 | Headquarters | | 4 | 2 | 2 | Woburn Branch | | 4 | 2 | 2 | Quincy Branch | | 4 | 2 | 2 | So. NH Branch | | 5 | 2 | 2 | Headquarters | | 5 | 2 | 2 | Woburn Branch | | 5 | 2 | 2 | Quincy Branch | | 5 | 2 | 2 | So. NH Branch | | 7 | 3 | 3 | Headquarters | | 7 | 3 | 3 | Woburn Branch | | 7 | 3 | 3 | Quincy Branch | | 7 | 3 | 3 | So. NH Branch | | 8 | 3 | 3 | Headquarters | | 8 | 3 | 3 | Woburn Branch | | 8 | 3 | 3 | Quincy Branch | | 8 | 3 | 3 | So. NH Branch | | 10 | 4 | 1 | Headquarters |
208 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 10 | 4 | 1 | Woburn Branch | | 10 | 4 | 1 | Quincy Branch | | 10 | 4 | 1 | So. NH Branch | ... ... ... | 24 | 10 | 4 | Headquarters | | 24 | 10 | 4 | Woburn Branch | | 24 | 10 | 4 | Quincy Branch | | 24 | 10 | 4 | So. NH Branch | | 25 | 10 | 4 | Headquarters | | 25 | 10 | 4 | Woburn Branch | | 25 | 10 | 4 | Quincy Branch | | 25 | 10 | 4 | So. NH Branch | | 27 | 11 | 2 | Headquarters | | 27 | 11 | 2 | Woburn Branch | | 27 | 11 | 2 | Quincy Branch | | 27 | 11 | 2 | So. NH Branch | | 28 | 12 | 4 | Headquarters | | 28 | 12 | 4 | Woburn Branch | | 28 | 12 | 4 | Quincy Branch | | 28 | 12 | 4 | So. NH Branch | | 29 | 13 | 3 | Headquarters | | 29 | 13 | 3 | Woburn Branch | | 29 | 13 | 3 | Quincy Branch | | 29 | 13 | 3 | So. NH Branch | +------------+---------+----------------+---------------+ 96 rows in set (0.07 sec)
Anscheinend ist etwas schiefgegangen: Die Abfrage sollte nicht mehr als 24 Zeilen zurückgeben, da sich 24 Zeilen in der account-Tabelle befinden. Da der Server jedoch keine zwei gleichnamigen Spalten in den beiden Tabellen finden konnte, war es ihm auch nicht möglich, eine Join-Bedingung zu generieren, daher nahm er einen Cross Join der beiden Tabellen vor. Dabei kamen 96 Zeilen heraus (24 Konten mal 4 Zweigstellen). Sollte man so etwas riskieren, nur um ein wenig Tipperei bei der Eingabe der Join-Bedingung zu sparen? Keinesfalls! Bitte machen Sie um diesen Join-Typ einen Bogen und verwenden Sie Inner Joins mit expliziten Join-Bedingungen.
Testen Sie Ihr Wissen Die folgenden Übungen sollen Ihre Kenntnisse über Outer und Cross Joins festigen. Die Lösungen finden Sie in Anhang C.
Max. Linie
Übung 10-1 Schreiben Sie eine Abfrage, die jeden Produktnamen zusammen mit den zugehörigen Konten zurückgibt (nutzen Sie dabei die product_cd-Spalte der account-Tabelle, um eine
Testen Sie Ihr Wissen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
209
Max. Linie
Links Verbindung zur product-Tabelle zu schaffen). Beziehen Sie alle Produkte ein, auch die, zu denen noch keine Konten eröffnet wurden.
Übung 10-2 Überarbeiten Sie die Abfrage aus Übung 10-1 so, dass der andere Outer Join-Typ verwendet wird (d.h., wenn Sie in Übung 10-1 einen Left Outer Join verwendet haben, benutzen Sie jetzt einen Right Outer Join). Es sollen dieselben Ergebnisse herauskommen.
Übung 10-3 Schreiben Sie einen Outer Join der account-Tabelle mit den Tabellen individual und business (auf der Spalte account.cust_id), sodass die Ergebnismenge eine Zeile pro Konto enthält. Es sollen die Spalten account.account_id, account.product_cd, individual.fname, individual.lname und business.name einbezogen werden.
Übung 10-4 (für Tüftler) Überlegen Sie sich eine Abfrage, um die Menge {1, 2, 3,…, 99, 100} zu generieren. (Hinweis: Verwenden Sie einen Cross Join mit mindestens zwei from-Klausel-Unterabfragen.)
Max. Linie
Max. Linie 210 | Kapitel 10: Weitere Joins
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 11
KAPITEL 11
Bedingungslogik
Es gibt Situationen, in denen Ihre SQL-Logik, abhängig davon, welchen Wert eine Spalte oder ein Ausdruck hat, entweder die eine oder die andere Richtung einschlagen soll. Im vorliegenden Kapitel werden Anweisungen behandelt, die sich je nachdem, welche Daten sie bei der Ausführung vorfinden, unterschiedlich verhalten können.
Was ist Bedingungslogik? Bedingungslogik ist nichts weiter als die Fähigkeit, während der Programmausführung einen von mehreren möglichen Pfaden einzuschlagen. Wenn Sie Kundendaten abfragen, können Sie abhängig von der Art der Kunden entweder die Spalten fname/lname aus der individual-Tabelle oder die Spalte name aus der business-Tabelle betrachten. Mit Outer Joins könnten Sie beide Strings abrufen und den Aufrufer selbst entscheiden lassen, welchen Wert er verwenden möchte:
Max. Linie
mysql> SELECT c.cust_id, c.fed_id, c.cust_type_cd, -> CONCAT(i.fname, ' ', i.lname) indiv_name, -> b.name business_name -> FROM customer c LEFT OUTER JOIN individual i -> ON c.cust_id = i.cust_id -> LEFT OUTER JOIN business b -> ON c.cust_id = b.cust_id; +---------+-------------+--------------+-----------------+------------------------+ | cust_id | fed_id | cust_type_cd | indiv_name | business_name | +---------+-------------+--------------+-----------------+------------------------+ | 1 | 111-11-1111 | I | James Hadley | NULL | | 2 | 222-22-2222 | I | Susan Tingley | NULL | | 3 | 333-33-3333 | I | Frank Tucker | NULL | | 4 | 444-44-4444 | I | John Hayward | NULL | | 5 | 555-55-5555 | I | Charles Frasier | NULL | | 6 | 666-66-6666 | I | John Spencer | NULL | | 7 | 777-77-7777 | I | Margaret Young | NULL | | 8 | 888-88-8888 | I | Louis Blake | NULL | | 9 | 999-99-9999 | I | Richard Farley | NULL | | 10 | 04-1111111 | B | NULL | Chilton Engineering |
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
211
Links | 11 | 04-2222222 | B | NULL | Northeast Cooling Inc. | | 12 | 04-3333333 | B | NULL | Superior Auto Body | | 13 | 04-4444444 | B | NULL | AAA Insurance Inc. | +---------+-------------+--------------+-----------------+------------------------+ 13 rows in set (0.13 sec)
Der Aufrufer kann sich die Spalte cust_type_cd anschauen und entscheiden, ob er die indiv_name- oder die business_name-Spalte verwenden möchte. Doch stattdessen könnte man auch Bedingungslogik in einem Case-Ausdruck (einer Fallunterscheidung) einsetzen, um den Kundentyp zu ermitteln und dann den passenden String zurückzuliefern: mysql> SELECT c.cust_id, c.fed_id, -> CASE -> WHEN c.cust_type_cd = 'I' -> THEN CONCAT(i.fname, ' ', i.lname) -> WHEN c.cust_type_cd = 'B' -> THEN b.name -> ELSE 'Unknown' -> END name -> FROM customer c LEFT OUTER JOIN individual i -> ON c.cust_id = i.cust_id -> LEFT OUTER JOIN business b -> ON c.cust_id = b.cust_id; +---------+-------------+------------------------+ | cust_id | fed_id | name | +---------+-------------+------------------------+ | 1 | 111-11-1111 | James Hadley | | 2 | 222-22-2222 | Susan Tingley | | 3 | 333-33-3333 | Frank Tucker | | 4 | 444-44-4444 | John Hayward | | 5 | 555-55-5555 | Charles Frasier | | 6 | 666-66-6666 | John Spencer | | 7 | 777-77-7777 | Margaret Young | | 8 | 888-88-8888 | Louis Blake | | 9 | 999-99-9999 | Richard Farley | | 10 | 04-1111111 | Chilton Engineering | | 11 | 04-2222222 | Northeast Cooling Inc. | | 12 | 04-3333333 | Superior Auto Body | | 13 | 04-4444444 | AAA Insurance Inc. | +---------+-------------+------------------------+ 13 rows in set (0.00 sec)
Diese Version der Abfrage gibt eine einzige name-Spalte zurück, generiert von dem CaseAusdruck, der auf der zweiten Zeile der Abfrage beginnt. Der Ausdruck sieht sich den Wert der Spalte cust_type_cd an und gibt entweder den Vor- und Nachnamen des Privatkunden oder den Firmennamen des Geschäftskunden zurück.
Der Case-Ausdruck Max. Linie
Alle bedeutenden Datenbanksysteme haben eingebaute Funktionen, um die in den meisten Programmiersprachen vorhandene if-then-else-Anweisung nachzuahmen (zum Bei-
212 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts spiel die Funktion decode( ) von Oracle, die Funktion if( ) von MySQL und die Funktion coalesce( ) von SQL Server). Auch die Case-Ausdrücke sind für die if-then-else-Logik da, haben jedoch zusätzlich zwei Vorteile gegenüber eingebauten Funktionen: • Der Case-Ausdruck ist Teil des SQL-Standards (SQL92) und wurde von Oracle Database, SQL Server, MySQL, Sybase, PostgreSQL, IBM UDB und anderen implementiert. • Case-Ausdrücke sind in die SQL-Grammatik eingebaut und können in select-, insert-, update- und delete-Anweisungen eingebunden werden. Die nächsten Abschnitte werden Ihnen die beiden verschiedenen Arten von Case-Ausdrücken näher bringen und dann praktische Beispiele dafür zeigen.
Searched Case-Ausdrücke Der oben gezeigte Case-Ausdruck ist ein Beispiel eines Searched Case-Ausdrucks, der folgende Syntax hat: CASE WHEN C1 THEN E1 WHEN C2 THEN E2 ... WHEN CN THEN EN [ELSE ED] END
In dieser Definition stehen die Symbole C1, C2, …, CN für Bedingungen und die Symbole E1, E2, …, EN für Ausdrücke, die von dem Case-Ausdruck zurückgegeben werden. Ist die Bedingung einer when-Klausel wahr, liefert der Case-Ausdruck den ihm zugeordneten Ausdruck zurück. Zusätzlich repräsentiert das ED-Symbol den Default-Ausdruck, der zurückgeliefert wird, wenn keine der Bedingungen C1, C2, …, CN als true ausgewertet wird (da die else-Klausel optional ist, steht sie in eckigen Klammern). Alle Rückgabeausdrücke der verschiedenen when-Klauseln müssen denselben Datentyp ergeben (z.B. date, number, varchar). Hier sehen Sie ein Beispiel für einen Searched Case-Ausdruck:
Max. Linie
CASE WHEN employee.title = 'Head Teller' THEN 'Head Teller' WHEN employee.title = 'Teller' AND YEAR(employee.start_date) > 2007 THEN 'Teller Trainee' WHEN employee.title = 'Teller' AND YEAR(employee.start_date) < 2006 THEN 'Experienced Teller' WHEN employee.title = 'Teller' THEN 'Teller' ELSE 'Non-Teller' END
Der Case-Ausdruck | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 213
Links Dieser Case-Ausdruck gibt einen String zurück, der verwendet werden kann, um Stundensätze zu ermitteln, Namensschilder zu drucken usw. Wenn der Case-Ausdruck ausgewertet wird, werden die when-Klauseln der Reihe nach von oben nach unten betrachtet. Sobald eine Bedingung einer when-Klausel true ist, wird der zugehörige Ausdruck zurückgegeben, und alle weiteren when-Klauseln werden ignoriert. Ist keine when-Klausel-Bedingung true, wird der Ausdruck in der else-Klausel zurückgegeben. Auch wenn das obige Beispiel String-Ausdrücke zurückgibt, müssen Sie daran denken, dass Case-Ausdrücke jeden Typ von Ausdruck liefern können, einschließlich Unterabfragen. Hier sehen Sie eine neue Version der Abfrage nach den Namen von Privat- und Firmenkunden, die anstelle von Outer Joins Unterabfragen verwendet, um Daten aus den Tabellen individual und business zu holen: mysql> SELECT c.cust_id, c.fed_id, -> CASE -> WHEN c.cust_type_cd = 'I' THEN -> (SELECT CONCAT(i.fname, ' ', i.lname) -> FROM individual i -> WHERE i.cust_id = c.cust_id) -> WHEN c.cust_type_cd = 'B' THEN -> (SELECT b.name -> FROM business b -> WHERE b.cust_id = c.cust_id) -> ELSE 'Unknown' -> END name -> FROM customer c; +---------+-------------+------------------------+ | cust_id | fed_id | name | +---------+-------------+------------------------+ | 1 | 111-11-1111 | James Hadley | | 2 | 222-22-2222 | Susan Tingley | | 3 | 333-33-3333 | Frank Tucker | | 4 | 444-44-4444 | John Hayward | | 5 | 555-55-5555 | Charles Frasier | | 6 | 666-66-6666 | John Spencer | | 7 | 777-77-7777 | Margaret Young | | 8 | 888-88-8888 | Louis Blake | | 9 | 999-99-9999 | Richard Farley | | 10 | 04-1111111 | Chilton Engineering | | 11 | 04-2222222 | Northeast Cooling Inc. | | 12 | 04-3333333 | Superior Auto Body | | 13 | 04-4444444 | AAA Insurance Inc. | +---------+-------------+------------------------+ 13 rows in set (0.01 sec)
Max. Linie
Diese Version der Abfrage benennt in der from-Klausel nur die customer-Tabelle und ruft die jeweiligen Kundennamen mithilfe von korrelierten Unterabfragen ab. Ich gebe dieser Version gegenüber der Outer Join-Version den Vorzug, da der Server nur nach Bedarf Daten aus den Tabellen individual und business liest, anstatt immer alle drei Tabellen mit einem Join zu verbinden.
214 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Einfache Case-Ausdrücke Der einfache Case-Ausdruck ist dem Searched Case-Ausdruck ziemlich ähnlich, aber weniger flexibel. Seine Syntax ist wie folgt: CASE V0 WHEN V1 THEN E1 WHEN V2 THEN E2 ... WHEN VN THEN EN [ELSE ED] END
In dieser Definition steht V0 für einen Wert und die Symbole V1, V2, …, VN für andere Werte, die mit V0 verglichen werden. Die Symbole E1, E2, …, EN repräsentieren wieder Ausdrücke, die von dem Case-Ausdruck zurückgegeben werden, und ED den Default-Ausdruck, der zurückgeliefert wird, wenn keiner der Werte V1, V2, …, VN mit V0 übereinstimmt. Das folgende Beispiel zeigt einen einfachen Case-Ausdruck: CASE customer.cust_type_cd WHEN 'I' THEN (SELECT CONCAT(i.fname, ' ', i.lname) FROM individual I WHERE i.cust_id = customer.cust_id) WHEN 'B' THEN (SELECT b.name FROM business b WHERE b.cust_id = customer.cust_id) ELSE 'Unknown Customer Type' END
Einfache Case-Ausdrücke sind weniger mächtig als Searched Case-Ausdrücke, da Sie keine eigenen Bedingungen angeben können, sondern stattdessen das System Gleichheitsbedingungen anlegt. Damit Sie besser verstehen, was ich meine, zeige ich Ihnen hier einen CaseAusdruck, der die gleiche Logik hat wie der vorhergehende einfache Case-Ausdruck: CASE WHEN customer.cust_type_cd = 'I' THEN (SELECT CONCAT(i.fname, ' ', i.lname) FROM individual I WHERE i.cust_id = customer.cust_id) WHEN customer.cust_type_cd = 'B' THEN (SELECT b.name FROM business b WHERE b.cust_id = customer.cust_id) ELSE 'Unknown Customer Type' END
Max. Linie
Mit Searched Case-Ausdrücken können Sie Wertebereichsbedingungen, Ungleichheitsbedingungen und mehrteilige Bedingungen mit and/or/not konstruieren. Aus diesem Grund rate ich in allen außer den ganz trivialen Fällen immer zu Searched Case-Ausdrücken.
Der Case-Ausdruck | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
215
Max. Linie
Links Beispiele für Case-Ausdrücke Die folgenden Abschnitte zeigen einige Beispiele, die den Nutzen von Bedingungslogik in SQL-Anweisungen veranschaulichen sollen.
Umwandlungen von Ergebnismengen Vielleicht mussten auch Sie schon einmal Aggregationen mit einer endlichen Wertemenge wie beispielsweise der Menge der Wochentage ausführen, wollten aber, dass die Ergebnismenge eine einzige Zeile mit nur einem Wert anstatt für jeden Wert eine Zeile enthalten sollte. Als Beispiel wollen wir annehmen, Sie wurden aufgefordert, eine Abfrage zu schreiben, die ermittelt, wie viele Konten zwischen 2000 und 2005 eröffnet wurden: mysql> SELECT YEAR(open_date) year, COUNT(*) how_many -> FROM account -> WHERE open_date > '1999-12-31' -> AND open_date < '2006-01-01' -> GROUP BY YEAR(open_date); +------+----------+ | year | how_many | +------+----------+ | 2000 | 3 | | 2001 | 4 | | 2002 | 5 | | 2003 | 3 | | 2004 | 9 | +------+----------+ 5 rows in set (0.00 sec)
Sie wurden allerdings auch gebeten, nur eine Datenzeile mit sechs Spalten (eine für jedes Jahr im Bereich) zu liefern. Um diese Ergebnismenge in eine einzelne Zeile mit sechs Spalten umzuwandeln, müssen Sie sechs Spalten anlegen und in jeder dieser Spalten nur die Zeilen des betreffenden Jahres aufsummieren:
Max. Linie
mysql> SELECT -> SUM(CASE -> WHEN EXTRACT(YEAR -> ELSE 0 -> END) year_2000, -> SUM(CASE -> WHEN EXTRACT(YEAR -> ELSE 0 -> END) year_2001, -> SUM(CASE -> WHEN EXTRACT(YEAR -> ELSE 0 -> END) year_2002, -> SUM(CASE -> WHEN EXTRACT(YEAR -> ELSE 0 -> END) year_2003,
FROM open_date) = 2000 THEN 1
FROM open_date) = 2001 THEN 1
FROM open_date) = 2002 THEN 1
FROM open_date) = 2003 THEN 1
216 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts -> SUM(CASE -> WHEN EXTRACT(YEAR FROM open_date) = 2004 THEN 1 -> ELSE 0 -> END) year_2004, -> SUM(CASE -> WHEN EXTRACT(YEAR FROM open_date) = 2005 THEN 1 -> ELSE 0 -> END) year_2005 -> FROM account -> WHERE open_date > '1999-12-31'; +-----------+-----------+-----------+-----------+-----------+-----------+ | year_2000 | year_2001 | year_2002 | year_2003 | year_2004 | year_2005 | +-----------+-----------+-----------+-----------+-----------+-----------+ | 3 | 4 | 5 | 3 | 9 | 0 | +-----------+-----------+-----------+-----------+-----------+-----------+ 1 row in set (0.01 sec)
Jede der sechs Spalten dieser Abfrage ist mit Ausnahme des Jahreswerts identisch. Wenn die Funktion extract( ) das für diese Spalte gewünschte Jahr zurückgibt, liefert der CaseAusdruck den Wert 1; anderenfalls wird 0 zurückgegeben. Werden alle seit 2000 eröffneten Konten zusammengerechnet, steht in jeder Spalte, wie viele Konten jeweils in diesem Jahr eröffnet wurden. Solche Umwandlungen sind natürlich nur für kleine Wertemengen praktikabel; wenn Sie für jedes Jahr seit 1905 auf diese Weise die Anzahl der neuen Konten ermitteln wollten, würde die Arbeit rasch langweilig. Auch wenn das für dieses Buch etwas zu fortgeschritten ist, sollten wir erwähnen, dass SQL Server und Oracle Database 11g speziell für derartige Abfragen PIVOT-Klauseln bieten.
Selektive Aggregation In Kapitel 9 zeigte ich Ihnen bereits eine Teillösung für ein Beispiel, in dem gezeigt wurde, wie man Konten ermittelt, deren Salden nicht mit den Rohdaten in der transactionTabelle übereinstimmen. Die Lösung stellte ich nur zum Teil vor, da für eine vollständige Lösung Bedingungslogik erforderlich ist. Da auch dieses Thema nun behandelt wurde, kann ich Ihnen endlich die vollständige Lösung zeigen. Hier sehen Sie das, was in Kapitel 9 weggelassen wurde: SELECT CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!') FROM account a WHERE (a.avail_balance, a.pending_balance) (SELECT SUM(), SUM() FROM transaction t WHERE t.account_id = a.account_id);
Max. Linie
Diese Abfrage bildet die Summe der Einzeltransaktionen für ein Konto mithilfe einer korrelierten Unterabfrage der transaction-Tabelle. Beim Aufsummieren von Transaktionen sind zwei Dinge zu bedenken:
Beispiele für Case-Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
|
217
Max. Linie
Links • Da Transaktionsbeträge immer positiv sind, müssen Sie anhand des Transaktionstyps ermitteln, ob es sich um einen Geldeingang oder einen Geldausgang handelt, und bei Geldausgängen entsprechend das Vorzeichen umkehren (mit –1 multiplizieren). • Wenn das Datum in der Spalte funds_avail_date größer als das aktuelle Datum ist, sollte die Transaktion im Saldo der ausstehenden Buchungen (pending balance), aber nicht im Saldo des verfügbaren Saldos (available balance) berücksichtigt werden. Während aus dem verfügbaren Saldo einige Transaktionen herausgerechnet werden müssen, sind im ausstehenden Saldo alle Transaktionen enthalten, sodass dieser die einfachere der beiden Berechnungen ist. Hier sehen Sie den Case-Ausdruck, mit dem der ausstehende Saldo berechnet wird: CASE WHEN transaction.txn_type_cd = 'DBT' THEN transaction.amount * -1 ELSE transaction.amount END
Es werden also alle Geldausgänge mit –1 multipliziert und alle Geldeingänge so gelassen, wie sie sind. Die gleiche Logik gilt auch für die Berechnung des verfügbaren Saldos; dieser darf allerdings nur Transaktionen widerspiegeln, die bereits stattgefunden haben. Daher hat der Case-Ausdruck zur Berechnung des verfügbaren Saldos eine zusätzliche whenKlausel: CASE WHEN transaction.funds_avail_date > CURRENT_TIMESTAMP( ) THEN 0 WHEN transaction.txn_type_cd = 'DBT' THEN transaction.amount * -1 ELSE transaction.amount END
Mit der ersten when-Klausel tragen Buchungen, die noch in der Schwebe sind, wie beispielsweise Scheckeinlösungen unter Eingangsvorbehalt, nur $0 zu der Gesamtsumme bei. Hier sehen Sie die endgültige Abfrage mit den beiden Case-Ausdrücken:
Max. Linie
SELECT CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!') FROM account a WHERE (a.avail_balance, a.pending_balance) (SELECT SUM(CASE WHEN t.funds_avail_date > CURRENT_TIMESTAMP( ) THEN 0 WHEN t.txn_type_cd = 'DBT' THEN t.amount * -1 ELSE t.amount END), SUM(CASE
218 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts WHEN t.txn_type_cd = 'DBT' THEN t.amount * -1 ELSE t.amount END) FROM transaction t WHERE t.account_id = a.account_id);
Durch die Bedingungslogik werden die sum( )-Aggregatfunktionen mit Daten gefüttert, die von den beiden Case-Ausdrücken vorbehandelt wurden, sodass immer nur die korrekten Beträge in die Summe einfließen.
Prüfung auf Vorhandensein Manchmal möchte man herausfinden, ob eine Beziehung zwischen zwei Entitäten unabhängig von quantitativen Verhältnissen überhaupt existiert. Angenommen, Sie möchten ermitteln, ob ein Kunde überhaupt Giro- oder Sparkonten besitzt, ohne Rücksicht darauf, ob er nur eines oder mehrere dieser Konten hat. Die folgende Abfrage verwendet mehrere Case-Ausdrücke, um zwei Ausgabespalten zu generieren: Eine zeigt an, ob der Kunde Girokonten hat, und die andere, ob er Sparkonten hat:
Max. Linie
mysql> SELECT c.cust_id, c.fed_id, c.cust_type_cd, -> CASE -> WHEN EXISTS (SELECT 1 FROM account a -> WHERE a.cust_id = c.cust_id -> AND a.product_cd = 'CHK') THEN 'Y' -> ELSE 'N' -> END has_checking, -> CASE -> WHEN EXISTS (SELECT 1 FROM account a -> WHERE a.cust_id = c.cust_id -> AND a.product_cd = 'SAV') THEN 'Y' -> ELSE 'N' -> END has_savings -> FROM customer c; +---------+-------------+--------------+--------------+-------------+ | cust_id | fed_id | cust_type_cd | has_checking | has_savings | +---------+-------------+--------------+--------------+-------------+ | 1 | 111-11-1111 | I | Y | Y | | 2 | 222-22-2222 | I | Y | Y | | 3 | 333-33-3333 | I | Y | N | | 4 | 444-44-4444 | I | Y | Y | | 5 | 555-55-5555 | I | Y | N | | 6 | 666-66-6666 | I | Y | N | | 7 | 777-77-7777 | I | N | N | | 8 | 888-88-8888 | I | Y | Y | | 9 | 999-99-9999 | I | Y | N | | 10 | 04-1111111 | B | Y | N | | 11 | 04-2222222 | B | N | N | | 12 | 04-3333333 | B | Y | N | | 13 | 04-4444444 | B | N | N | +---------+-------------+--------------+--------------+-------------+ 13 rows in set (0.00 sec)
Beispiele für Case-Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
219
Links Jeder Case-Ausdruck enthält eine korrelierte Unterabfrage der Tabelle account; eine sucht nach Girokonten und die andere nach Sparkonten. Da jede when-Klausel den exists-Operator verwendet, werden die Bedingungen als true ausgewertet, sofern der Kunde mindestens eines der gewünschten Konten besitzt. In anderen Fällen können Sie eventuell danach fragen, wie viele Zeilen vorhanden sind, aber nur bis zu einem gewissen Grad. Die nächste Abfrage verwendet zum Beispiel einen einfachen Case-Ausdruck, um die Anzahl der Konten pro Kunde zu zählen und dann 'None', '1', '2' oder '3+' zurückzugeben: mysql> SELECT c.cust_id, c.fed_id, c.cust_type_cd, -> CASE (SELECT COUNT(*) FROM account a -> WHERE a.cust_id = c.cust_id) -> WHEN 0 THEN 'None' -> WHEN 1 THEN '1' -> WHEN 2 THEN '2' -> ELSE '3+' -> END num_accounts -> FROM customer c; +---------+-------------+--------------+--------------+ | cust_id | fed_id | cust_type_cd | num_accounts | +---------+-------------+--------------+--------------+ | 1 | 111-11-1111 | I | 3+ | | 2 | 222-22-2222 | I | 2 | | 3 | 333-33-3333 | I | 2 | | 4 | 444-44-4444 | I | 3+ | | 5 | 555-55-5555 | I | 1 | | 6 | 666-66-6666 | I | 2 | | 7 | 777-77-7777 | I | 1 | | 8 | 888-88-8888 | I | 2 | | 9 | 999-99-9999 | I | 3+ | | 10 | 04-1111111 | B | 2 | | 11 | 04-2222222 | B | 1 | | 12 | 04-3333333 | B | 1 | | 13 | 04-4444444 | B | 1 | +---------+-------------+--------------+--------------+ 13 rows in set (0.01 sec)
Da ich in dieser Abfrage die Kunden nicht danach unterteilen wollte, ob sie zwei oder mehr Konten haben, erstellt der Case-Ausdruck lediglich eine '3+'-Kategorie. Eine solche Abfrage kann nützlich sein, wenn man Kunden auf Grund einer neuen Kontoeröffnung ansprechen möchte.
Fehler bei einer Division durch null
Max. Linie
Wenn Sie Berechnungen durchführen, zu denen eine Division gehört, müssen Sie dafür sorgen, dass der Nenner niemals gleich null sein kann. Zwar quittieren manche Datenbankserver, wie beispielsweise Oracle Database, eine Division durch null mit einem Fehler, doch MySQL setzt das Ergebnis einer solchen Berechnung einfach auf null:
220 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT 100 / 0; +---------+ | 100 / 0 | +---------+ | NULL | +---------+ 1 row in set (0.00 sec)
Damit Ihre Berechnungen keine Fehler auslösen oder, was noch schlimmer wäre, aus unerfindlichen Gründen null-Werte zurückliefern, sollten Sie alle Nenner in eine Bedingung einhüllen, wie es nachfolgend gezeigt wird:
Max. Linie
mysql> SELECT a.cust_id, a.product_cd, a.avail_balance / -> CASE -> WHEN prod_tots.tot_balance = 0 THEN 1 -> ELSE prod_tots.tot_balance -> END percent_of_total -> FROM account a INNER JOIN -> (SELECT a.product_cd, SUM(a.avail_balance) tot_balance -> FROM account a -> GROUP BY a.product_cd) prod_tots -> ON a.product_cd = prod_tots.product_cd; +---------+------------+------------------+ | cust_id | product_cd | percent_of_total | +---------+------------+------------------+ | 10 | BUS | 0.000000 | | 11 | BUS | 1.000000 | | 1 | CD | 0.153846 | | 6 | CD | 0.512821 | | 7 | CD | 0.256410 | | 9 | CD | 0.076923 | | 1 | CHK | 0.014488 | | 2 | CHK | 0.030928 | | 3 | CHK | 0.014488 | | 4 | CHK | 0.007316 | | 5 | CHK | 0.030654 | | 6 | CHK | 0.001676 | | 8 | CHK | 0.047764 | | 9 | CHK | 0.001721 | | 10 | CHK | 0.322911 | | 12 | CHK | 0.528052 | | 3 | MM | 0.129802 | | 4 | MM | 0.321915 | | 9 | MM | 0.548282 | | 1 | SAV | 0.269431 | | 2 | SAV | 0.107773 | | 4 | SAV | 0.413723 | | 8 | SAV | 0.209073 | | 13 | SBL | 1.000000 | +---------+------------+------------------+ 24 rows in set (0.13 sec)
Beispiele für Case-Ausdrücke This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
221
Links Hier wird der Anteil jedes Kontensaldos am Gesamtsaldo aller Konten dieses Typs berechnet. Da manche Produkttypen, wie beispielsweise Firmendarlehen, einen Nullsaldo haben können, falls gerade alle Kredite zurückgezahlt sind, binde ich einen CaseAusdruck ein, um zu gewährleisten, dass der Nenner nie gleich null sein kann.
Bedingte Updates Wenn Sie Zeilen in einer Tabelle ändern, müssen Sie gelegentlich entscheiden, auf welche Werte Sie bestimmte Spalten setzen können. Vielleicht müssen Sie nach einer neuen Transaktion entscheiden, ob die Spalten avail_balance, pending_balance und last_activity_date der account-Tabelle aktualisiert werden müssen. Die letzten beiden Spalten lassen sich zwar leicht ändern, aber um die Spalte avail_balance richtig modifizieren zu können, müssen Sie wissen, ob der Betrag der Transaktion direkt verfügbar ist, indem Sie die Spalte funds_avail_date der transaction-Tabelle prüfen. Wurde zum Beispiel die TransaktionsID 999 soeben eingefügt, können Sie mit folgender update-Anweisung die drei Spalten der account-Tabelle modifizieren: 1 UPDATE account 2 SET last_activity_date = CURRENT_TIMESTAMP(), 3 pending_balance = pending_balance + 4 (SELECT t.amount * 5 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END 6 FROM transaction t 7 WHERE t.txn_id = 999), 8 avail_balance = avail_balance + 9 (SELECT 10 CASE 11 WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0 12 ELSE t.amount * 13 CASE t.txn_type_cd WHEN 'DBT' THEN -1 ELSE 1 END 14 END 15 FROM transaction t 16 WHERE t.txn_id = 999) 17 WHERE account.account_id = 18 (SELECT t.account_id 19 FROM transaction t 20 WHERE t.txn_id = 999);
Diese Anweisung enthält insgesamt drei Case-Ausdrücke: Zwei davon (Zeilen 5 und 13) dienen dazu, das Vorzeichen der Beträge von Geldausgängen umzukehren, und der dritte (Zeile 10) schaut auf das Datum, an dem das Geld verfügbar wird. Liegt das Datum in der Zukunft, wird zum verfügbaren Saldo null addiert, ansonsten wird der Transaktionsbetrag in die Summe aufgenommen.
Der Umgang mit null-Werten
Max. Linie
Zwar ist es ganz passend, in einer Tabellenspalte, deren Wert unbekannt ist, null-Werte zu speichern, aber diese null-Werte für die Anzeige oder für die Verwendung in Ausdrü-
222 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts cken abzurufen ist manchmal ganz und gar nicht passend. Angenommen, Sie möchten auf einem Bildschirmformular zur Dateneingabe lieber das Wort »unbekannt« anzeigen lassen, als ein völlig leeres Feld zu haben. Beim Abfragen der Daten können Sie mithilfe eines Case-Ausdrucks für den Wert null diesen String einsetzen: SELECT emp_id, fname, lname, CASE WHEN title IS NULL THEN 'Unbekannt' ELSE title END FROM employee;
In Berechnungen haben null-Werte oft auch ein null-Ergebnis zur Folge, wie dieses Beispiel zeigt: mysql> SELECT (7 * 5) / ((3 + 14) * null); +-----------------------------+ | (7 * 5) / ((3 + 14) * null) | +-----------------------------+ | NULL | +-----------------------------+ 1 row in set (0.08 sec)
Also sind Case-Ausdrücke nützlich, um in Berechnungen einen null-Wert in eine Zahl umzuwandeln (normalerweise 0 oder 1), die es ermöglicht, einen von null verschiedenen Wert zurückzugeben. Wenn Sie eine Berechnung mit der Spalte account.avail_balance ausführen, könnten Sie beispielsweise eine 0 für Additionen oder Subtraktionen oder eine 1 für Multiplikationen oder Divisionen für Konten einfügen, die zwar eröffnet sind, aber noch keinen Saldo haben: SELECT + CASE WHEN avail_balance IS NULL THEN 0 ELSE avail_balance END + ...
Gestattet eine numerische Spalte null-Werte, ist es immer eine gute Idee, in sämtlichen Berechnungen mit dieser Spalte Bedingungslogik zu verwenden, um brauchbare Resultate zu erzielen.
Testen Sie Ihr Wissen Die folgenden Beispiele prüfen Ihre Fähigkeiten im Umgang mit Bedingungslogik. Vergleichen Sie Ihre Lösungen mit denen in Anhang C.
Max. Linie
Max. Linie Testen Sie Ihr Wissen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
223
Links Übung 11-1 Schreiben Sie die folgende Abfrage, die einen einfachen Case-Ausdruck enthält, so um, dass sie mit einem Searched Case-Ausdruck dieselben Ergebnisse erzielt. Versuchen Sie, mit möglichst wenigen when-Klauseln auszukommen. SELECT emp_id, CASE title WHEN 'President' THEN 'Management' WHEN 'Vice President' THEN 'Management' WHEN 'Treasurer' THEN 'Management' WHEN 'Loan Manager' THEN 'Management' WHEN 'Operations Manager' THEN 'Operations' WHEN 'Head Teller' THEN 'Operations' WHEN 'Teller' THEN 'Operations' ELSE 'Unknown' END FROM employee;
Übung 11-2 Schreiben Sie die folgende Abfrage so um, dass sie eine Ergebnismenge mit einer einzigen Zeile mit vier Spalten (eine für jede Zweigstelle) zurückgibt. Nennen Sie die vier Spalten branch_1 bis branch_4. mysql> SELECT open_branch_id, COUNT(*) -> FROM account -> GROUP BY open_branch_id; +----------------+----------+ | open_branch_id | COUNT(*) | +----------------+----------+ | 1 | 8 | | 2 | 7 | | 3 | 3 | | 4 | 6 | +----------------+----------+ 4 rows in set (0.00 sec)
Max. Linie
Max. Linie 224 | Kapitel 11: Bedingungslogik This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 12
KAPITEL 12
Transaktionen
Alle bisherigen Beispiele in diesem Buch waren einzelne SQL-Anweisungen. Obwohl das für schnelle Berichte oder Skripten zur Datenwartung wahrscheinlich die Norm ist, umfasst eine komplexere Anwendungslogik häufig mehrere SQL-Anweisungen, die gemeinsam, als eine logische Arbeitseinheit, ausgeführt werden müssen. Das vorliegende Kapitel beschreibt die Voraussetzungen und die notwendige Infrastruktur, um mehrere SQL-Anweisungen zusammen auszuführen.
Mehrbenutzerdatenbanken In Datenbankmanagementsystemen kann nicht nur ein Einzelbenutzer Daten abfragen und modifizieren, sondern auch mehrere Benutzer gleichzeitig. Wenn jeder Benutzer nur Abfragen ausführt, wie es bei einem Data Warehouse während der Geschäftszeiten durchaus der Fall sein kann, dann hat der Datenbankserver damit kaum Probleme. Doch wenn einige der Benutzer Daten hinzufügen oder modifizieren, muss der Server einiges tun, um seine Bücher ordentlich zu führen. Angenommen, Sie erstellen einen Report, der für alle in Ihrer Zweigstelle eröffneten Konten den verfügbaren Saldo anzeigt. Während Sie den Bericht erstellen, finden jedoch noch folgende andere Dinge statt: • Ein Kassierer nimmt in Ihrer Zweigstelle eine Einzahlung eines Kunden entgegen. • Ein Kunde beendet im Foyer eine Abhebung am Geldautomaten. • Die Monatsultimo-Anwendung der Bank schreibt auf den Konten Zinsen gut. Während Ihr Report läuft, nehmen also gleich mehrere Benutzer Änderungen an den Daten vor. Welche Zahlen soll der Bericht dann ausweisen? Die Antwort hängt zum Teil davon ab, wie Ihr Server mit Sperren umgeht, einem Mechanismus, der im nachfolgenden Abschnitt beschrieben wird.
Max. Linie
Max. Linie | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
225
Links Sperren Sperren ist der Mechanismus, den der Datenbankserver nutzt, um den gleichzeitigen Zugriff auf Datenressourcen zu steuern. Wenn ein Teil der Datenbank gesperrt ist, müssen alle Benutzer, die diese Daten verändern (oder möglicherweise lesen) wollen, warten, bis die Sperre freigegeben wurde. Die meisten Server bieten zwei Sperrstrategien: • Wer Daten schreibt, muss vom Server eine Schreibsperre beantragen und erhalten, um Daten modifizieren zu können, und wer Daten liest, muss vom Server eine Lesesperre beantragen und empfangen, um Daten abfragen zu können. Es können zwar mehrere Benutzer gleichzeitig Daten lesen, aber es kann immer nur eine einzige Schreibsperre pro Tabelle (oder Tabellenteil) vorhanden sein. Leseanforderungen werden blockiert, bis die Schreibsperre wieder freigegeben wurde. • Wer Daten schreibt, muss vom Server eine Schreibsperre beantragen und erhalten, um Daten modifizieren zu können, aber Leser benötigen keine Sperre, um Daten abfragen zu können. Stattdessen gewährleistet der Server, dass der Leser vom Beginn bis zum Ende seiner Abfrage eine konsistente View der Daten präsentiert bekommt (die Daten bleiben scheinbar unverändert, auch wenn gerade jemand sie ändert). Dieses Vorgehen bezeichnet man als Versionierung. Beide Vorgehensweisen haben ihre Vor- und Nachteile. Der erste Ansatz kann lange Wartezeiten verursachen, wenn zu viele Lese- und Schreibanforderungen gleichzeitig eintreffen, und der zweite kann Probleme aufwerfen, wenn langwierige Abfragen ausgeführt werden, während die Daten gerade geändert werden. Einer der drei in diesem Buch behandelten Server, nämlich Microsoft SQL Server, verwendet den ersten Ansatz, Oracle Database verwendet den zweiten, und MySQL verwendet beide (je nachdem, welches Speichermodul Sie verwenden, doch dieses wird weiter unten in diesem Kapitel erklärt).
Granularität von Sperren Auch die Entscheidung, wie eine Ressource gesperrt wird, kann auf Basis mehrerer verschiedener Strategien getroffen werden. Der Server kann eine Sperre auf einer von drei unterschiedliche Ebenen oder Granularitäten einrichten: Tabellensperren Verhindert, dass mehrere Benutzer gleichzeitig Daten derselben Tabelle ändern. Seitensperren Verhindert, dass mehrere Benutzer gleichzeitig Daten derselben Tabellenseite ändern (eine Seite ist ein Speichersegment, normalerweise zwischen 2 KByte und 16 KByte groß).
Max. Linie
Zeilensperren Verhindert, dass mehrere Benutzer gleichzeitig Daten derselben Tabellenzeile ändern.
226 | Kapitel 12: Transaktionen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Auch die Granularitäten haben ihre Vor- und Nachteile. Um ganze Tabellen zu sperren, ist wenig Buchführung erforderlich, doch andererseits kommt es dabei mit wachsender Zahl der Benutzer zu unverhältnismäßigen Wartezeiten. Zeilensperren erfordern andererseits mehr an Buchführung, ermöglichen es aber, dass mehrere Benutzer dieselbe Tabelle ändern können, solange sie sich nur für unterschiedliche Zeilen interessieren. Von den drei in diesem Buch beschriebenen Servern verwendet Microsoft SQL Server Seiten- und Zeilensperren, Oracle Database Zeilensperren und MySQL Tabellen-, Seiten- oder Zeilensperren (wieder abhängig von der Wahl des Speichermoduls). Unter bestimmten Umständen erweitert SQL Server Zeilensperren zu Seitensperren und Seitensperren zu Tabellensperren, Oracle Database hingegen macht das nie. Um noch einmal auf den Report zurückzukommen: Die Daten, die auf den Berichtsseiten auftauchen, werden entweder den Zustand widerspiegeln, den die Datenbank zu Beginn der Ausführung des Reports hatte (wenn Ihr Server mit Versionierung arbeitet), oder den Zustand, den sie hatte, als der Server der Report-Anwendung eine Lesesperre gab (wenn Ihr Server sowohl Lese- als auch Schreibsperren verwendet).
Was ist eine Transaktion? Wenn Datenbankserver immer zu 100% verfügbar wären, wenn Benutzer ihre Programme immer bis zum Ende laufen ließen und wenn Anwendungen immer zum Abschluss kämen, ohne je ihre Ausführung wegen eines fatalen Fehlers abzubrechen, dann gäbe es über nebenläufigen Datenbankzugriff nicht viel zu sagen. Da man sich jedoch auf keines dieser Dinge verlassen kann, ist ein zusätzliches Element erforderlich, damit mehrere Benutzer auf dieselben Daten zugreifen können. Dieses zusätzliche Element im großen Puzzle des Parallelzugriffs ist die Transaktion, ein Mittel, um mehrere SQL-Anweisungen so zusammenzufassen, dass entweder alle oder keine von ihnen Erfolg hat (eine Eigenschaft, die als Atomarität bezeichnet wird). Wenn Sie versuchen, $500 von Ihrem Spar- auf Ihr Girokonto zu übertragen, wären Sie etwas befremdet, wenn das Geld zwar von Ihrem Sparkonto abgebucht, aber niemals Ihrem Girokonto gutgeschrieben würde. Egal welcher Grund hinter dem Scheitern steckt (Server aus Wartungsgründen heruntergefahren, Timeout der Seitensperrenanforderung auf der account-Tabelle usw.): Sie wollen Ihre $500 zurück. Zum Schutz gegen solche Fehler startet das Programm, das Ihre Überweisung bearbeitet, zuerst eine Transaktion, führt dann die SQL-Anweisungen aus, die notwendig sind, um das Geld vom Spar- auf das Girokonto zu übertragen, und beendet die Transaktion erst, wenn alles gut gegangen ist, mit dem commit-Befehl. Treten jedoch unvorhergesehene Dinge ein, würde das Programm einen rollback-Befehl geben, damit der Server alle seit Beginn der Transaktion vorgenommenen Änderungen wieder rückgängig macht. Der gesamte Vorgang könnte ungefähr so aussehen:
Max. Linie
Max. Linie Was ist eine Transaktion? | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
227
Links START TRANSACTION; /* Geld vom ersten Konto abheben, auf ausreichenden Saldo achten */ UPDATE account SET avail_balance = avail_balance – 500 WHERE account_id = 9988 AND avail_balance > 500; IF THEN /* Geld dem zweiten Konto gutschreiben */ UPDATE account SET avail_balance = avail_balance + 500 WHERE account_id = 9989; IF THEN /* Alles hat geklappt, Änderungen festschreiben */ COMMIT; ELSE /* Etwas ging schief, alle Änderungen dieser Transaktion zurückrollen */ ROLLBACK; END IF; ELSE /* Saldo nicht ausreichend oder Fehler während des Updates */ ROLLBACK; END IF;
Zwar mag dieser Codeblock so aussehen, als sei er in einer der prozeduralen Sprachen geschrieben, die von den größeren Datenbankherstellern angeboten werden, wie beispielsweise PL/SQL von Oracle oder Transact SQL von Microsoft, aber er ist in Pseudocode verfasst und ahmt keine spezielle Sprache nach.
Der obige Codeblock startet zuerst eine Transaktion und versucht dann, $500 vom Girokonto abzubuchen und dieselben $500 dem Sparkonto gutzuschreiben. Wenn alles gut geht, wird die Transaktion mit einem Commit festgeschrieben, wenn nicht, wird sie zurückgerollt, das heißt, alle Datenänderungen seit Beginn der Transaktion werden rückgängig gemacht. Durch die Transaktion gewährleistet das Programm, dass Sie Ihre 500 Dollar auf jeden Fall wiederfinden: Entweder bleibt das Geld auf dem Sparkonto, oder es wird auf das Girokonto übertragen, aber keinesfalls versackt es in einem schwarzen Loch. Egal ob die Transaktion committet oder zurückgerollt wurde, alle während der Transaktion erworbenen Ressourcen (z.B. Schreibsperren) werden nach ihrem Abschluss wieder freigegeben.
Max. Linie
Natürlich: Wenn das Programm beide update-Anweisungen fertig ausführt, aber der Server herunterfährt, ehe ein commit oder rollback abgesetzt werden kann, wird die Transaktion, sobald der Server wieder online ist, zurückgerollt. (Eine Aufgabe, die jeder Datenbanserver erledigen muss, ehe er wieder online gehen kann, besteht darin, eventuell noch nicht abgeschlossene Transaktionen zu finden und zurückzurollen, die gerade liefen, als er herunterfuhr.) Wenn Ihr Programm eine Transaktion beendet und ein commit
228 | Kapitel 12: Transaktionen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts absetzt, der Server aber herunterfährt, bevor die Änderungen permanent gespeichert wurden (d.h. die im Speicher sitzenden Daten noch nicht auf die Festplatte geschrieben wurden), muss der Datenbankserver zusätzlich die Änderungen aus Ihrer Transaktion neu anwenden, wenn er neu gestartet wird (eine Eigenschaft, die als durability bezeichnet wird).
Transaktion starten Die Datenbankserver erstellen Transaktionen auf eine von zwei möglichen Arten: • Da immer eine aktive Transaktion mit einer Datenbanksession assoziiert ist, gibt es keine Notwendigkeit und auch keine Methode, eine Transaktion explizit zu starten. Wenn die aktuelle Transaktion endet, beginnt der Server automatisch eine neue Transaktion für Ihre Session. • Solange Sie nicht ausdrücklich eine Transaktion starten, werden einzelne SQLAnweisungen automatisch unabhängig voneinander committet. Um eine Transaktion zu beginnen, müssen Sie einen entsprechenden Befehl geben. Von den drei Servern schlägt Oracle Database den ersten Weg ein, während Microsoft SQL Server und MySQL den zweiten verfolgen. Ein Vorteil von Oracles Transaktionen ist: Selbst wenn Sie nur einen einzigen SQL-Befehl geben, haben Sie die Möglichkeit, die Änderungen zurückzurollen, wenn das Ergebnis nicht Ihren Erwartungen entspricht oder Sie es sich anders überlegt haben. Haben Sie also vergessen, Ihrer delete-Anweisung eine where-Klausel hinzuzufügen, haben Sie immer die Möglichkeit, den Schaden wieder zu reparieren (vorausgesetzt, Sie haben Ihren Kaffee schon getrunken und überhaupt gemerkt, dass Sie nicht alle 125.000 Kundendatensätze in Ihrer Tabelle löschen wollten). Mit MySQL und SQL Server dagegen ist es so: Sobald Sie auf die Enter-Taste hauen, werden die Änderungen, die Ihre SQL-Anweisung vorgenommen hat, dauerhaft übernommen (es sei denn, Ihr DBA kann die Originaldaten aus einem Backup oder auf andere Weise wiederherstellen). Der SQL:2003-Standard enthält einen start transaction-Befehl, den man immer verwenden kann, wenn man explizit eine Transaktion beginnen möchte. Während MySQL sich an den Standard hält, müssen die Benutzer von SQL Server stattdessen begin Transaction sagen. Beide Server laufen so lange, bis Sie explizit eine Transaktion starten, im sogenannten Autocommit-Modus. Das bedeutet, dass Einzelanweisungen vom Server automatisch festgeschrieben werden. Sie können also wählen, ob Sie in einer Transaktion arbeiten und entsprechende Befehle zum Starten oder Beenden einer Transaktion absetzen möchten oder ob Sie den Server einfach automatisch einzelne Anweisungen committen lassen.
Max. Linie
Sowohl MySQL als auch SQL Server gestatten es, den Autocommit-Modus für einzelne Sessions auszuschalten. In diesem Fall behandeln sie Transaktionen genau wie Oracle Database. Bei SQL Server deaktivieren Sie den Autocommit-Modus mit folgendem Befehl: SET IMPLICIT_TRANSACTIONS ON
Was ist eine Transaktion? | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
229
Max. Linie
Links MySQL verwendet dafür diese Anweisung: SET AUTOCOMMIT=0
Wenn Sie den Autocommit-Modus verlassen haben, finden alle SQL-Anweisungen in einer Transaktion statt und müssen ausdrücklich committet oder zurückgerollt werden. Mein Rat: Schalten Sie immer, wenn Sie sich einloggen, den AutocommitModus ab und gewöhnen Sie sich an, alle Ihre SQL-Anweisungen in einer Transaktion auszuführen. Das kann Ihnen die Peinlichkeit ersparen, Ihren DBA um die Wiederherstellung von Daten bitten zu müssen, die Sie versehentlich gelöscht haben.
Transaktion beenden Sobald eine Transaktion begonnen hat, sei es nun explizit mit einem start transactionBefehl oder implizit durch den Datenbankserver, müssen Sie die Transaktion auch wieder beenden, damit die Änderungen festgeschrieben werden. Dieses tun Sie mit dem commitBefehl, der den Server anweist, die Änderungen als permanent zu kennzeichnen und Ressourcen (Seiten- oder Zeilensperren), die während der Transaktion benötigt wurden, wieder freizugeben. Wenn Sie dagegen beschließen, alle Änderungen seit Beginn der Transaktion wieder rückgängig zu machen, müssen Sie den rollback-Befehl geben, der den Server anweist, alle Daten wieder in den Zustand zurückzuversetzen, den sie vor der Transaktion hatten. Nachdem das rollback abgeschlossen ist, werden alle von Ihrer Session benutzten Ressourcen freigegeben. Eine Transaktion kann allerdings nicht nur mit einem commit oder rollback enden, sondern auch auf andere Weise, sei es als indirekte Folge Ihres Tuns oder aus Gründen, auf die Sie keinen Einfluss haben: • Der Server fährt herunter. In diesem Fall wird Ihre Transaktion automatisch zurückgerollt, wenn der Server neu gestartet wird. • Sie führen eine SQL-Schemaanweisung wie alter table aus. Dann wird die laufende Transaktion committet und eine neue gestartet. • Sie geben einen neuen start transaction-Befehl. Dann wird die vorherige Transaktion committet. • Der Server beendet Ihre Transaktion vorzeitig, weil er einen Deadlock findet, an dem Ihre Transaktion schuld ist. In diesem Fall wird die Transaktion zurückgerollt, und Sie bekommen eine Fehlermeldung.
Max. Linie
Das erste und das dritte dieser vier Szenarien sind recht einfach, doch die anderen beiden sind erklärungsbedürftig. Im zweiten Fall gilt: Da Änderungen an einer Datenbank – das Hinzufügen einer neuen Tabelle oder eines Index oder das Entfernen einer Spalte oder Ähnliches – nicht zurückgerollt werden können, müssen Befehle, die das Datenbank-
230 | Kapitel 12: Transaktionen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Schema ändern, außerhalb einer Transaktion stattfinden. Wenn bei einem solchen Befehl gerade eine Transaktion läuft, wird der Server sie committen, um dann den SQL-SchemaBefehl auszuführen und hinterher eine neue Transaktion für Ihre Sitzung zu starten. Da der Server Sie nicht über die Vorgänge informiert, sollten Sie darauf achten, dass Anweisungen, die als Einheit ausgeführt werden müssen, nicht unbeabsichtigt vom Server in mehrere Transaktionen zerlegt werden. Das vierte Szenario tritt ein, wenn ein Deadlock erkannt wird. Ein Deadlock tritt ein, wenn zwei verschiedene Transaktionen jeweils auf Ressourcen der anderen warten. Ein Beispiel: Transaktion A hat gerade die account-Tabelle geändert und wartet auf eine Schreibsperre der transaction-Tabelle, während Transaktion B eine Zeile in die transaction-Tabelle eingefügt hat und auf eine Schreibsperre der account-Tabelle wartet. Wenn beide Transaktionen zufällig dieselbe Seite oder Zeile ändern (je nach der vom Datenbankserver verwendeten Granularität), können beide ewig warten, da die andere Transaktion die benötigte Ressource nicht freigibt. Datenbankserver müssen immer auf der Hut vor solchen Situationen sein, damit der Durchsatz nicht ins Stocken kommt. Wenn ein Deadlock entdeckt wird, sucht der Server eine der Transaktionen aus (entweder willkürlich oder nach irgendwelchen Kriterien) und rollt sie zurück, damit die andere Transaktion weiterlaufen kann. Zumeist kann die abgebrochene Transaktion neu gestartet und erfolgreich zu Ende geführt werden, ohne dass es zu einer neuen Deadlock-Situation kommt.
Die Wahl des richtigen Speichermoduls Wenn Sie mit Oracle Database oder Microsoft SQL Server arbeiten, gibt es nur einen einzigen Codesatz für Datenbankoperationen der untersten Ebene, wie etwa die Abfrage einer bestimmten Tabellenzeile anhand eines Primärschlüsselwerts. Doch der MySQL-Server wurde so ausgelegt, dass mehrere verschiedene Speichermodule für die Datenbankfunktionalität der untersten Ebene zur Verfügung stehen, also auch für Sperren und Transaktionsmanagement. Seit Version 6.0 hat MySQL folgende Speichermodule zu bieten:
Max. Linie
MyISAM Ein Modul, das keine Transaktionen kennt und Tabellensperren verwendet. MEMORY Ein Modul, das keine Transaktionen kennt und für Tabellen verwendet wird, die im Arbeitsspeicher liegen. BDB Ein Modul, das Transaktionen kennt und Sperren auf Seitenebene verwendet. InnoDB Ein Modul, das Transaktionen kennt und Sperren auf Zeilenebene verwendet. Merge Ein spezielles Modul, das mehrere identische MyISAM-Tabellen wie eine einzige Tabelle aussehen lässt (Tabellenpartitionierung).
Max. Linie
– Fortsetzung –
Was ist eine Transaktion? | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
231
Links Maria Ein MyISAM-Ersatz, den es seit Version 6.0.6 gibt und der vollständige Wiederherstellungsfähigkeiten bietet. Falcon Ein neues (seit 6.0.4), leistungsstarkes Modul mit Transaktionsunterstützung, das Sperren auf Zeilenebene nutzt. Archive Ein spezielles Modul, das große Mengen nicht indizierter Daten speichern kann und hauptsächlich Archivierungszwecken dient. Nun könnte man denken, dass man eine dieser Lösungen für die gesamte Datenbank aussuchen muss, aber weit gefehlt: MySQL ist so flexibel, dass Sie das passende Speichermodul auf Tabellenbasis wählen können. Allerdings sollten Sie für alle Tabellen, die an Transaktionen beteiligt sind, das InnoDB- oder das Falcon-Speichermodul auswählen, da diese durch Zeilensperren und Versionierung von allen Lösungen die beste Nebenläufigkeit bieten. Sie können beim Anlegen einer Tabelle ein Speichermodul explizit angeben oder eine vorhandene Tabelle auf ein anderes Modul umstellen. Wenn Sie nicht wissen, welches Speichermodul der betreffenden Tabelle zugewiesen ist, können Sie den show table-Befehl wie folgt einsetzen: mysql> SHOW TABLE STATUS LIKE 'transaction' \G *************************** 1. row *************************** Name: transaction Engine: InnoDB Version: 10 Row_format: Compact Rows: 21 Avg_row_length: 780 Data_length: 16384 Max_data_length: 0 Index_length: 49152 Data_free: 0 Auto_increment: 22 Create_time: 2008-02-19 23:24:36 Update_time: NULL Check_time: NULL Collation: latin1_swedish_ci Checksum: NULL Create_options: Comment: 1 row in set (1.46 sec)
Max. Linie
Ein Blick auf die zweite Angabe verrät Ihnen, dass die Transaktionstabelle bereits das InnoDB-Modul verwendet. Wäre das nicht der Fall, könnten Sie der Transaktionstabelle mit folgendem Befehl das InnoDB-Modul zuweisen: ALTER TABLE Transaktion ENGINE = INNODB;
232 | Kapitel 12: Transaktionen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Anders als im oben beschriebenen zweiten Szenario meldet die Datenbank einen Fehler, der Sie darüber informiert, dass Ihre Transaktion wegen eines Deadlocks zurückgerollt wurde. MySQL würde beispielsweise die Fehlernummer 1213 melden, zu der folgender Text gehört: Message: Deadlock found when trying to get lock; try restarting transaction
Wie die Fehlermeldung schon sagt, ist es ganz vernünftig, eine Transaktion, die wegen eines Deadlocks zurückgerollt wurde, später noch einmal zu versuchen. Doch wenn Deadlocks gehäuft auftreten, müssen unter Umständen die Anwendungen, die auf den Datenbankserver zugreifen, geändert werden, um die Wahrscheinlichkeit von Deadlocks zu mindern. (Das wird oft dadurch gewährleistet, dass immer in der gleichen Reihenfolge auf Datenressourcen zugegriffen wird, indem beispielsweise immer zuerst die Kontendaten geändert und danach die Transaktionsdaten eingefügt werden.)
Savepoints In manchen Fällen kommt es zu Problemen, wenn mitten in einer Transaktion ein Rollback erforderlich wird, Sie aber nicht alle bisher getanen Arbeiten ungeschehen machen möchten. Für solche Situationen können Sie einen oder mehrere Savepoints in einer Transaktion definieren und verwenden, um die Transaktion eben nur bis zu diesem Punkt zurückzurollen und nicht vollständig bis zum Anfang. Da alle Savepoints einen Namen bekommen müssen, können Sie mehrere Savepoints in einer Transaktion definieren. Einen Savepoint namens my_savepoint legt man folgendermaßen an: SAVEPOINT my_savepoint;
Um die Transaktion bis zu einem bestimmten Savepoint zurückzurollen, geben Sie einfach den rollback-Befehl, gefolgt von den Schlüsselwörtern to savepoint und dem Namen des Savepoints: ROLLBACK TO SAVEPOINT my_savepoint;
Hier sehen Sie ein Anwendungsbeispiel für Savepoints: START TRANSACTION; UPDATE product SET date_retired = CURRENT_TIMESTAMP() WHERE product_cd = 'XYZ'; SAVEPOINT before_close_accounts;
Max. Linie
UPDATE account SET status = 'CLOSED', close_date = CURRENT_TIMESTAMP(), last_activity_date = CURRENT_TIMESTAMP() WHERE product_cd = 'XYZ';
Max. Linie
ROLLBACK TO SAVEPOINT before_close_accounts; COMMIT;
Was ist eine Transaktion? | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
233
Links Letztlich wird durch diese Transaktion das seltsame XYZ-Produkt zurückgezogen, aber keines der Konten geschlossen. Wenn Sie Savepoints verwenden, müssen Sie an Folgendes denken: • Trotz des Namens wird durch einen Savepoint nichts gespeichert. Wenn Sie Ihre Transaktion festschreiben möchten, müssen Sie am Ende immer ein commit absetzen. • Wenn Sie einen rollback vornehmen, ohne einen Savepoint zu benennen, werden alle Savepoints in der Transaktion ignoriert, und die gesamte Transaktion wird rückgängig gemacht. Wenn Sie SQL Server benutzen, erstellen Sie einen Savepoint mit dem proprietären Befehl save transaction und veranlassen mit rollback transaction den Rollback zu einem
Savepoint, wobei Sie jedes Mal den Savepoint-Namen hinter den Befehl setzen.
Testen Sie Ihr Wissen Testen Sie Ihr Verständnis von Transaktionen, indem Sie die folgende Übung durcharbeiten. Wenn Sie fertig sind, können Sie Ihre Lösung mit der in Anhang C vergleichen.
Übung 12-1 Öffnen Sie eine Transaktion, um $50 von Frank Tuckers Geldmarktkonto (Money Market) auf sein Girokonto (Checking) zu übertragen. Dazu müssen Sie zwei Zeilen in die Tabelle transaction einfügen und zwei Zeilen in der Tabelle account aktualisieren.
Max. Linie
Max. Linie 234 | Kapitel 12: Transaktionen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
First
Kapitel 13
KAPITEL 13
Indizes und Constraints
Da der Schwerpunkt dieses Buchs auf Programmiertechniken liegt, haben sich die ersten zwölf Kapitel mit denjenigen Elementen von SQL beschäftigt, die für mächtige select-, insert-, update- und delete-Anweisungen benötigt werden. Es gibt jedoch noch andere Datenbank-Features, die den Code indirekt beeinflussen. Dieses Kapitel konzentriert sich auf zwei dieser Features: Indizes und Constraints.
Indizes Wenn Sie eine Zeile in eine Tabelle einfügen, versucht der Datenbankserver nicht, diese Daten an einen bestimmten Ort in der Tabelle zu setzen. Schreiben Sie zum Beispiel eine neue Zeile in die department-Tabelle, setzt der Server sie nicht in numerischer Reihenfolge anhand der Werte der dept_id-Spalte oder in alphabetischer Reihenfolge anhand der Werte der name-Spalte zwischen die anderen Spalten, sondern platziert sie einfach an die nächste verfügbare freie Stelle (der Server pflegt für jede Tabelle eine Liste mit freiem Platz). Wenn Sie die department-Tabelle abfragen, muss der Server daher jede Zeile der Tabelle inspizieren, um die Frage zu beantworten. Nehmen wir zum Beispiel an, dass Sie folgende Abfrage haben: mysql> SELECT dept_id, name -> FROM department -> WHERE name LIKE 'A%'; +---------+----------------+ | dept_id | name | +---------+----------------+ | 3 | Administration | +---------+----------------+ 1 row in set (0.03 sec)
Max. Linie
Um alle Abteilungen zu finden, deren Name mit 'A' beginnt, muss der Server jede Zeile der department-Tabelle aufsuchen und den Inhalt der Spalte name betrachten. Fängt der Name mit 'A' an, wird die Zeile in die Ergebnismenge geladen. Diese Art des Zugriffs bezeichnet man als Table Scan (Tabellendurchsuchung).
| This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
235
Max. Linie
Links Diese Methode funktioniert zwar prächtig bei einer Tabelle mit nur drei Zeilen, aber stellen Sie sich vor, wie lange es dauern würde, diese Abfrage für eine Tabelle mit drei Millionen Zeilen zu beantworten. Irgendwo zwischen drei und drei Millionen Zeilen wird eine Grenze überschritten, ab der der Server die Frage ohne zusätzliche Hilfen nicht mehr in angemessener Zeit beantworten kann. Diese Hilfe bekommt er in der Form von einem oder mehreren Indizes auf der department-Tabelle. Selbst wenn Sie noch nie etwas von einem Datenbankindex gehört haben, wissen Sie natürlich, was ein Index ist (dieses Buch hat zum Beispiel auch einen). Ein Index ist einfach nur ein Mechanismus, um eine bestimmte Sache in einer Ressource wiederzufinden. Jede technische Publikation hat zum Beispiel hinten einen Index, um ein bestimmtes Wort oder einen Begriff in dieser Publikation wiederzufinden. Der Index listet diese Begriffe in alphabetischer Reihenfolge auf. So kann der Leser schnell einen bestimmten Buchstaben im Index nachschlagen, den gewünschten Eintrag suchen und dann die zugehörige Seite oder Textstelle aufschlagen, auf oder in der der Begriff vorkommt. Wie eine Person einen Index nutzen kann, um Begriffe aus einer Veröffentlichung wiederzufinden, kann ein Datenbankserver Indizes nutzen, um Zeilen in einer Tabelle wiederzufinden. Indizes sind spezielle Tabellen, die im Gegensatz zu normalen Datentabellen in einer bestimmten Ordnung gehalten werden. Ein Index enthält jedoch nicht alle Daten über eine Entität, sondern nur die Spalte(n), die benötigt werden, um Zeilen in der Datentabelle ausfindig zu machen, zusammen mit Informationen darüber, wo diese Zeilen physisch gespeichert sind. Indizes vereinfachen also den Abruf einer Teilmenge von Tabellenzeilen und -spalten, ohne dass dafür alle Zeilen der Tabelle durchgegangen werden müssen.
Indexerstellung Kehren wir noch einmal zu der department-Tabelle zurück: Vielleicht haben Sie beschlossen, die name-Spalte zu indizieren, damit Abfragen, die den Namen einer Abteilung ganz oder teilweise angeben, sowie update- oder delete-Operationen, in denen der Abteilungsname vorkommt, schneller vonstatten gehen. Um einer MySQL-Datenbank einen solchen Index hinzuzufügen, tun Sie Folgendes: mysql> ALTER TABLE department -> ADD INDEX dept_name_idx (name); Query OK, 3 rows affected (0.08 sec) Records: 3 Duplicates: 0 Warnings: 0
Max. Linie
Diese Anweisung erstellt einen Index (genauer gesagt, einen B-Baum-Index, aber dazu später mehr) auf der Spalte department.name. Der Index selbst bekommt den Namen dept_name_idx. Wenn dieser Index steht, kann die Abfrageoptimierung (siehe Kapitel 3) sich entscheiden, ihn zu benutzen, wenn es Vorteile bringt (wenn, wie in der departmentTabelle, nur drei Zeilen vorliegen, kann die Optimierung auch entscheiden, den Index zu ignorieren und die ganze Tabelle zu lesen). Sind mehrere Indizes auf einer Tabelle vorhanden, muss die Optimierung entscheiden, welcher für welche SQL-Anweisung am vorteilhaftesten ist.
236 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Da MySQL Indizes als optionale Komponenten einer Tabelle behandelt, müssen Sie den Befehl alter table geben, um einen Index anzulegen oder zu entfernen. Andere Datenbankserver, darunter SQL Server und Oracle Database, behandeln Indizes als selbstständige Schema-Objekte. Sowohl SQL Server als auch Oracle generieren daher den Index mit einem create index-Befehl, wie hier: CREATE INDEX dept_name_idx ON department (name);
Seit MySQL 5.0 gibt es die create index-Anweisung, die allerdings auf die alter table-Anweisung abgebildet ist.
Bei allen Datenbankservern können Sie die verfügbaren Indizes anschauen. MySQLBenutzer können mit dem show-Befehl alle Indizes auf einer konkreten Tabelle anzeigen lassen: mysql> SHOW INDEX FROM department \G *************************** 1. row *************************** Table: department Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: dept_id Collation: A Cardinality: 3 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: *************************** 2. row *************************** Table: department Non_unique: 0 Key_name: dept_name_uidx Seq_in_index: 1 Column_name: name Collation: A Cardinality: 3 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: 2 rows in set (0.02 sec)
Max. Linie
Die Ausgabe zeigt, dass es zwei Indizes in der department-Tabelle gibt: einen namens PRIMARY auf der dept_id-Spalte und einen mit dem Namen dept_name_idx auf der nameSpalte. Da ich bisher nur einen Index angelegt habe (dept_name_idx), fragen Sie sich nun vielleicht, woher der andere kommt: Als die department-Tabelle erstellt wurde, enthielt die create table-Anweisung einen Constraint, der die dept_id-Spalte als Primärschlüssel für die Tabelle definierte. Die Tabelle wurde mit folgender Anweisung angelegt:
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Indizes |
237
Max. Linie
Links CREATE TABLE department (dept_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(20) NOT NULL, CONSTRAINT pk_department PRIMARY KEY (dept_id) );
Bei der Erstellung der Tabelle hat der MySQL-Server automatisch einen Index auf der Primärschlüsselspalte generiert, also hier auf der Spalte dept_id, und diesen Index PRIMARY genannt. Constraints werden weiter unten in diesem Kapitel behandelt. Wenn Sie nach der Erstellung eines Index beschließen, dass dieser nicht nützlich ist, können Sie ihn folgendermaßen wieder löschen: mysql> ALTER TABLE department -> DROP INDEX dept_name_idx; Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0
SQL Server und Oracle Database verwenden zum Löschen eines Index den Befehl drop index auf folgende Weise: DROP INDEX dept_name_idx; (Oracle) DROP INDEX dept_name_idx ON department (SQL Server)
MySQL unterstützt jetzt auch eine drop index-Anweisung.
Eindeutige (unique) Indizes Beim Datenbankentwurf muss immer bedacht werden, welche Spalten Werte doppelt enthalten dürfen und welche nicht. So wäre es zum Beispiel zulässig, in der individualTabelle zwei Kunden mit dem Namen John Smith zu haben, da beide Zeilen einen anderen Identifier (cust_id), ein anderes Geburtsdatum und eine andere Steuernummer (customer.fed_id) haben und dadurch auseinander zu halten sind. Dagegen würden Sie jedoch nicht zwei Abteilungen gleichen Namens in der department-Tabelle erlauben. Die Regel, dass Abteilungsnamen nicht doppelt vorkommen dürfen, können Sie mit einem eindeutigen (unique) Index auf der Spalte department.name erzwingen. Ein eindeutiger Index spielt mehrere Rollen: Er hat alle Vorteile eines normalen Index, ist jedoch zugleich auch ein Mechanismus, um Doppelnennungen von Werten, sogenannte Duplikate, in der indizierten Spalte zu unterbinden. Wann immer eine Zeile eingefügt oder die indizierte Spalte geändert wird, prüft der Datenbankserver den eindeutigen Index, um zu erkennen, ob der betreffende Wert in einer anderen Zeile der Tabelle bereits vorhanden ist. Hier sehen Sie, wie ein eindeutiger Index auf der Spalte department.name angelegt wird:
Max. Linie
mysql> ALTER TABLE department -> ADD UNIQUE dept_name_idx (name); Query OK, 3 rows affected (0.04 sec) Records: 3 Duplicates: 0 Warnings: 0
238 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Bei SQL Server und Oracle Database muss bei der Indexerstellung lediglich das Schlüsselwort unique hinzugefügt werden: CREATE UNIQUE INDEX dept_name_idx ON department (name);
Wenn dieser Index vorhanden ist, erhalten Sie bei dem Versuch, eine zweite Abteilung namens 'Operations' einzufügen, eine Fehlermeldung: mysql> INSERT INTO department (dept_id, name) -> VALUES (999, 'Operations'); ERROR 1062 (23000): Duplicate entry 'Operations' for key 'dept_name_idx'
Erstellen Sie keine eindeutigen Indizes auf Primärschlüsselspalten, da der Server bei Primärschlüsselwerten ohnehin bereits die Eindeutigkeit prüft. Sie können jedoch durchaus mehrere eindeutige Indizes auf derselben Tabelle anlegen, wenn es Ihnen ratsam erscheint.
Mehrspalten-Indizes Zusätzlich zu den bisher gezeigten Einzelspalten-Indizes können Sie auch Indizes erstellen, die sich über mehrere Spalten erstrecken. Wenn Sie zum Beispiel Mitarbeiter nach Vor- und Nachnamen suchen, können Sie einen Index auf beiden Spalten anlegen: mysql> ALTER TABLE employee -> ADD INDEX emp_names_idx (lname, fname); Query OK, 18 rows affected (0.10 sec) Records: 18 Duplicates: 0 Warnings: 0
Dieser Index wird nützlich für Abfragen sein, die sowohl den Vor- als auch den Nachnamen oder einfach nur den Nachnamen suchen, aber nicht für Abfragen, die lediglich den Vornamen des Mitarbeiters nennen. Der Grund wird klar, wenn Sie überlegen, wie Sie die Telefonnummer einer Person suchen würden: Wenn Sie Vor- und Nachnamen dieser Person kennen, finden Sie die Nummer rasch im Telefonbuch, da dieses zuerst den Nachund dann den Vornamen aufführt. Kennen Sie jedoch nur den Vornamen des Gesuchten, müssten Sie jeden Eintrag des Telefonbuchs durchgehen, um alle Einträge mit diesem Vornamen ausfindig zu machen. Daher sollten Sie schon beim Aufbau eines Mehrspalten-Index genau überlegen, welche Spalte Sie als erste aufführen, welche als zweite usw., damit der Index später den größtmöglichen Nutzen bringt. Nichts hindert Sie daran, mehrere Indizes zu erstellen, die dieselben Spalten in unterschiedlicher Reihenfolge nennen, wenn Sie dies für notwendig halten, um eine angemessene Antwortzeit zu erzielen.
Indextypen
Max. Linie
Indizes sind ein mächtiges Werkzeug, doch da es so viele verschiedene Arten von Daten gibt, genügt nicht immer eine einzige Indexstrategie. Die folgenden Abschnitte zeigen, welche Arten von Indizes die verschiedenen Server bieten.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Indizes |
239
Max. Linie
Links B-Baum-Indizes Alle bisher gezeigten Indizes sind balancierte Bäume, besser bekannt unter dem Namen B-Baum-Indizes. Da MySQL, Oracle Database und SQL Server alle auf B-Baum-Indizes voreingestellt sind, wird immer ein B-Baum-Index generiert, wenn nicht explizit ein anderer Indextyp verlangt wird. Wie Sie sich denken können, sind B-Baum-Indizes als Suchbäume organisiert, wobei ein oder mehrere Ebenen von Zweigknoten zu einer einzigen Ebene von Blattknoten hinführen. Zweigknoten dienen der Navigation durch den Baum, während Blattknoten die eigentlichen Werte und ihren Platz speichern. Ein BBaum-Index auf der Spalte employee.lname könnte zum Beispiel wie in Abbildung 13-1 aussehen. A-M N-Z
A-C D-F G-I J-M
Barker Blake
Fleming Fowler
Gooding Grossman Hawthorne
N-P Q-S T-V W-Z
Jameson Markham Mason
Parker Portman
Roberts Smith
Tucker Tulman Tyler
Ziegler
Abbildung 13-1: Beispiel eines B-Baums
Würden Sie nun eine Abfrage starten, um alle Mitarbeiter zu suchen, deren Nachname mit 'G' anfängt, würde der Server den obersten Zweigknoten (den sogenannten Wurzelknoten) betrachten und dann der Verknüpfung zu dem Zweigknoten folgen, der Nachnamen mit den Anfangsbuchstaben A bis M behandelt. Dieser Zweigknoten würde seinerseits den Server weiter an den Blattknoten verweisen, der Nachnamen mit den Anfangsbuchstaben G bis I behandelt. Der Server würde dann beginnen, die Werte in dem Blattknoten zu lesen, bis er auf einen Wert stößt, der nicht mehr mit 'G' anfängt (in diesem Fall wäre das 'Hawthorne').
Max. Linie
Wenn Zeilen in der employee-Tabelle eingefügt, geändert oder gelöscht werden, versucht der Server, den Baum auszubalancieren, damit nicht auf der einen Seite des Wurzelknotens viel mehr Zweig- und Blattknoten stehen als auf der anderen. Der Server kann Zweige hinzufügen oder entfernen, um die Werte gleichmäßiger zu verteilen, und er kann sogar eine ganze Ebene von Zweigknoten neu einziehen oder tilgen. Dadurch, dass er den Baum ausbalanciert, kann der Server rasch die Blattknoten durchschauen,
240 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts um die gewünschten Werte zu finden, ohne viele Ebenen von Zweigknoten durchgehen zu müssen.
Bitmap-Indizes Nun eignen sich B-Baum-Indizes zwar hervorragend für Spalten, die viele verschiedene Werte enthalten (zum Beispiel Vor- und Nachnamen von Kunden), aber sie sind unhandlich für Spalten, die nur wenige Werte erlauben. Nehmen wir zum Beispiel an, Sie möchten einen Index auf der Spalte account.product_cd erstellen, um rasch alle Konten eines bestimmten Typs abfragen zu können (Girokonten, Sparkonten usw.). Da es nur acht verschiedene Produkte gibt und manche davon viel verbreiteter sind als andere, kann es schwierig werden, einen balancierten B-Baum-Index zu pflegen, wenn die Anzahl der Konten steigt. Für Spalten, die nur wenige verschiedene Werte, aber viele Zeilen enthalten (Daten mit geringer Kardinalität), ist eine andere Indexstrategie erforderlich. Um mit solchen Fällen effizienter umgehen zu können, bietet Oracle Database sogenannte Bitmap-Indizes, die für jeden in der Spalte gespeicherten Wert eine Bitmap erstellen. Abbildung 13-2 zeigt, wie ein solcher Bitmap-Index für die Daten in der Spalte account.product_cd aussehen würde.
Abbildung 13-2: Beispiel für eine Bitmap
Max. Linie
Der Index enthält sechs Bitmaps, nämlich für jeden Wert der Spalte product_cd eine (zwei der acht verfügbaren Produkte werden nicht genutzt). Jede Bitmap enthält für jede der 24 Zeilen der Kontentabelle einen Wert, entweder 0 oder 1. Wenn Sie also den Server alle Tagesgeldkonten (product_cd = 'MM') abrufen lassen, sucht er einfach alle 1-Werte in der MM-Bitmap und gibt die Zeilen 7, 10 und 18 zurück. Der Server kann auch Bitmaps kombinieren, wenn Sie mehrere Werte suchen: Falls Sie zum Beispiel alle Tagesgeldkonten und Sparkonten (product_cd = 'MM' oder product_cd = 'SAV') abfragen möchten, kann der Server eine OR-Operation auf den Bitmaps MM und SAV ausführen und gibt die Zeilen 2, 5, 7, 9, 10, 16 und 18 zurück.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Indizes |
241
Max. Linie
Links Bitmap-Indizes sind eine schöne, kompakte Indexlösung für Daten mit geringer Kardinalität, die jedoch zusammenbricht, wenn die Anzahl der Werte in der Spalte im Verhältnis zur Zeilenzahl zu hoch wird (dann haben wir es mit Daten hoher Kardinalität zu tun). In diesem Fall müsste der Server nämlich zu viele Bitmaps pflegen. Sie würden zum Beispiel niemals versuchen, einen Bitmap-Index auf einer Primärschlüsselspalte zu erstellen, da diese die größtmögliche Kardinalität hat (für jede Zeile einen unterschiedlichen Wert). Oracle-Benutzer generieren Bitmap-Indizes, indem sie einfach das Schlüsselwort bitmap in die create index-Anweisung schreiben: CREATE BITMAP INDEX acc_prod_idx ON account (product_cd);
Bitmap-Indizes trifft man oft in Data Warehouse-Umgebungen an, in denen große Mengen von Daten generell auf Spalten mit relativ wenigen unterschiedlichen Werten indiziert werden (z.B. Quartalsumsätze, geografische Gebiete, Produkte, Vertriebsleute).
Textindizes Wenn Ihre Datenbank Dokumente speichert, müssen Sie den Benutzern vielleicht die Möglichkeit geben, nach Wörtern oder Begriffen in diesen Dokumenten zu fahnden. Bestimmt möchten Sie nicht, dass der Server jedes Mal, wenn ein Suchbegriff eingegeben wird, jedes Dokument öffnet und dieses nach dem Begriff durchforstet. Doch die traditionellen Indexstrategien sind für diesen Fall ungeeignet. Für solche Situationen bieten MySQL, SQL Server und Oracle Database spezielle Index- und Suchmechanismen für Dokumente; SQL Server und MySQL kennen die sogenannten Volltextindizes (in MySQL stehen diese nur für das MyISAM-Speichermodul zur Verfügung), und Oracle Database bietet mächtige Tools unter dem Sammelbegriff Oracle Text. Das Durchsuchen von Dokumenten ist so speziell, dass ich Ihnen hier kein Beispiel zeige, aber Sie sollen immerhin wissen, welche Möglichkeiten zur Verfügung stehen.
Verwendung von Indizes Indizes werden generell vom Server genutzt, um Zeilen in einer bestimmten Tabelle rasch aufzufinden. Danach geht der Server zu der mit dem Index verbundenen Tabelle, um die zusätzlichen, vom Benutzer angeforderten Informationen herauszuholen. Betrachten Sie die folgende Abfrage:
Max. Linie
mysql> SELECT emp_id, fname, lname -> FROM employee -> WHERE emp_id IN (1, 3, 9, 15, 22); +--------+---------+----------+ | emp_id | fname | lname | +--------+---------+----------+ | 1 | Michael | Smith | | 3 | Robert | Tyler | | 9 | Jane | Grossman | | 15 | Frank | Portman | +--------+---------+----------+ 4 rows in set (0.00 sec)
242 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts In dieser Abfrage kann der Server den Primärschlüsselindex auf der Spalte emp_id zum Auffinden der Personalnummern 1, 3, 9 und 15 in der employee-Tabelle nutzen und dann aus diesen vier Zeilen die Vor- und Nachnamen holen. Wenn allerdings bereits der Index alles Notwendige enthält, um die Abfrage zu beantworten, muss die zugehörige Tabelle gar nicht erst aufgesucht werden. Zur Veranschaulichung wollen wir uns ansehen, wie die Abfrageoptimierung dieselbe Abfrage behandelt, wenn unterschiedliche Indizes verfügbar sind. Die Abfrage, in der Kontensalden bestimmter Kunden zusammengerechnet werden, sieht folgendermaßen aus: mysql> SELECT cust_id, SUM(avail_balance) tot_bal -> FROM account -> WHERE cust_id IN (1, 5, 9, 11) -> GROUP BY cust_id; +---------+----------+ | cust_id | tot_bal | +---------+----------+ | 1 | 4557.75 | | 5 | 2237.97 | | 9 | 10971.22 | | 11 | 9345.55 | +---------+----------+ 4 rows in set (0.00 sec)
Um zu sehen, wie die Abfrageoptimierung von MySQL diese Abfrage ausführt, frage ich den Server mit der explain-Anweisung nach dem Ausführungsplan, anstatt die Abfrage tatsächlich auszuführen: mysql> EXPLAIN SELECT cust_id, SUM(avail_balance) tot_bal -> FROM account -> WHERE cust_id IN (1, 5, 9, 11) -> GROUP BY cust_id \G *************************** 1. row *************************** id: 1 select_type: SIMPLE -Tabelle: account type: index possible_keys: fk_a_cust_id key: fk_a_cust_id key_len: 4 ref: NULL rows: 24 Extra: Using where 1 row in set (0.00 sec)
Max. Linie
Jeder Datenbankserver bietet Mittel und Wege, um nachzuschauen, wie die Abfrageoptimierung mit einer SQL-Anweisung umgeht. SQL Server zeigt Ihnen seinen Ausführungsplan, wenn Sie vor Ausführung der Abfrage den Befehl set showplan_text on erteilen. Oracle Database hat die explain plan-Anweisung, die den Ausführungsplan in eine Spezialtabelle namens plan_table schreibt.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Indizes |
Max. Linie 243
Links Im Großen und Ganzen sagt Ihnen dieser Ausführungsplan Folgendes: • Anhand des fk_a_cust_id-Index werden in der account-Tabelle Zeilen gesucht, die der where-Klausel entsprechen. • Der Server erwartet, nach dem Lesen des Index alle 24 Zeilen der account-Tabelle lesen zu müssen, um die verfügbaren Salden zu sammeln, da er nicht weiß, dass es außer den Kunden mit den Nummern 1, 5, 9 und 11 noch andere geben könnte. fk_a_cust_id ist ein weiterer automatisch vom Server angelegter Index, doch dieses Mal
handelt es sich um einen Fremdschlüssel-Constraint anstelle eines Primärschlüssel-Constraints (darüber später mehr). Da der fk_a_cust_id-Index auf der account.cust_id liegt, nutzt der Server ihn, um die Kundennummern 1, 5, 9 und 11 in der account-Tabelle zu finden, und schaut dann in die zugehörigen Zeilen, um die verfügbaren Salden abzufragen und zusammenzurechnen. Jetzt werde ich einen neuen Index namens acc_bal_idx auf den Spalten cust_id und avail_balance hinzufügen: mysql> ALTER TABLE account -> ADD INDEX acc_bal_idx (cust_id, avail_balance); Query OK, 24 rows affected (0.03 sec) Records: 24 Duplicates: 0 Warnings: 0
Und nun wollen wir einmal sehen, wie die Abfrageoptimierung dieselbe Abfrage angeht, wenn sie diesen Index zur Verfügung hat: mysql> EXPLAIN SELECT cust_id, SUM(avail_balance) tot_bal -> FROM account -> WHERE cust_id IN (1, 5, 9, 11) -> GROUP BY cust_id \G *************************** 1. row *************************** id: 1 select_type: SIMPLE -Tabelle: account type: range possible_keys: acc_bal_idx key: acc_bal_idx key_len: 4 ref: NULL rows: 8 Extra: Using where; Using index 1 row in set (0.01 sec)
Die beiden Ausführungspläne weisen folgende Unterschiede auf: • Die Optimierung nutzt den neuen Index acc_bal_idx anstelle des alten Index fk_a_ cust_id. • Sie rechnet damit, nur acht anstelle der 24 Zeilen durchsehen zu müssen.
Max. Linie
• Die account-Tabelle wird zur Ermittlung der Abfrageergebnisse gar nicht mehr benötigt (erkennbar an Using index in der Spalte Extra).
244 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Der Server kann also entweder mithilfe von Indizes Zeilen in der zugehörigen Tabelle lokalisieren, oder er kann den Index selbst verwenden, als sei er eine Tabelle, sofern der Index alle Spalten enthält, die für die Abfrage erforderlich sind. Das Vorgehen, das ich Ihnen gerade gezeigt habe, bezeichnet man als Abfrage-Tuning. Zum Tuning gehört, dass man sich eine SQL-Anweisung genau anschaut und erkennt, welche Ressourcen der Server zu ihrer Ausführung benötigt. Dabei kann man sowohl die SQL-Anweisung ändern als auch die Datenbankressourcen modifizieren, um eine Anweisung effizienter zu machen. Tuning ist ein umfangreiches Thema. Ich empfehle Ihnen dringend, entweder den Tuning-Guide Ihres Servers oder ein gutes Buch zum Thema zu lesen, um alle für Ihren Server verfügbaren Tuning-Möglichkeiten kennenzulernen.
Der Nachteil von Indizes Wenn Indizes so toll sind, warum dann nicht einfach alles indizieren? Nun, bitte denken Sie daran, dass jeder Index auch seinerseits wieder eine Tabelle ist (eine besondere Art von Tabelle, aber eben doch eine Tabelle). Jedes Mal, wenn eine Zeile einer Datentabelle hinzugefügt oder gelöscht wird, müssen alle Indizes auf dieser Tabelle modifiziert werden. Wird eine Zeile geändert, müssen alle Indizes der betroffenen Spalte(n) ebenfalls geändert werden. Je mehr Indizes Sie also haben, umso mehr Arbeit muss der Server leisten, um alle Schema-Objekte auf dem Laufenden zu halten, und das führt in der Tendenz dazu, dass alles langsamer geht. Indizes brauchen auch Speicherplatz und etwas Pflege von Seiten der Administratoren. Daher sollten Sie einen Index nur dann hinzufügen, wenn seine Notwendigkeit klar erkennbar ist. Wenn Sie nur einen Index für spezielle Zwecke haben, wie etwa eine monatliche Wartungsroutine, können Sie den Index anlegen, die Routine laufen lassen und dann den Index bis zum nächsten Mal wieder löschen. In Data Warehouses sind Indizes während der Geschäftszeiten von zentraler Bedeutung, wenn viele Benutzer Reports und Ad-hoc-Abfragen laufen lassen, aber sie sind problematisch, wenn Daten über Nacht in das Warehouse geladen werden. Daher ist es in solchen Umgebungen üblich, die Indizes vor dem Laden der Daten zu löschen und dann vor der Eröffnung des Warehouses wieder neu zu erstellen. In der Regel sollten Sie anstreben, weder zu wenige noch zu viele Indizes zu haben. Wenn Sie nicht sicher sind, wie viele Indizes Sie haben sollten, können Sie nach folgender Strategie vorgehen:
Max. Linie
• Sorgen Sie dafür, dass alle Primärschlüsselspalten indiziert sind (die meisten Server legen automatisch eindeutige Indizes an, wenn Sie einen Primärschlüssel-Constraint erstellen). Bei mehrspaltigen Primärschlüsseln sollten Sie erwägen, zusätzliche Indizes auf einer Untermenge der Primärschlüsselspalten oder allen Primärschlüsselspalten, aber in anderer Reihenfolge als in der Definition des Primärschlüssel-Constraints, zu erstellen.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Indizes |
245
Max. Linie
Links • Legen Sie Indizes auf allen Spalten an, die in Fremdschlüssel-Constraints referenziert werden. Denken Sie daran, dass der Server prüft, ob es keine Kindzeilen mehr gibt, wenn eine Elternzeile gelöscht wird, er also eine Abfrage absetzen muss, die nach einem bestimmten Wert in einer Spalte sucht. Gibt es auf der Spalte keinen Index, muss die gesamte Tabelle durchsucht werden. • Indizieren Sie alle Spalten, die häufig zum Abrufen von Daten verwendet werden. Die meisten Datumsspalten sind gute Kandidaten, ebenso kurze String-Spalten (3 bis 50 Zeichen). Nachdem Sie Ihren anfänglichen Satz von Indizes erstellt haben, sollten Sie versuchen, tatsächliche Abfragen auf Ihren Tabellen nachzuvollziehen und Ihre Indizierungsstrategie gegebenenfalls so anpassen, dass sie den Zugriffspfaden entspricht, die am häufigsten verwendet werden.
Constraints Ein Constraint ist einfach eine Einschränkung, die für eine oder mehrere Spalten einer Tabelle gilt. Es gibt mehrere Typen von Constraints: Primärschlüssel-Constraints Kennzeichnen die Spalte(n), die in einer Tabelle die Eindeutigkeit gewährleisten. Fremdschlüssel-Constraints Sorgen dafür, dass eine oder mehrere Spalten nur Werte enthalten, die aus den Primärschlüsselspalten einer anderen Tabelle stammen. Können auch die zulässigen Werte in anderen Tabellen einschränken, wenn update cascade- oder delete cascadeRegeln eingerichtet werden. Unique-Constraints Schränken eine oder mehrere Spalten dergestalt ein, dass sie innerhalb einer Tabelle nur eindeutige Werte enthalten dürfen (Primärschlüssel-Constraints sind eine besondere Art von Unique-Constraints). Check-Constraints Schränken die zulässigen Werte für eine Spalte ein. Ohne Constraints ist die Konsistenz einer Datenbank in Gefahr. Wenn der Server zum Beispiel erlaubt, eine Kundennummer in der customer-Tabelle zu ändern, ohne sie gleichzeitig auch in der account-Tabelle zu ändern, haben Sie am Ende Konten, zu denen es keine gültigen Kundendatensätze mehr gibt (sogenannte verwaiste Zeilen). Haben Sie jedoch Primär- und Fremdschlüssel-Constraints eingerichtet, meldet der Server entweder einen Fehler, wenn Sie versuchen, Daten, die von anderen Tabellen referenziert werden, zu ändern oder zu löschen, oder er reicht diese Änderungen für Sie an die betreffenden anderen Tabellen weiter (darüber später mehr).
Max. Linie
Max. Linie 246 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Rechts Wenn Sie Fremdschlüssel-Constraints auf dem MySQL-Server einsetzen möchten, müssen Sie das InnoDB-Speichermodul für Ihre Tabellen verwenden. Vom Falcon-Modul werden Fremdschlüssel-Constraints in Version 6.0.4 noch nicht unterstützt. In späteren Versionen sollen sie unterstützt werden.
Constraints anlegen Constraints werden normalerweise gleichzeitig mit der zugehörigen Tabelle in der create table-Anweisung definiert. Zur Veranschaulichung sehen Sie hier eine Anweisung aus dem Skript, das das Schema der Musterdatenbank dieses Buchs generiert: CREATE TABLE product (product_cd VARCHAR(10) NOT NULL, name VARCHAR(50) NOT NULL, product_type_cd VARCHAR (10) NOT NULL, date_offered DATE, date_retired DATE, CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) REFERENCES product_type (product_type_cd), CONSTRAINT pk_product PRIMARY KEY (product_cd) );
Die product-Tabelle hat zwei Constraints: Einer besagt, dass die Spalte product_cd als Primärschlüssel für die Tabelle dient, und der andere, dass die Spalte product_type_cd ein Fremdschlüssel zur product_type-Tabelle ist. Alternativ kann die product-Tabelle auch ohne Constraints angelegt und die Primär- und Fremdschlüssel-Constraints können später mit alter table-Anweisungen hinzugefügt werden: ALTER TABLE product ADD CONSTRAINT pk_product PRIMARY KEY (product_cd); ALTER TABLE product ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) REFERENCES product_type (product_type_cd);
Wenn Sie einen Primär- oder Fremdschlüssel-Constraint entfernen möchten, verwenden Sie wieder die alter table-Anweisung, geben aber drop anstelle von add an: ALTER TABLE product DROP PRIMARY KEY; ALTER TABLE product DROP FOREIGN KEY fk_product_type_cd;
Max. Linie
Ein Primärschlüssel-Constraint wird zwar normalerweise nicht gelöscht, aber Fremdschlüssel-Constraints werden durchaus bei bestimmten Wartungsoperationen schon einmal gelöscht und danach wieder hergestellt.
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Constraints |
247
Max. Linie
Links Constraints und Indizes Sie haben es bereits weiter oben in diesem Kapitel gelesen: Zusammen mit einem Constraint wird manchmal auch automatisch ein Index angelegt. Doch die Datenbankserver behandeln die Beziehung zwischen Constraints und Indizes durchaus unterschiedlich. Tabelle 13-1 zeigt, wie MySQL, SQL Server und Oracle Database diese Beziehung handhaben. Tabelle 13-1: Erstellung von Constraints Constraint-Typ
MySQL
SQL Server
Oracle Database
Primärschlüssel-Constraints
Generiert eindeutigen Index.
Generiert eindeutigen Index.
Nutzt vorhandenen oder erstellt neuen Index.
Fremdschlüssel-Constraints
Generiert Index.
Generiert keinen Index.
Generiert keinen Index.
Unique-Constraints
Generiert eindeutigen Index.
Generiert eindeutigen Index.
Nutzt vorhandenen oder erstellt neuen Index.
MySQL generiert also einen neuen Index, um Primärschlüssel-, Fremdschlüssel- und Unique-Constraints durchzusetzen, während SQL Server einen neuen Index zwar für Primärschlüssel- und Unique-Constraints, aber nicht für Fremdschlüssel-Constraints generiert und Oracle Database wie SQL Server vorgeht, aber einen vorhandenen Index (wenn ein passender existiert) nutzt, um Primärschlüssel- und Unique-Constraints zu erzwingen. SQL Server und Oracle Database generieren zwar beide keinen Fremdschlüssel-Constraint, aber in der Dokumentation beider Server wird dazu geraten, Indizes für jeden Fremdschlüssel anzulegen.
Kaskadierende Constraints Wenn Fremdschlüssel-Constraints vorhanden sind, meldet der Server einen Fehler, sobald ein Benutzer versucht, eine Zeile derart einzufügen oder zu ändern, dass die Fremdschlüsselspalte in der Elterntabelle keinen passenden Wert mehr finden kann. Zur Veranschaulichung sehen Sie hier die Daten der Tabellen product und product_type: mysql> SELECT product_type_cd, name -> FROM product_type; +-----------------+-------------------------------+ | product_type_cd | name | +-----------------+-------------------------------+ | ACCOUNT | Customer Accounts | | INSURANCE | Insurance Offerings | | LOAN | Individual und Business Loans | +-----------------+-------------------------------+ 3 rows in set (0.00 sec)
Max. Linie
mysql> SELECT product_type_cd, product_cd, name -> FROM product -> ORDER BY product_type_cd;
248 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts +-----------------+------------+-------------------------+ | product_type_cd | product_cd | name | +-----------------+------------+-------------------------+ | ACCOUNT | CD | certificate of deposit | | ACCOUNT | CHK | checking account | | ACCOUNT | MM | money market account | | ACCOUNT | SAV | savings account | | LOAN | AUT | auto loan | | LOAN | BUS | business line of credit | | LOAN | MRT | home mortgage | | LOAN | SBL | small business loan | +-----------------+------------+-------------------------+ 8 rows in set (0.01 sec)
Es gibt drei verschiedene Werte für die Spalte product_type_cd in der product_typeTabelle (ACCOUNT, INSURANCE und LOAN). Von diesen drei Werten werden zwei (ACCOUNT und LOAN) in der Spalte product_type_cd der product-Tabelle referenziert. Die folgende Anweisung versucht, die Spalte product_type_cd in der product-Tabelle auf einen Wert zu setzen, der in der product_type-Tabelle nicht existiert: mysql> UPDATE product -> SET product_type_cd = 'XYZ' -> WHERE product_type_cd = 'LOAN'; ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails ('bank'.'product', CONSTRAINT 'fk_product_type_cd' FOREIGN KEY ('product_type_cd') REFERENCES 'product_type' ('product_type_cd'))
Wegen des Fremdschlüssel-Constraints auf der Spalte product.product_type_cd erlaubt der Server diese Änderung nicht, weil er in der product_type-Tabelle keine Zeile mit dem Wert XYZ in der product_type_cd-Spalte findet. Der Fremdschlüssel-Constraint unterbindet also eine Änderung einer Kindzeile, wenn in der Elterntabelle kein entsprechender Wert zu finden ist. Doch was geschieht, wenn wir versuchen, die parent-Zeile in der product_type-Tabelle in XYZ zu ändern? Die folgende update-Anweisung versucht, den Produkttyp LOAN auf XYZ umzustellen: mysql> UPDATE product_type -> SET product_type_cd = 'XYZ' -> WHERE product_type_cd = 'LOAN'; ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails ('bank'.'product', CONSTRAINT 'fk_product_type_cd' FOREIGN KEY ('product_type_cd') REFERENCES 'product_type' ('product_type_cd'))
Max. Linie
Wieder wird ein Fehler verursacht – dieses Mal, weil in der product-Tabelle keine Kindzeilen stehen, deren product_type_cd-Spalte den Wert LOAN aufweist. Dies ist das Standardverhalten von Fremdschlüssel-Constraints, aber nicht das einzig mögliche: Sie können den Server auch veranlassen, die Änderung für alle Kindzeilen zu übernehmen und dadurch die Integrität der Daten zu bewahren. Dieses sogenannte kaskadierende Update ist eine Variation des Fremdschlüssel-Constraints. Sie können es installieren, indem Sie
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Constraints |
249
Max. Linie
Links den vorhandenen Fremdschlüssel entfernen und einen neuen hinzufügen, der die on update cascade-Klausel enthält: mysql> ALTER TABLE product -> DROP FOREIGN KEY fk_product_type_cd; Query OK, 8 rows affected (0.02 sec) Records: 8 Duplicates: 0 Warnings: 0 mysql> ALTER TABLE product -> ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) -> REFERENCES product_type (product_type_cd) -> ON UPDATE CASCADE; Query OK, 8 rows affected (0.03 sec) Records: 8 Duplicates: 0 Warnings: 0
Mit diesem abgeänderten Constraint verhält sich die obige update-Anweisung etwas anders: mysql> UPDATE product_type -> SET product_type_cd = 'XYZ' -> WHERE product_type_cd = 'LOAN'; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
Dieses Mal hat die Anweisung Erfolg. Um zu schauen, wie die Änderung an die productTabelle weitergereicht wurde, sehen Sie hier noch einmal die Daten beider Tabellen: mysql> SELECT product_type_cd, name -> FROM product_type; +-----------------+-------------------------------+ | product_type_cd | name | +-----------------+-------------------------------+ | ACCOUNT | Customer Accounts | | INSURANCE | Insurance Offerings | | XYZ | Individual und Business Loans | +-----------------+-------------------------------+ 3 rows in set (0.02 sec)
Max. Linie
mysql> SELECT product_type_cd, product_cd, name -> FROM product -> ORDER BY product_type_cd; +-----------------+------------+-------------------------+ | product_type_cd | product_cd | name | +-----------------+------------+-------------------------+ | ACCOUNT | CD | certificate of deposit | | ACCOUNT | CHK | checking account | | ACCOUNT | MM | money market account | | ACCOUNT | SAV | savings account | | XYZ | AUT | auto loan | | XYZ | BUS | business line of credit | | XYZ | MRT | home mortgage | | XYZ | SBL | small business loan | +-----------------+------------+-------------------------+ 8 rows in set (0.01 sec)
250 | Kapitel 13: Indizes und Constraints This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Es ist zu erkennen, dass die Änderung an der product_type-Tabelle sich auch in der product-Tabelle niederschlägt. Doch neben kaskadierenden Updates können auch kaskadierende Deletes verlangt werden. Ein kaskadierendes Delete löscht Zeilen, die aus einer Elterntabelle entfernt wurden, automatisch auch in der Kindtabelle. Ein kaskadierendes Delete wird mit der on delete cascade-Klausel formuliert: ALTER TABLE product ADD CONSTRAINT fk_product_type_cd FOREIGN KEY (product_type_cd) REFERENCES product_type (product_type_cd) ON UPDATE CASCADE ON DELETE CASCADE;
Ist diese Version des Constraints eingerichtet, ändert der Server Kindzeilen der productTabelle, wenn eine Zeile der product_type-Tabelle geändert wird, und löscht Kindzeilen der product-Tabelle, wenn eine Zeile in der product_type-Tabelle gelöscht wird. Kaskadierende Constraints stellen einen Fall dar, in dem Constraints direkt auf den Code Einfluss nehmen, den Sie schreiben. Sie müssen wissen, welche Constraints in Ihrer Datenbank kaskadierende Updates und/oder Deletes sind, um die Auswirkungen Ihrer updateund delete-Anweisungen genau einschätzen zu können.
Testen Sie Ihr Wissen Arbeiten Sie die folgenden Übungen durch, um Ihr Wissen zu Indizes und Constraints zu testen. Wenn Sie fertig sind, vergleichen Sie Ihre Lösungen mit denen in Anhang C.
Übung 13-1 Ändern Sie die Tabelle account so, dass Kunden nur noch ein Konto für ein bestimmtes Produkt haben können.
Übung 13-2 Erstellen Sie einen mehrspaltigen Index auf der Tabelle transaction, der in den folgenden Abfragen genutzt werden könnte: SELECT txn_date, account_id, txn_type_cd, amount FROM transaction WHERE txn_date > cast('2008-12-31 23:59:59' as datetime);
Max. Linie
SELECT txn_date, account_id, txn_type_cd, amount FROM transaction WHERE txn_date > cast('2008-12-31 23:59:59' as datetime) AND amount < 1000;
Testen Sie Ihr Wissen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 251
First Kapitel 14
KAPITEL 14
Views
Sauber entworfene Anwendungen bieten in der Regel eine öffentliche Schnittstelle, verbergen die Details der Implementierung jedoch und halten sich so zukünftige Änderungen am Entwurf offen, die keine Auswirkungen auf die Endbenutzer haben. Beim Entwurf Ihrer Datenbank können Sie Ähnliches erreichen, indem Sie Ihre Tabellen privat halten und dem Benutzer den Zugriff auf die Daten nur über einen Satz von Views gestatten. Dieses Kapitel versucht zu definieren, was Views sind, wie sie erstellt werden und wann und wie Sie sie einsetzen sollten.
Was sind Views? Eine View ist nichts anderes als ein Mechanismus zur Abfrage von Daten. Anders als Tabellen haben Views nichts mit der Speicherung von Daten zu tun. Sie müssen sich also keine Gedanken darüber machen, dass Views Ihnen Festplattenplatz rauben. Eine View erstellen Sie, indem Sie einer select-Anweisung einen Namen zuweisen und die Abfrage dann speichern, damit andere sie nutzen können. Andere Benutzer können Ihre View dann einsetzen, um auf die Daten zuzugreifen, als würden sie die Tabellen direkt abfragen (sie müssen nicht einmal wissen, dass sie mit einer View arbeiten). Betrachten wir ein einfaches Beispiel: Nehmen wir an, Sie möchten die gesetzlichen Identifikationsnummern (Sozialversicherungsnummern und Unternehmensidentitäten) in der Tabelle customer teilweise verdecken. Beispielsweise benötigt der Kundendienst eventuell die Möglichkeit, auf den letzten Teil einer Identifikationsnummer zuzugreifen, um die Identität eines Anrufers zu prüfen. Aber würde man die vollständige Nummer öffentlich machen, würde das die Datenschutzrichtlinien des Unternehmens verletzen. Anstatt direkten Zugriff auf die Tabelle customer zu gestatten, definieren Sie deswegen eine View namens customer_vw und verlangen, dass diese vom gesamten Bankpersonal genutzt wird, um auf Kundendaten zuzugreifen. Hier ist die View-Definition:
Max. Linie
CREATE VIEW customer_vw (cust_id, fed_id,
|
253
Max. Linie
Links cust_type_cd, address, city, state, zipcode ) AS SELECT cust_id, concat('ends in ', substr(fed_id, 8, 4)) fed_id, cust_type_cd, address, city, state, postal_code FROM customer;
Der erste Teil der Anweisung führt die Spaltennamen der View auf, die nicht mit denen der zugrunde liegenden Tabelle identisch sein müssen (die View customer_vw hat beispielsweise eine Spalte namens zipcode, die der customer.postal_code-Spalte zugeordnet ist). Der zweite Teil der Anweisung ist eine select-Abfrage, die einen Ausdruck für alle Spalten der View enthalten muss. Wird die create view-Anweisung ausgeführt, speichert der Datenbankserver die ViewDefinition, damit sie später verwendet werden kann. Die Abfrage wird nicht ausgeführt, und es werden keine Daten abgerufen oder gespeichert. Sobald die View erstellt ist, können Benutzer sie genauso abfragen wie eine gewöhnliche Tabelle: mysql> SELECT cust_id, fed_id, cust_type_cd -> FROM customer_vw; +---------+--------------+--------------+ | cust_id | fed_id | cust_type_cd | +---------+--------------+--------------+ | 1 | ends in 1111 | I | | 2 | ends in 2222 | I | | 3 | ends in 3333 | I | | 4 | ends in 4444 | I | | 5 | ends in 5555 | I | | 6 | ends in 6666 | I | | 7 | ends in 7777 | I | | 8 | ends in 8888 | I | | 9 | ends in 9999 | I | | 10 | ends in 111 | B | | 11 | ends in 222 | B | | 12 | ends in 333 | B | | 13 | ends in 444 | B | +---------+--------------+--------------+ 13 rows in set (0.02 sec)
Max. Linie
Die Abfrage, die der Server tatsächlich ausführt, ist weder die, die der Benutzer eingereicht hat, noch die, die mit der View-Definition verknüpft ist. Stattdessen verschmilzt der Server beides zu einer neuen Abfrage, die in diesem Fall folgende Form hat:
254 | Kapitel 14: Views
Max. Linie
Rechts SELECT cust_id, concat('ends in ', substr(fed_id, 8, 4)) fed_id, cust_type_cd FROM customer;
Obwohl die Definition der View customer_vw sieben Spalten der Tabelle customer umfasst, ruft die vom Server ausgeführte Abfrage nur drei dieser sieben Spalten ab. Wie Sie später in diesem Kapitel sehen werden, ist das ein wichtiger Unterschied, wenn einige der Spalten in Ihrer View an Funktionen oder Unterabfragen gebunden sind. Aus Perspektive des Benutzers gleicht eine View einer Tabelle vollkommen. Wenn Sie wissen wollen, welche Spalten in einer View verfügbar sind, nutzen Sie MySQLs (oder Oracles) describe-Befehl, um sie zu untersuchen: mysql> describe customer_vw; +--------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+-------+ | cust_id | int(10) unsigned | NO | | 0 | | | fed_id | varchar(12) | YES | | NULL | | | cust_type_cd | enum('I','B') | NO | | NULL | | | address | varchar(30) | YES | | NULL | | | city | varchar(20) | YES | | NULL | | | state | varchar(20) | YES | | NULL | | | postal_code | varchar(10) | YES | | NULL | | +--------------+------------------+------+-----+---------+-------+ 7 rows in set (1.40 sec)
Bei der Abfrage einer View können Sie alle Klauseln der select-Anweisung einsetzen, GROUP BY, HAVING und ORDER BY eingeschlossen. Hier ist ein Beispiel: mysql> SELECT cust_type_cd, count(*) -> FROM customer_vw -> WHERE state = 'MA' -> GROUP BY cust_type_cd -> ORDER BY 1; +--------------+----------+ | cust_type_cd | count(*) | +--------------+----------+ | I | 7 | | B | 2 | +--------------+----------+ 2 rows in set (0.22 sec)
Außerdem können Sie Views in einer Abfrage mit anderen Tabellen (oder anderen Views) verknüpfen:
Max. Linie
mysql> SELECT cst.cust_id, cst.fed_id, bus.name -> FROM customer_vw cst INNER JOIN business bus -> ON cst.cust_id = bus.cust_id; +---------+-------------+------------------------+ | cust_id | fed_id | name | +---------+-------------+------------------------+ | 10 | ends in 111 | Chilton Engineering |
Max. Linie Was sind Views? |
255
Links | 11 | ends in 222 | Northeast Cooling Inc. | | 12 | ends in 333 | Superior Auto Body | | 13 | ends in 444 | AAA Insurance Inc. | +---------+-------------+------------------------+ 4 rows in set (0.24 sec)
Diese Abfrage verknüpft die customer_vw-View mit der business-Tabelle, um nur Geschäftskunden abzurufen.
Warum Views verwenden? Im vorangegangenen Abschnitt habe ich eine einfache View vorgeführt, deren einziger Zweck war, den Inhalt der Spalte customer.fed_id zu verbergen. Gerade zu diesem Zweck werden Views zwar häufig eingesetzt, dennoch gibt es zahlreiche weitere Gründe für den Einsatz von Views, wie ich in den folgenden Unterabschnitten zeigen werde.
Datensicherheit Wenn Sie eine Tabelle erstellen und Benutzern gestatten, sie abzufragen, können diese auf jede Spalte und Zeile in der Tabelle zugreifen. Wie ich zuvor angemerkt habe, kann es allerdings sein, dass Ihre Tabelle einige Spalten mit schützenswerten Daten wie Identifikations- oder Kreditkartennummern enthält. Es ist nicht nur nicht ratsam, derartige Daten für alle Benutzer zugreifbar zu machen, es könnte auch die Datenschutzrichtlinien eines Unternehmens oder sogar Gesetze verletzen. In solchen Situationen ist es am besten, wenn man die Tabelle privat hält (d.h. keinem Benutzer eine select-Berechtigung für sie gibt) und dann einen oder mehrere Views erstellt, die Spalten mit schützenswerten Daten entweder nicht einschließen oder unkenntlich machen (wie bei dem 'ends in ####'-Verfahren, das wir für die customer_vw. fed_id-Spalte gewählt haben). Sie können auch beschränken, auf welche Zeilen bestimmte Benutzer zugreifen dürfen, indem Sie in Ihrer View-Definition eine where-Klausel einsetzen. Die folgende View-Definition sorgt beispielsweise dafür, dass nur Geschäftskunden abgerufen werden können:
Max. Linie
CREATE VIEW business_customer_vw (cust_id, fed_id, cust_type_cd, address, city, state, zipcode ) AS SELECT cust_id, concat('ends in ', substr(fed_id, 8, 4)) fed_id, cust_type_cd, address,
256 | Kapitel 14: Views
Max. Linie
Rechts city, state, postal_code FROM customer WHERE cust_type_cd = 'B'
Stellen Sie diese View der Geschäftskundenabteilung zur Verfügung, kann sie nur auf Geschäftskonten zugreifen, da die where-Klausel der View immer in ihre Abfragen eingebaut wird. Oracle Database-Nutzer haben eine andere Möglichkeit, Zeilen und Spalten einer Tabelle zu schützen: Virtual Private Database (VPD). VPD ermöglicht Ihnen, Ihre Tabellen mit Richtlinien auszustatten, die dafür sorgen, dass der Server die Abfragen von Benutzern so modifiziert, dass die Richtlinien eingehalten werden. Wenn Sie beispielsweise eine Richtlinie einrichten, die festlegt, dass Personal aus der Geschäftskundenabteilung nur Geschäftskonten sehen kann, wird allen Abfragen des entsprechenden Personals auf der Tabelle customer die Bedingung cust_ type_cd = 'B' hinzugefügt.
Datenaggregation Berichtsanwendungen benötigen in der Regel zusammengestellte Daten. Views sind ein wunderbares Mittel, es so erscheinen zu lassen, als wären diese Daten im Voraus zusammengestellt und gemeinsam in der Datenbank gespeichert worden. Nehmen wir beispielsweise an, dass eine Anwendung monatlich einen Bericht generiert, der für alle Kunden aufführt, wie viele Konten sie haben und wie hoch die gesamten Einlagen sind. Anstatt Anwendungsentwicklern zu gestatten, Abfragen auf den Grundtabellen durchzuführen, könnten Sie ihnen die folgende View zur Verfügung stellen.
Max. Linie
CREATE VIEW customer_totals_vw (cust_id, cust_type_cd, cust_name, num_accounts, tot_deposits ) AS SELECT cst.cust_id, cst.cust_type_cd, CASE WHEN cst.cust_type_cd = 'B' THEN (SELECT bus.name FROM business bus WHERE bus.cust_id = cst.cust_id) ELSE (SELECT concat(ind.fname, ' ', ind.lname) FROM individual ind WHERE ind.cust_id = cst.cust_id) END cust_name, sum(CASE WHEN act.status = 'ACTIVE' THEN 1 ELSE 0 END) tot_active_accounts, sum(CASE WHEN act.status = 'ACTIVE' THEN act.avail_balance ELSE 0 END) tot_balance FROM customer cst INNER JOIN account act ON act.cust_id = cst.cust_id GROUP BY cst.cust_id, cst.cust_type_cd;
Warum Views verwenden? |
Max. Linie 257
Links Dieses Verfahren bietet Ihnen als Datenbankdesigner eine Menge Flexibilität. Sollten Sie irgendwann entscheiden, dass sich die Abfrageleistung erheblich verbessern könnte, wenn die Daten tatsächlich vorab in einer Tabelle zusammengestellt und nicht über eine View zusammmengezogen würden, könnten Sie eine customer_totals-Tabelle erstellen und die View customer_totals_vw so ändern, dass die Daten aus dieser Tabelle abgerufen werden. Bevor Sie die View-Definition ändern, können Sie sie einsetzen, um die neue Tabelle zu füllen. Hier sind die für dieses Szenario erforderlichen SQL-Anweisungen: mysql> CREATE TABLE customer_totals -> AS -> SELECT * FROM customer_totals_vw; Query OK, 13 rows affected (3.33 sec) Records: 13 Duplicates: 0 Warnings: 0 mysql> CREATE OR REPLACE VIEW customer_totals_vw -> (cust_id, -> cust_type_cd, -> cust_name, -> num_accounts, -> tot_deposits -> ) -> AS -> SELECT cust_id, cust_type_cd, cust_name, -> FROM customer_totals; Query OK, 0 rows affected (0.02 sec)
num_accounts,
tot_deposits
Ab diesem Zeitpunkt ziehen alle Abfragen, die die View customer_totals_vw nutzen, ihre Daten aus der neuen Tabelle customer_totals. Das führt dazu, dass alle Benutzer eine Leistungsverbesserung erfahren, ohne dass sie dazu ihre eigenen Abfragen ändern müssen.
Komplexität verbergen Einer der häufigsten Gründe für den Einsatz von Views ist der Schutz der Endbenutzer vor Komplexität. Nehmen wir beispielsweise an, monatlich würde ein Bericht erstellt, der die Anzahl an Angestellten, die Gesamtzahl aktiver Konten und die Gesamtzahl an Transaktionen für alle Zweigstellen aufführt. Statt zu erwarten, dass der Autor des Berichts die vier Tabellen ansteuert, die er zum Sammeln der erforderlichen Daten benötigt, könnten Sie eine View anbieten, der folgendermaßen aussieht:
Max. Linie
CREATE VIEW branch_activity_vw (branch_name, city, state, num_employees, num_active_accounts, tot_transactions ) AS SELECT br.name, br.city, br.state,
258 | Kapitel 14: Views
Max. Linie
Rechts (SELECT count(*) FROM employee emp WHERE emp.assigned_branch_id = br.branch_id) num_emps, (SELECT count(*) FROM account acnt WHERE acnt.status = 'ACTIVE' AND acnt.open_branch_id = br.branch_id) num_accounts, (SELECT count(*) FROM transaction txn WHERE txn.execution_branch_id = br.branch_id) num_txns FROM branch br;
Diese View-Definition ist interessant, weil drei der sechs Spaltenwerte über skalare Unterabfragen generiert werden. Setzt jemand diese View ein, ohne dabei auf die Spalte num_ employees, num_active_accounts oder tot_transactions zuzugreifen, wird keine der Unterabfragen ausgeführt.
Partitionierte Daten verknüpfen Manche Datenbankentwürfe teilen große Tabellen in mehrere Teile, um die Leistung zu verbessern. Würde die Tabelle transaction zu groß werden, könnte sich der Designer beispielsweise entscheiden, sie in zwei Tabellen aufzuspalten: transaction_current mit den Daten der letzten sechs Monate und transaction_historic mit allen Daten, die älter als sechs Monate sind. Will ein Benutzer alle Transaktionen für ein bestimmtes Konto sehen, müsste er also beide Tabellen abfragen. Wenn Sie eine View erstellen, die beide Tabellen abfragt und die Ergebnisse kombiniert, können Sie es so aussehen lassen, als wären alle Transaktionsdaten in einer einzigen Tabelle gespeichert. Dies ist die View-Definition: CREATE VIEW transaction_vw (txn_date, account_id, txn_type_cd, amount, teller_emp_id, execution_branch_id, funds_avail_date ) AS SELECT txn_date, account_id, txn_type_cd, execution_branch_id, funds_avail_date FROM transaction_historic UNION ALL SELECT txn_date, account_id, txn_type_cd, execution_branch_id, funds_avail_date FROM transaction_current;
Max. Linie
amount,
teller_emp_id,
amount,
teller_emp_id,
Hier ist der Einsatz einer View empfehlenswert, weil dies dem Designer ermöglicht, die Struktur der zugrunde liegenden Daten zu ändern, ohne dass alle Datenbankbenutzer dadurch genötigt werden, ihre Abfragen anzupassen.
Warum Views verwenden? |
259
Max. Linie
Links Aktualisierbare Views Sie stellen Ihren Benutzern also einen Satz von Views zur Verfügung, über die sie Daten abrufen können. Was aber ist, wenn diese Benutzer dieselben Daten auch ändern können müssen? Es könnte recht seltsam wirken, wenn Sie Ihre Benutzer zwingen, Daten über eine View abzurufen, ihnen im Gegenzug aber gestatten, mit update oder insert direkt die zugrunde liegenden Tabellen zu ändern. Deswegen erlauben Ihnen MySQL, Oracle Database und SQL Server, Daten über eine View zu ändern – solange Sie bestimmte Bedingungen einhalten. Bei MySQL ist eine View beispielsweise aktualisierbar, wenn die folgenden Bedingungen erfüllt sind: • Es werden keine Aggregatfunktionen eingesetzt (max(), min(), avg() etc.). • Die View nutzt keine group by- oder having-Klauseln. • Die select- oder from-Klausel enthält keine Unterabfragen, und Unterabfragen in der where-Klausel referenzieren keine der Tabellen in der from-Klausel. • Die View nutzt kein union, union all oder distinct. • Die from-Klausel enthält mindestens eine Tabelle oder eine aktualisierbare View. • Wenn es mehrere Tabellen oder Views gibt, nutzt die from-Klausel nur Inner Joins. Wie nützlich aktualisierbare Views sein können, kann man am besten zeigen, indem man mit einer einfachen View-Definition beginnt und dann zu einer komplexeren View weitergeht.
Einfache Views aktualisieren Eine einfachere View als die vom Anfang dieses Kapitels werden wir kaum finden. Beginnen wir also mit dieser:
Max. Linie
CREATE VIEW customer_vw (cust_id, fed_id, cust_type_cd, address, city, state, zipcode ) AS SELECT cust_id, concat('ends in ', substr(fed_id, 8, 4)) fed_id, cust_type_cd, address, city, state, postal_code FROM customer;
260 | Kapitel 14: Views
Max. Linie
Rechts Die View customer_vw fragt eine einzelne Tabelle ab und leitet dabei nur eine einzige der sieben Spalten über einen Ausdruck ab. Diese View-Definition verletzt keine der zuvor aufgeführten Einschränkungen. Sie können sie also einsetzen, um wie hier Daten in der Tabelle customer zu verändern: mysql> UPDATE customer_vw -> SET city = 'Woooburn' -> WHERE city = 'Woburn'; Query OK, 1 row affected (0.34 sec) Rows matched: 1 Changed: 1 Warnings: 0
Wie Sie sehen, behauptet die Anweisung, dass sie nur eine Zeile verändert hat. Werfen wir einen Blick in die zugrunde liegende customer-Tabelle, um das zu überprüfen: mysql> SELECT DISTINCT city FROM customer; +------------+ | city | +------------+ | Lynnfield | | Woooburn | | Quincy | | Waltham | | Salem | | Wilmington | | Newton | +------------+ 7 rows in set (0.12 sec)
Die meisten Spalten in dieser View können Sie auf diese Weise ändern, nur die Spalte fed_ id nicht, da sie von einem Ausdruck abgeleitet ist: mysql> UPDATE customer_vw -> SET city = 'Woburn', fed_id = '999999999' -> WHERE city = 'Woooburn'; ERROR 1348 (HY000): Column 'fed_id' is not updatable
Das sollte hier kein Problem sein, da diese View ja gerade den Zweck hatte, Identifikationsnummern unkenntlich zu machen. Wenn Sie über die View customer_vw Daten einfügen möchten, haben Sie Pech. Views, die abgeleitete Spalten enthalten, können nicht verwendet werden, um Daten einzufügen, auch nicht, wenn die abgeleiteten Spalten in die Anweisung nicht eingeschlossen werden. Die folgende Anweisung versucht beispielsweise, nur die Spalten cust_id, cust_type_cd und city über die customer_vw-View zu füllen: mysql> INSERT INTO customer_vw(cust_id, cust_type_cd, city) -> VALUES (9999, 'I', 'Worcester'); ERROR 1471 (HY000): The target table customer_vw of the INSERT is not insertable -into
Max. Linie
Nachdem Sie die Einschränkungen bei einfachen Views gesehen haben, zeigt der nächste Abschnitt, wie man eine View verwendet, die mehrere Tabellen verknüpft.
Aktualisierbare Views
|
261
Max. Linie
Links Komplexe Views aktualisieren Obgleich Views auf einer einzigen Tabelle sicher recht häufig sind, schließen viele der Views, die Ihnen begegnen werden, in der from-Klausel der zugrunde liegenden Anweisung mehrere Tabellen ein. Die folgende View verknüpft beispielsweise die Tabellen business und customer, damit alle Daten für Geschäftskunden leicht abgefragt werden können: CREATE VIEW business_customer_vw (cust_id, fed_id, address, city, state, postal_code, business_name, state_id, incorp_date ) AS SELECT cst.cust_id, cst.fed_id, cst.address, cst.city, cst.state, cst.postal_code, bsn.name, bsn.state_id, bsn.incorp_date FROM customer cst INNER JOIN business bsn ON cst.cust_id = bsn.cust_id WHERE cust_type_cd = 'B';
Diese View können Sie einsetzen, um Daten in den Tabellen customer oder business zu aktualisieren, wie die folgende Anweisung zeigt: mysql> UPDATE business_customer_vw -> SET postal_code = '99999' -> WHERE cust_id = 10; Query OK, 1 row affected (0.09 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> UPDATE business_customer_vw -> SET incorp_date = '2008-11-17' -> WHERE cust_id = 10; Query OK, 1 row affected (0.11 sec) Rows matched: 1 Changed: 1 Warnings: 0
Max. Linie
Die erste Anweisung modifiziert die Spalte customer.postal_code, die zweite die Spalte business.incorp_date. Vielleicht fragen Sie sich, was passiert, wenn Sie versuchen, Zeilen aus beiden Tabellen in einer einzigen Anweisung zu aktualisieren. Finden wir es heraus:
262 | Kapitel 14: Views
Max. Linie
Rechts mysql> UPDATE business_customer_vw -> SET postal_code = '88888', incorp_date = '2008-10-31' -> WHERE cust_id = 10; ERROR 1393 (HY000): Can not modify more than one base table through a join view 'bank.business_customer_vw'
Wie Sie sehen, dürfen Sie zwar beide zugrunde liegenden Tabellen verändern, aber nur, solange Sie nicht versuchen, das in einer einzigen Anweisung zu machen. Versuchen wir jetzt, in beide Tabellen Daten für einen neuen Kunden einzufügen (cust_id = 99): mysql> INSERT INTO business_customer_vw -> (cust_id, fed_id, address, city, state, postal_code) -> VALUES (99, '04-9999999', '99 Main St.', 'Peabody', 'MA', '01975'); Query OK, 1 row affected (0.07 sec) mysql> INSERT INTO business_customer_vw -> (cust_id, business_name, state_id, incorp_date) -> VALUES (99, 'Ninety-Nine Restaurant', '99-999-999', '1999-01-01'); ERROR 1393 (HY000): Can not modify more than one base table through a join view 'bank.business_customer_vw'
Die erste Anweisung, die versucht, Daten in die Tabelle customer einzufügen, funktioniert, aber die zweite, die veranlassen möchte, ein Zeile in die Tabelle business einzufügen, löst eine Exception aus. Die zweite Anweisung schlägt fehl, weil beide Tabellen eine cust_idSpalte haben, die cust_id-Spalte in der View-Definition aber auf die Spalte customer.cust_ id abgebildet ist. Deswegen ist es nicht möglich, über die oben definierte View Daten in die Tabelle business einzufügen. Auch Oracle Database und SQL Server gestatten das Aktualisieren und Einfügen von Daten über Views, wie bei MySQL gibt es aber viele Einschränkungen. Wenn Sie jedoch bereit sind, etwas PL/SQL oder Transact-SQL zu schreiben, können Sie eine Funktionalität nutzen, die als Instead-of-Trigger (Stattdessen-Trigger) bezeichnet wird, die Ihnen im Wesentlichen ermöglicht, insert-, update- und delete-Anweisungen auf einer View abzufangen, um spezifischen Code für den Einbau der Änderungen auszuführen. Ohne ein derartiges Feature gibt es einfach zu viele Einschränkungen, als dass Aktualisierungen über Views eine praktikable Strategie für nicht-triviale Anwendungen wären.
Testen Sie Ihr Wissen Testen Sie Ihr Verständnis von Views, indem Sie die folgenden Übungen durcharbeiten. Wenn Sie fertig sind, vergleichen Sie Ihre Lösungen mit denen in Anhang C.
Max. Übung 14-1 Max. Erstellen Sie eine View, die die Tabelle employee abfragt und folgende Ausgabe generiert, Linie wenn sie ohne where-Klausel abgefragt wird: Linie Testen Sie Ihr Wissen |
263
Links +-----------------+------------------+ | supervisor_name | employee_name | +-----------------+------------------+ | NULL | Michael Smith | | Michael Smith | Susan Barker | | Michael Smith | Robert Tyler | | Robert Tyler | Susan Hawthorne | | Susan Hawthorne | John Gooding | | Susan Hawthorne | Helen Fleming | | Helen Fleming | Chris Tucker | | Helen Fleming | Sarah Parker | | Helen Fleming | Jane Grossman | | Susan Hawthorne | Paula Roberts | | Paula Roberts | Thomas Ziegler | | Paula Roberts | Samantha Jameson | | Susan Hawthorne | John Blake | | John Blake | Cindy Mason | | John Blake | Frank Portman | | Susan Hawthorne | Theresa Markham | | Theresa Markham | Beth Fowler | | Theresa Markham | Rick Tulman | +-----------------+------------------+ 18 rows in set (1.47 sec)
Übung 14-2 Der Präsident der Bank hätte gern einen Bericht, der für jede Zweigstelle den Namen und die Stadt anzeigt und zusätzlich die Gesamtsumme aller Konten, die an dieser Zweigstelle eröffnet wurden. Erstellen Sie eine View, die diese Daten generiert.
Max. Linie
Max. Linie 264 | Kapitel 14: Views
First Kapitel 15
KAPITEL 15
Metadaten
Zusätzlich zu den verschiedenen Daten, die Benutzer in eine Datenbank einfügen, muss ein Datenbankserver auch Informationen zu allen Datenbankobjekten (Tabellen, Views, Indizes und so weiter) speichern, die erstellt wurden, um diese Daten zu speichern. Wenig überraschend, speichert der Datenbankserver diese Daten in einer Datenbank. Dieses Kapitel befasst sich damit, wie und wo diese Daten, die als Metadaten bezeichnet werden, gespeichert sind, wie Sie darauf zugreifen und wie Sie sie nutzen können, um flexible Systeme aufzubauen.
Daten über Daten Metadaten sind im Wesentlichen Daten über Daten. Jedes Mal, wenn Sie ein Datenbankobjekt erstellen, muss der Datenbankserver verschiedene Informationen festhalten. Wenn Sie eine Tabelle mit mehreren Spalten, einem Primärschlüssel-Constraint, drei Indizes und einem Fremdschlüssel-Constraint erstellen, muss der Datenbankserver beispielsweise die folgenden Informationen speichern: • Tabellenname • Informationen zur Tabellenspeicherung (Tablespace, anfängliche Größe usw.) • Speichermodul • Spaltennamen • Datentypen der Spalten • Defaultwerte der Spalten • NOT NULL-Constraints für Spalten • Primärschlüsselspalten • Primärschlüsselname • Name des Primärschlüsselindex • Indexnamen
Max. Linie |
265
Max. Linie
Links • Indextypen (B-Baum, Bitmap) • indizierte Spalten • Sortierfolge auf den indizierten Spalten (aufsteigend oder absteigend) • Informationen zur Indexspeicherung • Fremdschlüsselname • Fremdschlüsselspalten • die mit dem Fremdschlüssel verknüpfte Tabelle/Spalte Zusammen bezeichnet man diese Daten als Data Dictionary oder Systemkatalog. Der Datenbankserver muss diese Daten dauerhaft speichern und schnell abrufen können, damit er SQL-Anweisungen überprüfen und ausführen kann. Zusätzlich muss der Datenbankserver diese Daten sichern, damit sie nur über einen geeigneten Mechanismus wie die alter table-Anweisung geändert werden können. Es gibt zwar Standards für den Austausch von Metadaten zwischen verschiedenen Servern, aber alle Datenbankserver nutzen unterschiedliche Mechanismen zur Veröffentlichung von Metadaten, beispielsweise: • Eine Sammlung von Views wie Oracles user_tables- und all_constraints-Views. • Eine Sammlung vom System gespeicherter Prozeduren wie SQL Servers sp_tablesProzedur oder Oracles dbms_metadata-Paket. • Eine spezielle Datenbank wie MySQLs information_schema-Datenbank. SQL Server bietet neben den gespeicherten Systemprozeduren, die ein Überrest seiner Sybase-Abstammung sind, ebenfalls ein spezielles Schema namens information_schema, das automatisch für jede Datenbank bereitgestellt wird. MySQL und SQL Server bieten diese Schnittstelle beide, um dem ANSI SQL:2003-Standard zu genügen. Der Rest dieses Kapitels befasst sich mit den information_schema-Objekten, die in MySQL und SQL Server verfügbar sind.
Information_Schema Alle Objekte in der information_schema-Datenbank (bzw. bei SQL Server dem Schema) sind Views. Anders als der describe-Befehl, den ich in mehreren Kapiteln dieses Buchs eingesetzt habe, um die Struktur verschiedener Tabellen und Views zu zeigen, können die Views in information_schema abgefragt und deswegen (mehr dazu später in diesem Kapitel) programmatisch genutzt werden. Hier ist ein Beispiel, das zeigt, wie man die Namen aller Tabellen in der Datenbank bank abruft:
Max. Linie
mysql> -> -> ->
SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'bank' ORDER BY 1;
266 | Kapitel 15: Metadaten
Max. Linie
Rechts +----------------------+------------+ | table_name | table_type | +----------------------+------------+ | account | BASE TABLE | | branch | BASE TABLE | | branch_activity_vw | VIEW | | business | BASE TABLE | | business_customer_vw | VIEW | | customer | BASE TABLE | | customer_vw | VIEW | | department | BASE TABLE | | employee | BASE TABLE | | employee_vw | VIEW | | individual | BASE TABLE | | nh_customer_vw | VIEW | | officer | BASE TABLE | | product | BASE TABLE | | product_type | BASE TABLE | | transaction | BASE TABLE | +----------------------+------------+ 16 rows in set (0.02 sec)
Neben den verschiedenen Tabellen, die wir vor langer Zeit in Kapitel 2 erstellt haben, zeigt die Ergebnismenge auch einige der Views, die ich in Kapitel 14 vorgeführt habe. Wenn Sie die Views ausschließen möchten, müssen Sie einfach der where-Klausel eine weitere Bedingung hinzufügen: mysql> SELECT table_name, table_type -> FROM information_schema.tables -> WHERE table_schema = 'bank' AND table_type = 'BASE TABLE' -> ORDER BY 1; +--------------+------------+ | table_name | table_type | +--------------+------------+ | account | BASE TABLE | | branch | BASE TABLE | | business | BASE TABLE | | customer | BASE TABLE | | department | BASE TABLE | | employee | BASE TABLE | | individual | BASE TABLE | | officer | BASE TABLE | | product | BASE TABLE | | product_type | BASE TABLE | | transaction | BASE TABLE | +--------------+------------+ 11 rows in set (0.01 sec)
Wenn Sie nur Informationen zu den Views haben möchten, können Sie information_ schema.views abfragen. Zusätzlich zu den View-Namen können Sie weitere Informationen
Max. Linie
abfragen, wie einen Schalter, der anzeigt, ob eine View aktualisierbar ist:
Information_Schema |
Max. Linie 267
Links mysql> SELECT table_name, is_updatable -> FROM information_schema.views -> WHERE table_schema = 'bank' -> ORDER BY 1; +----------------------+--------------+ | table_name | is_updatable | +----------------------+--------------+ | branch_activity_vw | NO | | business_customer_vw | YES | | customer_vw | YES | | employee_vw | YES | | nh_customer_vw | YES | +----------------------+--------------+ 5 rows in set (1.83 sec)
Außerdem können Sie über die Spalte view_definition die der View zugrunde liegende Abfrage abrufen, vorausgesetzt, diese ist nicht zu groß (bei MySQL kleiner als 4.000 Zeichen). Spalteninformationen zu Tabellen und Views sind über die columns-View verfügbar. Die folgende Abfrage zeigt Spalteninformationen für die Tabelle account: mysql> SELECT column_name, data_type, character_maximum_length char_max_len, -> numeric_precision num_prcsn, numeric_scale num_scale -> FROM information_schema.columns -> WHERE table_schema = 'bank' AND table_name = 'account' -> ORDER BY ordinal_position; +--------------------+-----------+--------------+-----------+-----------+ | column_name | data_type | char_max_len | num_prcsn | num_scale | +--------------------+-----------+--------------+-----------+-----------+ | account_id | int | NULL | 10 | 0 | | product_cd | varchar | 10 | NULL | NULL | | cust_id | int | NULL | 10 | 0 | | open_date | date | NULL | NULL | NULL | | close_date | date | NULL | NULL | NULL | | last_activity_date | date | NULL | NULL | NULL | | status | enum | 6 | NULL | NULL | | open_branch_id | smallint | NULL | 5 | 0 | | open_emp_id | smallint | NULL | 5 | 0 | | avail_balance | float | NULL | 10 | 2 | | pending_balance | float | NULL | 10 | 2 | +--------------------+-----------+--------------+-----------+-----------+ 11 rows in set (0.02 sec)
Die Spalte ordinal_position habe ich nur eingeschlossen, um die Spalten in der Reihenfolge anzeigen zu lassen, in der sie der Tabelle hinzugefügt wurden. Informationen zu den Indizes einer Tabelle können Sie über die View information_schema. statistics abrufen. Das zeigt die folgende Abfrage, die Informationen zu den Indizes auf der Tabelle account abruft:
Max. Linie
mysql> -> -> ->
SELECT index_name, non_unique, seq_in_index, column_name FROM information_schema.statistics WHERE table_schema = 'bank' AND table_name = 'account' ORDER BY 1, 3;
268 | Kapitel 15: Metadaten
Max. Linie
Rechts +----------------+------------+--------------+----------------+ | index_name | non_unique | seq_in_index | column_name | +----------------+------------+--------------+----------------+ | acc_bal_idx | 1 | 1 | cust_id | | acc_bal_idx | 1 | 2 | avail_balance | | fk_a_branch_id | 1 | 1 | open_branch_id | | fk_a_emp_id | 1 | 1 | open_emp_id | | fk_product_cd | 1 | 1 | product_cd | | PRIMARY | 0 | 1 | account_id | +----------------+------------+--------------+----------------+ 6 rows in set (0.09 sec)
Auf der Tabelle account gibt es fünf Indizes, von denen einer zwei Spalten umfasst (acc_ bal_idx) und einer eindeutig ist (PRIMARY). Die verschiedenen Arten von Constraints (Fremdschlüssel, Primärschlüssel, Unique), die erstellt wurden, können Sie über information_schema.table_constraints abrufen. Hier ist eine Abfrage, die alle Constraints im Schema bank abruft:
Max. Linie
mysql> SELECT constraint_name, table_name, constraint_type -> FROM information_schema.table_constraints -> WHERE table_schema = 'bank' -> ORDER BY 3,1; +--------------------+--------------+-----------------+ | constraint_name | table_name | constraint_type | +--------------------+--------------+-----------------+ | fk_a_branch_id | account | FOREIGN KEY | | fk_a_cust_id | account | FOREIGN KEY | | fk_a_emp_id | account | FOREIGN KEY | | fk_b_cust_id | business | FOREIGN KEY | | fk_dept_id | employee | FOREIGN KEY | | fk_exec_branch_id | transaction | FOREIGN KEY | | fk_e_branch_id | employee | FOREIGN KEY | | fk_e_emp_id | employee | FOREIGN KEY | | fk_i_cust_id | individual | FOREIGN KEY | | fk_o_cust_id | officer | FOREIGN KEY | | fk_product_cd | account | FOREIGN KEY | | fk_product_type_cd | product | FOREIGN KEY | | fk_teller_emp_id | transaction | FOREIGN KEY | | fk_t_account_id | transaction | FOREIGN KEY | | PRIMARY | branch | PRIMARY KEY | | PRIMARY | account | PRIMARY KEY | | PRIMARY | product | PRIMARY KEY | | PRIMARY | department | PRIMARY KEY | | PRIMARY | customer | PRIMARY KEY | | PRIMARY | transaction | PRIMARY KEY | | PRIMARY | officer | PRIMARY KEY | | PRIMARY | product_type | PRIMARY KEY | | PRIMARY | employee | PRIMARY KEY | | PRIMARY | business | PRIMARY KEY | | PRIMARY | individual | PRIMARY KEY | | dept_name_idx | department | UNIQUE | +--------------------+--------------+-----------------+ 26 rows in set (2.28 sec)
Max. Linie Information_Schema |
269
Links Tabelle 15-1 zeigt die komplette Sammlung an information_schema-Views, die in MySQL 6.0 zur Verfügung stehen. Tabelle 15-1: Information_schema-Views
Max. Linie
View-Name
Bietet Informationen zu Folgendem:
Schemata
Datenbanken
Tables
Tabellen und Views
Columns
Spalten von Tabellen und Views
Statistics
Indizes
User_Privileges
wer welche Rechte für welche Schema-Objekte hat
Schema_Privileges
wer welche Rechte für welche Datenbanken hat
Table_Privileges
wer welche Rechte für welche Tabellen hat
Column_Privileges
wer welche Rechte für welche Spalten welcher Tabellen hat
Character_Sets
welche Zeichensätze verfügbar sind
Collations
welche Collations für welche Zeichensätze verfügbar sind
Collation_Character_Set_Applicability
welche Zeichensätze für welche Collation verfügbar sind
Table_Constraints
Unique-, Fremd- und Primärschlüssel-Constraints
Key_Column_Usage
mit den Schlüsselspalten verknüpften Contraints
Routines
Gespeicherte Routinen (Prozeduren und Funktionen)
Views
Views
Triggers
Tabellen-Trigger
Plugins
Server-Plug-ins
Engines
verfügbare Speichermodule
Partitions
Tabellenpartitionen
Events
geplante Ereignisse
Process_List
laufende Prozesse
Referential_Constraints
Fremdschlüssel
Global_Status
Server-Statusinformationen
Session_Status
Session-Statusinformationen
Global_Variables
Server-Statusvariablen
Session_Variables
Session-Statusvariablen
Parameters
Parameter für gespeicherte Prozeduren und Funktionen
Profiling
Benutzer-Profiling-Informationen
Obwohl einige dieser Views wie engines, events und plugins für MySQL spezifisch sind, sind viele von ihnen auch unter SQL Server verfügbar. Wenn Sie Oracle Database einsetzen, sollten Sie online einen Blick in den Online Oracle Database Reference Guide werfen und nach Informationen zu den user_-, all_- und dba_-Views schauen.
270 | Kapitel 15: Metadaten
Max. Linie
Rechts Mit Metadaten arbeiten Wie ich bereits erwähnte, eröffnet uns die Möglichkeit, über SQL-Abfragen Informationen zu unseren Schema-Objekten abzurufen, einige interessante Perspektiven. Dieser Abschnitt zeigt mehrere Arten, wie Sie Metadaten in Ihren Anwendungen nutzen können.
Skripten zur Schema-Generierung Obwohl einige Projektteams einen Vollzeit-Datenbankdesigner beschäftigen, der den Entwurf und die Implementierung der Datenbank überwacht, wählen viele doch einen Ansatz, bei dem eine Reihe von Personen Datenbankobjekte erstellen darf. Nach einigen Wochen oder Monaten der Entwicklung müssen Sie eventuell ein Skript schreiben, das die verschiedenen Tabellen, Indizes, Views und so weiter anlegt, die das Team eingerichtet hat. Obwohl es eine Vielzahl von Hilfswerkzeugen gibt, die derartige Skripten für Sie generieren, können Sie die erforderlichen Informationen auch aus den information_schemaViews abrufen und das Skript selbst generieren. Bauen wir als Beispiel ein Skript auf, das die Tabelle bank.customer erstellt. Hier ist der vollständige Befehl, der genutzt wurde, um die Tabelle einzurichten (ich habe ihn aus dem Skript herausgezogen, das verwendet wurde, um die Beispieldatenbank zu erstellen): create table customer (cust_id integer unsigned not null auto_increment, fed_id varchar(12) not null, cust_type_cd enum('I','B') not null, address varchar(30), city varchar(20), state varchar(20), postal_code varchar(10), constraint pk_customer primary key (cust_id) );
Obwohl es mit Sicherheit einfacher wäre, das Skript unter Verwendung einer prozeduralen Sprache (z.B. Transact-SQL oder Java) zu erstellen, werde ich, da es in diesem Buch um SQL geht, eine einzige Abfrage erstellen, die diese create table-Anweisung generiert. Der erste Schritt ist die Abfrage von information_schema.columns, um Informationen zu den Spalten in der Tabelle abzurufen:
Max. Linie
mysql> -> -> -> -> -> -> -> -> -> ->
SELECT 'CREATE TABLE customer (' create_table_statement UNION ALL SELECT cols.txt FROM (SELECT concat(' ',column_name, ' ', column_type, CASE WHEN is_nullable = 'NO' THEN ' not null' ELSE '' END, CASE WHEN extra IS NOT NULL THEN concat(' ', extra)
Max. Linie Mit Metadaten arbeiten |
271
Links -> ELSE '' -> END, -> ',') txt -> FROM information_schema.columns -> WHERE table_schema = 'bank' AND table_name = 'customer' -> ORDER BY ordinal_position -> ) cols -> UNION ALL -> SELECT ')'; +-----------------------------------------------------+ | create_table_statement | +-----------------------------------------------------+ | CREATE TABLE customer ( | | cust_id int(10) unsigned not null auto_increment, | | fed_id varchar(12) not null , | | cust_type_cd enum('I','B') not null , | | address varchar(30) , | | city varchar(20) , | | state varchar(20) , | | postal_code varchar(10) , | | ) | +-----------------------------------------------------+ 9 rows in set (0.04 sec)
Das hat uns schon recht weit gebracht. Jetzt müssen wir nur noch Abfragen auf den Views table_constraints und key_column_usage ergänzen, um Informationen zu dem Primärschlüssel-Constraint abzurufen:
Max. Linie
mysql> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> ->
SELECT 'CREATE TABLE customer (' create_table_statement UNION ALL SELECT cols.txt FROM (SELECT concat(' ',column_name, ' ', column_type, CASE WHEN is_nullable = 'NO' THEN ' not null' ELSE '' END, CASE WHEN extra IS NOT NULL THEN concat(' ', extra) ELSE '' END, ',') txt FROM information_schema.columns WHERE table_schema = 'bank' AND table_name = 'customer' ORDER BY ordinal_position ) cols UNION ALL SELECT concat(' constraint primary key (') FROM information_schema.table_constraints WHERE table_schema = 'bank' AND table_name = 'customer' AND constraint_type = 'PRIMARY KEY' UNION ALL SELECT cols.txt
272 | Kapitel 15: Metadaten
Max. Linie
Rechts -> FROM -> (SELECT concat(CASE WHEN ordinal_position > 1 THEN ' ,' -> ELSE ' ' END, column_name) txt -> FROM information_schema.key_column_usage -> WHERE table_schema = 'bank' AND table_name = 'customer' -> AND constraint_name = 'PRIMARY' -> ORDER BY ordinal_position -> ) cols -> UNION ALL -> SELECT ' )' -> UNION ALL -> SELECT ')'; +-----------------------------------------------------+ | create_table_statement | +-----------------------------------------------------+ | CREATE TABLE customer ( | | cust_id int(10) unsigned not null auto_increment, | | fed_id varchar(12) not null , | | cust_type_cd enum('I','B') not null , | | address varchar(30) , | | city varchar(20) , | | state varchar(20) , | | postal_code varchar(10) , | | constraint primary key ( | | cust_id | | ) | | ) | +-----------------------------------------------------+ 12 rows in set (0.02 sec)
Um zu prüfen, ob die Abfrage ordentlich geformt ist, kopiere ich die Abfrageausgabe ins mysql-Tool (den Tabellennamen habe ich in customer2 geändert, damit sich die beiden Tabellen nicht ins Gehege kommen): mysql> CREATE TABLE customer2 ( -> cust_id int(10) unsigned not null auto_increment, -> fed_id varchar(12) not null , -> cust_type_cd enum('I','B') not null , -> address varchar(30) , -> city varchar(20) , -> state varchar(20) , -> postal_code varchar(10) , -> constraint primary key ( -> cust_id -> ) -> ); Query OK, 0 rows affected (0.14 sec)
Max. Linie
Die Anweisung wurde ohne Fehler ausgeführt, und die bank-Datenbank enthält jetzt eine customer2-Tabelle. Damit die Abfrage eine wohlgeformte create table-Anweisung für jede beliebige Tabelle bildet, muss noch etwas mehr Arbeit geleistet werden (beispiels-
Mit Metadaten arbeiten |
273
Max. Linie
Links weise müssen Indizes und Fremdschlüssel-Constraints berücksichtigt werden), aber das überlasse ich Ihnen als Übung.
Deployment-Überprüfung Viele Organisationen richten Zeitfenster zur Datenbankwartung ein, in denen bestehende Datenbankobjekte administriert (beispielsweise Partitionen hinzugefügt oder gelöscht) und neue Schema-Objekte und Code eingerichtet werden können. Es ist empfehlenswert, nachdem die Deployment-Skripten gelaufen sind, ein Prüfskript auszuführen, um zu sichern, dass die neuen Schema-Objekte eingerichtet wurden und die erforderlichen Spalten, Indizes, Primärschlüssel und so weiter enthalten. Hier ist eine Abfrage, die die Anzahl an Spalten, Indizes und Primärschlüssel-Constraints (0 oder 1) für alle Tabellen im bankSchema liefert: mysql> SELECT tbl.table_name, -> (SELECT count(*) FROM information_schema.columns clm -> WHERE clm.table_schema = tbl.table_schema -> AND clm.table_name = tbl.table_name) num_columns, -> (SELECT count(*) FROM information_schema.statistics sta -> WHERE sta.table_schema = tbl.table_schema -> AND sta.table_name = tbl.table_name) num_indexes, -> (SELECT count(*) FROM information_schema.table_constraints tc -> WHERE tc.table_schema = tbl.table_schema -> AND tc.table_name = tbl.table_name -> AND tc.constraint_type = 'PRIMARY KEY') num_primary_keys -> FROM information_schema.tables tbl -> WHERE tbl.table_schema = 'bank' AND tbl.table_type = 'BASE TABLE' -> ORDER BY 1; +--------------+-------------+-------------+------------------+ | table_name | num_columns | num_indexes | num_primary_keys | +--------------+-------------+-------------+------------------+ | account | 11 | 6 | 1 | | branch | 6 | 1 | 1 | | business | 4 | 1 | 1 | | customer | 7 | 1 | 1 | | department | 2 | 2 | 1 | | employee | 9 | 4 | 1 | | individual | 4 | 1 | 1 | | officer | 7 | 2 | 1 | | product | 5 | 2 | 1 | | product_type | 2 | 1 | 1 | | transaction | 8 | 4 | 1 | +--------------+-------------+-------------+------------------+ 11 rows in set (13.83 sec)
Max. Linie
Diese Anweisung könnten Sie vor und nach Verteilung der neuen Objekte ausführen und dann die Unterschiede zwischen den beiden Ergebnissen überprüfen, um zu sichern, dass das Deployment erfolgreich war.
274 | Kapitel 15: Metadaten
Max. Linie
Rechts Dynamisch SQL generieren Einige Programmiersprachen wie Oracles PL/SQL und Microsofts Transact-SQL sind Obermengen zur Sprache SQL, d.h., dass ihre Grammatik neben den üblichen prozeduralen Konstrukten wie »if-then-else« und »while« auch SQL-Anweisungen einschließt. Andere Sprachen wie Java ermöglichen die Kommunikation mit relationalen Datenbanken, schließen in ihrer Syntax aber keine SQL-Anweisungen ein, was bedeutet, dass in ihnen alle SQL-Anweisungen in Strings gefasst werden müssen. Deswegen gestatten die meisten relationalen Datenbankserver, SQL Server, Oracle Database und MySQL eingeschlossen, dass SQL-Anweisungen über Strings an den Server geschickt werden. Schickt man Strings an ein Datenbanksystem, statt seine SQL-Schnittstelle zu nutzen, bezeichnet man das in der Regel als dynamische SQL-Ausführung. Oracles PL/SQL bietet beispielsweise einen execute immediate-Befehl, über den Sie einen String abschicken können, damit er ausgeführt wird, SQL Server bietet zur dynamischen Ausführung von SQL-Anweisungen hingegen eine gespeicherte Systemprozedur namens sp_executesql. Bei MySQL können Sie mithilfe der Anweisungen prepare, execute und deallocate dynamisch SQL ausführen. Hier ist ein einfaches Beispiel: mysql> SET @qry = 'SELECT cust_id, cust_type_cd, fed_id FROM customer'; Query OK, 0 rows affected (0.07 sec) mysql> PREPARE dynsql1 FROM @qry; Query OK, 0 rows affected (0.04 sec) Statement prepared
Max. Linie
mysql> EXECUTE dynsql1; +---------+--------------+-------------+ | cust_id | cust_type_cd | fed_id | +---------+--------------+-------------+ | 1 | I | 111-11-1111 | | 2 | I | 222-22-2222 | | 3 | I | 333-33-3333 | | 4 | I | 444-44-4444 | | 5 | I | 555-55-5555 | | 6 | I | 666-66-6666 | | 7 | I | 777-77-7777 | | 8 | I | 888-88-8888 | | 9 | I | 999-99-9999 | | 10 | B | 04-1111111 | | 11 | B | 04-2222222 | | 12 | B | 04-3333333 | | 13 | B | 04-4444444 | | 99 | I | 04-9999999 | +---------+--------------+-------------+ 14 rows in set (0.27 sec)
Max. Linie
mysql> DEALLOCATE PREPARE dynsql1; Query OK, 0 rows affected (0.00 sec)
Mit Metadaten arbeiten |
275
Links Diese set-Anweisung weist der Variablen qry einen String zu, der dann mit der Anweisung prepare an das Datenbanksystem geschickt wird (wo er geparst, geprüft und optimiert wird). Nachdem die Anweisung mit dem Aufruf von execute ausgeführt wurde, muss sie mit deallocate prepare geschlossen werden, damit die während der Ausführung in Anspruch genommenen Datenbankressourcen (z.B. Cursor) wieder freigegeben werden. Das nächste Beispiel zeigt Ihnen, wie Sie eine Abfrage mit Platzhaltern ausführen könnten, damit bei Laufzeit Bedingungen angegeben werden können: mysql> SET @qry = 'SELECT product_cd, name, product_type_cd, date_offered, date_retired FROM product WHERE product_cd = ?'; Query OK, 0 rows affected (0.00 sec) mysql> PREPARE dynsql2 FROM @qry; Query OK, 0 rows affected (0.00 sec) Statement prepared mysql> SET @prodcd = 'CHK'; Query OK, 0 rows affected (0.00 sec) mysql> EXECUTE dynsql2 USING @prodcd; +------------+------------------+-----------------+--------------+-------------+ | product_cd | name | product_type_cd | date_offered | date_retired| +------------+------------------+-----------------+--------------+-------------+ | CHK | checking account | ACCOUNT | 2004-01-01 | NULL | +------------+------------------+-----------------+--------------+-------------+ 1 row in set (0.01 sec) mysql> SET @prodcd = 'SAV'; Query OK, 0 rows affected (0.00 sec) mysql> EXECUTE dynsql2 USING @prodcd; +------------+-----------------+-----------------+--------------+--------------+ | product_cd | name | product_type_cd | date_offered | date_retired | +------------+-----------------+-----------------+--------------+--------------+ | SAV | savings account | ACCOUNT | 2004-01-01 | NULL | +------------+-----------------+-----------------+--------------+--------------+ 1 row in set (0.00 sec) mysql> DEALLOCATE PREPARE dynsql2; Query OK, 0 rows affected (0.00 sec)
Die Abfrage, die in der ersten Anweisung formuliert wird, enthält einen Platzhalter (das ? am Ende der Anweisung), über den zur Laufzeit Code angegeben werden kann. Die Anweisung wird einmal vorbereitet und dann zweimal ausgeführt, zum einen für den Produktcode 'CHK' und zum anderen für den Produktcode 'SAV', anschließend wird die Anweisung geschlossen.
Max. Linie
Was, fragen Sie sich vielleicht, hat denn das mit Metadaten zu tun? Wenn Sie dynamisches SQL einsetzen, um eine Tabelle abzufragen, wäre es dann nicht eine gute Idee, den Abfragestring auf Basis von Metadaten aufzubauen, anstatt die Tabellen-Definitionen in die
276 | Kapitel 15: Metadaten
Max. Linie
Rechts Abfrage einzugravieren? Das folgende Beispiel generiert den gleichen String mit dynamischem SQL, ruft die Spaltennamen aber aus der View information_schema.columns ab: mysql> SELECT concat('SELECT ', -> concat_ws(',', cols.col1, cols.col2, cols.col3, cols.col4, -> cols.col5, cols.col6, cols.col7, cols.col8, cols.col9), -> ' FROM product WHERE product_cd = ?') -> INTO @qry -> FROM -> (SELECT -> max(CASE WHEN ordinal_position = 1 THEN column_name -> ELSE NULL END) col1, -> max(CASE WHEN ordinal_position = 2 THEN column_name -> ELSE NULL END) col2, -> max(CASE WHEN ordinal_position = 3 THEN column_name -> ELSE NULL END) col3, -> max(CASE WHEN ordinal_position = 4 THEN column_name -> ELSE NULL END) col4, -> max(CASE WHEN ordinal_position = 5 THEN column_name -> ELSE NULL END) col5, -> max(CASE WHEN ordinal_position = 6 THEN column_name -> ELSE NULL END) col6, -> max(CASE WHEN ordinal_position = 7 THEN column_name -> ELSE NULL END) col7, -> max(CASE WHEN ordinal_position = 8 THEN column_name -> ELSE NULL END) col8, -> max(CASE WHEN ordinal_position = 9 THEN column_name -> ELSE NULL END) col9 -> FROM information_schema.columns -> WHERE table_schema = 'bank' AND table_name = 'product' -> GROUP BY table_name -> ) cols; Query OK, 1 row affected (0.02 sec) mysql> SELECT @qry; +---------------------------------------------------------------------------------------------------+ | @qry | +---------------------------------------------------------------------------------------------------+ | SELECT product_cd,name,product_type_cd,date_offered,date_retired FROM product WHERE product_cd = ? | +---------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> PREPARE dynsql3 FROM @qry; Query OK, 0 rows affected (0.01 sec) Statement prepared
Max. Linie
Max. Linie
mysql> SET @prodcd = 'MM'; Query OK, 0 rows affected (0.00 sec)
Mit Metadaten arbeiten |
277
Links mysql> EXECUTE dynsql3 USING @prodcd; +------------+----------------------+-----------------+--------------+--------------+ | product_cd | name | product_type_cd | date_offered | date_retired | +------------+----------------------+-----------------+--------------+--------------+ | MM | money market account | ACCOUNT | 2004-01-01 | NULL | +------------+----------------------+-----------------+--------------+--------------+ 1 row in set (0.00 sec) mysql> DEALLOCATE PREPARE dynsql3; Query OK, 0 rows affected (0.00 sec)
Die Abfrage spiegelt die ersten neun Spalten der Tabelle product, baut mithilfe der Funktionen concat und concat_ws einen Abfragestring auf und weist diesen String der Variablen qry zu. Dann wird die Abfrage wie zuvor ausgeführt. In der Regel wäre es sicher besser, die Abfrage mit einer prozeduralen Sprache mit Unterstützung für Schleifenkonstrukte, wie Java, PL/SQL, Transact-SQL oder MySQLs Sprache für gespeicherte Routinen, zu schreiben. Ich wollte hier allerdings ein reines SQL-Beispiel vorführen und musste deswegen die Anzahl der abgerufenen Spalten auf eine vertretbare Zahl begrenzen – in diesem Fall also auf neun.
Testen Sie Ihr Wissen Die folgenden Übungen sollen Ihr Verständnis von Metadaten testen. Wenn Sie fertig sind, vergleichen Sie Ihre Ergebnisse mit denen in Anhang C.
Übung 15-1 Schreiben Sie eine Abfrage, die alle Indizes im bank-Schema aufführt. Lassen Sie auch die Tabellennamen melden.
Übung 15-2 Schreiben Sie eine Abfrage, die eine Ausgabe generiert, die eingesetzt werden kann, um alle Indizes auf der Tabelle bank.employee anzulegen. Die Ausgabe sollte folgende Form haben: "ALTER TABLE ADD INDEX ()"
Max. Linie
Max. Linie 278 | Kapitel 15: Metadaten
First
Anhang A
ANHANG A
ER-Diagramm der Musterdatenbank
Abbildung A-1 (siehe nächste Seite) ist ein Entity-Relationship-(ER-)Diagramm für die in diesem Buch verwendete Musterdatenbank. Wie der Name schon sagt, stellt es die Entitäten, oder Tabellen, der Datenbank zusammen mit den zwischen diesen Tabellen herrschenden Fremdschlüsselbeziehungen dar. Hier sind ein paar Tipps, die Ihnen helfen, die Notation besser zu durchschauen: • Jedes Rechteck stellt eine Tabelle dar, wobei der Tabellenname über der linken oberen Ecke des Rechtecks steht. Die Primärschlüsselspalte(n) werden zuerst aufgeführt und sind von den Nicht-Schlüsselspalten durch eine Linie abgegrenzt. Unterhalb dieser Linie stehen die Nicht-Schlüsselspalten, wobei die Fremdschlüsselspalten mit »(FK)« gekennzeichnet sind. • Linien zwischen Tabellen stellen Fremdschlüsselbeziehungen dar. An den Enden der Linien ist die jeweils zulässige Anzahl markiert, also null (0), eins (1) oder viele ( ). Wenn Sie zum Beispiel die Beziehung zwischen der Konten- und der Produkttabelle betrachten, erkennen Sie, dass jedes Konto genau einem Produkt entsprechen muss, aber für jedes Produkt null, ein Konto oder viele Konten vorhanden sein können. Weitere Informationen zur Datenbankmodellierung finden Sie beispielsweise unter http:// de.wikipedia.org/wiki/Entity-Relationship-Modell.
Max. Linie
Max. Linie | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
279
Links branch branch_id: smallint unsigned name: varchar(20) address: varchar(30) city: varchar(20) state: varchar(2) zip: varchar(12)
product_type product_type_cd: varchar(10)
department dept_id: smallint unsigned name: varchar(20)
employee emp_id: smallint unsigned
name: varchar(50)
fname: varchar(20) lname: varchar(20) start_date: date end_date: date superior_emp_id: smallint unsigned (FK) dept_id: smallint unsigned (FK) title: varchar(20) assigned_branch_id: smallint unsigned (FK)
product product_cd: varchar(10) name: varchar(50) product_type_cd: varchar(10) (FK) date_offered: date date_retired: date
account account_id: integer unsigned product_cd: varchar(10) (FK) cust_id: integer unsigned (FK) open_date: date close_date: date last_activity_date: date status: varchar(10) open_branch_id: smallint unsigned (FK) open_emp_id: smallint unsigned (FK) avail_balance: float(10,2) pending_balance: float(10,2)
transaction txn_id: integer unsigned txn_date: datetime account_id: integer unsigned (FK) txn_type_cd: varchar(10) amount: double(10,2) teller_emp_id: smallint unsigned (FK) execution_branch_id: smallint unsigned (FK) funds_avail_date: datetime
customer cust_id: integer unsigned fed_id: varchar(12) cust_type_cd: char(2) address: varchar(30) city: varchar(20) state: varchar(20) postal_code: varchar(10)
officer officer_id: smallint unsigned cust_id: integer unsigned (FK) fname: varchar(30) lname: varchar(30) title: varchar(20) start_date: date end_date: date
Max. Linie
business cust_id: integer unsigned (FK) name: varchar(40) state_id: varchar(10) incorp_date: date
individual cust_id: integer unsigned (FK) fname: varchar(30) lname: varchar(30) birth_date: date
Abbildung A-1: ER-Diagramm
280 | Anhang A: ER-Diagramm der Musterdatenbank This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
First
Anhang B
ANHANG B
MySQL-Erweiterungen für SQL
Da dieses Buch für alle Beispiele den MySQL-Server verwendet, dachte ich, es wäre ganz nützlich für die Leser, die auch weiterhin mit MySQL arbeiten möchten, einen Anhang mit den Erweiterungen mitzuliefern, die MySQL für SQL ersonnen hat. Dieser Anhang stellt einige MySQL-Erweiterungen für select-, insert-, update- und delete-Anweisungen vor, die in bestimmten Situationen praktisch sein können.
Erweiterungen der SELECT-Anweisung Die MySQL-Implementierung der SELECT-Anweisung kennt zwei zusätzliche Klauseln, die in den folgenden Abschnitten vorgestellt werden.
Die limit-Klausel In manchen Situationen benötigen Sie gar nicht alle Zeilen, die eine Abfrage zurückliefert. Ein Beispiel: Sie konstruieren eine Abfrage, die jeden Bankkassierer zusammen mit der Anzahl der von ihm eröffneten Konten zurückgibt. Wenn Sie diese Abfrage nur gestartet haben, um die drei besten Kassierer zu ermitteln, denen von der Bank eine Auszeichnung verliehen wird, dann interessieren Sie sich nicht für den Viert-, Fünft- oder Sechstplatzierten. Für solche Fälle bietet die select-Anweisung von MySQL eine limit-Klausel, in der Sie die Anzahl der Rückgabezeilen einer Abfrage einschränken können. Um die Nützlichkeit der limit-Klausel darzustellen, schreibe ich zuerst eine Abfrage, die zeigt, wie viele Konten pro Kassierer eröffnet wurden:
Max. Linie
mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 | | 10 | 7 |
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
281
Links | 13 | 3 | | 16 | 6 | +-------------+----------+ 4 rows in set (0.31 sec)
Die Ergebnisse zeigen, dass vier Kassierer Konten eröffnet haben. Wenn Sie die Ergebnismenge auf nur drei Einträge einschränken möchten, können Sie eine limit-Klausel einfügen, in der Sie genau dies vorschreiben: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> LIMIT 3; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 | | 10 | 7 | | 13 | 3 | +-------------+----------+ 3 rows in set (0.06 sec)
Dank der limit-Klausel (auf der vierten Zeile der Abfrage) hat die Ergebnismenge jetzt nur noch drei Zeilen. Der vierte Kassierer (Personalnummer 16) wurde aus der Ergebnismenge herausgenommen.
Kombinationen von limit und order by Die vorige Abfrage gibt zwar nur noch drei Datensätze zurück, hat aber weiterhin ein kleines Problem: Sie haben noch nicht angegeben, für welche drei der vier Datensätze Sie sich interessieren. Wenn Sie drei bestimmte Datensätze suchen, im vorliegenden Fall die Kassierer, die die meisten Konten eröffnet haben, müssen Sie die limit-Klausel zusammen mit einer order by-Klausel verwenden: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> ORDER BY how_many DESC -> LIMIT 3; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 1 | 8 | | 10 | 7 | | 16 | 6 | +-------------+----------+ 3 rows in set (0.03 sec)
Max. Linie
Der Unterschied zwischen dieser und der vorherigen Abfrage besteht darin, dass die limit-Klausel nunmehr auf eine geordnete Menge angewendet wird. Das führt dazu, dass nur die drei Kassierer, die die meisten Konten eröffnet haben, in die Ergebnismenge auf-
282 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts genommen werden. Immer dann, wenn Sie nicht nur eine beliebige Menge von Datensätzen benötigen, müssen Sie die order by-Klausel mit einer limit-Klausel kombinieren. Die limit-Klausel wird erst dann angewendet, wenn alle Filter-, Gruppierungs- und Ordnungsarbeiten abgeschlossen sind. Daher hat sie auf die Ergebnismenge nie andere Auswirkungen als eben eine Beschränkung der Menge der Rückgabedatensätze.
Der optionale zweite Parameter der limit-Klausel Angenommen, Sie möchten anstelle der drei erfolgreichsten Kassierer alle außer den beiden erfolgreichsten suchen (die Bank möchte nicht die besten Kassierer auszeichnen, sondern die weniger erfolgreichen zu einer Vertriebsschulung schicken). Für solche Fälle kann die limit-Klausel einen optionalen zweiten Parameter einsetzen: Werden beide Parameter angegeben, zeigt der erste an, ab welchem Punkt Datensätze in die Endergebnismenge aufgenommen werden, und der zweite, wie viele Datensätze aufgenommen werden. Wenn Sie einen Datensatz nach seiner Nummer spezifizieren, müssen Sie daran denken, dass MySQL den ersten mit der Nummer 0 bezeichnet. Würden Sie also nach dem drittbesten Kassierer suchen, so könnten Sie folgendermaßen vorgehen: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> ORDER BY how_many DESC -> LIMIT 2, 1; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 16 | 6 | +-------------+----------+ 1 row in set (0.00 sec)
In diesem Beispiel werden die nullte und erste Zeile verworfen, und ab der zweiten Zeile werden Datensätze aufgenommen. Da der zweite Parameter in der limit-Klausel die 1 ist, gelangt allerdings nur ein einziger Datensatz in die Ergebnismenge. Möchten Sie nun beginnend ab der zweiten Position alle weiteren Datensätze aufnehmen, können Sie das zweite Argument der limit-Klausel so groß wählen, dass alle verbleibenden Datensätze damit abgedeckt werden. Wenn Sie nicht wissen, wie viele Kassierer Konten eröffnet haben, ermitteln Sie mit einer Abfrage wie der folgenden alle außer den beiden erfolgreichsten:
Max. Linie
mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> ORDER BY how_many DESC -> LIMIT 2, 999999999; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+
Erweiterungen der SELECT-Anweisung This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
283
Links | 16 | 6 | | 13 | 3 | +-------------+----------+ 2 rows in set (0.00 sec)
In dieser Version der Abfrage werden der nullte und der erste Datensatz verworfen und von den folgenden bis zu 999.999.999 in die Ergebnismenge aufgenommen (in diesem Fall gibt es zwar nur zwei weitere, aber es ist besser, ein wenig zu übertreiben, als hinterher mit einer Ergebnismenge dazustehen, in der gültige Einträge fehlen, weil Sie sich verschätzt haben).
Ranking-Abfragen Wenn man sie in Verbindung mit einer order by-Klausel einsetzt, werden Abfragen, die eine limit-Klausel enthalten, auch als Ranking-Abfragen bezeichnet, da sie die Möglichkeit bieten, Daten in eine bestimmte Rangfolge zu bringen. Ich habe Ihnen bereits gezeigt, wie man Bankkassierer nach der Anzahl der Kontoeröffnungen in eine Rangfolge bringen kann, doch auch viele andere Fragen, die im Geschäftsleben auftauchen, lassen sich mit Ranking-Abfragen beantworten: • Wer sind die fünf besten Verkäufer 2005? • Wer hat die drittmeisten Home Runs in der Geschichte des Baseball errungen? • Abgesehen von der Bibel und den Sprüchen des großen Vorsitzenden Mao: Welche sind die 98 weiteren Bestseller aller Zeiten? • Welche von unseren Eissorten sind die beiden am wenigsten verkauften? Bisher haben Sie gesehen, wie man die besten drei Kassierer, den drittbesten Kassierer und alle außer den beiden besten Kassierern findet. Wenn wir nun so etwas wie das aus dem vierten Beispiel tun wollten (also die Kassierer mit der schlechtesten Leistung herausfinden), müssten wir nur die Sortierreihenfolge umkehren, sodass die Ergebnisse ausgehend von den wenigsten Kontoeröffnungen bis hin zu den meisten angezeigt werden: mysql> SELECT open_emp_id, COUNT(*) how_many -> FROM account -> GROUP BY open_emp_id -> ORDER BY how_many ASC -> LIMIT 2; +-------------+----------+ | open_emp_id | how_many | +-------------+----------+ | 13 | 3 | | 16 | 6 | +-------------+----------+ 2 rows in set (0.24 sec)
Max. Linie
Durch einfaches Umkehren der Sortierreihenfolge (von ORDER BY how_many DESC in ORDER BY how_many ASC) gibt die Abfrage nun die beiden erfolglosesten Kassierer zurück. Daraus folgt: Indem Sie die limit-Klausel mit einer aufsteigenden oder absteigenden Sortierrei-
284 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts henfolge verwenden, konstruieren Sie Ranking-Abfragen, mit denen sich die meisten Fragen des Geschäftslebens beantworten lassen.
Die into outfile-Klausel Soll Ihre Abfrage die Ausgabe in eine Datei schreiben, können Sie die Ergebnisse markieren, in die Zwischenablage kopieren und in einen Editor einfügen. Doch wenn die Ergebnismenge sehr groß ist oder die Abfrage von einem Skript ausgeführt wird, benötigen Sie ein anderes Mittel, um die Resultate ohne weiteres Eingreifen in eine Datei zu laden. Für solche Situationen bietet MySQL die into outfile-Klausel, die den Namen einer Datei angibt, in die die Ergebnisse geladen werden sollen. Das folgende Beispiel schreibt die Abfrageergebnisse in eine Datei in meinem c:\temp-Verzeichnis: mysql> SELECT emp_id, fname, lname, start_date -> INTO OUTFILE 'C:\\TEMP\\emp_list.txt' -> FROM employee; Query OK, 18 rows affected (0.20 sec)
Vielleicht erinnern Sie sich noch an Kapitel 7: Der Backslash ist dazu da, ein anderes Zeichen in einem String als Literal zu kennzeichnen (er ist ein Escape-Symbol). Wenn Sie Windows benutzen, müssen Sie in Pfadnamen daher zwei Backslashs hintereinandersetzen.
Nun werden die Abfrageergebnisse nicht mehr auf dem Bildschirm angezeigt, sondern in die Datei emp_list.txt geschrieben, die dann folgendermaßen aussieht: 1 2 3 4 ... 16 17 18
Michael Susan Robert Susan
Smith 2001-06-22 Barker 2002-09-12 Tyler 2000-02-09 Hawthorne 2002-04-24
Theresa Markham 2001-03-15 Beth Fowler 2002-06-29 Rick Tulman 2002-12-12
Im Standardformat werden Spalten durch Tabulatorzeichen ('\t') und Zeilenumbrüche durch Newline-Zeichen ('\n') hinter jedem Datensatz angegeben. Wenn Sie eine bessere Kontrolle über das Format der Daten haben möchten, können Sie diverse zusätzliche Subklauseln mit der into outfile-Klausel verwenden. Möchten Sie beispielsweise die Daten in dem sogenannten Pipe-getrennten Format darstellen, können Sie die fields-Subklausel einsetzen, um zu veranlassen, dass das Zeichen '|' zwischen die Spalten gesetzt wird:
Max. Linie
mysql> SELECT emp_id, fname, lname, start_date -> INTO OUTFILE 'C:\\TEMP\\emp_list_delim.txt' -> FIELDS TERMINATED BY '|' -> FROM employee; Query OK, 18 rows affected (0.02 sec)
Erweiterungen der SELECT-Anweisung This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie |
285
Links Da MySQL es nicht gestattet, bei Verwendung von into outfile eine vorhandene Datei zu überschreiben, müssen Sie die alte Datei löschen, wenn Sie dieselbe Abfrage mehr als einmal ausführen.
Die Datei emp_list_delim.txt hat jetzt folgenden Inhalt: 1|Michael|Smith|2001-06-22 2|Susan|Barker|2002-09-12 3|Robert|Tyler|2000-02-09 4|Susan|Hawthorne|2002-04-24 ... 16|Theresa|Markham|2001-03-15 17|Beth|Fowler|2002-06-29 18|Rick|Tulman|2002-12-12
Neben dem Pipe-getrennten Format können Sie sich auch für das kommagetrennte Format entscheiden, indem Sie die Subklausel fields terminated by ',' verwenden. Doch wenn zu den Daten, die in die Datei geschrieben werden, auch Strings gehören, kann es problematisch werden, Felder durch Kommata abzugrenzen, da Kommata mit viel höherer Wahrscheinlichkeit in Strings vorkommen als Pipe-Zeichen. Betrachten Sie folgende Abfrage, die eine Zahl und zwei Strings, durch Kommata getrennt, in die Datei comma1.txt schreibt: mysql> SELECT data.num, data.str1, data.str2 -> INTO OUTFILE 'C:\\TEMP\\comma1.txt' -> FIELDS TERMINATED BY ',' -> FROM -> (SELECT 1 num, 'This string has no commas' str1, -> 'This string, however, has two commas' str2) data; Query OK, 1 row affected (0.04 sec)
Da die dritte Spalte in der Ausgabedatei (str2) ein String ist, der Kommata enthält, kann man sich schon denken, dass eine Anwendung in Schwierigkeiten kommt, wenn sie versucht, die comma1.txt-Datei zu lesen und die Zeilen in Spalten zu parsen. Doch der MySQL-Server hat für solche Situationen Vorsorge getroffen. Hier sehen Sie den Inhalt von comma1.txt: 1,This string has no commas,This string\, however\, has two commas
Vor die Kommata in der dritten Spalte (str2) wurden Backslashs als Escape-Zeichen gesetzt. Wenn Sie dieselbe Abfrage mit einem Pipe-getrennten Format laufen lassen, stehen vor den Kommata keine Escape-Zeichen, da sie nicht nötig sind. Möchten Sie ein anderes Escape-Zeichen verwenden, beispielsweise ein weiteres Komma, dann können Sie mit der Subklausel fields escaped by eines spezifizieren.
Max. Linie
Allerdings können Sie Trennzeichen nicht nur für Spalten, sondern auch für die verschiedenen Datensätze in Ihrer Datendatei angeben. Sollen diese Datensätze in der Ausgabedatei durch etwas anderes als das Newline-Zeichen getrennt werden, nehmen Sie die Subklausel lines:
286 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT emp_id, fname, lname, start_date -> INTO OUTFILE 'C:\\TEMP\\emp_list_atsign.txt' -> FIELDS TERMINATED BY '|' -> LINES TERMINATED BY '@' -> FROM employee; Query OK, 18 rows affected (0.03 sec)
Da ich kein Newline-Zeichen zwischen den Datensätzen verwende, sieht die Datei emp_ list_atsign.txt wie eine einzige lange Textzeile aus, in der die Datensätze durch das Zeichen '@' getrennt sind: 1|Michael|Smith|2001-06-22@2|Susan|Barker|2002-09-12@3|Robert|Tyler|2000-0209@4|Susan|Hawthorne|2002-04-24@5|John|Gooding|2003-11-14@6|Helen|Fleming|2004-0317@7|Chris|Tucker|2004-09-15@8|Sarah|Parker|2002-12-02@9|Jane|Grossman|2002-0503@10|Paula|Roberts|2002-07-27@11|Thomas|Ziegler|2000-10-23@12|Samantha|Jameson|2003-0108@13|John|Blake|2000-05-11@14|Cindy|Mason|2002-08-09@15|Frank|Portman|2003-0401@16|Theresa|Markham|2001-03-15@17|Beth|Fowler|2002-06-29@18|Rick|Tulman|2002-12-12@
Falls Sie einmal eine Datendatei generieren möchten, um sie in ein Tabellenkalkulationsprogramm zu laden oder inner- oder außerhalb Ihres Unternehmens zu verschicken, bietet Ihnen die into outfile-Klausel genügend Flexibilität, um jedes gewünschte Dateiformat zu wählen.
Kombinierte Insert/Update-Anweisungen Angenommen, Sie sollen eine Tabelle anlegen, die darüber informiert, welche Kunden welche Zweigstellen der Bank besuchen. Die Tabelle muss die Kundennummer, die Zweigstellennummer und eine datetime-Spalte aufweisen, in der festgehalten wird, wann der Kunde die Zweigstelle zum letzten Mal besucht hat. Immer wenn ein Kunde eine Zweigstelle erstmals betritt, wird eine neue Zeile in die Tabelle geschrieben, doch wenn er diese Zweigstelle schon vorher einmal besucht hatte, wird lediglich die Datumsspalte aktualisiert. Hier sehen Sie die Tabellen-Definition: CREATE TABLE branch_usage (branch_id SMALLINT UNSIGNED NOT NULL, cust_id INTEGER UNSIGNED NOT NULL, last_visited_on DATETIME, CONSTRAINT pk_branch_usage PRIMARY KEY (branch_id, cust_id) );
Zusammen mit den drei Spalten definiert die branch_usage-Tabelle einen PrimärschlüsselConstraint auf den Spalten branch_id und cust_id. Dadurch wird der Server nicht akzeptieren, dass der Tabelle Zeilen hinzugefügt werden, deren Zweigstelle/Kunde-Kombination bereits existiert.
Max. Linie
Angenommen, nach Einrichtung der Tabelle betritt der Kunde mit der Kundennummer 5 die Hauptstelle (Zweigstellennummer 1) allein in der ersten Woche drei Mal. Nach dem ersten Besuch wird eine Zeile in die branch_usage-Tabelle geschrieben, da für die Kundennummer 5 in Verbindung mit der Zweigstellennummer 1 noch kein Eintrag vorhanden ist:
Kombinierte Insert/Update-Anweisungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
287
Max. Linie
Links mysql> INSERT INTO branch_usage (branch_id, cust_id, last_visited_on) -> VALUES (1, 5, CURRENT_TIMESTAMP( )); Query OK, 1 row affected (0.02 sec)
Doch wenn der Kunde dieselbe Zweigstelle ein zweites Mal besucht, wird der bereits angelegte Eintrag lediglich aktualisiert, es wird kein neuer eingefügt. Sonst würde folgender Fehler entstehen: ERROR 1062 (23000): Duplicate entry '1-5' for key 1
Um diesen Fehler zu vermeiden, können Sie die Tabelle branch_usage abfragen, um festzustellen, ob die vorliegende Kunde/Zweigstelle-Kombination bereits existiert. Wenn nicht, wird ein Datensatz eingefügt; wenn ja, wird die vorhandene Zeile nur aktualisiert. Doch um Ihnen Mühe zu ersparen, haben die MySQL-Designer die insert-Anweisung erweitert: Sie können angeben, dass eine oder mehrere Spalten geändert werden sollen, wenn eine insert-Anweisung wegen eines Schlüsselduplikats scheitert. Die folgende Anweisung veranlasst den Server, die Spalte last_visited_on zu ändern, wenn die betreffende Kunde/Zweigstelle-Kombination in der branch_usage-Tabelle bereits angelegt ist: mysql> INSERT INTO branch_usage (branch_id, cust_id, last_visited_on) -> VALUES (1, 5, CURRENT_TIMESTAMP( )) -> ON DUPLICATE KEY UPDATE last_visited_on = CURRENT_TIMESTAMP( ); Query OK, 2 rows affected (0.02 sec)
Die on duplicate key-Klausel erlaubt es, dass dieselbe Anweisung jedes Mal ausgeführt wird, wenn der Kunde Nummer 5 Bankgeschäfte in der Zweigstelle Nummer 1 tätigt. Wenn man die Anweisung hundertmal ausführt, wird bei der ersten Ausführung eine einzige neue Zeile in die Tabelle geschrieben, und bei den nächsten 99 Ausführungen wird nur die Spalte last_visited_on auf das aktuelle Datum gesetzt. Diese Art von Operation nennt man auch Upsert, da sie eine Kombination aus update- und insert-Anweisung ist.
Alternative zum Replace-Befehl Vor Version 4.1 des MySQL-Servers wurden Upsert-Operationen mit der replace-Anweisung ausgeführt. Diese proprietäre Anweisung löscht eine vorhandene Zeile, wenn ein Primärschlüsselwert bereits existiert, und fügt dann die neue Zeile ein. Wenn Sie Version 4.1 oder höher verwenden, haben Sie die Wahl zwischen dem replace-Befehl und dem insert...on duplicate key-Befehl, wenn Sie Upsert-Operationen durchführen möchten.
Max. Linie
Allerdings führt der replace-Befehl eine Löschoperation durch, wenn er Doppelwerte findet. Das kann zu Spannungen führen, wenn Sie das InnoDB-Speichermodul nutzen und Fremdschlüssel-Constraints aktiviert haben. Wurden die Constraints mit der Option on delete cascade erstellt, können Zeilen anderer Tabellen ebenfalls automatisch gelöscht werden, wenn der replace-Befehl eine Zeile aus der Zieltabelle entfernt. Aus diesem Grund ist es generell sicherer, mit der on duplicate key-Klausel der insert Anweisung anstatt mit dem älteren replace-Befehl zu arbeiten.
288 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts Geordnete Updates und Deletes Weiter oben in diesem Anhang habe ich Ihnen gezeigt, wie man Abfragen mit einer Kombination aus limit-Klausel und order by-Klausel schreibt, um Rangordnungen herzustellen. Als Beispiel habe ich die drei Kassierer gesucht, die die meisten Konten eröffnet hatten. In MySQL können Sie die limit- und order by-Klausel auch in update- und delete-Anweisungen einsetzen und dadurch anhand eines Rankings ganz bestimmte Zeilen einer Tabelle löschen oder ändern. Angenommen, Sie sollen aus einer Tabelle Datensätze löschen, die dazu dienen, Kunden-Logins beim Onlinebanking-System der Bank zu verfolgen. Die Tabelle, in der die Kundennummer, das Datum und die Uhrzeit des Logins festgehalten werden, sieht folgendermaßen aus: CREATE TABLE login_history (cust_id INTEGER UNSIGNED NOT NULL, login_date DATETIME, CONSTRAINT pk_login_history PRIMARY KEY (cust_id, login_date) );
Die nächste Anweisung schreibt in die login_history-Tabelle Daten, indem sie einen Cross Join zwischen den Tabellen account und customer vornimmt und anhand der open_ date-Spalte der Kontentabelle die Login-Daten generiert: mysql> INSERT INTO login_history (cust_id, login_date) -> SELECT c.cust_id, -> ADDDATE(a.open_date, INTERVAL a.account_id * c.cust_id HOUR) -> FROM customer c CROSS JOIN account a; Query OK, 312 rows affected (0.03 sec) Records: 312 Duplicates: 0 Warnings: 0
In der Tabelle stehen jetzt 312 Zeilen mit relativ willkürlichen Daten. Ihre Aufgabe ist es, die Daten in der login_history-Tabelle einmal pro Monat zu betrachten, einen Report zu generieren, der anzeigt, wer das Onlinebanking nutzt, und dann alle außer den 50 jüngsten Einträgen aus der Tabelle zu löschen. Eine Möglichkeit wäre es, eine Abfrage mit order by und limit zu schreiben, um das 50st-letzte Login-Datum zu finden: mysql> SELECT login_date -> FROM login_history -> ORDER BY login_date DESC -> LIMIT 49,1; +---------------------+ | login_date | +---------------------+ | 2004-07-02 09:00:00 | +---------------------+ 1 row in set (0.00 sec)
Max. Linie
Mit dieser Information können Sie eine delete-Anweisung schreiben, die alle Daten löscht, deren login_date-Spalte kleiner ist (ein älteres Datum aufweist) als das von der Abfrage zurückgelieferte Datum:
Geordnete Updates und Deletes | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
289
Max. Linie
Links mysql> DELETE FROM login_history -> WHERE login_date < '2004-07-02 09:00:00'; Query OK, 262 rows affected (0.02 sec)
Die Tabelle enthält nun die 50 letzten Logins. Doch mit den MySQL-Erweiterungen kann dasselbe Ergebnis mit einer einzigen delete-Anweisung erzielt werden, die limit- und order by-Klauseln miteinander kombiniert. Nachdem die ursprünglichen 312 Zeilen der login_history-Tabelle abgerufen wurden, können Sie Folgendes tun: mysql> DELETE FROM login_history -> ORDER BY login_date ASC -> LIMIT 262; Query OK, 262 rows affected (0.05 sec)
Mit dieser Anweisung werden die Zeilen nach dem login_date in aufsteigender Reihenfolge sortiert und dann die ersten 262 gelöscht, sodass die 50 jüngsten Zeilen übrig bleiben. In diesem Beispiel musste ich die Anzahl der Zeilen in der Tabelle kennen, um die limit-Klausel konstruieren zu können (ursprünglich 312 Zeilen – 50 verbleibende Zeilen = 262 Löschungen). Es wäre besser, wenn man die Zeilen in absteigender Reihenfolge sortieren und den Server die ersten 50 Zeilen überspringen lassen könnte, um dann die verbleibenden Zeilen zu löschen: DELETE FROM login_history ORDER BY login_date DESC LIMIT 49, 9999999;
Doch leider gestattet MySQL nicht den optionalen zweiten Parameter, wenn die limit-Klausel in delete- oder update-Anweisungen benutzt wird.
Doch nicht nur zum Löschen, sondern auch zum Modifizieren von Daten sind die limitund order by-Klauseln nützlich. Angenommen, die Bank beschließt, Kundentreue zu belohnen, und überweist auf die zehn ältesten Konten jeweils $100: mysql> UPDATE account -> SET avail_balance = avail_balance + 100 -> WHERE product_cd IN ('CHK', 'SAV', 'MM') -> ORDER BY open_date ASC -> LIMIT 10; Query OK, 10 rows affected (0.06 sec) Rows matched: 10 Changed: 10 Warnings: 0
Diese Anweisung sortiert die Konten in aufsteigender Reihenfolge nach Eröffnungsdatum und modifiziert dann die ersten zehn Datensätze, die in diesem Fall die zehn ältesten Konten betreffen.
Max. Linie
Max. Linie 290 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Rechts Updates und Deletes mit mehreren Tabellen In manchen Situationen müssen Sie vielleicht Daten aus mehreren verschiedenen Tabellen ändern oder löschen, um eine bestimmte Aufgabe zu lösen. Wenn Sie in der BankDatenbank einen Dummy-Kunden vorfinden, der vom letzten Systemtest übrig geblieben ist, könnten Sie beispielsweise Daten aus den Tabellen account, customer und individual löschen. Für diesen Abschnitt lege ich Klone der Tabellen account, customer und individual an und nenne sie account2, customer2 und individual2. Das hat zwei Gründe: Erstens schützt es die Beispieldaten vor Änderungen, und zweitens verhindert es Probleme mit Fremdschlüssel-Constraints zwischen den Tabellen (darüber später mehr). Hier sehen Sie die create table-Anweisungen, mit denen die drei Klon-Tabellen angelegt werden: CREATE TABLE individual2 AS SELECT * FROM individual; CREATE TABLE customer2 AS SELECT * FROM customer; CREATE TABLE account2 AS SELECT * FROM account;
Wenn die Kundennummer des Dummy-Kunden die 1 ist, können Sie drei separate delete-Anweisungen für die drei Tabellen schreiben: DELETE FROM account2 WHERE cust_id = 1; DELETE FROM customer2 WHERE cust_id = 1; DELETE FROM individual2 WHERE cust_id = 1;
Doch anstelle dieser einzelnen delete-Anweisungen ermöglicht MySQL auch die Formulierung einer einzigen delete-Anweisung für mehrere Tabellen: mysql> DELETE account2, customer2, individual2 -> FROM account2 INNER JOIN customer2 -> ON account2.cust_id = customer2.cust_id -> INNER JOIN individual2 -> ON customer2.cust_id = individual2.cust_id -> WHERE individual2.cust_id = 1; Query OK, 5 rows affected (0.02 sec)
Max. Linie
Diese Anweisung entfernt insgesamt fünf Zeilen, je eine aus individual2 und customer2 und drei aus account2 (die Kundennummer 1 hat drei Konten). Die Anweisung enthält drei separate Klauseln:
Updates und Deletes mit mehreren Tabellen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
291
Max. Linie
Links delete
Gibt die Zieltabellen für die Löschoperation an. from
Gibt die Tabellen an, die zur Identifikation der zu löschenden Zeilen verwendet werden. Diese Klausel ist in Form und Funktion identisch mit einer from-Klausel in einer select-Anweisung. Nicht alle darin genannten Tabellen müssen in der delete-Klausel vorkommen. where
Enthält Filterbedingungen, um die zu löschenden Zeilen zu identifizieren. Die delete-Anweisung für mehrere Tabellen sieht ganz so aus wie eine select-Anweisung, nur dass anstelle der select-Klausel eine delete-Klausel steht. Wenn Sie Zeilen aus einer einzelnen Tabelle mit einer Mehrtabellen-delete-Anweisung löschen, merkt man den Unterschied noch weniger. Hier sehen Sie zum Beispiel eine select-Anweisung, die die Kontonummern aller Konten von John Hayward findet: mysql> SELECT account2.account_id -> FROM account2 INNER JOIN customer2 -> ON account2.cust_id = customer2.cust_id -> INNER JOIN individual2 -> ON individual2.cust_id = customer2.cust_id -> WHERE individual2.fname = 'John' -> AND individual2.lname = 'Hayward'; +------------+ | account_id | +------------+ | 8 | | 9 | | 10 | +------------+ 3 rows in set (0.01 sec)
Wenn Sie nach Durchsicht der Resultate entscheiden, alle drei Konten von Hayward aus der account2-Tabelle zu löschen, müssen Sie nur die select-Klausel der obigen Abfrage durch eine delete-Klausel ersetzen, in der die account2-Tabelle genannt wird: mysql> DELETE account2 -> FROM account2 INNER JOIN customer2 -> ON account2.cust_id = customer2.cust_id -> INNER JOIN individual2 -> ON customer2.cust_id = individual2.cust_id -> WHERE individual2.fname = 'John' -> AND individual2.lname = 'Hayward'; Query OK, 3 rows affected (0.01 sec)
Max. Linie
Hoffentlich hat Ihnen dies einen Eindruck davon vermittelt, wofür die delete- und fromKlauseln in einer Mehrtabellen-delete-Anweisung gut sind. Diese Anweisung ist funktionell identisch mit folgender Einzeltabellen-delete-Anweisung, die eine Unterabfrage verwendet, um die Kundennummer von John Hayward zu ermitteln:
292 | Anhang B: MySQL-Erweiterungen für SQL This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts DELETE FROM account2 WHERE cust_id = (SELECT cust_id FROM individual2 WHERE fname = 'John' AND lname = 'Hayward';
Wenn Sie eine Mehrtabellen-delete-Anweisung verwenden, um Zeilen aus nur einer einzigen Tabelle zu löschen, bedeutet dies lediglich, dass Sie ein abfrageähnliches Format mit Tabellen-Joins anstelle einer normalen delete-Anweisung mit Unterabfragen nutzen. Die eigentliche Mächtigkeit der Mehrtabellen-delete-Anweisungen liegt in ihrer Fähigkeit, in einer einzigen Anweisung Daten aus mehreren Tabellen zu löschen, wie es in der ersten Anweisung in diesem Abschnitt gezeigt wurde. Mit MySQL können Sie allerdings nicht nur Zeilen aus mehreren Tabellen löschen, sondern auch modifizieren (sogenanntes Mehrtabellen-Update). Angenommen, Ihre Bank schließt sich mit einer anderen Bank zusammen, und in den Datenbanken der beiden Banken überschneiden sich einige Kundennummern. Ihr Management beschließt, das Problem zu lösen, indem zu jeder Kundennummer Ihrer Datenbank die Zahl 10.000 hinzuaddiert wird, damit die Daten der zweiten Bank sicher importiert werden können. Die folgende Anweisung zeigt, wie die Kundennummer 3 in den Tabellen individual2, customer2 und account2 mit einer einzigen Anweisung geändert wird: mysql> UPDATE individual2 INNER JOIN customer2 -> ON individual2.cust_id = customer2.cust_id -> INNER JOIN account2 -> ON customer2.cust_id = account2.cust_id -> SET individual2.cust_id = individual2.cust_id + 10000, -> customer2.cust_id = customer2.cust_id + 10000, -> account2.cust_id = account2.cust_id + 10000 -> WHERE individual2.cust_id = 3; Query OK, 4 rows affected (0.01 sec) Rows matched: 5 Changed: 4 Warnings: 0
Diese Anweisung ändert vier Zeilen: je eine in den Tabellen individual2 und customer2 und zwei in der Tabelle account2. Die Syntax eines Mehrtabellen-update ist der eines Einzeltabellen-update sehr ähnlich, nur dass die update-Klausel mehrere Tabellen und die zugehörigen Join-Bedingungen aufführt, anstatt nur eine einzelne Tabelle zu benennen. Wie das Einzeltabellen-update hat auch die Version mit mehreren Tabellen eine set-Klausel. Der Unterschied besteht darin, dass Tabellen, die in der update-Klausel referenziert werden, in der set-Klausel modifiziert werden.
Max. Linie
Wenn Sie das InnoDB-Speichermodul nutzen, können Sie wahrscheinlich nicht mit Mehrtabellen-delete- und -update-Anweisungen arbeiten, wenn die beteiligten Tabellen Fremdschlüssel-Constraints aufweisen. Das Speichermodul garantiert nämlich nicht, dass die Änderungen in einer Reihenfolge vorgenommen werden, die den Constraints entspricht. Verwenden Sie bitte in diesem Fall stattdessen Einzeltabellen-Anweisungen in der richtigen Reihenfolge, um die Fremdschlüssel-Constraints nicht zu verletzen.
Updates und Deletes mit mehreren Tabellen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 293
First
Anhang C
ANHANG C
Lösungen der Übungen
Kapitel 3 3-1 Fragen Sie die Personalnummer (Employee ID), den Vornamen und den Nachnamen aller Bankangestellten ab. Sortieren Sie die Daten zuerst nach Nachnamen und dann nach Vornamen.
Max. Linie
mysql> SELECT emp_id, fname, lname -> FROM employee -> ORDER BY lname, fname; +--------+----------+-----------+ | emp_id | fname | lname | +--------+----------+-----------+ | 2 | Susan | Barker | | 13 | John | Blake | | 6 | Helen | Fleming | | 17 | Beth | Fowler | | 5 | John | Gooding | | 9 | Jane | Grossman | | 4 | Susan | Hawthorne | | 12 | Samantha | Jameson | | 16 | Theresa | Markham | | 14 | Cindy | Mason | | 8 | Sarah | Parker | | 15 | Frank | Portman | | 10 | Paula | Roberts | | 1 | Michael | Smith | | 7 | Chris | Tucker | | 18 | Rick | Tulman | | 3 | Robert | Tyler | | 11 | Thomas | Ziegler | +--------+----------+-----------+ 18 rows in set (0.01 sec)
Max. Linie |
This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
295
Links 3-2 Fragen Sie die Kontonummer (Account ID), die Kundennummer (Customer ID) und den Saldo (balance) für alle Konten ab, deren Status gleich 'ACTIVE' und deren Saldo größer als $2.500 ist. mysql> SELECT account_id, cust_id, avail_balance -> FROM account -> WHERE status = 'ACTIVE' -> AND avail_balance > 2500; +------------+---------+---------------+ | account_id | cust_id | avail_balance | +------------+---------+---------------+ | 3 | 1 | 3000.00 | | 10 | 4 | 5487.09 | | 13 | 6 | 10000.00 | | 14 | 7 | 5000.00 | | 15 | 8 | 3487.19 | | 18 | 9 | 9345.55 | | 20 | 10 | 23575.12 | | 22 | 11 | 9345.55 | | 23 | 12 | 38552.05 | | 24 | 13 | 50000.00 | +------------+---------+---------------+ 10 rows in set (0.00 sec)
3-3 Schreiben Sie für die account-Tabelle eine Abfrage, die die IDs der Mitarbeiter liefert, die Konten eröffnet haben (verwenden Sie dazu die Spalte account.open_emp_id). Für jeden Angestellten soll eine einzige Zeile zurückgeliefert werden. mysql> SELECT DISTINCT open_emp_id -> FROM account; +-------------+ | open_emp_id | +-------------+ | 1 | | 10 | | 13 | | 16 | +-------------+ 4 rows in set (0.00 sec)
3-4 Füllen Sie die (durch gekennzeichneten) Lücken für diese Abfrage mehrerer Datensätze aus, um die unten gezeigten Ergebnisse zu erhalten.
Max. Linie
mysql> SELECT p.product_cd, a.cust_id, a.avail_balance -> FROM product p INNER JOIN account -> ON p.product_cd =
296 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts -> WHERE p. = 'ACCOUNT'; +------------+---------+---------------+ | product_cd | cust_id | avail_balance | +------------+---------+---------------+ | CD | 1 | 3000.00 | | CD | 6 | 10000.00 | | CD | 7 | 5000.00 | | CD | 9 | 1500.00 | | CHK | 1 | 1057.75 | | CHK | 2 | 2258.02 | | CHK | 3 | 1057.75 | | CHK | 4 | 534.12 | | CHK | 5 | 2237.97 | | CHK | 6 | 122.37 | | CHK | 8 | 3487.19 | | CHK | 9 | 125.67 | | CHK | 10 | 23575.12 | | CHK | 12 | 38552.05 | | MM | 3 | 2212.50 | | MM | 4 | 5487.09 | | MM | 9 | 9345.55 | | SAV | 1 | 500.00 | | SAV | 2 | 200.00 | | SAV | 4 | 767.77 | | SAV | 8 | 387.99 | +------------+---------+---------------+ 21 rows in set (0.02 sec)
Die richtigen Werte für , und sind: 1. a 2. a.product_cd 3. product_type_cd
Kapitel 4 4-1 Welche Transaction IDs würden durch folgende Filterbedingungen zurückgegeben? txn_date < '2005-02-26' AND (txn_type_cd = 'DBT' OR amount > 100)
Die Transaction IDs 1, 2, 3, 5, 6 und 7.
4-2 Welche Transaction IDs würden durch diese Filterbedingungen zurückgegeben?
Max. Linie
Max. Linie
account_id IN (101,103) AND NOT (txn_type_cd = 'DBT' OR amount > 100)
Die Transaction IDs 4 und 9.
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
297
Links 4-3 Schreiben Sie eine Abfrage, die alle im Jahr 2002 eröffneten Konten zurückliefert. mysql> SELECT account_id, open_date -> FROM account -> WHERE open_date BETWEEN '2002-01-01' AND '2002-12-31'; +------------+------------+ | account_id | open_date | +------------+------------+ | 6 | 2002-11-23 | | 7 | 2002-12-15 | | 12 | 2002-08-24 | | 20 | 2002-09-30 | | 21 | 2002-10-01 | +------------+------------+ 5 rows in set (0.01 sec)
4-4 Schreiben Sie eine Abfrage, die alle Privatkunden ermittelt, deren Nachname an zweiter Stelle ein 'a' und danach irgendwo ein 'e' enthält. mysql> SELECT cust_id, lname, fname -> FROM individual -> WHERE lname LIKE '_a%e%'; +---------+--------+---------+ | cust_id | lname | fname | +---------+--------+---------+ | 1 | Hadley | James | | 9 | Farley | Richard | +---------+--------+---------+ 2 rows in set (0.02 sec)
Kapitel 5 5-1 Füllen Sie die (durch gekennzeichneten) Lücken der folgenden Abfrage aus, um die gezeigten Resultate zu erhalten:
Max. Linie
mysql> SELECT e.emp_id, e.fname, e.lname, b.name -> FROM employee e INNER JOIN b -> ON e.assigned_branch_id = b.; +--------+----------+-----------+---------------+ | emp_id | fname | lname | name | +--------+----------+-----------+---------------+ | 1 | Michael | Smith | Headquarters | | 2 | Susan | Barker | Headquarters | | 3 | Robert | Tyler | Headquarters | | 4 | Susan | Hawthorne | Headquarters |
298 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | 5 | John | Gooding | Headquarters | | 6 | Helen | Fleming | Headquarters | | 7 | Chris | Tucker | Headquarters | | 8 | Sarah | Parker | Headquarters | | 9 | Jane | Grossman | Headquarters | | 10 | Paula | Roberts | Woburn Branch | | 11 | Thomas | Ziegler | Woburn Branch | | 12 | Samantha | Jameson | Woburn Branch | | 13 | John | Blake | Quincy Branch | | 14 | Cindy | Mason | Quincy Branch | | 15 | Frank | Portman | Quincy Branch | | 16 | Theresa | Markham | So. NH Branch | | 17 | Beth | Fowler | So. NH Branch | | 18 | Rick | Tulman | So. NH Branch | +--------+----------+-----------+---------------+ 18 rows in set (0.03 sec)
Die korrekten Werte für und sind: 1. branch 2. branch_id
5-2 Schreiben Sie eine Abfrage, die die Kontonummer (Account ID) jedes Privatkunden (customer.cust_type_cd = 'I') zusammen mit seiner Federal ID (customer.fed_id) und dem Namen des Produkts ausgibt, auf dem sein Konto basiert (product.name).
Max. Linie
mysql> SELECT a.account_id, c.fed_id, p.name -> FROM account a INNER JOIN customer c -> ON a.cust_id = c.cust_id -> INNER JOIN product p -> ON a.product_cd = p.product_cd -> WHERE c.cust_type_cd = 'I'; +------------+-------------+------------------------+ | account_id | fed_id | name | +------------+-------------+------------------------+ | 1 | 111-11-1111 | checking account | | 2 | 111-11-1111 | savings account | | 3 | 111-11-1111 | certificate of deposit | | 4 | 222-22-2222 | checking account | | 5 | 222-22-2222 | savings account | | 6 | 333-33-3333 | checking account | | 7 | 333-33-3333 | money market account | | 8 | 444-44-4444 | checking account | | 9 | 444-44-4444 | savings account | | 10 | 444-44-4444 | money market account | | 11 | 555-55-5555 | checking account | | 12 | 666-66-6666 | checking account | | 13 | 666-66-6666 | certificate of deposit | | 14 | 777-77-7777 | certificate of deposit | | 15 | 888-88-8888 | checking account | | 16 | 888-88-8888 | savings account |
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 299
Links | 17 | 999-99-9999 | checking account | | 18 | 999-99-9999 | money market account | | 19 | 999-99-9999 | certificate of deposit | +------------+-------------+------------------------+ 19 rows in set (0.00 sec)
5-3 Schreiben Sie eine Abfrage, die alle Mitarbeiter findet, deren Vorgesetzter einer anderen Abteilung angehört. Fragen Sie die Employee IDs, den Vornamen (First Name) und den Nachnamen (Last Name) des Mitarbeiters ab. mysql> SELECT e.emp_id, e.fname, e.lname -> FROM employee e INNER JOIN employee mgr -> ON e.superior_emp_id = mgr.emp_id -> WHERE e.dept_id != mgr.dept_id; +--------+-------+-----------+ | emp_id | fname | lname | +--------+-------+-----------+ | 4 | Susan | Hawthorne | | 5 | John | Gooding | +--------+-------+-----------+ 2 rows in set (0.00 sec)
Kapitel 6 6-1 Es seien Menge A = {L M N O P} und Menge B = {P Q R S T}. Welche Mengen werden von folgenden Operationen generiert? • A union B • A union all B • A intersect B • A except B
• A union B = {L M N O P Q R S T} • A union all B = {L M N O P P Q R S T} • A intersect B = {P} • A except B = {L M N O}
6-2
Max. Linie
Schreiben Sie eine zusammengesetzte Abfrage, die die Vor- und Nachnamen aller Privatkunden sowie die Vor- und Nachnamen aller Mitarbeiter findet.
300 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT fname, lname -> FROM individual -> UNION -> SELECT fname, lname -> FROM employee; +----------+-----------+ | fname | lname | +----------+-----------+ | James | Hadley | | Susan | Tingley | | Frank | Tucker | | John | Hayward | | Charles | Frasier | | John | Spencer | | Margaret | Young | | Louis | Blake | | Richard | Farley | | Michael | Smith | | Susan | Barker | | Robert | Tyler | | Susan | Hawthorne | | John | Gooding | | Helen | Fleming | | Chris | Tucker | | Sarah | Parker | | Jane | Grossman | | Paula | Roberts | | Thomas | Ziegler | | Samantha | Jameson | | John | Blake | | Cindy | Mason | | Frank | Portman | | Theresa | Markham | | Beth | Fowler | | Rick | Tulman | +----------+-----------+ 27 rows in set (0.01 sec)
6-3 Sortieren Sie die Ergebnisse aus Übung 6-2 nach der Spalte lname.
Max. Linie
mysql> SELECT fname, lname -> FROM individual -> UNION ALL -> SELECT fname, ename -> FROM employee -> ORDER BY lname; +----------+-----------+ | fname | lname | +----------+-----------+ | Susan | Barker | | Louis | Blake |
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 301
Links | John | Blake | | Richard | Farley | | Helen | Fleming | | Beth | Fowler | | Charles | Frasier | | John | Gooding | | Jane | Grossman | | James | Hadley | | Susan | Hawthorne | | John | Hayward | | Samantha | Jameson | | Theresa | Markham | | Cindy | Mason | | Sarah | Parker | | Frank | Portman | | Paula | Roberts | | Michael | Smith | | John | Spencer | | Susan | Tingley | | Chris | Tucker | | Frank | Tucker | | Rick | Tulman | | Robert | Tyler | | Margaret | Young | | Thomas | Ziegler | +----------+-----------+ 27 rows in set (0.01 sec)
Kapitel 7 7-1 Schreiben Sie eine Abfrage, die das 17. bis 25. Zeichen des Strings »Please find the substring in this string« findet. mysql> SELECT SUBSTRING('Please find the substring in this string',17,9); +------------------------------------------------------------+ | SUBSTRING('Please find the substring in this string',17,9) | +------------------------------------------------------------+ | substring | +------------------------------------------------------------+ 1 row in set (0.00 sec)
7-2
Max. Linie
Schreiben Sie eine Abfrage, die den absoluten Wert und das Vorzeichen (–1, 0 oder 1) der Zahl –25.76823 findet. Geben Sie die Zahl außerdem auf das nächste Hundertstel gerundet zurück.
302 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts mysql> SELECT ABS(-25.76823), SIGN(-25.76823), ROUND(-25.76823, 2); +----------------+-----------------+---------------------+ | ABS(-25.76823) | SIGN(-25.76823) | ROUND(-25.76823, 2) | +----------------+-----------------+---------------------+ | 25.76823 | -1 | -25.77 | +----------------+-----------------+---------------------+ 1 row in set (0.00 sec)
7-3 Schreiben Sie eine Abfrage, die nur den Monat des aktuellen Datums zurückliefert. mysql> SELECT EXTRACT(MONTH FROM CURRENT_DATE( )); +----------------------------------+ | EXTRACT(MONTH FROM CURRENT_DATE) | +----------------------------------+ | 5 | +----------------------------------+ 1 row in set (0.02 sec)
(Wahrscheinlich bekommen Sie ein anderes Ergebnis, es sei denn, Sie führen diese Abfrage ausgerechnet im Monat Mai aus.)
Kapitel 8 8-1 Schreiben Sie eine Abfrage, um die Zeilen der account-Tabelle zu zählen. mysql> SELECT COUNT(*) -> FROM account; +----------+ | count(*) | +----------+ | 24 | +----------+ 1 row in set (0.32 sec)
8-2 Ändern Sie die Abfrage aus Übung 8-1 so, dass sie die Konten pro Kunden zählt. Zeigen Sie für jeden Kunden die Kundennummer (Customer ID) und die Anzahl der Konten an.
Max. Linie
mysql> SELECT cust_id, COUNT(*) -> FROM account -> GROUP BY cust_id; +---------+----------+ | cust_id | count(*) | +---------+----------+ | 1 | 3 | | 2 | 2 |
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 303
Links | 3 | 2 | | 4 | 3 | | 5 | 1 | | 6 | 2 | | 7 | 1 | | 8 | 2 | | 9 | 3 | | 10 | 2 | | 11 | 1 | | 12 | 1 | | 13 | 1 | +---------+----------+ 13 rows in set (0.00 sec)
8-3 Ändern Sie die Abfrage aus Übung 8-2 so, dass nur Kunden gemeldet werden, die mindestens zwei Konten haben. mysql> SELECT cust_id, COUNT(*) -> FROM account -> GROUP BY cust_id -> HAVING COUNT(*) >= 2; +---------+----------+ | cust_id | COUNT(*) | +---------+----------+ | 1 | 3 | | 2 | 2 | | 3 | 2 | | 4 | 3 | | 6 | 2 | | 8 | 2 | | 9 | 3 | | 10 | 2 | +---------+----------+ 8 rows in set (0.04 sec)
8-4 (für Tüftler) Finden Sie den Gesamtsaldo (available balance) nach Produkt und Zweigstelle heraus, wo mehrere Konten pro Produkt und Zweigstelle existieren. Ordnen Sie die Ergebnisse nach dem Gesamtsaldo (vom höchsten bis zum niedrigsten).
Max. Linie
mysql> SELECT product_cd, open_branch_id, SUM(avail_balance) -> FROM account -> GROUP BY product_cd, open_branch_id -> HAVING COUNT(*) > 1 -> ORDER BY 3 DESC; +------------+----------------+--------------------+ | product_cd | open_branch_id | SUM(avail_balance) | +------------+----------------+--------------------+ | CHK | 4 | 67852.33 |
304 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | MM | 1 | 14832.64 | | CD | 1 | 11500.00 | | CD | 2 | 8000.00 | | CHK | 2 | 3315.77 | | CHK | 1 | 782.16 | | SAV | 2 | 700.00 | +------------+----------------+--------------------+ 7 rows in set (0.01 sec)
Hinweis: Da MySQL ORDER BY SUM(avail_balance) DESC; nicht akzeptieren würde, war ich gezwungen, die Sortierspalte anhand ihrer Position anzugeben.
Kapitel 9 9-1 Schreiben Sie für die account-Tabelle eine Abfrage, die eine Filterbedingung mit einer nicht-korrelierten Unterabfrage der product-Tabelle verwendet, um alle Darlehenskonten zu finden (product.product_type_cd = 'LOAN'). Rufen Sie die Kontonummer (Account ID), den Produktcode, die Kundennummer (Customer ID) und den verfügbaren Saldo (available balance) ab. mysql> SELECT account_id, product_cd, cust_id, avail_balance -> FROM account -> WHERE product_cd IN (SELECT product_cd -> FROM product -> WHERE product_type_cd = 'LOAN'); +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+ | 21 | BUS | 10 | 0.00 | | 22 | BUS | 11 | 9345.55 | | 24 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 3 rows in set (0.07 sec)
9-2 Überarbeiten Sie die Abfrage aus 9-1, indem Sie eine korrelierte Unterabfrage der productTabelle verwenden, um dieselben Ergebnisse zu erhalten.
Max. Linie
mysql> SELECT a.account_id, a.product_cd, a.cust_id, a.avail_balance -> FROM account a -> WHERE EXISTS (SELECT 1 -> FROM product p -> WHERE p.product_cd = a.product_cd -> AND p.product_type_cd = 'LOAN'); +------------+------------+---------+---------------+ | account_id | product_cd | cust_id | avail_balance | +------------+------------+---------+---------------+
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 305
Links | 21 | BUS | 10 | 0.00 | | 22 | BUS | 11 | 9345.55 | | 24 | SBL | 13 | 50000.00 | +------------+------------+---------+---------------+ 3 rows in set (0.01 sec)
9-3 Verbinden Sie folgende Abfrage per Join mit der employee-Tabelle, um zu zeigen, wie erfahren die einzelnen Mitarbeiter sind: SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt UNION ALL SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt UNION ALL SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt
Geben Sie der Unterabfrage den Alias »levels« und fragen Sie nach der Personalnummer (Employee ID), dem Vornamen, dem Nachnamen und der Erfahrung (levels.name). (Hinweis: Schreiben Sie eine Join-Bedingung, die mithilfe einer Ungleichheitsbedingung feststellt, welcher Level für die Spalte employee.start_date gilt.)
Max. Linie
mysql> SELECT e.emp_id, e.fname, e.lname, levels.name -> FROM employee e INNER JOIN -> (SELECT 'trainee' name, '2004-01-01' start_dt, '2005-12-31' end_dt -> UNION ALL -> SELECT 'worker' name, '2002-01-01' start_dt, '2003-12-31' end_dt -> UNION ALL -> SELECT 'mentor' name, '2000-01-01' start_dt, '2001-12-31' end_dt) levels -> ON e.start_date BETWEEN levels.start_dt AND levels.end_dt; +--------+----------+-----------+---------+ | emp_id | fname | lname | name | +--------+----------+-----------+---------+ | 6 | Helen | Fleming | trainee | | 7 | Chris | Tucker | trainee | | 2 | Susan | Barker | worker | | 4 | Susan | Hawthorne | worker | | 5 | John | Gooding | worker | | 8 | Sarah | Parker | worker | | 9 | Jane | Grossman | worker | | 10 | Paula | Roberts | worker | | 12 | Samantha | Jameson | worker | | 14 | Cindy | Mason | worker | | 15 | Frank | Portman | worker | | 17 | Beth | Fowler | worker | | 18 | Rick | Tulman | worker | | 1 | Michael | Smith | mentor | | 3 | Robert | Tyler | mentor | | 11 | Thomas | Ziegler | mentor | | 13 | John | Blake | mentor | | 16 | Theresa | Markham | mentor | +--------+----------+-----------+---------+ 18 rows in set (0.00 sec)
306 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts 9-4 Schreiben Sie eine Abfrage der employee-Tabelle, die die Personalnummer (Employee ID), den Vornamen, den Nachnamen sowie die Namen der Abteilung (department) und der Zweigstelle (branch) liefert, in der der betreffende Mitarbeiter arbeitet. Verwenden Sie keine Tabellen-Joins. mysql> SELECT e.emp_id, e.fname, e.lname, -> (SELECT d.name FROM department d -> WHERE d.dept_id = e.dept_id) dept_name, -> (SELECT b.name FROM branch b -> WHERE b.branch_id = e.assigned_branch_id) branch_name -> FROM employee e; +--------+----------+-----------+----------------+---------------+ | emp_id | fname | lname | dept_name | branch_name | +--------+----------+-----------+----------------+---------------+ | 1 | Michael | Smith | Administration | Headquarters | | 2 | Susan | Barker | Administration | Headquarters | | 3 | Robert | Tyler | Administration | Headquarters | | 4 | Susan | Hawthorne | Operations | Headquarters | | 5 | John | Gooding | Loans | Headquarters | | 6 | Helen | Fleming | Operations | Headquarters | | 7 | Chris | Tucker | Operations | Headquarters | | 8 | Sarah | Parker | Operations | Headquarters | | 9 | Jane | Grossman | Operations | Headquarters | | 10 | Paula | Roberts | Operations | Woburn Branch | | 11 | Thomas | Ziegler | Operations | Woburn Branch | | 12 | Samantha | Jameson | Operations | Woburn Branch | | 13 | John | Blake | Operations | Quincy Branch | | 14 | Cindy | Mason | Operations | Quincy Branch | | 15 | Frank | Portman | Operations | Quincy Branch | | 16 | Theresa | Markham | Operations | So. NH Branch | | 17 | Beth | Fowler | Operations | So. NH Branch | | 18 | Rick | Tulman | Operations | So. NH Branch | +--------+----------+-----------+----------------+---------------+ 18 rows in set (0.12 sec)
Kapitel 10 10-1 Schreiben Sie eine Abfrage, die jeden Produktnamen zusammen mit den zugehörigen Konten zurückgibt (nutzen Sie dabei die product_cd-Spalte der account-Tabelle, um eine Verbindung zur product-Tabelle zu schaffen). Beziehen Sie alle Produkte ein, auch die, zu denen noch keine Konten eröffnet wurden.
Max. Linie
mysql> SELECT p.product_cd, a.account_id, a.cust_id, a.avail_balance -> FROM product p LEFT OUTER JOIN account a -> ON p.product_cd = a.product_cd;
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 307
Links +------------+------------+---------+---------------+ | product_cd | account_id | cust_id | avail_balance | +------------+------------+---------+---------------+ | AUT | NULL | NULL | NULL | | BUS | 21 | 10 | 0.00 | | BUS | 22 | 11 | 9345.55 | | CD | 3 | 1 | 3000.00 | | CD | 13 | 6 | 10000.00 | | CD | 14 | 7 | 5000.00 | | CD | 19 | 9 | 1500.00 | | CHK | 1 | 1 | 1057.75 | | CHK | 4 | 2 | 2258.02 | | CHK | 6 | 3 | 1057.75 | | CHK | 8 | 4 | 534.12 | | CHK | 11 | 5 | 2237.97 | | CHK | 12 | 6 | 122.37 | | CHK | 15 | 8 | 3487.19 | | CHK | 17 | 9 | 125.67 | | CHK | 20 | 10 | 23575.12 | | CHK | 23 | 12 | 38552.05 | | MM | 7 | 3 | 2212.50 | | MM | 10 | 4 | 5487.09 | | MM | 18 | 9 | 9345.55 | | MRT | NULL | NULL | NULL | | SAV | 2 | 1 | 500.00 | | SAV | 5 | 2 | 200.00 | | SAV | 9 | 4 | 767.77 | | SAV | 16 | 8 | 387.99 | | SBL | 24 | 13 | 50000.00 | +------------+------------+---------+---------------+ 26 rows in set (0.01 sec)
10-2 Überarbeiten Sie die Abfrage aus Übung 10-1 so, dass der andere Outer Join-Typ verwendet wird (d.h., wenn Sie in Übung 10-1 einen Left Outer Join verwendet haben, benutzen Sie jetzt einen Right Outer Join). Es sollen dieselben Ergebnisse herauskommen.
Max. Linie
mysql> SELECT p.product_cd, a.account_id, a.cust_id, a.avail_balance -> FROM account a RIGHT OUTER JOIN product p -> ON p.product_cd = a.product_cd; +------------+------------+---------+---------------+ | product_cd | account_id | cust_id | avail_balance | +------------+------------+---------+---------------+ | AUT | NULL | NULL | NULL | | BUS | 21 | 10 | 0.00 | | BUS | 22 | 11 | 9345.55 | | CD | 3 | 1 | 3000.00 | | CD | 13 | 6 | 10000.00 | | CD | 14 | 7 | 5000.00 | | CD | 19 | 9 | 1500.00 | | CHK | 1 | 1 | 1057.75 | | CHK | 4 | 2 | 2258.02 |
308 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | CHK | 6 | 3 | 1057.75 | | CHK | 8 | 4 | 534.12 | | CHK | 11 | 5 | 2237.97 | | CHK | 12 | 6 | 122.37 | | CHK | 15 | 8 | 3487.19 | | CHK | 17 | 9 | 125.67 | | CHK | 20 | 10 | 23575.12 | | CHK | 23 | 12 | 38552.05 | | MM | 7 | 3 | 2212.50 | | MM | 10 | 4 | 5487.09 | | MM | 18 | 9 | 9345.55 | | MRT | NULL | NULL | NULL | | SAV | 2 | 1 | 500.00 | | SAV | 5 | 2 | 200.00 | | SAV | 9 | 4 | 767.77 | | SAV | 16 | 8 | 387.99 | | SBL | 24 | 13 | 50000.00 | +------------+------------+---------+---------------+ 26 rows in set (0.02 sec)
10-3 Schreiben Sie einen Outer Join der account-Tabelle mit den Tabellen individual und business (auf der Spalte account.cust_id), sodass die Ergebnismenge eine Zeile pro Konto enthält. Es sollen die Spalten account.account_id, account.product_cd, individual.fname, individual.lname und business.name einbezogen werden.
Max. Linie
mysql> SELECT a.account_id, a.product_cd, -> i.fname, i.lname, b.name -> FROM account a LEFT OUTER JOIN business b -> ON a.cust_id = b.cust_id -> LEFT OUTER JOIN individual i -> ON a.cust_id = i.cust_id; +------------+------------+----------+---------+------------------------+ | account_id | product_cd | fname | lname | name | +------------+------------+----------+---------+------------------------+ | 1 | CHK | James | Hadley | NULL | | 2 | SAV | James | Hadley | NULL | | 3 | CD | James | Hadley | NULL | | 4 | CHK | Susan | Tingley | NULL | | 5 | SAV | Susan | Tingley | NULL | | 6 | CHK | Frank | Tucker | NULL | | 7 | MM | Frank | Tucker | NULL | | 8 | CHK | John | Hayward | NULL | | 9 | SAV | John | Hayward | NULL | | 10 | MM | John | Hayward | NULL | | 11 | CHK | Charles | Frasier | NULL | | 12 | CHK | John | Spencer | NULL | | 13 | CD | John | Spencer | NULL | | 14 | CD | Margaret | Young | NULL | | 15 | CHK | Louis | Blake | NULL | | 16 | SAV | Louis | Blake | NULL |
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 309
Links | 17 | CHK | Richard | Farley | NULL | | 18 | MM | Richard | Farley | NULL | | 19 | CD | Richard | Farley | NULL | | 20 | CHK | NULL | NULL | Chilton Engineering | | 21 | BUS | NULL | NULL | Chilton Engineering | | 22 | BUS | NULL | NULL | Northeast Cooling Inc. | | 23 | CHK | NULL | NULL | Superior Auto Body | | 24 | SBL | NULL | NULL | AAA Insurance Inc. | +------------+------------+----------+---------+------------------------+ 24 rows in set (0.05 sec)
10-4 (für Tüftler) Überlegen Sie sich eine Abfrage, um die Menge {1, 2, 3, …, 99, 100} zu generieren. (Hinweis: Verwenden Sie einen Cross Join mit mindestens zwei from-Klausel-Unterabfragen.) SELECT ones.x + tens.x + 1 FROM (SELECT 0 x UNION ALL SELECT 1 x UNION ALL SELECT 2 x UNION ALL SELECT 3 x UNION ALL SELECT 4 x UNION ALL SELECT 5 x UNION ALL SELECT 6 x UNION ALL SELECT 7 x UNION ALL SELECT 8 x UNION ALL SELECT 9 x) ones CROSS JOIN (SELECT 0 x UNION ALL SELECT 10 x UNION ALL SELECT 20 x UNION ALL SELECT 30 x UNION ALL SELECT 40 x UNION ALL SELECT 50 x UNION ALL SELECT 60 x UNION ALL SELECT 70 x UNION ALL SELECT 80 x UNION ALL SELECT 90 x) tens;
Kapitel 11 11-1 Schreiben Sie die folgende Abfrage, die einen einfachen Case-Ausdruck enthält, so um, dass sie mit einem Searched Case-Ausdruck dieselben Ergebnisse erzielt. Versuchen Sie, mit möglichst wenigen when-Klauseln auszukommen.
Max. Linie
SELECT emp_id, CASE title WHEN 'President' THEN 'Management'
310 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts WHEN 'Vice President' THEN 'Management' WHEN 'Treasurer' THEN 'Management' WHEN 'Loan Manager' THEN 'Management' WHEN 'Operations Manager' THEN 'Operations' WHEN 'Head Teller' THEN 'Operations' WHEN 'Teller' THEN 'Operations' ELSE 'Unknown' END FROM employee; SELECT emp_id, CASE WHEN title LIKE '%President' OR title = 'Loan Manager' OR title = 'Treasurer' THEN 'Management' WHEN title LIKE '%Teller' OR title = 'Operations Manager' THEN 'Operations' ELSE 'Unknown' END FROM employee;
11-2 Schreiben Sie die folgende Abfrage so um, dass sie eine Ergebnismenge mit einer einzigen Zeile mit vier Spalten (eine für jede Zweigstelle) zurückgibt. Nennen Sie die vier Spalten branch_1 bis branch_4. mysql> SELECT open_branch_id, COUNT(*) -> FROM account -> GROUP BY open_branch_id; +----------------+----------+ | open_branch_id | COUNT(*) | +----------------+----------+ | 1 | 8 | | 2 | 7 | | 3 | 3 | | 4 | 6 | +----------------+----------+ 4 rows in set (0.00 sec)
Max. Linie
mysql> SELECT -> SUM(CASE WHEN open_branch_id = 1 THEN -> SUM(CASE WHEN open_branch_id = 2 THEN -> SUM(CASE WHEN open_branch_id = 3 THEN -> SUM(CASE WHEN open_branch_id = 4 THEN -> FROM account; +----------+----------+----------+----------+ | branch_1 | branch_2 | branch_3 | branch_4 | +----------+----------+----------+----------+ | 8 | 7 | 3 | 6 | +----------+----------+----------+----------+ 1 row in set (0.02 sec)
1 1 1 1
ELSE ELSE ELSE ELSE
0 0 0 0
END) END) END) END)
branch_1, branch_2, branch_3, branch_4
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 311
Links Kapitel 12 12-1 Öffnen Sie eine Transaktion, um $50 von Frank Tuckers Geldmarktkonto (Money Market) auf sein Girokonto (Checking) zu übertragen. Dazu müssen Sie zwei Zeilen in die Tabelle transaction einfügen und zwei Zeilen in der Tabelle account aktualisieren. START TRANSACTION; SELECT i.cust_id, (SELECT a.account_id FROM account a WHERE a.cust_id = i.cust_id AND a.product_cd = 'MM') mm_id, (SELECT a.account_id FROM account a WHERE a.cust_id = i.cust_id AND a.product_cd = 'chk') chk_id INTO @cst_id, @mm_id, @chk_id FROM individual i WHERE i.fname = 'Frank' AND i.lname = 'Tucker'; INSERT INTO transaction (txn_id, txn_date, account_id, txn_type_cd, amount) VALUES (NULL, now(), @mm_id, 'CDT', 50); INSERT INTO transaction (txn_id, txn_date, account_id, txn_type_cd, amount) VALUES (NULL, now(), @chk_id, 'DBT', 50); UPDATE account SET last_activity_date = now(), avail_balance = avail_balance - 50 WHERE account_id = @mm_id; UPDATE account SET last_activity_date = now(), avail_balance = avail_balance + 50 WHERE account_id = @chk_id; COMMIT;
Kapitel 13 13-1
Max. Linie
Ändern Sie die Tabelle account so, dass Kunden nur noch ein Konto für ein bestimmtes Produkt haben können. ALTER TABLE account ADD CONSTRAINT account_unq1 UNIQUE (cust_id, product_cd);
312 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts 13-2 Erstellen Sie einen mehrspaltigen Index auf der Tabelle transaction, der in den folgenden Abfragen genutzt werden könnte: SELECT txn_date, account_id, txn_type_cd, amount FROM transaction WHERE txn_date > cast('2008-12-31 23:59:59' as datetime); SELECT txn_date, account_id, txn_type_cd, amount FROM transaction WHERE txn_date > cast('2008-12-31 23:59:59' as datetime) AND amount < 1000; CREATE INDEX txn_idx01 ON transaction (txn_date, amount);
Kapitel 14 14-1 Erstellen Sie eine View, die die Tabelle employee abfragt und folgende Ausgabe generiert, wenn sie ohne where-Klausel abgefragt wird: +-----------------+------------------+ | supervisor_name | employee_name | +-----------------+------------------+ | NULL | Michael Smith | | Michael Smith | Susan Barker | | Michael Smith | Robert Tyler | | Robert Tyler | Susan Hawthorne | | Susan Hawthorne | John Gooding | | Susan Hawthorne | Helen Fleming | | Helen Fleming | Chris Tucker | | Helen Fleming | Sarah Parker | | Helen Fleming | Jane Grossman | | Susan Hawthorne | Paula Roberts | | Paula Roberts | Thomas Ziegler | | Paula Roberts | Samantha Jameson | | Susan Hawthorne | John Blake | | John Blake | Cindy Mason | | John Blake | Frank Portman | | Susan Hawthorne | Theresa Markham | | Theresa Markham | Beth Fowler | | Theresa Markham | Rick Tulman | +-----------------+------------------+ 18 rows in set (1.47 sec)
Max. Linie
mysql> -> -> -> ->
CREATE VIEW supervisor_vw (supervisor_name, employee_name ) AS
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 313
Links -> SELECT concat(spr.fname, ' ', spr.lname), -> concat(emp.fname, ' ', emp.lname) -> FROM employee emp LEFT OUTER JOIN employee spr -> ON emp.superior_emp_id = spr.emp_id; Query OK, 0 rows affected (0.12 sec) mysql> SELECT * FROM supervisor_vw; +-----------------+------------------+ | supervisor_name | employee_name | +-----------------+------------------+ | NULL | Michael Smith | | Michael Smith | Susan Barker | | Michael Smith | Robert Tyler | | Robert Tyler | Susan Hawthorne | | Susan Hawthorne | John Gooding | | Susan Hawthorne | Helen Fleming | | Helen Fleming | Chris Tucker | | Helen Fleming | Sarah Parker | | Helen Fleming | Jane Grossman | | Susan Hawthorne | Paula Roberts | | Paula Roberts | Thomas Ziegler | | Paula Roberts | Samantha Jameson | | Susan Hawthorne | John Blake | | John Blake | Cindy Mason | | John Blake | Frank Portman | | Susan Hawthorne | Theresa Markham | | Theresa Markham | Beth Fowler | | Theresa Markham | Rick Tulman | +-----------------+------------------+ 18 rows in set (0.17 sec)
14-2 Der Präsident der Bank hätte gern einen Bericht, der für jede Zweigstelle den Namen und die Stadt anzeigt und zusätzlich den Gesamtsaldo aller Konten, die an dieser Zweigstelle eröffnet wurden. Erstellen Sie eine View, die diese Daten generiert. mysql> CREATE VIEW branch_summary_vw -> (branch_name, -> branch_city, -> total_balance -> ) -> AS -> SELECT b.name, b.city, sum(a.avail_balance) -> FROM branch b INNER JOIN account a -> ON b.branch_id = a.open_branch_id -> GROUP BY b.name, b.city; Query OK, 0 rows affected (0.00 sec)
Max. Linie
mysql> SELECT * FROM branch_summary_vw; +---------------+-------------+---------------+ | branch_name | branch_city | total_balance | +---------------+-------------+---------------+
314 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie
Rechts | Headquarters | Waltham | 27882.57 | | Quincy Branch | Quincy | 53270.25 | | So. NH Branch | Salem | 68240.32 | | Woburn Branch | Woburn | 21361.32 | +---------------+-------------+---------------+ 4 rows in set (0.01 sec)
Kapitel 15 15-1 Schreiben Sie eine Abfrage, die alle Indizes im bank-Schema aufführt. Lassen Sie auch die Tabellennamen melden.
Max. Linie
mysql> SELECT DISTINCT table_name, index_name -> FROM information_schema.statistics -> WHERE table_schema = 'bank'; +--------------+--------------------+ | table_name | index_name | +--------------+--------------------+ | account | PRIMARY | | account | account_unq1 | | account | fk_product_cd | | account | fk_a_branch_id | | account | fk_a_emp_id | | account | acc_bal_idx | | branch | PRIMARY | | business | PRIMARY | | customer | PRIMARY | | department | PRIMARY | | department | dept_name_idx | | employee | PRIMARY | | employee | fk_dept_id | | employee | fk_e_branch_id | | employee | fk_e_emp_id | | individual | PRIMARY | | officer | PRIMARY | | officer | fk_o_cust_id | | product | PRIMARY | | product | fk_product_type_cd | | product_type | PRIMARY | | transaction | PRIMARY | | transaction | fk_t_account_id | | transaction | fk_teller_emp_id | | transaction | fk_exec_branch_id | | transaction | txn_idx01 | +--------------+--------------------+ 26 rows in set (0.00 sec)
Anhang C: Lösungen der Übungen | This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Max. Linie 315
Links 15-2 Schreiben Sie eine Abfrage, die eine Ausgabe generiert, die eingesetzt werden kann, um alle Indizes auf der Tabelle bank.employee anzulegen. Die Ausgabe sollte folgende Form haben: "ALTER TABLE ADD INDEX ()" mysql> SELECT concat( -> CASE -> WHEN st.seq_in_index = 1 THEN -> concat('ALTER TABLE ', st.table_name, ' ADD', -> CASE -> WHEN st.non_unique = 0 THEN ' UNIQUE ' -> ELSE ' ' -> END, -> 'INDEX ', -> st.index_name, ' (', st.column_name) -> ELSE concat(' ', st.column_name) -> END, -> CASE -> WHEN st.seq_in_index = -> (SELECT max(st2.seq_in_index) -> FROM information_schema.statistics st2 -> WHERE st2.table_schema = st.table_schema -> AND st2.table_name = st.table_name -> AND st2.index_name = st.index_name) -> THEN ');' -> ELSE '' -> END -> ) index_creation_statement -> FROM information_schema.statistics st -> WHERE st.table_schema = 'bank' -> AND st.table_name = 'employee' -> ORDER BY st.index_name, st.seq_in_index; +---------------------------------------------------------------------+ | index_creation_statement | +---------------------------------------------------------------------+ | ALTER TABLE employee ADD INDEX fk_dept_id (dept_id); | | ALTER TABLE employee ADD INDEX fk_e_branch_id (assigned_branch_id); | | ALTER TABLE employee ADD INDEX fk_e_emp_id (superior_emp_id); | | ALTER TABLE employee ADD UNIQUE INDEX PRIMARY (emp_id); | +---------------------------------------------------------------------+ 4 rows in set (0.20 sec)
Max. Linie
Max. Linie 316 | Anhang C: Lösungen der Übungen This is the Title of the Book, eMatter Edition Copyright © 2009 O’Reilly & Associates, Inc. All rights reserved.
Index
Symbole ( ) (runde Klammern), Bedingungsauswertung 64 + (Verkettungs)-Operator 126 ’ (einfache Anführungszeichen) 118
A Abfragen ausführen 41---43 Bedingungslogik 211 case-Ausdruck 212---223 gruppieren 147---149 Indizes 235---245 Joins 81---88 Bedingungen 97 Cross Joins 201 drei und mehr Tabellen verbinden 88---93 Equi-Joins/Non-Equi-Joins 94---97 Natural Joins 207---209 Outer Joins 194 Self-Joins 93 Klauseln 43---60 from 49---52 group by 55 having 55 limit 281 ohne 18 order by 56---60 where 53---55 Mehrbenutzer-Datenbanken 225 Ranking 284 Versionierung 226
Views 50 zusammengesetzte 105 Mengenoperations-Regeln 111---114 (siehe auch Unterabfragen) abgleichen Bedingungen 73 Wildcards 74 abschneiden, Strings 116 absteigende Sortierreihenfolge 58 Aggregatfunktionen 148---155 count( ) 149 count(*) 149 Aggregation selektive 217 Views zur Zusammenstellung von Daten einsetzen 257 Aliase Spalten-, einfügen 46 Tabellen-, definieren 52 all-Operator 169 American National Standards Institute (siehe ANSI) Anführungszeichen, eingebettete 118 ANSI (American National Standards Institute) 7 Join-Syntax 86 ANSI-Modus 117 Anweisungen create table 28 dynamische SQL-Ausführung in MySQL 275 für aktualisierbare Views 260 Gültigkeitsbereich 163
| 317
insert 32 Werte 33 Klassen 8 Problembehebung 36 Schema- vs. Daten- X Schemata, erstellen 27 select Abfrageklauseln 43---48 Erweiterungen 281---287 übergeordnete 163 Unterabfragen 163 anwenden 178---188 korrelierte 174---178 nicht-korrelierte 165---174 Typen 164 any-Operator 171 Anzeigen von Indizes 237 Archive-Speichermodul 232 Argumente einargumentige numerische Funktionen 128 größer als Null 127 truncate( )-Funktion 131 arithmetische Operationen in Filterbedingungen 66 arithmetische Operatoren 128 as-Schlüsselwort einsetzen mit Spaltenaliasen 47 einsetzen mit Tabellenaliasen 52 Assistenten, Konfigurationsassistent starten 16 atomare Sperren 134 aufsteigende Sortierreihenfolge 58 Ausdrücke Aggregatfunktionen 153 Bedingungen, erstellen 66 case 212---223 durchsuchen 75 gruppieren 156 reguläre, anwenden 76 select-Anweisungen 45 sortieren 59 Unterabfragen als Generatoren 185 auswerten, Bedingungen 63---66 Autocommit-Modus 229 Auto-Increment-Feature, starten 31
B Bank-Schema 38 B-Baum-Indizes 240
318 | Index
BDB-Speichermodul 231 Bedingungen auswerten 63---66 erstellen 66 Filter 53 Gleichheits- 66 Gruppen filtern 159 Joins, Vergleich mit Filterbedingungen 97 Typen 66---76 Unterabfragen in Filterbedingungen 184 Updates 222 Bedingungslogik 211 case-Ausdruck 212---223 Befehle commit 227, 230 replace 288 rollback 227, 230 show index 237 Benutzer Mehrbenutzer-Datenbanken 225 Transaktionen 227---234 Bereiche Bedingungen 68 Strings 71 between-Operator 69 Beziehungen, ER-Diagramm 279 Bitmap-Indizes 241 Blattknoten 240
C C (Programmiersprache), Toolkits zur SQLIntegration 10 C# (Programmiersprache), Toolkits zur SQLIntegration 10 C++ (Programmiersprache), Toolkits zur SQLIntegration 10 case-Ausdruck 212---223 cast( )-Funktion 138, 144 ceil( )-Funktion 130 char( )-Funktion 120 CHAR-Datentyp 115 check-Constraints 27, 246 CLOB-Datentyp 116 Codd, Dr. E. F. 4, 7 columns-View 268 commit-Befehl 227, 230 concat( )-Funktion 120, 125
Constraints 246---251 Abfrage, die die Anzahl an PrimärschlüsselConstraints liefert 274 Check Constraints 27 erstellen 247 Fremdschlüssel 30 generieren 248 Indizes 248 Informationen zu information_schema abrufen 269 Informationen zu Primärschlüssel-Constraints abrufen 272 kaskadierende 248 Primärschlüssel 27 Übungen 251 convert_tz( )-Funktion 142 Coordinated Universal Time (UTC) 134 count( )-Funktion 149 count(*)-Funktion 149 create index-Befehl 237 create table-Anweisungen 28 Abfragen generieren mit Hilfe von information_schema 271 Wohlgeformtheit prüfen 273 create view-Anweisungen 253 Cross Joins 201---207
D Data Dictionaries 9, 266 Datafeed generieren 182 date_add( )-Funktion 143, 204 datediff( )-Funktion 143 Datenanweisungen X Datenbanken 1---7 Constraints 246---251 ER-Diagramm 279 Indizes 235---245 Mehrbenutzer- 225 Sperren 226 MySQL 13, 15 Natural Joins 207---209 nicht-relationale Datenbanksysteme 2 relationale Modelle 4 Terminologie 7 Transaktionen 227---234 Datenquellen, Unterabfragen als 179 Datentypen 17---24 numerische Daten 21---22
temporale Daten 23---24 Zeichen 18 anwenden in Strings 115---128 CHAR 115 CLOB 116 Text 116 Varchar 115 Datum/Uhrzeit-Spalten, befüllen 137 Datumsangaben Bestandteile 136 formatieren 24 generieren 138 String-in-Datum-Konvertierungen 137 unzulässige Konvertierungen 37 zurückgeben 140 (siehe auch temporale Datentypen) Deadlocks 230 deallocate-Anweisungen 275 definieren Abfrage-Joins 81---88 null 29 Tabellenaliase 52 Zeichenspalten 18 Deployment-Prüfung für Schemaobjekte 274 describe-Befehl (desc) 28 Views betrachten 255 Dezimalpunkte (numerische Datentypen) 22 Diagramm (ER) 279 Dictionaries 9 distinct-Schlüsselwort 48 Division durch Null-Fehler 220 Drei-Wege-Outer Joins 196 Duplikate Zeilen, löschen 47 Durability 229 durchsuchen Ausdrücke 75 case-Ausdrücke 213 limit-Klauseln 282 Wildcards 74 (siehe auch Indizes) dynamische SQL-Ausführung 275
E Ebenen von Sperren 226 einargumentige numerische Funktionen 128 einfache Anführungszeichen (’) 118 einfache case-Ausdrücke 215
Index | 319
einfügen Daten in Tabellen 31 Indizes 236 Intervalle 141 Schlüsselwörter 48 Spaltenaliase 46 eingebaute Funktionen numerische 128 select-Anweisungen 45 Strings 121 (siehe auch Funktionen) eingebettete Anführungszeichen 118 einzelne Spalte gruppieren 155 Unterabfragen 166 Elemente, zählen 152 Englisch-Zeichensätze 19 Entitäten 7 Entity-Relationship-(ER-)Diagramm 279 Entwurf Tabellen 25 Equi-Joins 94---97 ER-(Entity-Relationship)Diagramm 279 Ergebnismengen 7 case-Ausdrücke 216 zusammengesetzte Abfragen, sortieren 111 Zwischenergebnisse 91 erstellen Bedingungen 66 Mehrspalten-Indizes 239 Schema-Anweisungen 27 Strings, zeichenweise 120 Erweiterungen, select-Anweisungen 281---287 erzeugen, Tabellen 180, 202 Escape-Zeichen für Strings 118 except-Operationen 103 except-Operator 109 execute-Anweisungen 275 exists-Operator 176 explizite Gruppen 150 extract( )-Funktion 142
F fehlender Fremdschlüssel 36 Fehler Division durch Null 220 kaskadierende Constraints 248 (siehe auch Problembehebung)
320 | Index
Filterbedingungen in where-Klauseln 63 filtern Bedingungen 53 auswerten 63---66 erstellen 66 Typen 66---76 Unterabfragen 184 Vergleich mit Join-Bedingungen 97 Gruppen 149, 159 null 76---79 Fließkommatypen 22 Fließkommazahl, Genauigkeit 130 floor( )-Funktion 130 formatieren Constraints 247 Datumsangaben 24, 136 Indizes 236 Mehrspalten-Indizes 239 Sonderzeichen 119 Tabellen 25---30 Views 50 Fremdschlüssel 4, 6, 7 Constraints 30, 246 ER-Diagramm 279 fehlender 36 selbstreferenzierende 93 Friedl, Jeffrey 76 from-Klausel 49---52 für aktualisierbare Views 260 in select-Anweisungen 52 Join-Reihenfolge und 90 on-Subklausel 84 ANSI-Join-Syntax in 87 using-Subklausel 85 Funktionen Aggregat- 148---155 count( ) 149 count(*) 149 cast( ) 138, 144 ceil( ) 130 char( ) 120 concat( ) 120, 125 convert_tz( ) 142 date_add( ) 143, 204 datediff( ) 143 Datumsangaben generieren 138 zurückgeben 140
einargumentige numerische 128 extract( ) 142 floor( ) 130 getutcdate( ) 134 Konvertierung 144 last_day( ) 142 length( ) 121 locate( ) 122 mod( ) 129 new_time( ) 142 now( ) 17 position( ) 121 pow( ) 129 quote( ) 119 replace( ) 127 round( ) 131 sign( ) 133 str_to_date( ) 139 strcmp( ) 122 Strings, zurückgeben 121, 142 stuff( ) 127 truncate( ) 131 Zahlen, zurückgeben 143
G Ganzzahl, Integer 22 Genauigkeit Fließkommatypen 22 Zahlen, steuern 130 geordnete Löschungen/Änderungen 289---290 geringe Kardinalität, Daten 241 getutcdate( )-Funktion 134 Gleichheitsbedingungen 66 GMT (Greenwich Mean Time) 134 Granularitäten von Sperren 226 Greenwich Mean Time (GMT) 134 Größe ändern bei Texttypen 20 Größenordnung (Fließkommatypen) 22 group by-Klausel 55 gruppieren 147---149 Aggregatfunktionen 150---155 Ausdrücke 156 einzelne Spalte 155 Elemente, zählen 152 filtern 159 generieren 155---159 implizite oder explizite Gruppen 150 mehrere Spalten 156
Rollups 157 Unterabfragen 183 Gültigkeitsbereich, Anweisungen 163
H having-Klausel 55 hierarchische Datenbanksysteme 2 hohe Kardinalität, Daten 242
I implizite Gruppen 150 Indizes 235---245 Abfrage, die die Anzahl an Indizes liefert 274 anwenden 242 anzeigen 237 B-Baum 240 Bitmap- 241 Constraints 248 erstellen 236 Informationen zu information_schema abrufen 268 Knoten 240 löschen 238 mehrere Spalten 239 modifizieren 245 show-Befehl 237 Text 242 Typen 239 Übungen 251 unique- 238 Volltext 242 information_schema-Objekte 266 columns-View 277 Information zu Constraints 269 Information zu Tabellenindizes 268 verfügbare Views in MySQL 6.0 270 Views in 267 Inner Joins 84 InnoDB-Speichermodul 231 in-Operator 167 insert-Anweisung 32 Daten über Views einfügen 263 update-Anweisung, kombinieren 287 Werte 33 Installieren von MySQL 15 Integer Ganzzahl 22 runden 131
Index | 321
Integration von Toolkits 10 intersect-Operatoren 108 Intervalle einfügen 141 Typen 140 into outfile-Klausel 285
J Java SQL-Anweisungen und 275 Toolkits zur SQL-Integration 10 Joins 81---88 ANSI-Syntax für 86 Bedingungen 97 Cross Joins 201---207 drei oder mehr Tabellen verbinden 88---93 Join-Abfolge angeben 90 Equi-Joins/Non-Equi-Joins 94---97 Natural Joins 207---209 Outer Joins 191---201 Bedingungslogik 211 Drei-Wege- 196 Left/Right 195 Self 198 Self-Joins 93 Views mit anderen Tabellen oder Views verknüpfen 255
K Kartesische Produkte 82 kaskadierende Änderungen 249 Constraints 248 Klassen Anweisungen 8 Klauseln Abfragen 43---60 from 49---52 group by 55 having 55 ohne 18 order by 56---60 where 53---55 into outfile 285 limit 281 order by 34
322 | Index
select (Aggregatfunktionen) 148 where (gruppierende Filterbedingungen) 159 Knoten, Indizes 240 Kollation 71 kombinieren insert/update-Anweisungen 287 limit-Klauseln/order by-Klauseln 282 mehrere Tabellen 105 Kommandozeilentool mysql 10, 17 Konfigurationsassistent, starten 16 konfigurieren MySQL-Datenbanken 15 Standardzeichensätze 20 (siehe auch formatieren) Konvertierungen Funktionen 144 String-in-Datum 137 unzulässige Datumsangaben 37 Werte 144 korrelierte Unterabfragen 165, 174---178
L Laden von MySQL-Zeitzonendaten 135 last_day( )-Funktion 142 Left Outer Joins 195 length( )-Funktion 121 Lesesperren 226 limit-Klausel 281 Literale 45 locate( )-Funktion 122 löschen Daten mehrerer Tabellen 291---293 Duplikate 47 geordnete Deletes 289---290 Indizes 238 Tabellendaten 35 Lösungen zu den Übungsaufgaben 295---311
M manipulieren Daten Gruppen generieren 155---159 mit korrelierten Unterabfragen 177 Strings 121 temporale Daten 140---144 Mehrbenutzer-Datenbanken 225
mehrere Bedingungen, runde Klammern ( ) 64 mehrere Spalten gruppieren 156 Indizes 239 Unterabfragen 172 mehrere Tabellen Abfragen Bedingungen 97 Equi-Joins/Non-Equi-Joins 94---97 Joins 81---88 Joins mit drei und mehr Tabellen 88---93 Self-Joins 93 löschen/ändern 291---293 union-Operatoren 105 Mehrzeilen-Unterabfragen 166 MEMORY-Speichermodul 231 Mengen Grundlagen der Mengenlehre 101---105 Mengenoperatoren 105---110 nicht überlappende 108 Regeln für Mengenoperationen 111---114 (siehe auch Ergebnismengen) Mengenlehre 101---105 Merge-Speichermodul 231 Metadaten 9, 265 information_schema-Objekte 266 Informationen in 265 nutzen in Skripten zur Schema-Generierung 271 nutzen zur Deployment-Überprüfung 274 nutzen, um dynamisch SQL zu generieren 275 Übungen zu 278 veröffentlicht von Datenbankservern 266 Mitgliedschaftsbedingungen 71 mod( )-Funktion 129 modifizieren Abfragen, Inner Joins 84 Daten, mit Gleichheitsbedingungen 68 Indizes 245 Tabellen 30---36 Modus prüfen und ändern für MySQL 117 Multibyte-Zeichensätze 19 Multiparent-Hierarchien 4 MyISAM-Speichermodul 231 MySQL aktualisierbare Views 260 Beispieldatenbank erstellen 16
Datenbanken erstellen 15 definieren 13 dynamisch SQL ausführen 275 geordnete Änderungen und Löschungen 289---290 Indizes, einfügen 236 information_schema-Datenbank 266 Join-Abfolge festlegen 90 select-Anweisung limit-Klausel 281 into outfile-Klausel 285 Zeitzonen 135 mysql-Kommandozeilentool 10 anwenden 17 create table-Anweisung 28 --xml-Option 35
N Natural Joins 207---209 negative Werte, vorzeichenbehaftete Daten 132 Netzwerk-Datenbanksysteme 3 new_time( )-Funktion 142 nicht eindeutiger Primärschlüssel 36 nicht überlappende Mengen 108 nicht vorzeichenbehaftete Daten 22 nicht-korrelierte Unterabfragen 165---174 nicht-prozedurale Sprachen 9 nicht-relationale Datenbanksysteme 2 Non-Equi-Joins 94---97 Normalisierung 6, 25 not in-Operator 73 not-Operator, anwenden 65 now( )-Funktion 17 Nullen 22 Argumente größer als 127 Division durch Null-Fehler 220 Nullwerte 29 Aggregatfunktionen 153 case-Ausdrücke 222 definieren 29 filtern 76---79 numerische Daten, generieren 128---133 numerische Datentypen 21---22 numerische eingebaute Funktionen 128 numerische Platzhalter, sortieren mit 59 numerische Schlüsseldaten, generieren 31
Index | 323
O on-Subklausel der from-Klausel ANSI Join-Syntax in 87 Operationen, Mengen 101---105, 111---114 Operatoren 126 all 169 any 171 arithmetische 128 Bedingungen, erstellen 66 between 69 except 109 exists 176 in 167 intersect 108 Mengen- 105---110 not 65 not in 73 regexp 124 union 105 Verkettungs- (+) 126 Optimierung von Tabellen 25 Optimierungen 9 Oracle Database dynamische SQL-Ausführung 275 Einfügen und Aktualisieren von Daten über Views 263 Join-Abfolge festlegen 90 Metadaten 266 Oracle PL/SQL-Sprache 275 Oracle Text 242 order by-Klausel 34, 56---60 limit-Klausel, kombinieren mit 282 Outer Joins 85, 191---201 Bedingungslogik 211 Drei-Wege- 196 Left/Right 195 Self 198
P Parameter, limit-Klausel 283 Passwörter, mysql-Kommandozeilentool 17 Perl, Toolkit zur SQL-Integration 10 PL/SQL-Sprache 10, 275 Platzhalter, numerische sortieren 59 position( )-Funktion 121 positive Werte, vorzeichenbehaftete Daten 132 pow( )-Funktion 129
324 | Index
Präzedenz numerische Daten, generieren 128 von Mengenoperationen 112 prepare-Anweisungen 275 Primärschlüssel 4 Constraints 27, 246 Anzahl abfragen 274 Informationen abrufen zu 272 nicht-eindeutiger 36 Problembehebung Anweisungen 36 Division durch Null-Fehler 220 Spaltenwerte 37 prozedurale Sprachen 9 Prüfen auf Vorhandensein 219 Python, Toolkit zur SQL-Integration 10
Q quote( )-Funktion 119
R Ranking von Abfragen 284 referenzieren, übergeordnete Abfrage 50 Regeln, Mengenoperationen 111---114 regexp-Operatoren 124 reguläre Ausdrücke, anwenden 76 relationale Datenbanken 4, 13 replace( )-Funktion 127 replace-Befehl, ersetzen 288 Reporting Mehrbenutzer-Datenbanken 225 Task-orientierte Unterabfragen 182 Richtlinien für Mengenoperationen 104 Right Outer Joins 195 rollback-Befehl 227, 230 Rollups, gruppieren 157 round( )-Funktion 131 runde Klammern ( ), Bedingungsauswertung 64 runden Integer 131 Zahlen 130
S Savepoints (Transaktionen) 233 Schaltjahre 142
Schemata Anweisungen X Bank- 38 Deployment überprüfen 274 erstellen 27 information_schema 266 Skripten zur Generierung 271 Schlüssel Fremd- 4 Constraints 30, 246 ER-Diagramm 279 fehlender 36 selbstreferenzierende 93 Primär- 4 Constraints 27, 246 nicht-eindeutiger 36 zusammengesetzte 4 Schlüsselwörter, einfügen 48 Schnittmengen 102 Schreibsperren 226 Seitensperren 226 selbstreferenzierende Fremdschlüssel 93 select-Anweisungen Abfragen 43---48 aktualisierbare Views 260 Erweiterungen 281---287 Views abfragen 255 select-Klausel, Aggregatfunktionen 148 select-Schlüsselwort 48 selektive Aggregation 217 Self Outer Joins 198 Self-Joins 93 Sequences 31 Server Aggregatfunktionen 150---155 Indizes, anzeigen 237 MySQL, installieren 15 Natural Joins 207---209 sperren 226 SQL92-Join-Syntax 86 Transaktionen beenden 230 Savepoints 233 starten 229 SET-Befehl 117 show-Befehl, Indizes 237 Sicherheit Datensicherheit mit Views 256 mysql-Kommandozeilentool 17
sign( )-Funktion 133 Single-Parent-Hierarchien 3 Skalar-Unterabfragen 165 Sommerzeit 133 sortieren aufsteigende/absteigende Sortierreihenfolge 58 Ausdrücke 59 Kollation 71 numerische Platzhalter 59 zusammengesetzte Abfragen, Ergebnisse 111 Spalten 4, 7 Abfrage, die die Anzahl an Spalten zurückliefert 274 Aliase, einfügen 46 befüllen 116 Check Constraints 27 Constraints 246---251 Datum/Uhrzeit, befüllen 137 einzelne Spalte gruppieren 155 Unterabfragen 166 Indizes, einfügen 236 mehrere Spalten gruppieren 156 Indizes 239 Unterabfragen 172 Natural Joins 207---209 null 29 temporale Datentypen 24 vorzeichenbehaftete Daten 132 Werte, Problembehebung 37 Zeichen, definieren 18 Speichermodule 226 auswählen 231 Sperren und 227 Sperren 225 Deadlocks 230 Ebenen 226 Sprachen nichtprozedurale 9 PL/SQL 10 TransactSQL 10 sprechende Schlüssel 6 SQL (Structured Query Language) definieren 7---13 dynamisch generieren 275
Index | 325
SQL Server Daten über Views einfügen und aktualisieren 263 dynamische SQL-Ausführung 275 Join-Abfolge festlegen 90 Metadaten und information_schema 266 XML-Ausgaben für Abfragen generieren 35 SQL92-Join-Syntax 86 Standardzeichensätze, definieren 20 starten Auto-Increment-Feature 31 Konfigurationsassistent 16 Transaktionen 229 Steuern der Genauigkeit von Zahlen 130 str_to_date( )-Funktion 139 STRAIGHT_JOIN-Schlüsselwort 90 strcmp( )-Funktionen 122 strict-Modus 117 Strings abschneiden 116 anwenden 115---128 Bereiche 71 eingebaute Funktionen 121 Escape-Zeichen 118 Funktionen Rückgabe von Strings 125 Rückgabe von Zahlen 121 zurückgeben 125, 142 generieren 116 konvertieren 144 manipulieren 121 SQL-Anweisungen an Datenbankserver übergeben als 275 String-in-Datum-Konvertierungen 137 temporale Daten, generieren 136 umwandeln in Datetimes in MySQL 37 Wildcards, anwenden 74 stuff( )-Funktion 127 Surrogatschlüssel 6 Syntax ANSI-Join 86 case-Ausdrücke 213 Systemkatalog 266
T Tabellen 4, 7, 225 Aliase, definieren 52 ändern 34
326 | Index
anlegen 25---30 befüllen 30---36 Constraints 246---251 Cross Joins 201---207 Daten erzeugen 180 Entwurf 25 ER-Diagramm 279 from-Klausel 49---52 group by-Klausel 55 having-Klausel 55 Indizes 235---245 löschen 35 mehrere Bedingungen 97 Equi-Joins/Non-Equi-Joins 94---97 Joins 81---88 Joins mit drei und mehr Tabellen 88---93 Self-Joins 93 union-Operatoren 105 Mengenoperation, Richtlinien 104 modifizieren 30---36 Natural Joins 207---209 optimieren 25 order by-Klausel 56---60 Outer Joins 191---201 Bedingungslogik 211 Drei-Wege- 196 Left/Right 195 Self 198 Sperren 226 Unterabfragen 49, 163 anwenden 91, 178---188 korrelierte 174---178 nicht-korrelierte 165---174 Typen 164 Verknüpfungen 51 where-Klausel 53---55 zusammengestellte Daten 258 (siehe auch Datenbanken) Tabellenaliase 52 Task-orientierte Unterabfragen 182 Tastaturen, Sonderzeichen 119 temporale Daten anwenden 133---135 generieren 136---140 manipulieren 140---144 temporale Datentypen 23---24 Terminologie für Datenbanken 7
Text Größe ändern 20 Indizes 242 Typen 20 Text-Datentyp 116 Toolkits, Integration 10 Tools mysql-Kommandozeilentool 10 anwenden 17 create table-Anweisung 28 Oracle Text 242 Transact-SQL-Sprache 10, 275 Transaktionen 227---234 beenden 230 Mehrbenutzer-Datenbanken 225 Savepoints 233 selektive Aggregation 218 starten 229 Übungen zu 234 Transformationen, Ergebnismengen 216 treibende Tabelle 90 Trends, gruppieren 147---149 truncate( )-Funktion 131 Typen Bedingungen 66---76 Constraints 246---251 Indizes 239 Intervalle 140 Text 20 Unterabfragen 164 korrelierte 174---178 nicht-korrelierte 165---174
U übergeordnete Abfrage referenzieren 50 übergeordnete Anweisungen 163 überlappende Daten 105, 108 Übungsaufgaben, Lösungen 295---311 Uhrzeit, Konfiguration von Zeitzonen 134 Uhrzeiten 23 (siehe auch temporale Datentypen) Ungleichheitsbedingungen 67 union all-Operator 105---108 partitionierte Daten in Views bereitstellen 259 union-Operator 105---108 unique-Constraints 246 unique-Indizes 238
Universal Time 134 Unterabfragen anwenden 72, 178---188 als Ausdrucksgeneratoren 185 als Datenquellen 179 einzelne Spalte 166 in Filterbedingungen 184 gruppieren 183 in Anweisungen für aktualisierbare Views 260 mehrere Spalten 172 mehrere Zeilen 166 skalare 165 Tabellen, anwenden als 91 Task-orientierte 182 Typen 164 korrelierte 174---178 nicht-korrelierte 165---174 Übersicht 163 Unterabfrage-generierte Tabellen 49 unzulässige Datumskonvertierungen 37 update-Anweisungen, kombinieren mit insertAnweisungen 287 Updates Bedingungen 222 geordnete 289---290 kaskadierende 249 mehrere Tabellen 291---293 Tabellendaten 34 Views 260 einfache Views aktualisieren 260 komplexe Views aktualisieren 262 UTC (Coordinated Universal Time) 134
V Varchar-Datentyp 115 Vereinigungsmengen 101 Vergleichsoperatoren in Filterbedingungen 66 Verkettungsoperator (+) 126 Verknüpfungen, Tabellen 51 verschiedene Werte, zählen 152 Versionierung 226 Verwalten von Datenbanken 1 Views 50, 253 abfragen 254 aktualisierbare 260 Definition 253
Index | 327
erstellen mit create view-Anweisung 253 Gründe für die Verwendung 256 Datensicherheit 256 Datenzusammenstellung 257 Komplexität verbergen 258 partitionierte Daten verbinden 259 in information_schema 266 Informationen abrufen zu 267 information_schema-Views in MySQL 6.0 abrufen 270 Übungen zu 263 untersuchen mit describe-Befehl 255 Visual Basic, Toolkits zur SQL-Integration 10 Volltextindizes 242 Vorhandensein, prüfen 219 vorzeichenbehaftete Daten 132
W Werte insert-Anweisungen 33 konvertieren 144 null Aggregatfunktionen 153 case-Ausdrücke 222 filtern 76---79 Spalten, Problembehebung 37 verschiedene, zählen 152 vorzeichenbehaftete Daten 132 where-Klausel 53---55 ANSI-Join-Syntax in 87 Bedingungen, auswerten 63---66 Filterbedingungen in 63 gruppierende Filterbedingungen 159 Wildcards, anwenden 74 Windows, installieren von MySQL 15
X XML-Ausgabe generieren für Abfragen 35
328 | Index
Z Zahlen Aggregatfunktionen 154 Funktionen, zurückgeben 143 Genauigkeit, steuern 130 runden 130 Stringfunktionen, Rückgabe von 121 zählen verschiedener Werte 152 Zeichen 18 Datentypen anwenden in Strings 115---128 CHAR 115 CLOB 116 Text 116 Varchar 115 Sonderzeichen, formatieren 119 Wildcards 74 Zeilen 4, 7 Aggregatfunktionen 150---155 Duplikate, löschen 47 limit-Klausel 281 Outer Joins 191---201 Sperren 226 Unterabfragen, mehrere Zeilen 166 Zeitzonen 133 MySQL 135 Zugriff Transaktionen 227---234 zurückgeben Datumsangaben 140 Strings 142 Zahlen 143 zusammengesetzte Abfragen 105 Mengenoperations-Regeln 111---114 zusammengesetzte Schlüssel 4 Zweigknoten 240 Zwischenergebnismengen 91