124 36 6MB
German Pages 344 Year 1998
Sandini Bib
MySQL lernen
Sandini Bib
Die Lernen-Reihe In der Lernen-Reihe des Addison-Wesley Verlages sind die folgenden Titel bereits erschienen bzw. in Vorbereitung: André Willms C-Programmierung lernen 432 Seiten, ISBN 3-8273-1405-4 André Willms C++-Programmierung lernen 408 Seiten, ISBN 3-8273-1342-2 Guido Lang, Andreas Bohne Delphi 6 lernen 432 Seiten, ISBN 3-8273-1776-2 Jürgen Bayer Programmieren lernen 528 Seiten, ISBN 3-8273-1951-X Judy Bishop Java lernen 768 Seiten, ISBN 3-8273-1794-0 Michael Ebner SQL lernen 264 Seiten, ISBN 3-8273-2025-9 René Martin XML und VBA lernen 336 Seiten, ISBN 3-8273-1952-8 René Martin VBA mit Word 2002 lernen 393 Seiten, ISBN 3-8273-1897-1 Olivia Adler, Hartmut Holzgraefe PHP lernen 288 Seiten, ISBN 3-8273-2000-3 Patrizia Sabrina Prudenzi VBA mit Excel 2000 lernen 512 Seiten, ISBN 3-8273-1572-7 Frank Eller C# lernen 384 Seiten, ISBN 3-8273-2045-3 Jörg Krause ASP.NET lernen 416 Seiten, ISBN 3-8273-2018-6
Sandini Bib
Thomas Demmig
MySQL lernen Anfangen, anwenden, verstehen
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Sandini Bib
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. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt.
10 9 8 7 6 5 4 3 2 1 06 05 04 03 ISBN 3-8273-2070-4 © 2003 by Addison Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Korrektorat: Lektorat: Herstellung: Satz: Druck und Verarbeitung: Printed in Germany
4
Barbara Thoben, Köln Christine Depta, Unterschleißheim Martin Asbach, [email protected] Ulrike Hempel, [email protected] mediaService, Siegen Media Print, Paderborn
Sandini Bib
I
Inhaltsverzeichnis
le
n e rn
V
Vorwort
9
1 1.1 1.2 1.3 1.4 1.5 1.6
Datenbanken Was Sie in diesem Kapitel lernen Was ist eine Datenbank? Vom Karteikasten zum RDBMS Aufbau einer Datenbank Einsatzbeispiele MySQL
13 13 13 14 17 21 22
2 2.1 2.2 2.3 2.4 2.5 2.6 2.7
Einsteigen in MySQL Was Sie in diesem Kapitel lernen Von der Realität zum Modell – Das Beispielszenario Verbinden mit MySQL SQL Anlegen einer Datenbank Erstellen der Tabellen Fragen
25 25 25 28 31 33 38 70
3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9
Daten Was Sie in diesem Kapitel lernen Eintragen, Abfragen, Ändern, Löschen – die Basis Daten eintragen Daten abfragen Daten ändern Daten löschen Funktionen in SQL Komplexe Abfragen Fragen
71 71 72 78 81 97 99 100 108 116
5
Sandini Bib
6
4 4.1 4.2 4.3 4.4 4.5 4.6
Strukturelles Was Sie in diesem Kapitel lernen Strukturänderungen Indizes Systeminformationen Optimieren von Tabellen Fragen
117 117 118 125 136 153 154
5 5.1 5.2 5.3 5.4 5.5 5.6
Zugriffsmöglichkeiten Was Sie in diesem Kapitel lernen Admin-Tools zu MySQL phpMyAdmin Import und Export Schnittstellen Fragen
155 155 156 173 179 197 220
6 6.1 6.2 6.3 6.4 6.5
Sicherheit Was Sie in diesem Kapitel lernen Datensicherung Datenprüfung und Datenreparatur Datenschutz Fragen
221 221 222 226 236 256
7 7.1 7.2 7.3
MySQL und das Web Was Sie in diesem Kapitel lernen LAMP/WAMP MySQL und PHP
257 257 258 274
A A.1 A.2 A.3
Installation von MySQL Download der Dateien Installation unter Red Hat Linux Installation unter Windows
297 297 299 300
B
Reservierte Wörter in MySQL
303
C C.1 C.2 C.3
Was fehlt in MySQL? Unterabfragen Transaktionen Weitere fehlende Elemente
307 307 311 312
, Q K D OW V Y H U ] HL F K Q L V
Sandini Bib
D Neu in MySQL 4.x D.1 Neu in MySQL 4.0 D.2 Geplant in MySQL 4.1
315 315 317
E E.1 E.2 E.3
Hilfe zu MySQL Dokumentation zu MySQL Bücher zu MySQL Newsgroups
319 319 319 320
F F.1 F.2 F.3 F.4 F.5
Lösungen zu den Fragen Kapitel 2 Kapitel 3 Kapitel 4 Kapitel 5 Kapitel 6
321 321 322 324 324 325
G
Glossar
327
Stichwortverzeichnis
337
,QKDOWVYHU]HLFKQLV
7
Sandini Bib
Sandini Bib
V
Vorwort
le
n e rn
MySQL ist wohl die am häufigsten verwendete freie relationale Datenbank. Sie ist schnell, stabil, zuverlässig und (meist) kostenlos. Viele Webserver greifen im Hintergrund auf eine MySQL-Datenbank zu. MySQL hat nicht alles, was andere Datenbanksysteme bieten. Es beschränkt sich auf die wichtigsten Dinge, die vor allem als Backend-Datenbank nötig sind. Dafür ist das oberste Ziel von MySQL Geschwindigkeit und Stabilität. Die Verwendung von MySQL ist nicht schwierig, sofern man etwas Grundwissen über Datenbanken besitzt. Dieses Buch will Ihnen dieses Grundwissen und natürlich vor allem die Nutzung von MySQL näher bringen. Dabei benötigen Sie wenig Vorkenntnisse über Datenbanken, es reicht, wenn Sie mit Ihrem Betriebssystem umgehen können. Im Laufe des Buches werde ich immer wieder auch ein wenig in die Tiefe gehen, weil ich der Meinung bin, dass man dadurch häufig ein besseres Verständnis dafür erhält, warum etwas so ist, wie es ist. MySQL läuft auf sehr vielen verschiedenen Betriebssystemen. In diesem Buch werden wir uns hauptsächlich mit Linux (speziell Red Hat) und Windows befassen, es sollte aber auch kein Problem sein, das Erlernte für andere Betriebssysteme umzusetzen. Der Aufbau des Buches sieht wie folgt aus: 1. Im ersten Kapitel geht es ganz allgemein um Datenbanken. Es erklärt
Ihnen, was sie sind und woraus sie bestehen. Weiterhin wird MySQL vorgestellt, die Idee und Geschichte dahinter erzählt und gezeigt, was MySQL kann und was es nicht kann. 2. Im zweiten Kapitel lernen wir SQL kennen, die Datenbankabfra-
gesprache schlechthin. Eine Musikschulverwaltung als Beispielszenario wird Schritt für Schritt in eine Datenbankstruktur umgesetzt. Dabei lernen Sie den Zugang zu MySQL kennen und erfahren, wie die Strukturen erstellt werden können.
Sandini Bib
3. Das dritte Kapitel befasst sich mit den Daten, den eigentlichen
Hauptakteuren. Eintragen, Ändern, Löschen und – ganz wichtig – Abfragen von Daten sind Thema dieses Abschnitts. Schwerpunkt hierbei ist der SELECT-Befehl, mit dem Sie sowohl einfache wie auch komplexe Abfragen schreiben können, um die gewünschten Informationen zu erhalten. 4. Das vierte Kapitel erläutert Ihnen Strukturen und deren Bedeutung.
Es beschreibt Änderungen an den Datenstrukturen gefolgt von Indizes, die für die Geschwindigkeit einer Datenbank von äußerster Wichtigkeit sind. Schließlich zeigt es auch noch, wie Sie sich die Systeminformationen von MySQL beschaffen können. 5. Kapitel fünf befasst sich mit den Verbindungsmöglichkeiten zu My-
SQL. Hier erläutere ich die verschiedenen Programme mit ihren unterschiedlichen Aufgabenbereichen. Wie Sie Daten „am Stück“ aus MySQL heraus oder in es hineinbekommen ist ebenfalls Gegenstand dieses Kapitels. Danach werden ich Ihnen die wichtigsten APIs für Perl und C++ vorstellen. Diese sind nötig, um sich aus eigenen Programmen mit dem MySQL-Server verbinden zu können. 6. Das sechste Kapitel befasst sich mit Datensicherung und -sicherheit.
Um im Falle eines Fehlers keinen Datenverlust zu haben, ist eine regelmäßige Sicherung absolut notwendig. Das Kapitel beschreibt, was für Möglichkeiten Sie haben und wie Sie vorgehen können. Der zweite Teil des Kapitels beschäftigt sich damit, wie Sie die Daten vor den Zugriffen Unbefugter schützen können. 7. Im siebten Kapitel schließlich geht es um das Zusammenspiel von
MySQL mit einem Web-Server. Der Aufbau eines LAMP- oder WAMPSystems wird hier ebenso beschrieben wie der Zugriff auf MySQL über die Programmiersprache PHP, welche gerne für solche Aufgaben verwendet wird. 8. Die Anhänge enthalten Informationen über die Installation von
MySQL, reservierte Wörter, fehlende Elemente des Servers und die Möglichkeiten für Workarounds, Neuerungen und Planungen für die Version 4.x sowie weitere Hilfsquellen. Am Ende der meisten Kapitel finden Sie einige Fragen, mit denen Sie überprüfen können, ob Sie den Inhalt des Kapitels verstanden haben. Die Antworten sind am Ende des Buches zusammengefasst, so dass Sie selber eine Kontrolle Ihres Wissens vornehmen können. Nachdem Sie alle Kapitel durchgearbeitet haben, sollten Sie problemlos mit MySQL umgehen und auch aufwändigere Aufgaben erledigen können. Die Modellierung von Daten, Administration des Servers, der Imund Export sowie die Verbindung zu Web-Servern sollten nun für Sie
9RUZRUW
Sandini Bib
nicht mehr völlig unverständlich sein. Und wenn es doch einmal Probleme gibt, wissen Sie dann auch, wo Ihnen geholfen werden kann. Damit Sie nicht all die beschriebenen Programme und Tools aus dem Internet laden müssen, haben ich für Sie fast alle erwähnten Dateien auf der beiliegenden CD-ROM zusammengestellt. Sie finden dort natürlich MySQL, aber auch phpMyAdmin, MySQL++, MyODBC, Perl, PHP und den Apache-Webserver. Unter dem jeweiligen Verzeichnisnamen liegen die für die Installation notwendigen Dateien für Linux und Windows. Die Vorgehensweise zum Einrichten ist in den entsprechenden Kapiteln beschrieben. Weiterhin sind sämtliche im Buch aufgeführten Skripte ebenfalls auf der CD-ROM zu finden. Um Ihnen die Orientierung im Buch zu erleichtern, finden Sie am Rand der Seiten immer wieder bestimmte Symbole (oder Icons), die einen besonderen Hinweischarakter haben. Die folgenden Icons werden in diesem Buch verwendet: Das Referenz-Icon verweist auf Dateien, die Sie auf der CD-ROM finden. Dieses Symbol steht für einen Hinweis, der Sie auf besonders nützliche Informationen aufmerksam machen soll. Dieses Icon schließlich stellt eine Warnung dar, um Sie auf mögliche Fehlerquellen hinzuweisen.
9RUZRUW
Sandini Bib
Sandini Bib
1
Datenbanken
le
n e rn
Was ist das – eine Datenbank? Und was ist das Besondere an MySQL? Dies ist der Inhalt dieses Kapitels. Datenbanken sind ein wichtiger Teil der Computerlandschaft und ohne sie wäre so gut wie jedes größere Programm – vor allem Geschäftsanwendungen – hilflos.
1.1
Was Sie in diesem Kapitel lernen
In diesem Kapitel geht es erst einmal um einen allgemeinen Überblick über Daten und deren Organisation. Es wird erläutert, was eine Datenbank ist und wie allgemein oder speziell dieser Begriff genutzt wird. Dann wird der Weg von der „guten alten Zeit“ hin zur modernen Datenbank skizziert und gezeigt, wie eine Datenbank aussieht und was sie vollbringt. Anschließend zähle ich ein paar grundlegende Elemente einer Datenbank auf, um dann einige Einsatzbeispiele zu beschreiben. Schließlich geht es um MySQL (darum sollte es in diesem Buch eigentlich die ganze Zeit gehen), wie es entstand, was dahinter steckt und warum es meist kostenlos ist.
1.2
Was ist eine Datenbank?
Im Prinzip ist jede halbwegs strukturierte Zusammenstellung von Informationen eine Datenbank. Dabei muss sie nicht einmal elektronisch sein. Das Telefonbuch ist eine Datenbank, denn man findet für eine Person oder eine Firma die Telefonnummer. Ein Fahrplan ist eine Datenbank: durch ihn lässt sich herausbekommen, wie und wann man von A nach B kommt. Ein Lexikon ist eine Datenbank: zu vielen Stichworten erhält man Erklärungen und Beispiele. Heutzutage wird der Begriff „Datenbank“ meist etwas eingeschränkter verwendet. Im Allgemeinen versteht man darunter eine oder mehrere
:DV 6LH LQ GLHVHP .DSLWHO OHUQHQ
Sandini Bib
Listen mit Daten, die mehr oder weniger sinnvoll sortiert sind und aus denen man die gewünschten Informationen möglichst leicht herausbekommt. Zudem sind die Daten über einen Computer erreichbar. So kann man zum Beispiel ein Adressprogramm auf seinem PC anweisen, die Adressen der wichtigsten Freunde auszugeben, damit man diese im nächsten Urlaub anschreiben kann. Oder man lässt sich heraussuchen, auf welcher der vielen CDs, die man besitzt, ein bestimmtes Lied zu finden ist. Und wenn man sich dabei auch nur noch an Bruchstücke des Titels erinnert, kann das Programm zur CD-Verwaltung trotzdem eine kleine Auswahl an möglichen CDs bieten, die man gemeint hat.
1.3
Vom Karteikasten zum RDBMS
Das, was heute in einer Datenbank auf dem Computer verwaltet wird, befand sich früher meist in einem Karteikasten, Aktenschrank oder Archiv. Die Karteikarten oder Akten waren in einem bestimmten System sortiert, zum Beispiel nach Nachname und Vorname, Kundennummer, Geburtsdatum oder Ort. Sinnvollerweise handelte es sich dabei um die Sortierreihenfolge, durch die man bei einem Großteil der Suchen schnell an den gewünschten Inhalt kam. Was aber, wenn man häufiger nach einem anderen Kriterium suchen wollte, welches nicht der Sortierreihenfolge entsprach? Angenommen, man hatte seine Kundendaten nach Nachname und Vorname sortiert (was ja meist am sinnvollsten war). Nun wollte man alle Kunden in einem bestimmten Ort anschreiben. Was konnte man tun? Im Prinzip konnte man Listen mit allen Kunden erstellen, die nach dem Ort sortiert waren. Mit diesen Listen – die natürlich möglichst aktuell gehalten werden mussten – ließen sich nun alle Kunden eines Ortes heraussuchen. Mit Computern lässt sich so etwas viel einfacher und vor allem deutlich schneller erledigen. Ein Computer kann sehr schnell sortieren und finden. Legt man also seine Liste mit Kunden im Rechner ab, kann man diese zum Beispiel beliebig sortiert ausgeben und – viel wichtiger – man kann nach beliebigen Kriterien suchen lassen. So ist es damit ein Leichtes, alle Kunden zu finden, die in einem bestimmten Ort wohnen. Nun kann man sich ein Programm zur Adressverwaltung von Kundendaten schreiben. Dies ermöglicht das Erfassen der Kundendaten, das Ändern, Suchen, Sortieren und was auch immer. Was muss dieses Programm können, abgesehen von der Ein- und Ausgabe? Es soll die Daten sinnvoll abspeichern, schnell in ihnen suchen, sie nach beliebigen Kriterien sortieren und dazu auch sicher und zuverlässig speichern. Vielleicht möchte man jetzt auch seine Produkte per Computer verwalten. Man erfasst dazu den Namen, die Nummer, die Kategorie, den Lagerort
'DWHQEDQNHQ
Sandini Bib
und auch die Preise. Auch hier möchte man eventuell nach verschiedenen Daten suchen können. Es zeigt sich, dass im Prinzip ganz ähnliche Anforderungen an das Programm gestellt werden, wie bei der Kundenverwaltung. Sicher, die Ein- und Ausgabe wird anders sein, aber grundsätzlich müssen die Daten auch sicher abgelegt werden, man muss sie durchsuchen und sortieren können. Eine ganze Reihe von Funktionen sind also identisch. Diese Funktionen stellen die Grundlage eines Datenbankmanagementsystems (DBMS) dar. Solch ein Programm ist speziell darauf ausgelegt, Daten unter anderem zu sichern, zu verwalten, in ihnen etwas zu finden und sie zu sortieren. Um so etwas schnell, sicher und auch für große Mengen an Daten handhabbar zu machen, gibt es elegante und trickreiche Algorithmen, die von einem DBMS genutzt werden. So braucht man sich bei einer eigenen Applikation nicht mehr um die Speicherung und den Zugriff auf die Daten zu kümmern, sondern kann sich ganz auf die eigentliche Programmlogik und die Einund Ausgabe konzentrieren. Die Datenbank wird dann über eine Programmierschnittstelle angesprochen. Es gibt verschiedene Konzepte, wie man die Daten in einer Datenbank verwaltet: • Hierarchische Datenbank: Die Daten werden in einer Baumstruktur abgelegt. So besteht zum Beispiel eine Firma aus mehreren Abteilungen, diese wiederum aus Gruppen und die Gruppen aus einzelnen Mitarbeitern. Diese Struktur kann man auf eine Datenbank übertragen. Hierarchische Datenbanken wurden früher häufig genutzt (als Datenbanken quasi nur auf Großrechnern liefen). Abbildung 1.1 zeigt ein Beispiel für eine hierarchische Struktur.
Firma Lager
Einkauf
Verkauf
Schmidt
Herms
Werner
Meier
Gebert
Kirch
Müller
Schmitt
Schulze Abbildung 1.1: Beispiel für eine hierarchische Struktur
• Netzwerkdatenbank: In einer Netzwerkdatenbank lassen sich die einzelnen Informationen beliebig miteinander verknüpfen. Über diese Verknüpfungen kann man auf die Daten auch wieder zugreifen. Auch
9RP .DUWHLNDVWHQ ]XP 5'%06
Sandini Bib
Netzwerkdatenbanken werden heutzutage nicht mehr sehr häufig genutzt. Abbildung 1.2 zeigt ein Beispiel für eine Netzwerkstruktur.
Firma
Einkauf
Projekt 1
Meier
Verkauf
Projekt 2
Müller
Projekt 3
Schulze
Schmidt
Abbildung 1.2: Beispiel für eine Netzwerkstruktur
• Relationale Datenbanken: Dies ist das heute am meisten verwendete Konzept zur Speicherung von Daten. Dabei werden diese in einzelnen Tabellen (Relationen) verwaltet, die aus Zeilen und Spalten bestehen. Die Tabellen lassen sich über Schlüsselfelder miteinander verknüpfen, um Beziehungen zu realisieren. In Abbildung 1.3 sieht man ein Beispiel für eine (kleine) relationale Datenbank. Es gibt die drei Tabellen Kunden, Produkte und Auftrag, die miteinander über Schlüssel verbunden sind.
Kunden KuNr 1001 1002 1003 1004 ....
Produkte
Name Meier Müller Herms Gebert ...
Auftrag
Ort
... ... Berlin Bremen ... Dresden ... ... Köln ... ...
AufNr 901 911 923 945 ...
KuNr 1001 1001 1003 1004 ...
PrNr Name 101 Isozet 212 Nanolen 331 Optax 148 Relan .... ...
PrNr Anzahl ... 101 10 ... 148 23 ... 212 4 ... 331 55 ... ... ... ...
Abbildung 1.3: Beispiel für eine relationale Struktur
'DWHQEDQNHQ
Typ 01 02 01 03 ...
... ... ... ... ... ...
Sandini Bib
• Objektorientierte Datenbanken: Diese Datenbanken verwalten die Daten als Einheiten, die nicht nur die Werte enthalten, sondern auch noch die Methoden, um etwas mit diesen Werten machen zu können. Sie sind eigentlich ideal für die Nutzung mit den aktuellen objektorientierten Programmiersprachen geeignet, trotzdem sind sie nicht sehr verbreitet. Meist wird heutzutage mit objektorientierten Programmiersprachen entwickelt, die Daten aber relational gespeichert. Einige Datenbanksysteme können neben den Daten auch Programme direkt speichern und ausführen (zum Beispiel in Java), dies ist dann ein objektrelationaler Ansatz. Der Großteil der heutzutage verwendeten Datenbanken baut auf dem relationalen Modell auf. Daher soll hier – neben der Tatsache, dass MySQL dieses Modell nutzt – im Weiteren auch nur darauf eingegangen werden.
1.4
Aufbau einer Datenbank
Eine (relationale) Datenbank besteht aus verschiedenen Elementen, die im Folgenden zumindest kurz beschrieben werden sollen.
1.4.1 Tabellen Weiter oben steht, dass die Daten in Tabellen abgespeichert sind. Eine Tabelle ist immer dafür gedacht, eine bestimmte Art von Daten aufzunehmen, zum Beispiel Adressen, Produkte oder Aufträge. In einem relationalen Datenbanksystem gibt es viele solcher Tabellen für verschiedene Arten von Werten. Sie sind aufgeteilt in Zeilen, in denen einzelne Datensätze stehen, und Spalten, welche die Felder der Datensätze definieren. Jeder Spalte ist (neben anderen Parametern) ein Name und ein Datentyp zugeordnet, der festlegt, was in dieser Spalte gespeichert werden kann. So kann es eine Spalte mit dem Namen NAME geben, die (sinnigerweise) den Namen eines Kunden enthält und so definiert ist, dass sie beliebige (alphanumerische) Zeichen bis zu einer Länge von 40 Zeichen aufnimmt. Eine andere Spalte heißt PLZ, soll die Postleitzahl enthalten und ist so definiert, dass auch nur Ziffern eingetragen werden können. In Abbildung 1.4 sieht man eine Beispieltabelle mit den einzelnen Elementen Spalte, Zeile bzw. Datensatz und Feld. Die Kombination aus einem Datensatz und einer Spalte ergibt ein Feld. In diesem Feld ist immer eine einzelne Information abgelegt (man kann natürlich auch mehr ablegen, das ist aber meist ein Zeichen für einen schlechten Datenbank-Entwurf und wird später behandelt).
Sandini Bib
Spalte
Kundennr Name
Zeile/ Datensatz
1001 1002 1003 1004 ...
Meier Müller Schmidt Herms ...
PLZ 28357 69190 47877 68219 ...
Ort Bremen Walldorf Willich Mannheim ...
Feld Abbildung 1.4: Elemente einer Tabelle
1.4.2 Beziehungen Eine Tabelle wird man meist nicht einzeln nutzen. Wie schon in Abbildung 1.3 zu sehen, benötigt man fast immer mehrere Tabellen. Jede Tabelle sollte dabei einen Primärschlüssel besitzen. Dieser besteht aus einer oder mehrerer Spalten, die für jeden Datensatz zusammen einen eigenen Wert haben und die einzelnen Zeilen somit eindeutig identifizieren. Im Beispiel hat die Tabelle Kunden den Primärschlüssel KuNr (für Kundennummer), die Tabelle Produkte den Schlüssel PrNr (für Produktnummer) und die Tabelle Auftrag den Schlüssel AufNr (für Auftragsnummer). In dieser letzten Tabelle wird nun auf die beiden anderen Tabellen verwiesen, um festzuhalten, welcher Kunde welches Produkt haben möchte. Dafür bedient man sich derer Primärschlüssel, die an dieser Stelle als Fremdschlüssel zum Einsatz kommen. Auf diese Weise kann man Beziehungen zwischen Tabellen in einer relationalen Datenbank einrichten. Je nach Produkt müssen diese Beziehungen durch die Anwendungslogik abgebildet werden oder die Datenbank berücksichtigt diese Verbindungen selber. MySQL unterstützt zwar Primärschlüssel (d.h. eindeutige Tabellenfelder), aus verschiedenen Gründen aber keine Fremdschlüssel.
1.4.3 Indizes Indizes sind streng genommen für eine Datenbank nicht unbedingt nötig, werden aber andererseits für eine halbwegs zügige Verarbeitung unbedingt gebraucht. Sie helfen, häufige Abfragen schneller zu machen. In einer Tabelle werden die Datensätze normalerweise unsortiert abgelegt. Die Reihenfolge mag zwar meist der des Eintragen entsprechen, aber es liegt im Ermessen des Datenbanksystems, die Sätze möglichst effektiv
'DWHQEDQNHQ
Sandini Bib
zu speichern. Wenn man nun nach bestimmten Werten sucht, kann es sinnvoll sein, auf die Datensätze in einer vorsortierten Reihenfolge zugreifen zu können. Dazu legt man einen Index an, der für die entsprechenden Suchfelder in alphabetischer oder numerischer Abfolge Verweise auf die Datensätze speichert. Durch einen Index ist das Datenbanksystem nun nicht mehr gezwungen, die gesamte Tabelle auf der Suche nach einem Wert abzugrasen, sondern es kann mit wenigen Schritten den passenden Satz finden. Indizes haben aber auch einen Nachteil: sie müssen bei jeder Aktualisierung der Tabelle nachgezogen werden (das macht das Datenbanksystem selber), was natürlich Zeit kostet. Je mehr Indizes man also für eine Tabelle definiert hat, desto schneller kann eine Suche sein, aber desto langsamer wird auch das Aktualisieren von Daten. Man sollte daher Indizes nur für die häufigsten Suchvorgänge anlegen, um einen guten Kompromiss zu finden.
1.4.4 Weitere Elemente Es gibt noch weitere Elemente in einer Datenbank, die hilfreich sein können, aber nicht in allen Datenbanksystemen vorhanden sind: • Regeln/Constraints: Eine Regel oder auch Constraint ist eine Einschränkung von möglichen Inhalten für ein Feld, welche bei der Definition einer Tabelle zusätzlich zum Datentyp angegeben wird. Somit kann man den Wertebereich noch weiter einschränken oder auch angeben, ob der Wert des Feldes in einer anderen Tabelle vorhanden sein muss. MySQL kann gewisse Regeln verarbeiten, allerdings nur in eingeschränktem Maße. • Datenbank-interne Programme (Stored Procedures): Auch in einer Datenbank können Programme und Funktionen gespeichert werden, um häufig wiederkehrende Abläufe sichern zu können. Der Vorteil solcher Programme ist, dass sie unabhängig von der ausführenden Applikation sind und häufig auch schneller ablaufen können. Jedes Datenbanksystem hat dabei seine eigene Programmiersprache, manche können auch mit Java arbeiten. MySQL bietet diese Möglichkeit nicht an, mit der Ausnahme von selbstdefinierten Funktionen, die allerdings zum einen auch außerhalb der Datenbank definiert sind und zum anderen nicht unter Windows funktionieren. • Sichten/Views: Eine Sicht bzw. View ist eine vordefinierte Abfrage auf Tabellen. Sie dient dazu, Anfragen einfacher zu gestalten oder auch, bestimmte Felder vor Benutzern zu verbergen. MySQL bietet keine Sichten an.
Sandini Bib
• Trigger: Dies sind interne Programme, die ausgeführt werden, sobald ein bestimmtes Ereignis eintritt. Dabei kann es sich zum Beispiel um das Hinzufügen, Ändern oder Löschen von Datensätzen in einer Tabelle handeln. Trigger sind nützlich, um erweiterte Regeln zu definieren, die nicht von der ausführenden Applikation abhängen. Auch Trigger sind nicht in MySQL vorhanden und müssen mit der Applikationslogik nachgestellt werden. Es mag so aussehen, als ob MySQL keine „richtige“ Datenbank ist, da so vieles nicht vorhanden ist. Das hat aber seine guten Gründe. MySQL ist darauf ausgerichtet, möglichst schnell und effizient seine Arbeit zu erledigen. Die meisten der oben aufgeführten Punkte sind zwar hilfreich, lassen sich aber fast alle auf die eine oder andere Weise ersetzen und kosten die Datenbank zudem viel Verwaltungsaufwand. Durch den Verzicht darauf kann MySQL so schnell sein. Andere Punkte sind durchaus auf der Liste der abzuarbeitenden Wünsche, wie zum Beispiel Sichten. Kann oder will man wirklich nicht auf diese Punkte verzichten, muss man sich entweder mit den „Workarounds“ beschäftigen oder letztendlich auf ein anderes Datenbanksystem ausweichen.
1.4.5 Standalone vs. Client/Server Wollte man früher auf eine Datenbank zugreifen, musste man sich direkt an den Rechner setzen oder eine Terminalverbindung dorthin aufbauen. Dies ist natürlich nicht sehr komfortabel. Mittlerweile arbeiten nahezu alle größeren Datenbanken nach dem Client/Server-Prinzip. Das bedeutet, dass auf einem Rechner der Datenbank-Server läuft, d.h. Dienste für andere Programme anbietet. Auf anderen Rechnern (oder auch auf dem gleichen Computer) laufen Client-Applikationen (sei es die interaktive Applikation für den direkten Datenbankzugriff oder „richtige“ Programme, die die Daten in der Datenbank nutzen), die sich mit dem Datenbank-Server verbinden und seine Dienste nutzen. Auch MySQL nutzt dieses Prinzip. Dabei ist es durchaus häufig, dass Serverund Client-Applikation auf dem gleichen Rechner laufen. Durch Sperren gerade verwendeter Daten wird verhindert, dass mehrere Clients gleichzeitig die selben Daten ändern und es dadurch zu Problemen kommt. Auch sogenannte „Desktop-Datenbanken“ wie Microsoft Access können prinzipiell von mehreren Rechnern im Netz gleichzeitig genutzt werden. Sie sind aber nicht unbedingt darauf ausgelegt und es bereitet nicht unerhebliche Probleme, wenn man sie im wirklichen Leben auf diese Art und Weise verwenden will. Diese Systeme bezeichnet man als „Standalone-Systeme“.
'DWHQEDQNHQ
Sandini Bib
1.5
Einsatzbeispiele
Wofür kann man nun eigentlich eine Datenbank nutzen? Nun, es gibt natürlich unendlich viele Beispiele und ein paar davon will ich hier aufzählen: • Die klassische Adressverwaltung: Name, Straße, PLZ, Ort, Telefon usw. für verschiedene Kontaktgruppen, wie Bekannte, Verwandte, Kunden, Lieferanten usw. Dies kann man beliebig erweitern, indem man zum Beispiel beliebig viele Adressen pro Kontakt zulässt und dabei eine Standardadresse markiert. Oder man ermöglicht verschiedene Kontaktarten (Telefon, Fax, E-Mail, SMS, ...), die auch mehrfach vorkommen können. • Genauso klassisch: eine CD-Verwaltung: Titel, Interpret, Stücke, Länge, Genres usw. Auch hier kann man deutlich mehr ins Detail gehen, indem man mehrere Interpreten pro CD und auch pro Stück zulässt, um dann bei einer Suche auch das Lied „Don't Give Up“ zu finden, wenn man nach Kate Bush sucht (das hat sie nämlich mit Peter Gabriel gesungen und findet sich unter anderem auf „Shaking the Tree“ von Peter Gabriel). • Vereinsverwaltung: Natürlich als zentrale Information die Mitglieder mit Name, Adresse, Eintrittsdatum, Geburtsdatum usw. Dazu die finanziellen Aspekte: Wer hat welchen Mitgliedsstatus, wieviel muss er bezahlen, wieviel hat er schon bezahlt, geschieht es per Lastschrift oder Überweisung und noch vieles mehr. Man kann bei einem Kanuverein zum Beispiel auch verwalten, wer welches Boot wo liegen hat und welche Leihboote bereitstehen. • Nun mehr ins Geschäftliche: Die Lagerverwaltung: Ein- und Ausgänge werden verzeichnet und man kann nachschauen, wo man was findet. Sinkt der Bestand einer Ware unter einen bestimmten Level, kann automatisch nachbestellt werden. Eventuelle Verfallsdaten lassen sich ebenso berücksichtigen wie besondere Lagervorschriften. • Dazu passend: Die Auftragsverwaltung: Kunden (natürlich aus der Kundenverwaltung) bestellen Waren. Bei der Bestellung kann direkt geprüft werden, ob noch genug Waren vorhanden sind oder wann sie bei zu geringem Lagerbestand kommen werden. Der Lagerbestand kann sofort um die Bestellung reduziert werden und das Erstellen der Rechnung lässt sich auch automatisieren. Die letzten beiden Beispiele zeigen schon, dass man durch einen geschickten Entwurf verschiedene Bereiche eines Unternehmens miteinander verknüpfen kann. Dabei dient die Datenbank meist nur als Grundlage für die Datenspeicherung; die gesamte Geschäftslogik findet sich auf Applikationsebene.
(LQVDW]EHLVSLHOH
Sandini Bib
Diese Trennung ist nicht unbedingt streng, so lässt sich ein Teil der Geschäftslogik auch direkt in der Datenbank abbilden (über Trigger, Regeln und gespeicherte Prozeduren) oder die Anwendungsprogramme als Daten selbst in Tabellen speichern. Letzteres findet sich unter anderem bei dem Beispiel für Unternehmenssoftware: SAP R/3. Hier ist – bis auf die grundlegendsten Bibliotheken – alles in der Datenbank abgelegt: die Programme und Transaktionen und natürlich auch die Daten. Nun läuft R/3 nicht gerade auf MySQL, worauf ich aber hinaus will, ist, dass es mit einer Datenbank alleine nicht getan ist. Man benötigt eigentlich immer eine passende Applikation dazu, egal ob sie im Web oder lokal läuft. Natürlich gibt es auch bei MySQL Programme zum Verwalten der Daten (die in diesem Buch auch den Schwerpunkt bilden), aber der eigentliche Nutzer der Daten sollte nicht gerade über diese auf die Datenbank zugreifen. Im nächsten Kapitel werden wir ein Beispielszenario etwas detaillierter beleuchten, welches uns durch das restliche Buch begleiten wird.
1.6
MySQL
Wir haben uns bisher eher allgemein mit Datenbanken beschäftigt und sind nur ab und zu auf MySQL eingegangen. In diesem Abschnitt soll es nun nur um MySQL gehen: wie es entstanden ist, was das Besondere daran ist, was es kostet und wer es einsetzen darf.
1.6.1 Was ist MySQL? MySQL ist ein relationales Datenbankmanagementsystem, wie weiter oben beschrieben. Seine Quelltexte sind frei verfügbar und können selber angepasst werden. Das primäre Ziel ist vor allem Geschwindigkeit, nicht Vollständigkeit. So werden Sie längst nicht alle Funktionen finden, welche die „großen“ Datenbanken bieten, auch das wurde schon kurz erwähnt und wird im Anhang noch erläutert werden. Aber ein Großteil dessen, was man im Allgemeinen bei einem RDBMS benötigt, ist vorhanden, so dass sich MySQL für sehr viele Zwecke sehr wohl eignet. Und es ist wirklich schnell. Auf den Webseiten von MySQL (www.mysql.com) finden sich Benchmark-Ergebnisse, die sich sehen lassen können. Zudem kann auch MySQL mit sehr großen Datenmengen umgehen. Dies alles hat zur Folge, dass MySQL bei vielen, auch großen, Unternehmen auf der ganzen Welt eingesetzt wird.
'DWHQEDQNHQ
Sandini Bib
1.6.2 Die Geschichte und Idee hinter MySQL Die Entwickler von MySQL wollten ursprünglich den Code der (ebenso freien) Datenbank mSQL nutzen, um für die Firma TcX Webanwendungen zu implementieren. Dabei war geplant, die eigenen Tabellen über eigene Routinen anzusprechen. Allerdings stellte man schnell fest, dass mSQL nicht so flott war, wie man es eigentlich benötigte, und sich auch nicht so leicht anpassen ließ, wie man es sich wünschte. Also wurde eine neue SQL-Schnittstelle für die eigenen Tabellen geschrieben, die sich allerdings stark an die mSQL-Syntax anlehnte. Damit war sichergestellt, dass vorhandene Software und bestehende Tools leicht mit MySQL zusammenarbeiten konnte. Dies geschah 1994. Etwa ein Jahr später wurde MySQL im Internet veröffentlicht. Im Laufe der Zeit wurde das System immer umfangreicher und erhielt mehr und mehr Möglichkeiten. Aber immer wurde darauf geachtet, das System nicht langsamer zu machen. Wenn heutzutage Dinge fehlen, werden sie entweder demnächst entwickelt oder es wurde bewusst auf sie verzichtet, da sie im Vergleich zu ihrem Nutzen die Geschwindigkeit zu sehr reduzieren würden. MySQL AB, die Firma hinter MySQL, wurde 1995 in Schweden von Michael „Monty“ Widenius, David Axmark und Allan Larsson gegründet. Michael und David gehörten schon lange der Open Source-Szene an und auch MySQL sollte darin begründet sein. MySQL AB entwickelt MySQL, versucht, es bekannter zu machen und bietet vor allem auch Support dafür an (der durchaus kostenpflichtig ist). Ziel ist weiterhin, einen schnellen, kompakten und stabilen Datenbankserver bereitzustellen.
1.6.3 Elemente von MySQL MySQL besteht vor allem aus dem eigentlichen Datenbank-Server. Diesen gibt es in verschiedenen Varianten und auf vielen unterschiedlichen Betriebssystemen. Die Varianten ermöglichen es, entweder einen besonders schnellen Server auszuwählen, der manche Funktionen nicht beinhaltet, oder einen zu nutzen, der mehr bietet, aber nicht so schnell ist wie sein „eingeschränkter“ Bruder. Man kann aus diversen Betriebssystemen wählen, unter anderem Linux, FreeBSD und vielen anderen Unix-Betriebssystemen (kommerziellen wie freien), sowie Windows, Mac OS usw. Dabei werden allerdings nicht alle Betriebssysteme gleich weit unterstützt, manche spezielleren Teile sind nur unter Linux/Unix vorhanden.
0\64/
Sandini Bib
Neben dem eigentlichen Server gibt es natürlich noch weitere Programme, denn nur mit dem Server alleine kommt man nicht weit. Man braucht noch Client-Programme, um auf den Server zugreifen zu können. Zunächst ist da der interaktive Client, in dem man SQL-Befehle ausführen und sich Ausgaben anzeigen lassen kann. Nun wird man einem normalen Endbenutzer nicht zumuten wollen, direkt die Datenbank zu befragen, daher dient dieses Programm eher dem Entwickler und Administrator. Neben diesem interaktiven Client gibt es weitere Tools, die zur Administration der Datenbank dienen und auf diesem Client aufbauen. Weiterhin gibt es natürlich Programme, um die Datenbank zu sichern und wieder einzuspielen. Um anderen Applikationen den Zugriff auf die Datenbank zu ermöglichen, kann man sie über verschiedene Schnittstellen (APIs, Application Programming Interfaces) ansprechen. Zu den wichtigsten gehören das PHP-API, das Perl-DBI-API und MyODBC, die ODBC-Schnittstelle von MySQL. Zudem gibt es APIs für C, C++, Java (via JDBC) und andere Sprachen.
1.6.4 Lizenzen und Kosten bei MySQL Man sagt immer, MySQL ist eine „freie“ Datenbank. Nun stimmt das zwar im Prinzip, aber es gibt durchaus Szenarien, bei denen man eine Lizenz erwerben muss. Grundsätzlich ist MySQL tatsächlich kostenlos und Open Source. Man erhält den Quellcode und kann mit ihm arbeiten, ihn anpassen und erweitern. MySQL läuft unter der GPL, der GNU General Public License. Wenn man mit dieser Lizenz Probleme hat, weil man zum Beispiel seine eigenen Sourcen nicht veröffentlichen will, kann man bei MySQL AB Lizenzen erwerben. Wichtiger aber ist, dass man eine Lizenz erwerben muss, wenn man MySQL in seine eigene kommerzielle Applikation einbetten will oder wenn die eigene kommerzielle Applikation nur mit MySQL läuft. Aber auch dann ist der Preis einer Lizenz ausgesprochen günstig im Vergleich zu den sonstigen kommerziellen Datenbanksystemen. Nutzt man MySQL mit seiner eigenen Applikation kommerziell, muss aber keine Lizenzgebühr zahlen, wird es trotzdem gerne gesehen, wenn man einen Support-Vertrag abschließt, den es in verschiedenen Varianten gibt. Detailliertere Informationen zur Lizenzierung und vor allem zu den Support-Leveln finden sich auf den Webseiten von MySQL.
'DWHQEDQNHQ
Sandini Bib
2
Einsteigen in MySQL
le
n e rn
Bisher haben wir uns nur allgemein mit Datenbanken befasst und uns angeschaut, was MySQL eigentlich besonders ausmacht. Nun wollen wir tatsächlich in die Benutzung von MySQL einsteigen.
2.1
Was Sie in diesem Kapitel lernen
In diesem Kapitel lernen Sie unser Beispielszenario kennen. Wir setzen es in ein Modell aus Tabellen und Beziehungen um. Dann starten wir endlich MySQL – nachdem wir geklärt haben, was vorher schon vorhanden und eventuell gestartet sein muss. Wie kommuniziert man nun mit MySQL? Die Sprache SQL wird erläutert und dann eine Datenbank angelegt. Wir überlegen, wie die Tabellen mit welchen Spalten und welchen Wertetypen erstellt werden sollen. Dabei lernt man gleich die verschiedenen Feldtypen und Möglichkeiten kennen. Zudem werden wir Relationen zwischen Tabellen sehen und modellieren.
2.2
Von der Realität zum Modell – Das Beispielszenario
In einer Datenbank speichert man im Allgemeinen Daten ab, die aus dem wirklichen Leben kommen. Dabei muss man beachten, dass man die Informationen sinnvoll ablegt, um sie schnell (oder überhaupt) erreichen zu können. Zudem sind auch die Beziehungen so aufzubauen, dass sie zum einen die Realität widerspiegeln, zum anderen aber auch in der Datenbank abbildbar sind. Man muss also die Realität in einem Modell für die Datenbank abbilden. In diesem Abschnitt wollen wir ein Beispielszenario vorstellen, welches uns durch den Rest des Buches begleiten soll. In Büchern über Daten-
:DV 6LH LQ GLHVHP .DSLWHO OHUQHQ
Sandini Bib
banken werden als Beispiele häufig Schul- und Notenverwaltungen verwendet. Wir wollen ein etwas anderes Szenario nutzen, das deswegen nicht besser oder schlechter ist: Eine Musikschule.
2.2.1 Die Realität In einer Musikschule geht es naturgemäß um das Erlernen eines Instruments (wobei ich die Stimme hier auch als Instrument ansehen möchte, ebenso Sonderfälle wie die musikalische Früherziehung). Schüler kommen in die Schule, um in der Nutzung eines Instruments unterrichtet zu werden. Dies geschieht durch Lehrkräfte, die verschiedene Instrumente beherrschen. Manche Schüler haben Einzelunterricht, andere wiederum lernen in Gruppen. Dazu gibt es mehrere Orchester und Chöre, die unterschiedliche Bereiche abdecken (Jazz-Combo, Kammerorchester, Symphonieorchester, Rockband, Gospel-Chor, „klassischer“ Chor usw.). Um unterrichten und üben zu können, benötigt man Räume, die belegt werden müssen. Schließlich haben nicht alle Schüler eigene Instrumente – wenn es darum geht, auszuprobieren, ob einem ein Instrument wirklich Spaß macht, ist es nicht sinnvoll gleich mehrere Tausend Euro auszugeben. Also hat die Musikschule ein gewisses Repertoire an Leihinstrumenten, das sie den Schülern gegen Entgelt zur Verfügung stellt. Wenn die Leihinstrumente begehrt sind, macht auch eine Warteliste Sinn. Für die Teilnahme am Unterricht oder die Nutzung von Leihinstrumenten zahlen die Schüler Geld per Lastschrift – und die Lehrkräfte erhalten Einkommen. Man benötigt also die Kontodaten der Teilnehmer und muss wissen, wer woran teilnimmt.
2.2.2 Das Modell Im vorigen Abschnitt wurde einiges aufgezählt, was eine Musikschule ausmacht. Dabei wurde noch lange nicht alles berücksichtigt, so zum Beispiel die unterschiedliche Qualifikation der Lehrkräfte und der Wissensstand der Schüler. Aber es soll an dieser Stelle ausreichen und wir werden in diesem Buch auch nicht alles behandeln. Zunächst einmal wollen wir herausfinden, welche Elemente es gibt und was für Eigenschaften sie haben. Dies kann dann auf Tabellen und ihren Spalten abgebildet werden. Wir haben also: • Schüler: Ein Schüler hat natürlich einen Vor- und Nachnamen, ein Geburtsdatum, eine Straße, PLZ und Ort, an dem er wohnt, eine Tele-
Sandini Bib
fonnummer, unter der man ihn erreichen kann und eine Bankverbindung. • Lehrer: Auch der Lehrer hat Vor- und Nachnamen, eine Adresse bestehend aus Straße, PLZ und Ort, eine Telefonnummer und Bankdaten. • Leihinstrument: Ein Leihinstrument ist ein bestimmtes Instrument (Flöte, Gitarre, Geige usw.) von einem Hersteller und hat eine Typbezeichnung. Dazu kommt die Leihgebühr, die ein Schüler pro Monat für dieses Instrument zahlen muss. • Unterricht: Ein (Einzelunterricht) oder mehrere (Gruppenunterricht) Schüler werden von einem Lehrer in einem bestimmten Instrument jede Woche an einem bestimmten Wochentag, zu einer bestimmten Uhrzeit für eine gewisse Dauer in einem Raum unterrichtet. Die Kosten für den Schüler hängen vom Instrument ab und davon, ob es sich um Einzel- oder Gruppenunterricht handelt. • Orchester/Chor: Auch an einem Orchester oder Chor nehmen Schüler teil und werden von einem Leiter dirigiert. Dabei gibt es verschiedene Instrumente (oder Stimmen). Das Ganze findet an einem bestimmten Wochentag zu einer gewissen Uhrzeit für eine bestimmte Dauer statt. Dabei wird ein Raum belegt. Kosten fallen für die Schüler im Allgemeinen nicht an, da die Schule ein eigenes Interesse daran hat, Orchester und Chor im Angebot zu haben. Allerdings wird der Leiter eventuell eine Aufwandsentschädigung erhalten. Wie man sieht, sind die ersten drei Punkte wirkliche „Objekte“, die man anfassen kann. Die beiden letzten Punkte sind aber nicht wirklich materiell, sondern es handelt sich eher um Ereignisse (oder neudeutsch „Events“). Ein Element einer Datenbank, welches in einer Tabelle abgebildet werden soll, ist also nicht immer etwas Greifbares. Noch auffälliger wird dies bei den Leihinstrumenten. Sind schon Unterricht und Orchester/Chor nur Ereignisse, ist das Ausleihen eines Instruments oder erst recht die Position auf der Warteliste eher ein Zustand, der aber auch in Tabellen abgebildet werden kann und muss: • Ausleihe: Ein bestimmtes Leihinstrument wird von einem Datum an einen Schüler ausgeliehen. Ein Enddatum ist nicht unbedingt notwendig, da meist nicht klar ist, wie lange der Schüler das Instrument nutzen möchte. • Ausleihwarteliste: Das Leihinstrument ist in Benutzung und die interessierten Schüler stehen mit einer gewissen Position auf der Warteliste.
9RQ GHU 5HDOLWlW ]XP 0RGHOO ¥ 'DV %HLVSLHOV]HQDULR
Sandini Bib
Zudem gibt es Objekte, die immer wieder vorkommen, und für die es sich lohnt, Tabellen anzulegen, obwohl sie nicht direkt im Mittelpunkt stehen: • Instrument: Die Bezeichnung des Instruments und die Instrumentengruppe (Holzblasinstrument, Streichinstrument usw.) • Raum: Die Bezeichnung des Raums und die Etage, auf der er sich befindet (unter der Annahme, dass wir es mit nur einem Gebäude zu tun haben). Stecken wir diese Auswahl an Objekten in eigene Tabellen, vermeidet man das immer wiederkehrende Eingeben der Werte mit der Möglichkeit, sich zu verschreiben (Querfölte statt Querflöte) und mit der Gewissheit, immer die gleiche Bezeichnung zu nutzen (Geige, Violine oder was?). Solche Auswahllisten können ungemein hilfreich sein und sind deshalb hier schon aufgeführt, auch wenn sie nicht unbedingt notwendig wären. Aber allein im Rahmen eines guten Datenbank-Designs macht es Sinn, sie zu nutzen. Kann man dieses Modell jetzt direkt in Tabellen umsetzen? Im Prinzip schon, allerdings werden wir auf Unschönheiten stoßen, die wir dann beheben. Wir werden in Kapitel 2.6 die Tabellen zusammenstellen.
2.3
Verbinden mit MySQL
Jetzt wollen wir uns unsere Datenbank für die Musikschule erstellen. Aber wie kann man sich mit MySQL verbinden? Im vorigen Kapitel wurde geschildert, dass es sich bei MySQL um ein Client/Server-System handelt. Das bedeutet, dass sich der Datenbank-Server auf einem ganz anderen Rechner befinden kann. Daher sollen hier kurz die Voraussetzungen beschrieben werden, die für das weitere Gelingen notwendig sind.
2.3.1 Voraussetzungen Um einen MySQL-Client starten zu können, benötigt man zunächst einmal einen laufenden MySQL-Server. Dieser kann auf dem eigenen Rechner sein (was zu Beginn durchaus sinnvoll ist, da es manches vereinfacht), sich aber auch ganz woanders befinden. Sofern Sie einen eingerichteten MySQL-Server zur Verfügung haben, können Sie sich von dessen Administrator die Verbindungsdaten geben lassen. Ist dies nicht der Fall, finden Sie im Anhang ein Kapitel über die Installation von MySQL. Das ist nicht schwer und ist auch schnell erledigt.
Sandini Bib
Um sich die Angabe des Verzeichnisses mit den MySQL-Applikationen zu ersparen, bietet es sich an, den Pfad /bin unter Unix bzw. \bin unter Windows in die Pfadvariable des Betriebssystems mit aufzunehmen.
2.3.2 Das interaktive Programm – mysql mysql ist das Programm, mit dem so gut wie alle administrativen Tätigkeiten erledigt werden können. Man kann Datenbanken und Tabellen anlegen und anpassen, Daten eingeben, ändern und löschen, Rechte vergeben und entziehen und vieles mehr. Natürlich kann man auch hier die Daten abfragen, aber der Endanwender sollte mit diesem Tool eigentlich nichts zu tun bekommen.
mysql
Gestartet wird das Programm, indem man an der Kommandozeile den Befehl mysql aufruft: prompt> mysql
Auch wenn MySQL unter so gut wie jedem Betriebssystem auf diesem Planeten läuft, werde ich im Weiteren nur auf die Nutzung unter Linux und Windows eingehen. Wenn es darum geht, Befehle auf Betriebssystemebene einzugeben, ist die Anzeige des Prompt naturgemäß bei Linux anders als bei Windows. Aber selbst bei ein und dem selben Betriebssystem kann man sich den Prompt meist sehr flexibel einstellen. Daher werde ich im Folgenden immer dann, wenn das Betriebssystem im Spiel ist, folgende Darstellung wählen: prompt> mysql
Dabei kann es in Wirklichkeit bei Linux vielleicht so aussehen: truhe: ~/mysql> mysql
während es unter Windows eher so aussieht: C:\mysql> mysql
Dazu sollte möglichst – wie schon erwähnt – das Verzeichnis mit den Programmdateien von MySQL mit in den Standardpfad aufgenommen sein. Befindet sich der Server auf einem anderen Rechner, kann man das dem Programm mit dem Parameter -h mitteilen. Dahinter folgt dann der Name des entsprechenden Servers. Läuft der Datenbank-Server zum Beispiel auf dem Rechner truhe, müsste der Aufruf dementsprechend lauten:
Sandini Bib
prompt> mysql -h truhe
Wird auch die Angabe eines bestimmten Benutzers benötigt, geschieht dies über den Parameter -u, gefolgt vom Benutzernamen, sowie den Parameter -p, mit dem das Kennwort angegeben oder – falls nicht mit angegeben – abgefragt wird. Falls man sich also als Benutzer thomas am Server truhe anmelden möchte, geschieht dies per: prompt> mysql -h truhe -u demmig -p
Gibt man dem Parameter -p ein Kennwort direkt mit, darf zwischen -p und dem Kennwort kein Leerzeichen stehen. Allerdings ist es meist besser, das Kennwort nicht mit anzugeben, sondern es abfragen zu lassen, da man es dann nicht auf dem Bildschirm sehen kann. Man erhält dann die Aufforderung Enter password:
und kann das Kennwort verdeckt eingeben. Hat man dies erfolgreich getan (oder war gar keins notwendig), meldet sich MySQL mit einem informativen Text ähnlich dem folgenden: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 to server version: 3.23.51-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql>
Die letzte Zeile zeigt an, dass MySQL nun auf die Eingabe von Befehlen wartet. Zwei werden wir gleich kennen lernen: das Beenden und die Hilfefunktion. Zum Beenden des Programms (nicht des Servers) gibt man an der Eingabeaufforderung QUIT oder EXIT ein. Unter Linux funktioniert auch die Tastenkombination (Strg)(D). mysql verabschiedet sich nun mit einem kurzen
QUIT/EXIT
mysql> QUIT Bye prompt>
Unter MySQL ist es – von wenigen Ausnahmen abgesehen – unwichtig, ob Sie groß (QUIT), klein (quit) oder gemischt (QuIt) schreiben. Ich werde in diesem Buch die Befehle groß, Tabellen-, Datenbank- und andere Namen aber klein schreiben, wenn ich nicht auf etwas gesondert hinweisen möchte.
Sandini Bib
Um die Hilfe aufzurufen, gibt man an der Eingabeaufforderung schlicht HELP oder \h ein (bei der zweiten Variante muss das h allerdings klein sein). Man erhält dann folgenden Text:
HELP
mysql> HELP MySQL commands: Note that all text commands must be first on line and end with ';' help (\h) Display this help. ? (\?) Synonym for `help'. clear (\c) Clear command. connect (\r) Reconnect to the server. Optional arguments are db and host. ego (\G) Send command to mysql server, display result vertically. exit (\q) Exit mysql. Same as quit. go (\g) Send command to mysql server. notee (\t) Don't write into outfile. print (\p) Print current command. quit (\q) Quit mysql. rehash (\#) Rebuild completion hash. source (\.) Execute a SQL script file. Takes a file name as an argument. status (\s) Get status information from the server. tee (\T) Set outfile [to_outfile]. Append everything into given outfile. use (\u) Use another database. Takes database name as argument. Connection id: 11 (Can be used with mysqladmin kill) mysql>
Die in der Hilfe aufgeführten Befehle dienen eigentlich alle dazu, dem Programm mysql etwas mitzuteilen, aber nicht, um mit dem Server zu kommunizieren. Dazu bedient man sich der Sprache SQL, die im nächsten Abschnitt vorgestellt wird. Innerhalb von mysql kann man sich recht komfortabel mit den Cursortasten bewegen: (½] und (¼] blättern zum vorigen oder nächsten Befehl, der schon eingegeben worden war. (Bild½] und (Bild¼] springen an den Anfang und das Ende der Befehlsliste.
2.4
SQL
SQL steht für Structured Query Language. Es handelt sich dabei um die Abfragesprache für Datenbanken. Sie ist zwar theoretisch standardisiert, aber praktisch hat jeder Datenbank-Hersteller seine eigenen Besonderheiten, Ergänzungen und Abweichungen. SQL ist recht „sprechend“, d.h., ein Befehl liest sich fast wie ein normaler (englischer) Satz. Ein Beispiel ist
64/
Sandini Bib
SELECT name, ort FROM kunden WHERE plz BETWEEN 68000 AND 68999 ORDER BY name;
Dieser Befehl sagt: „Wähle den Namen und Ort der Kunden aus, deren Postleitzahl zwischen 68000 und 68999 liegt und sortiere sie nach dem Namen.“ Eigentlich klar, oder?
2.4.1 Geschichte von SQL Die Geschichte von SQL verläuft parallel zur Entwicklung der Datenbanken selber. 1969 erstellte Edgar F. Codd erstmals einen Bericht, der einen Ansatz zur Verknüpfung mehrerer Tabellen beschreibt. Dieser Bericht ist allerdings recht unbekannt, da er nur IBM-intern an einen eingeschränkten Kreis ging. Eine überarbeitete Version des Konzepts floss dann in einen 1970 erschienenen Artikel „A Relational Model of Data for Large Shared Data Banks“ im Journal der Association of Computer Machinery ein. 1974 gab es dann einen Prototyp eines RDBMS namens „System R“. Zwei Jahre später tauchte zum ersten Mal die Abfragesprache auf, die damals den Namen „Structured English Query Language“ (SEQUEL) erhielt. Im Laufe der Jahre mutierte der Name schließlich zu „Structured Query Language“ (SQL), wird aber häufig noch als „Sequel“ ausgesprochen (MySQL allerdings möchte bevorzugt „my es qu ell“ gesprochen werden). Im Jahr 1979 brachte die Firma „Relational Software, Inc.“ (mittlerweile Oracle) eine erste kommerzielle Version von SQL heraus. 1986 wurde ein erster Standard vom American National Standards Institute (ANSI) veröffentlicht, der mittlerweile in den Jahren 1992 und 1999 zwei Nachfolger fand. Alle „richtigen“ Datenbanken unterstützen SQL, auch wenn sich so gut wie keine komplett an den Standard hält. Alle haben ihn erweitert und/oder eingeschränkt oder verwenden eine leicht unterschiedliche Syntax. Allerdings sind die grundlegenden Elemente immer gleich, so dass man sich meist schnell zurechtfindet, wenn man den Datenbank-Hersteller wechselt.
2.4.2 Elemente von SQL Man kann SQL in vier Bereiche unterteilen: Datendefinition, Datenänderung, Datenkontrolle und Datenabfrage. Ich will sie im Folgenden kurz vorstellen. Datendefinition Die Befehle dieses Bereichs dienen dazu, die Strukturen zu definieren, in denen später die Daten abgelegt werden sollen. Dazu gehören zum Bei-
DDL
Sandini Bib
spiel Datenbanken und Tabellen. Auch zum Ändern oder Löschen von Strukturen gibt es Kommandos. Dieser Bereich wird auch Data Definition Language (DDL) genannt. Beispiele sind CREATE, ALTER und DROP. Datenänderung Mit den Befehlen dieses Abschnitts lassen sich die eigentlichen Daten einfügen, ändern und löschen. Der übliche Begriff für diesen Bereich ist Data Manipulation Language (DML); Beispiele dazu sind INSERT, UPDATE und DELETE.
DML
Datenkontrolle Nicht jeder soll alle Daten einer Datenbank einsehen und auch nicht alle Tätigkeiten ausführen dürfen. So macht es Sinn, dass nur der Datenbank-Entwickler und der Administrator Änderungen an der Struktur vornehmen können. Ein normaler Mitarbeiter wird aber sicherlich nur seine Daten nutzen sollen, und zum Beispiel vor allem keine Gehaltsdaten anderer Kollegen einsehen dürfen. Die Befehle zum Erteilen und Entziehen von Rechten (GRANT und REVOKE) gehören zum Bereich der Data Control Language (DCL).
DCL
Datenabfrage Zum Schluss der Bereich mit dem Befehl, der wohl am häufigsten genutzt wird: SELECT. Mit dieser Anweisung kann man die Daten aus den Tabellen auslesen. Übrigens kann man in den meisten Datenbank-Systemen auch die Datendefinitionen (also Tabellenstrukturen, Datenbanken usw.) mittels SELECT aus Systemtabellen auslesen (bei MySQL gibt es dazu andere Befehle).
2.5
Anlegen einer Datenbank
Wenn Sie sich mit MySQL verbunden haben, ist normalerweise noch keine Datenbank ausgewählt. Man kann auf jedem MySQL-Server mehrere Datenbanken anlegen. (Zudem kann man auf einem Rechner auch noch mehrere MySQL-Server laufen lassen.) Es gibt in jedem vollständig eingerichteten MySQL-Server schon zwei Datenbanken. Eine Übersicht darüber erhält man mit dem Befehl SHOW DATABASES. Geben Sie ihn in ihrem MySQL-Client ein und schließen Sie das Ganze mit einem Semikolon ab.
SHOW DATABASES
Sandini Bib
mysql> SHOW DATABASES; +----------+ | Database | +----------+ | mysql | | test | +----------+ 2 rows in set (0.11 sec)
Befehle werden in MySQL grundsätzlich mit einem Semikolon abgeschlossen. Wenn Sie es einmal vergessen und die Eingabetaste drücken, werden Sie Folgendes sehen: mysql> SHOW DATABASES ->
Der Pfeil nach rechts ist ein Zeichen dafür, dass MySQL noch eine Eingabe erwartet. Falls Ihnen dies unabsichtlich passiert, geben Sie einfach hier das Semikolon ein und drücken Sie die Eingabetaste: mysql> SHOW DATABASES -> ; +----------+ | Database | +----------+ | mysql | | test | +----------+ 2 rows in set (0.00 sec)
An diesem Beispiel kann man aber noch etwas anderes, sehr Praktisches erkennen: Ist ein Befehl einmal zu lang für eine einzelne Zeile, kann man jederzeit eine neue Zeile beginnen, bis man den Befehl mit einem Semikolon beendet. Das kann der Übersichtlichkeit durchaus förderlich sein: SELECT FROM WHERE ORDER BY
name, ort kunden plz BETWEEN 68000 AND 68999 name;
Es gibt zudem noch weitere Ausgaben der Konsole, die zeigen, dass auf etwas gewartet wird: mysql> SELECT * FROM kunden WHERE name = 'Meier '>
Sandini Bib
Hier fehlt ein einfaches Anführungszeichen. Wenn man an dieser Stelle nur ein Semikolon eingeben würde, käme man nicht weiter, da dies als Teil einer Zeichenkette angesehen würde. Daher kann man hier nur entkommen, indem man erst das Anführungszeichen und dann das Semikolon eingibt: mysql> SELECT * FROM kunden WHERE name = 'Meier '> ';
Das Gleiche gilt genauso für das doppelte Anführungszeichen: mysql> SELECT * FROM kunden WHERE name = "Meier "> ";
Hier muss man natürlich mit einem doppelten Anführungszeichen „auflösen“. Manche Befehle benötigen übrigens kein Semikolon als Abschluss, ich werde in diesen Fällen gesondert darauf hinweisen. Zwei Beispiele (bzw. drei) haben Sie schon kennen gelernt: QUIT, EXIT und HELP. Die angezeigte Ausgabe ist übrigens typisch für MySQL. In übersichtlicher Tabellenform werden Spalten und Zeilen angezeigt. Als Abschluss folgt die Angabe der ausgegebenen Zeilen und die Dauer, die für die Ausführung benötigt wurde. Diese Dauer ist allerdings nicht direkt mit der CPU-Zeit auf dem Server-Rechner gleichzusetzen, da unter anderem auch der Transport über das Netzwerk mit eingerechnet ist. Im Weiteren werde ich die letzte Zeile mit der Angabe der Zeilen und der Dauer nicht immer mit ausgeben. Wofür werden diese zwei Datenbanken nun verwendet? Zunächst zu test: Dies ist eine „Spieldatenbank“, in der man ausprobieren und herumdoktern kann. Die andere Datenbank, mysql, ist dagegen ausgesprochen wichtig. In ihr finden sich alle Systemdaten wie Benutzer, Berechtigungen und andere Parameter. Um eine Datenbank auszuwählen, verwendet man den Befehl USE, gefolgt vom Namen der Datenbank:
USE
mysql> USE test Database changed
Sandini Bib
Die Namen von Datenbanken (und auch Tabellen) sind unter Unix abhängig von Groß- oder Kleinschreibung! „test“, „Test“ und „TEST“ sind also verschiedene Datenbanken. Unter Windows gilt diese Einschränkung nicht, allerdings muss man innerhalb einer Abfrage die Schreibweise beibehalten. Auf Grund der unterschiedlichen Behandlung zwischen Unix und Windows empfiehlt es sich, eine einheitliche Schreibweise zu nutzen und vor allem keine Datenbanken (und natürlich auch keine Tabellen) anzulegen, die sich nur in ihrer Groß- und Kleinschreibung von einer anderen unterscheiden. USE ist ein weiterer Befehl, der kein Semikolon am Ende benötigt. Dafür
hat er noch eine andere Besonderheit: er muss komplett in einer Zeile stehen und darf nicht umbrochen werden. Falls man test zum Spielen nutzen will, ist zu beachten, dass jeder andere (sofern sich denn mehrere Nutzer mit MySQL befassen) auch auf diese Datenbank zugreifen darf und darin befindliche Tabellen und anderes verändern und löschen kann. Möchte man dies (bei einem Mehrbenutzersystem, dass auch tatsächlich als Solches genutzt wird) verhindern, muss man sich vom Datenbank-Administrator eine eigene Datenbank einrichten lassen, auf die man nur selber Zugriff hat. Wir werden hier eine eigene Datenbank erstellen, aber noch nicht weiter auf Benutzerberechtigungen eingehen. CREATE DATABASE
Um nun eine eigene Datenbank anzulegen, nutzen Sie den DDL-Befehl CREATE DATABASE. Diesem wird als Parameter der Name der Datenbank mitgegeben: mysql> CREATE DATABASE musikschule;
Damit hat man nun eine Datenbank namens „musikschule“ angelegt. Die vollständige Syntax von CREATE DATABASE lautet: CREATE DATABASE [IF NOT EXISTS] db_name;
Der in eckigen Klammern angegebene Teil ist optional, d.h. er muss nicht mit angegeben werden (aber wenn, dann bitte ohne die Klammern). Wird er weggelassen und die Datenbank ist schon vorhanden, gibt es eine Fehlermeldung. Mit Angabe von IF NOT EXISTS vermeidet man Fehlermeldungen, wenn eine Datenbank zum Beispiel per Skript angelegt wird. Was für Namen sind für Datenbanken gültig? Sie dürfen höchsten 64 Zeichen lang sein und müssen aus Zeichen bestehen, die in einem Verzeichnisnamen erlaubt sind, mit Ausnahme des Slashes „/“ und dem Punkt „.“. Für Tabellen gilt Ähnliches, nur dass statt einem Verzeichnis-
Sandini Bib
namen ein Dateiname ausschlaggebend ist. Die Einschränkung auf Verzeichnis- und Dateinamen erklärt sich daraus, dass Datenbanken im Verzeichnis data von MySQL als Ordner erstellt werden und Tabellen darunter in (mehreren) Dateien angelegt sind. Spalten und Aliase können alle Zeichen enthalten, wobei Spaltennamen bis zu 64 Zeichen lang sein dürfen, Aliase dagegen 255. Anders als bei anderen Datenbanksystemen sind hier auch reine Ziffernfolgen als Bezeichner erlaubt, auch wenn dies nicht unbedingt erstrebenswert ist. Ist ein Bezeichner eventuell mit anderen Elementen der Datenbank verwechselbar (zum Beispiel, weil er genauso heißt wie ein reserviertes Wort, siehe Anhang), muss er in Anführungszeichen stehen. Solche Fälle sollten aber soweit wie möglich vermieden werden, weil dadurch häufig schwierig zu identifizierende Fehler entstehen. Auch sollte man auf Umlaute und Ähnliches verzichten. Diese sind zwar prinzipiell möglich, man muss solche Bezeichner dann allerdings komplett mit einem accent grave umgeben. Ein Datenbank „Schüler“ müsste dann wie folgt angelegt werden: CREATE TABLE `Schüler` ...
Diese Anführungszeichen müssten dann bei jeder Nutzung des Bezeichners verwendet werden. Zudem ist nicht immer sichergestellt, dass ein anderer Benutzer, der mit dieser Datenbank arbeiten will, diese Zeichen leicht erreichen kann. Die somit neu erstellte Datenbank ist allerdings noch nicht aktiviert. Dies geschieht nun erst durch USE: mysql> USE musikschule
Man kann auch schon beim Starten von mysql dafür sorgen, dass eine Datenbank ausgewählt ist, indem man ihren Namen in der Kommandozeile mit angibt: prompt> mysql musikschule
Falls man Host und Benutzer mit angibt und auch ein Kennwort eingeben will, sähe das Ganze so aus: prompt> mysql -h truhe -u demmig -p musikschule
Man beachte, dass „musikschule“ hier nicht das Kennwort ist, da zwischen dem Parameter -p und dem Kennwort kein Leerzeichen stehen darf.
Sandini Bib
Somit haben wir unsere eigene Datenbank erstellt. Wenn man nun einen Blick in das Datenverzeichnis von MySQL wirft (zum Beispiel /var/ lib/mysql bzw. C:\mysql\data), sieht man ein leeres Verzeichnis mit dem Namen musikschule. Im nächsten Abschnitt geht es nun um die Tabellen, die es der Datenbank erst ermöglichen, mit Leben gefüllt zu werden.
2.6
Erstellen der Tabellen
Eine Datenbank haben wir nun, aber wohin mit den Daten? Dafür benötigen wir die Tabellen. Welche Tabellen benötigt werden, muss aus dem Modell ermittelt werden. Eine Tabelle besteht, wie schon im vorigen Kapitel erläutert, aus Zeilen und Spalten. Jede Spalte erhält einen Namen und eine Datentyp, der beschreibt, was für Daten in der Spalte abgelegt werden können. Dazu kommt die Angabe, ob die Spalte leer bleiben darf, ob sie Grundlage eines Indexes ist und ob bei Nichtangabe eines Wertes ein Standardwert genutzt werden soll.
2.6.1 Anlegen einer Tabelle Um eine Tabelle anzulegen, verwendet man den DDL-Befehl CREATE TABLE. Ein einfaches Beispiel dafür ist:
CREATE TABLE
CREATE TABLE raum (raum_id INT, raum_name VARCHAR(10), etage VARCHAR(3), personen INT);
Damit wird eine Tabelle mit dem Namen raum angelegt, die vier Spalten enthält: eine Kennung raum_id, die ganze Zahlen speichern kann und gleichzeitig der primäre Schlüssel der Tabelle ist, ein Name raum_name, der bis zu zehn beliebige Zeichen verwaltet, eine Etage, die drei Zeichen lang sein kann, und die Anzahl der Personen, die (sinnvoll) in den Raum hineinpassen. Der Datentyp INT steht dabei für ganze Zahlen zwischen -231 und +231-1 (also -2.147.483.648 bis +2.14.7483.647), VARCHAR für Zeichenketten bis zu einer maximalen Länge (die in Klammern angegeben ist). Nun gibt es hierbei einiges zu verbessern. Die raum_id darf nie leer sein, da es sich um den primären Schlüssel handelt.
Sandini Bib
Was ist ein primärer Schlüssel? Es handelt sich dabei um ein oder mehrere Felder, die zusammen eindeutig einen Datensatz charakterisieren. Man kann im Prinzip auch Tabellen ohne Primärschlüssel anlegen, dies empfiehlt sich allerdings überhaupt nicht. Es kann sonst später zum Beispiel sehr schwierig sein, Duplikate zu löschen, wenn es keine Möglichkeit gibt, sie irgendwie zu unterscheiden. Man kann als Primärschlüssel natürlich eine Kombination aus Feldern nehmen, die eindeutig sein sollten. Der Nachname wird gerne für so etwas genommen, reicht aber natürlich bei weitem nicht. Vor- und Nachname mögen in einem kleineren Datenbestand ausreichen, aber sehr viel weiter kommt man damit auch nicht. Vielleicht Vorname, Nachname, Ort? Sicher auch nicht. Wie viele Personen mit dem Namen Thomas Schmidt mag es zum Beispiel in Bremen geben? In meiner Schulzeit gab es an meiner Oberstufe zwei davon im gleichen Jahrgang – sie hatten annähernd identische Kurse und lebten beide in Bremen. Man unterschied sie in den aushängenden Listen schließlich mit Thomas Schmidt (H) und Thomas Schmidt (W) – H bzw. W standen für den Anfangsbuchstaben der vorigen Schule... Zudem ist es ausgesprochen ungeschickt, wenn sich ein Primärschlüssel ändern kann. Dann müssen nämlich auch alle Felder in anderen Tabellen, die auf diesen Schlüssel verweisen, geändert werden. Und Namen und Orte ändern sich schließlich schnell einmal – einer der beiden Thomas Schmidts im obigen Beispiel war ich, der bei der Hochzeit den Namen seiner Frau angenommen hat... Was lässt sich daraus folgern? Man sollte immer jeder Tabelle eine eigene Spalte für den Primärschlüssel spendieren. Diese Spalte sollte einen Wert enthalten, der unabhängig von den eigentlichen „Nutzdaten“ ist. Dafür bieten sich Integer-Werte (also Ganzzahlen) an, für die man zudem einen eleganten Mechanismus zum automatischen Erstellen neuer Werte nutzen kann. Ein primärer Schlüssel darf schließlich auch nie leer sein, da er sonst nicht mehr als Schlüssel dienen kann und auch bei Vergleichen nicht mehr so einfach zu berücksichtigen ist. Um dafür zu sorgen, dass eine Spalte immer einen Wert enthalten muss, verpasst man ihr in der Definition ein zusätzliches Argument: NOT NULL. Der Wert NULL bedeutet, dass kein Wert angegeben wurde. Er unterscheidet sich von der 0 bei Zahlen und von der leeren Zeichenkette „“ bei Strings. Man kann einer Spalte auch mitteilen, dass sie NULL enthalten darf, indem man das Argument NULL mitgibt. Allerdings ist dies die Standardvorgabe und erschwert eher den Lesefluss.
NOT NULL
Sandini Bib
Neben der raum_id sollte auch der Raumname immer einen Wert enthalten, da er einen Raum in einer für Menschen sinnvoll lesbaren Form enthält (sie würden die Form A5.35 doch sicherlich auch der Zahl 36.432 vorziehen, wenn aus A5.35 neben der Etage gleich auch der Bereich hervorgeht, und man davon ausgehen kann, dass sich A5.35 in der Nähe von A5.34 und A5.36 befindet, deren IDs einen ganz anderen Wert haben können, oder?). Die Etage kann ruhig leer bleiben, da sie nicht immer notwendig ist. Ebenso mag man eventuell die Zahl der Personen weglassen, da dies nicht von Interesse sein muss. Unser Befehl sieht nun also so aus: CREATE TABLE raum (raum_id INT NOT NULL, raum_name VARCHAR(10) NOT NULL, etage VARCHAR(3), personen INT);
(So nebenbei: es zwingt Sie keiner, Befehle in einer so lesbar untergliederten Form einzugeben, wie es hier geschieht. Ich selber mache das auch nicht immer. Aber es hilft ungemein bei der Fehlersuche und wenn man Skripten verwendet, da man dann leichter sehen kann, was sich der Autor bei der ganzen Sache gedacht hat.) AUTO INCREMENT
Wir wollen die Definition noch etwas weiter verfeinern. Da die raum_id ein Primärschlüssel ist, soll sie, wie schon weiter oben vorgeschlagen, automatisch generiert werden. Dazu gibt man das Argument AUTO_INCREMENT mit. Dieses sorgt dafür, dass das entsprechende Feld automatisch mit der höchsten bestehenden Nummer, zusätzlich um eins erhöht, gefüllt wird, falls nicht schon ein Wert übergeben wurde. Damit erhält man eine Nummer, die in dieser Tabelle noch nicht verwendet wurde. Es kann eventuell sein, dass es Lücken gibt (wenn zwischendurch Sätze gelöscht wurden), aber das ist für einen Primärschlüssel nicht wichtig. Bei AUTO_INCREMENT muss man beachten, dass immer nur ein Feld dieses Argument haben kann (von bestimmten exotischeren Tabellentypen abgesehen). Es muss sich dabei um ein ganzzahliges Feld handeln und die Verwendung von negativen Werten ist ausgesprochen unerwünscht, die Dokumentation zu MySQL rät deutlichst davon ab. Da nur positive Zahlen genutzt werden sollten, kann man die Spalte auch gleich mit einem weiteren Attribut namens UNSIGNED versehen. Damit sind keine negativen Werte möglich, die höchste speicherbare Zahl (und damit auch die Anzahl der möglichen Schlüssel) verdoppelt sich aber. Zusätzlich hat man die Möglichkeit, einen Startwert festzulegen, worauf ich aber nur in der späteren kompletten Beschreibung von CREATE TABLE eingehe.
Sandini Bib
Schließlich muss die Spalte mit AUTO_INCREMENT eindeutig sein. Man sorgt dafür, indem man sie mit einem eindeutigen Index versieht (siehe bei den Indizes im nächsten Kapitel oder in der kompletten Syntax weiter unten) oder das Attribut PRIMARY KEY anhängt. Auch für die Spalte personen kann man noch etwas tun. Normalerweise sollte in einen Raum immer mindestens eine Person hineinpassen, sonst bräuchte man ihn nicht mit in diese Tabelle aufnehmen (es geht schließlich um Übungsräume). Was spräche also dagegen, gleich einen Standardwert von eins mitzugeben? Dies erreicht man, indem als Attribut auch noch DEFAULT, gefolgt von dem gewünschten Standardwert übergeben wird. Wird nun beim Eintragen von Daten kein Wert für diese Spalte angegeben, erhält sie den vorher definierten Standardwert. Zusätzlich kann man auch hier dafür sorgen, dass die Spalte keine negativen Werte aufnehmen kann (wäre auch nicht wirklich sinnvoll).
DEFAULT
Jetzt hat sich die Definition also weiter verändert: CREATE TABLE raum (raum_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, raum_name VARCHAR(10) NOT NULL, etage VARCHAR(3), personen INT UNSIGNED DEFAULT 1);
Einen letzten Optimierungsschritt wollen wir noch vornehmen: die Datentypen an sich. Das Feld raum_id ist als vorzeichenloses INT definiert. Das bedeutet, dass Zahlen von 0 bis 232-1 verwendet werden können, was einem Speicherbedarf von 4 Byte entspricht. Nun ist es sehr unwahrscheinlich, dass man tatsächlich vier Milliarden Räume zur Verfügung hat, daher macht es durchaus Sinn, einen „kleineren“ Datentyp zu nutzen. Auch wenn heutzutage meist genug Speicher- und Festplattenplatz zur Verfügung steht, sollte man sich zumindest Gedanken über den richtigen Datentyp machen. Dabei darf natürlich nicht zu knapp kalkuliert werden (wie schnell ist eine einmal festgelegte Grenze dann doch überschritten), aber mit einem ordentlichen Puffer versehen kann man durchaus Datentypen nutzen, die nicht immer den kompletten Umfang ermöglichen, der nur dann ausgenutzt wird, wenn die gesamte Weltbevölkerung teilnimmt. Daher wollen wir hier für die raum_id den Datentyp SMALLINT wählen. Dieser ermöglicht (sofern mit UNSIGNED versehen) Werte von 0 bis 65.535 (= 216-1; 2 Byte), was reichen sollte. Genauso wird die Anzahl der Personen meist geringer sein als bei INT möglich. Nun könnte da SMALLINT schon zu klein sein (man stelle sich nur eine Stadion als Ort für eine Musikveranstaltung vor), daher wählen wir hier MEDIUMINT – vorzeichenlos von 0 bis 16.777.215 (= 224-1; 3 Byte).
Sandini Bib
Nun wollen wir etwas ins Detail gehen und uns die Etage vornehmen. Diese ist als VARCHAR(3) definiert. Das VAR in VARCHAR bedeutet, dass nur so viel Platz benötigt wird, wie der gespeicherte Text tatsächlich lang ist. Dazu kommt nur noch ein Byte für die Längenangabe. Nutzt man stattdessen den Typ CHAR, wird mit fester Länge abgespeichert und der übrige Platz mit Leerzeichen aufgefüllt. MySQL macht nun etwas, was als „stille Spaltendefinitionsänderung“ bezeichnet wird: Spalten vom Typ VARCHAR(n), deren maximale Länge n kleiner als vier ist, werden automatisch in den Typ CHAR(n) umgewandelt. Dies hat den Grund, dass die Speicherersparnis bis zu drei Zeichen zu gering ist (man benötigt schließlich immer noch ein Byte für die Länge), der Typ CHAR dafür aber einen kleinen Tick schneller ist als VARCHAR, weil er mit festen Längen rechnen kann. Um nun MySQL zuvorzukommen, wandeln wir den Typ selber um. Es ergibt sich schließlich folgende Definition: CREATE TABLE raum (raum_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, raum_name VARCHAR(10) NOT NULL, etage CHAR(3), personen MEDIUMINT UNSIGNED DEFAULT 1);
Führt man diesen Befehl nun in MySQL aus (und hat möglichst vorher die Datenbank musikschule gewählt), wird die Tabelle raum angelegt. Man kann sich die Definition anzeigen lassen. Dazu verwendet man den Befehl SHOW COLUMNS FROM table_name (siehe Abbildung 2.1)
SHOW COLUMNS
mysql> SHOW COLUMNS FROM raum; +-----------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+-----------------------+------+-----+---------+----------------+ | raum_id | smallint(5) unsigned | | PRI | NULL | auto_increment | | raum_name | varchar(10) | | | | | | etage | char(3) | YES | | NULL | | | personen | mediumint(8) unsigned | YES | | 1 | | +-----------+-----------------------+------+-----+---------+----------------+
Abbildung 2.1: Struktur der Tabelle raum
Man kann übrigens diese Ausgabe noch einschränken, wenn man nach dem Tabellennamen noch LIKE 'filter' anhängt. Dabei ist filter eine Zeichenkette, die die Spalten beschränkt, die ausgegeben werden sollen. Jokerzeichen sind erlaubt, dabei gilt (anders als im Betriebssystem, aber so wie immer bei SQL) das Prozentzeichen (%) als Ersatz für beliebig viele Zeichen und der Unterstrich (_) als Ersatz für genau ein Zeichen. Mit SHOW COLUMNS FROM raum LIKE 'raum%'; erhält man also die Ausgabe in Abbildung 2.2.
Sandini Bib
mysql> SHOW COLUMNS FROM raum LIKE 'raum%'; +-----------+----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+----------------------+------+-----+---------+----------------+ | raum_id | smallint(5) unsigned | | PRI | NULL | auto_increment | | raum_name | varchar(10) | | | | | +-----------+----------------------+------+-----+---------+----------------+
Abbildung 2.2: Eingeschränkte Ausgabe der Struktur der Tabelle raum
Die gleiche Ausgabe wie bei SHOW COLUMNS FROM erhält man, wenn man den Befehl DESCRIBE oder DESC nutzt. Er ist zwar eigentlich nur vorhanden, um es Umsteigern von Oracle etwas bequemer zu machen, aber durch seine Kürze auch für andere Nutzer sehr praktisch. Um dort die angezeigten Spalten einzuschränken, muss man den Filter direkt nach dem Tabellennamen angeben. Für das obige Ergebnis lautete der Befehl also DESCRIBE
DESCRIBE
raum 'raum%';
Tabellen werden auch gerne in sogenannten ER-Diagrammen (Entity Relationship-Diagramme) dargestellt. Die obige Tabelle ist in Abbildung 2.3 zu sehen. Eine einzelne Tabelle ist dabei natürlich nur die „Entity“, Relationships kommen erst zustande, wenn man mehrere Tabellen miteinander verbindet. Das wird in Kapitel 2.6.3 zu sehen sein.
Abbildung 2.3: Die Tabelle raum in einem ER-Diagramm
Um das Diagramm nicht allzu unübersichtlich zu gestalten (vor allem dann, wenn später mehr Tabellen dazukommen), wurden einige Parameter weggelassen. Entscheidend sind hier der Tabellenname sowie die Spaltennamen und Datentypen. Zusätzlich ist der Primärschlüssel raum_id unterstrichen.
2.6.2 Datentypen Bevor wir uns mit den restlichen Tabellen des Modells befassen, wollen wir zunächst aufzeigen, welche Datentypen es gibt und was für Besonderheiten sie haben. Numerische Werte Die numerischen Datentypen unterteilen sich noch einmal in exakte Typen mit begrenzter Stellenzahl und in Gleitkommatypen.
numerische Felder
Sandini Bib
Exakte Typen sind: TINYINT[(m)] [UNSIGNED] [ZEROFILL]
speichert ganze Zahlen im Bereich von -128 bis +127 (-27 bis +27-1). Wird das Attribut UNSIGNED mit angegeben, werden Zahlen von 0 bis +255 gespeichert (0 bis +28-1). Der Speicherbedarf beträgt 1 Byte. Der optionale Parameter m wird genutzt, um festzulegen, mit welcher Breite die Spalte auf dem Bildschirm ausgegeben wird. Er schränkt nicht den Wertebereich ein. Wird zusätzlich das Attribut ZEROFILL mit angegeben, werden bei der Ausgabe die fehlenden Stellen mit 0 aufgefüllt. Bei einer Definition TINYINT(2) ZEROFILL würde eine 3 als 03 ausgegeben werden, eine 123 aber weiterhin als 123. Wird ZEROFILL genutzt, fügt MySQL automatisch ein UNSIGNED mit hinzu. BIT
entspricht dem Typ TINYINT(1). BOOL
entspricht dem Typ TINYINT(1). SMALLINT[(m)] [UNSIGNED] [ZEROFILL]
speichert ganze Zahlen im Bereich von -32.768 bis +32.767 (-215 bis +215-1). Wird das Attribut UNSIGNED mit angegeben, werden Zahlen von 0 bis +65.535 gespeichert (0 bis +216-1). Der Speicherbedarf beträgt 2 Byte. Für die weiteren Attribute siehe TINYINT. MEDIUMINT[(m)] [UNSIGNED] [ZEROFILL]
speichert ganze Zahlen im Bereich von -8.388.608 bis +8.388.607 (-223 bis +223-1). Wird das Attribut UNSIGNED mit angegeben, werden Zahlen von 0 bis +16.777.215 gespeichert (0 bis +224-1). Der Speicherbedarf beträgt 3 Byte. Für die weiteren Attribute siehe TINYINT. INT[(m)] [UNSIGNED] [ZEROFILL]
speichert ganze Zahlen im Bereich von -2.147.483.648 bis +2.147.483.647 (-231 bis +231-1). Wird das Attribut UNSIGNED mit angegeben, werden Zahlen von 0 bis +4.294.967.295 gespeichert (0 bis +232-1). Der Speicherbedarf beträgt 4 Byte. Für die weiteren Attribute siehe TINYINT.
Sandini Bib
INTEGER[(m)] [UNSIGNED] [ZEROFILL]
entspricht exakt dem Typ INT. BIGINT[(m)] [UNSIGNED] [ZEROFILL]
speichert ganze Zahlen im Bereich von -9.223.372.036.854.775.808 bis +9.223.372.036.854.775.807 (-263 bis +263-1). Wird das Attribut UNSIGNED mit angegeben, werden Zahlen von 0 bis + 18.446.744.073.709.551.615 gespeichert (0 bis +264-1). Der Speicherbedarf beträgt 8 Byte. Für die weiteren Attribute siehe TINYINT. Beim Umgang mit Werten vom Typ BIGINT, speziell bei vorzeichenlosen Typen, muss man Vorsicht walten lassen und vorher speziell bei Berechnungen einen Blick in die Dokumentation von MySQL werfen. DECIMAL[(m[,d])] [UNSIGNED] [ZEROFILL]
speichert Zahlen als Zeichenketten ab, welche allerdings in Berechnungen normal verwendet werden können. m definiert die Anzahl der Stellen, d die Anzahl der Nachkommastellen. Die Werte müssen zwischen -3,402823466·1038 und -1,175494351·10-38 oder 1,175494351·10-38 und 3,402823466·1038 liegen oder 0 sein, allerdings ist die Anzahl der Stellen meistens die entscheidendere Begrenzung. Lässt man d weg, wird dies auf 0 gesetzt, lässt man auch m weg, wird dies auf 10 gesetzt. ZEROFILL hat die gleiche Bedeutung wie bei TINYINT. Der Speicherbedarf liegt bei m Byte plus 2 Byte für das Vorzeichen und den Dezimalpunkt. Sind keine Nachkommastellen angegeben, wird nur das Byte für das Vorzeichen benötigt. DEC[(m[,d])] [UNSIGNED] [ZEROFILL] DEC entspricht exakt dem Typ DECIMAL. NUMERIC[(m[,d])] [UNSIGNED] [ZEROFILL] NUMERIC ist eine weitere Darstellung für DECIMAL.
Gleitkommatypen sind: FLOAT[(m, d)] [ZEROFILL]
speichert Gleitkommazahlen einfacher Genauigkeit mit folgenden möglichen Werten: -3,402823466·1038 bis -1,175494351·10-38, 0 und 1,175494351·10-38 bis 3,402823466·1038. m und d bedeuten die Anzahl der Stellen bei der Ausgabe und die Zahl der Nachkommastellen. Anders als bei DECIMAL & Co ist hier die Ausgabebreite unerheblich für die
Sandini Bib
Größe der speicherbaren Zahl. Sind m und d nicht mit angegeben, sind diese tatsächlich undefiniert und es wird die nötige Stellenzahl bei der Ausgabe verwendet. ZEROFILL hat die übliche Funktion, wie bei TINYINT beschrieben, allerdings ist es für negative Werte (die dann trotzdem erlaubt sind) nicht empfehlenswert, da die Ausgabe solcher Werte unschön ist. Werte vom Typ FLOAT benötigen 4 Byte Speicherplatz. DOUBLE[(m, d)] [ZEROFILL]
Speichert Gleitkommazahlen doppelter Genauigkeit mit folgenden prinzipiell möglichen Werten: -1,7976931348623157·10308 bis -2,2250738585072014·10-308, 0 und 2,2250738585072014·10-308 bis 1,7976931348623157·10308. Die weiteren Parameter und Hinweise sind bei FLOAT nachzulesen. Werte vom Typ DOUBLE verbrauchen 8 Byte Speicherplatz. DOUBLE PRECISION[(m, d)] [ZEROFILL]
ist identisch zu DOUBLE. REAL[(m, d)] [ZEROFILL]
ist identisch zu DOUBLE. FLOAT (p) [ZEROFILL]
wurde aus Kompatibilitätsgründen zu ODBC aufgenommen und wird in Abhängigkeit von p als FLOAT (p £ 24) oder als DOUBLE (24 < p £ 53) abgespeichert. Stringwerte [NATIONAL] CHAR (m) [BINARY]
Zeichenketten
definiert eine Zeichenkette der Länge m. Werden weniger Zeichen genutzt, werden die restlichen mit Leerzeichen aufgefüllt. Das Attribut NATIONAL ist aus ANSI-Kompatibilitätsgründen aufgenommen und dient dazu, MySQL anzuweisen, den Standardzeichensatz zu verwenden. Allerdings ist dies sowieso das normale Verhalten von MySQL. Beim Sortieren und Vergleichen wird Rücksicht auf den Zeichensatz genommen sowie die Groß- und Kleinschreibung ignoriert. Will man dies vermeiden, muss man das Attribut BINARY angeben. m kann zwischen 0 und 255 liegen, als Speicherplatz werden m Byte verbraucht. Wird als Länge 0 angegeben, wird nur ein Bit benötigt, dafür können aber auch nur zwei verschiedene Werte abgelegt werden: NULL und eine leere Zeichenkette.
Sandini Bib
[NATIONAL] CHARACTER (m) [BINARY]
ist identisch zu obigem CHAR. NCHAR (m) [BINARY]
entspricht NATIONAL CHAR(m) [BINARY]. CHAR
entspricht dem Typ CHAR(1). [NATIONAL] VARCHAR (m) [BINARY]
speichert Zeichenketten variabler Länge, maximal allerdings m Zeichen. Die maximale Länge m kann zwischen 1 und 255 liegen. Anders als bei CHAR wird hier nur soviel Platz verbraucht, wie der String tatsächlich lang ist, plus einem Byte für die Länge. NATIONAL und BINARY entsprechen den Attributen bei CHAR. Leerzeichen am Ende des Strings werden automatisch abgeschnitten. [NATIONAL] CHARACTER VARYING (m) [BINARY]
entspricht dem Typ VARCHAR. TEXT
speichert maximal 65.535 Zeichen (= 216-1). TEXT ist wie ein langer VARCHAR, allerdings gibt es einige (wenige) Einschränkungen: Spalten dieses Typs können keinen Standardwert haben, Leerzeichen am Ende des Strings werden nicht abgeschnitten und es gibt gewisse Dinge, die man beim Gruppieren und Sortieren beachten muss (siehe dazu auch die Dokumentation zu MySQL). Felder dieses Typs werden separat gespeichert. BLOB
Felder dieses Typs verhalten sich wie Felder vom Typ TEXT, mit der Ausnahme, dass Groß- und Kleinschreibung beim Sortieren und Vergleichen berücksichtigt werden (also quasi ein langer VARCHAR BINARY). TINYTEXT
speichert maximal 255 Zeichen (= 28-1). Ansonsten ist dieser Typ identisch zu TEXT. TINYBLOB
speichert maximal 255 Zeichen (= 28-1). Ansonsten ist dieser Typ identisch zu BLOB.
Sandini Bib
MEDIUMTEXT
speichert maximal 16.777.215 Zeichen (= 224-1). Ansonsten ist dieser Typ identisch zu TEXT. MEDIUMBLOB
speichert maximal 16.777.215 Zeichen (= 224-1). Ansonsten ist dieser Typ identisch zu BLOB. LONGTEXT
speichert maximal 4.294.967.295 Zeichen (= 232-1). Ansonsten ist dieser Typ identisch zu TEXT. Allerdings sorgen Beschränkungen von MySQL in der Paketgröße des Client/Server-Protokolls und bei der Verwaltung von bestimmten Tabellentypen dafür, dass nicht die komplette Größe genutzt werden kann. LONGBLOB
speichert maximal 4.294.967.295 Zeichen (= 232-1). Ansonsten ist dieser Typ identisch zu BLOB. Es gilt die gleiche Einschränkung wie bei LONGTEXT. ENUM ('Wert1', 'Wert2', ...)
speichert einen Wert ab, der in der Liste der vorgegebenen Werte vorhanden sein muss. Es können maximal 65.535 verschiedene Werte angegeben werden. Dabei werden nur 1 Byte oder 2 Byte verbraucht, abhängig davon, ob es mehr als 255 verschiedene Werte gibt. SET ('Wert1', 'Wert2', ...)
speichert eine Menge von Werten ab, die aus den vorgegebenen Werten ausgewählt sein müssen. Maximal 64 verschiedene Werte sind möglich und abhängig von der Zahl unterschiedlicher Werte werden 1 Byte, 2 Byte, 3 Byte, 4 Byte oder 8 Byte verbraucht. Dieser Typ kann sehr nützlich sein, auch wenn er eigentlich einem sauberen Datenmodell widerspricht (bei dem solche Sets in einer eigenen Tabelle aufgelöst werden sollten, was aber nicht immer der performanteste Weg ist). Datumswerte und Uhrzeiten DATE
Datum/Uhrzeit
speichert ein Datum zwischen dem 1.1.1000 und dem 31.12.9999. Datumswerte werden im Format JJJJ-MM-TT angezeigt, lassen sich aber auf vielfältige Weise für Berechnungen und Ausgaben nutzen. Felder dieses Typs verbrauchen 3 Byte.
Sandini Bib
DATETIME
speichert ein Datum und eine Uhrzeit zwischen dem 1.1.1000 00:00:00 und dem 31.12.9999 23:59:59. Werte dieses Typs werden im Format JJJJ-MM-TT HH:MM:SS angezeigt, lassen sich aber wie Werte vom Type DATE unterschiedlichst nutzen. Felder vom Type DATETIME benötigen 4 Byte Speicher. TIMESTAMP [(m)]
speichert einen Zeitstempel in einem Bereich zwischen dem 1.1.1970 00:00:00 und einem Zeitpunkt im Jahr 2037. Ausgegeben werden die Werte in Abhängigkeit von m wie folgt: Ist m = 14 oder nicht angegeben, wird JJJJMMTTHHMMSS ausgegeben. Bei m = 12 wird JJMMTTHHMMSS, bei m = 8 JJJJMMTT und bei m = 6 JJMMTT ausgegeben. Ein solcher Zeitstempel eignet sich hervorragend, um Änderungen in einem Datensatz zu protokollieren. Setzt man nämlich ein Feld dieses Typs auf NULL oder gibt keinen eigenen Wert an, wird automatisch der momentane Zeitpunkt eingetragen. Zeitstempel werden immer mit 4 Byte abgespeichert. TIME
speichert eine Uhrzeit im Bereich -838:59:59 bis +838:59:59 ab. Uhrzeiten werden in MySQL in der Form HH:MM:SS ausgegeben, können aber mit verschiedenen Funktionen und in vielen Variationen verwendet werden. Felder dieses Typs benötigen 3 Byte Speicherplatz. YEAR [(2|4)]
speichert ein Jahr in einem Byte ab. Bei der 4-stelligen Version (die auch Standard ist), lassen sich die Jahre 1901 bis 2155 und 0 nutzen, bei der 2-stelligen Variante nur die Jahre 1970 bis 2069 (also 70–69).
2.6.3 Tabellen des Modells Für jedes Objekt oder Ereignis benötigen wir eine Tabelle, um es abzubilden. In Kapitel 2.2.2 haben wir die verschiedenen Elemente des Modells aufgeführt, die eigentlich direkt in eine Tabelle umgesetzt werden können. Die Eigenschaften des Objekts oder Ereignisses werden dann zu den Spalten der Tabelle. Eine Tabelle haben wir im Prinzip schon erstellt: die für die Räume. Aber das ist natürlich eine der einfachsten. Daher nehmen wir uns jetzt die restlichen Tabellen vor.
Sandini Bib
Beginnen wir mit den Schülern. Die zugehörige Tabelle schueler sollte folgende Spalten enthalten: • schueler_id: der Primärschlüssel als ganze Zahl, die automatisch hochgezählt wird. • nachname: ein Stringfeld der Länge 30. Die Länge eines Feldes ist immer ein Kompromiss zwischen Speicherverbrauch (der natürlich bei einem String variabler Länge nicht ins Gewicht fällt) und Einschränkung bei der Nutzung. Um auf einen Nachnamen mit 30 Zeichen zu kommen, muss man sich allerdings schon anstrengen. Der Nachname sollte immer gefüllt sein müssen. • vorname: ebenfalls ein String mit bis zu 30 Zeichen. • geburtsdatum: ein Datumsfeld, das auch leer bleiben darf (worauf ich bei den folgenden Feldern nicht mehr hinweise). • strasse: Stringfeld mit 30 Zeichen. • plz: Man ist geneigt, hier ein numerisches Feld mit bis zu fünf Stellen zu nehmen. Allerdings kann es dann zu zwei Problemen kommen: – Ausländische Postleitzahlen haben durchaus auch sechs Stellen. – Es gibt in Deutschland Postleitzahlen, die mit einer 0 beginnen. So würde dann die PLZ 01234 als 1234 ausgegeben werden, was dann eventuell als alte Postleitzahl interpretiert würde. Man könnte das Attribut ZEROFILL nutzen, allerdings ist dann immer noch nicht sichergestellt, dass ein externes Programm, welches auf die Daten zugreift, darauf Rücksicht nimmt (im Allgemeinen nämlich nicht). Daher sollte die Postleitzahl als Stringfeld mit sechs Zeichen angelegt werden. Generell sollte man auch Felder, die nur Ziffern enthalten, als Stringfelder deklarieren, sofern nicht mit ihnen gerechnet werden muss. Bei diesem Feld kann man auch CHAR statt VARCHAR nutzen, da es meist fast komplett gefüllt wird. • ort: ein Zeichenfeld mit 40 Zeichen. • telefon: Auch hier darf man aus nachvollziehbaren Gründen kein numerisches Feld nutzen; daher ein Stringfeld mit 20 Zeichen. • mobil: ebenso String mit 20 Zeichen. • email: ein Stringfeld mit 50 Zeichen. • einzug: Angabe, ob eine Einzugsermächtigung vorliegt. Dieses Feld hat den Typ BOOL. Zudem soll es als Standardwert eine 0 haben (für „keine Einzugsermächtigung“). • bankname: der Name der Bank, falls eine Einzugsermächtigung vorliegt. Die Länge des Stringfeldes beträgt 58 Stellen (das ist auch die Länge im offiziellen BLZ-Verzeichnis). • blz: Bankleitzahl: Zeichenfeld mit 8 Stellen.
Sandini Bib
• kontonr: Kontonummer, 10 Stellen als Zeichenkette. • kontoname: Name des Kontoinhabers, 30 Stellen Stringfeld. Damit sollten wir alle Elemente eines Schülers zunächst erschlagen haben. Der Befehl zum Anlegen der Tabelle lautet nun: CREATE TABLE schueler (schueler_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, nachname VARCHAR(30) NOT NULL, vorname VARCHAR(30), geburtsdatum DATE, strasse VARCHAR(30), plz CHAR(6), ort VARCHAR(40), telefon VARCHAR(20), mobil VARCHAR(20), email VARCHAR(50), einzug BOOL DEFAULT 0, bankname VARCHAR(58), blz CHAR(8), kontonr CHAR(10), kontoname VARCHAR(30));
Schaut man sich nun das Ergebnis mit DESCRIBE schueler an, sieht es wie in Abbildung 2.4 aus. mysql> DESCRIBE schueler; +--------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+----------------+ | schueler_id | int(10) unsigned | | PRI | NULL | auto_increment | | nachname | varchar(30) | | | | | | vorname | varchar(30) | YES | | NULL | | | geburtsdatum | date | YES | | NULL | | | strasse | varchar(30) | YES | | NULL | | | plz | varchar(6) | YES | | NULL | | | ort | varchar(40) | YES | | NULL | | | telefon | varchar(20) | YES | | NULL | | | mobil | varchar(20) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | | einzug | tinyint(1) | YES | | 0 | | | bankname | varchar(58) | YES | | NULL | | | blz | varchar(8) | YES | | NULL | | | kontonr | varchar(10) | YES | | NULL | | | kontoname | varchar(30) | YES | | NULL | | +--------------+------------------+------+-----+---------+----------------+
Abbildung 2.4: Struktur der Tabelle schueler
Dabei fällt auf, dass die Felder plz, blz und kontonr nun vom Typ VARCHAR sind. Das liegt daran, dass automatisch der Typ gewechselt wird, wenn in einer Tabelle mindestens ein Wert vom Typ VARCHAR vorkommt und das entsprechende Feld eine Länge von drei oder mehr Zeichen hat. Das soll uns hier aber nicht weiter stören, sondern einfach als Optimierung von MySQL angesehen werden.
Sandini Bib
Die weiteren Tabellen will ich hier nicht so detailliert aufführen, sondern lediglich die Tabellendefinitionen aufzählen und auf die einzelnen Besonderheiten und neuen Aspekte eingehen. Zunächst wäre da natürlich die Tabelle für die Lehrer. Sie ist der der Schüler sehr ähnlich, nur wird das Feld einzug nicht benötigt, da die Lehrer keine Erlaubnis geben müssen, um Geld zu erhalten. Hier die Definition für die Lehrer: CREATE TABLE lehrer (lehrer_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, nachname VARCHAR(30) NOT NULL, vorname VARCHAR(30), geburtsdatum DATE, strasse VARCHAR(30), plz CHAR(6), ort VARCHAR(40), telefon VARCHAR(20), mobil VARCHAR(20), email VARCHAR(50), bankname VARCHAR(58), blz CHAR(8), kontonr CHAR(10), kontoname VARCHAR(30));
Die Tabelle für die möglichen Räume haben wir schon weiter oben ausgearbeitet, deshalb hier nur kurz die Tabellendefinition als Wiederholung: CREATE TABLE raum (raum_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, raum_name VARCHAR(10) NOT NULL, etage CHAR(3), personen MEDIUMINT UNSIGNED DEFAULT 1);
Zudem haben wir eine Tabelle für die möglichen Instrumente. Auch diese ist recht einfach: CREATE TABLE instrument (instrument_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, instr_name VARCHAR(50) NOT NULL, instr_gruppe VARCHAR(50));
Jetzt kommen wir zum Unterricht. Auch dieser ist eigentlich auf den ersten Blick nicht so kompliziert. Wir benötigen (neben der obligaten ID) einen Lehrer, den oder die Schüler, das Instrument, um das es geht, den Raum, in dem alles stattfindet, den Wochentag und die Uhrzeit, die Kos-
Sandini Bib
ten pro Monat sowie den Vermerk, ob es sich um Einzel- oder Gruppenunterricht handelt. Man könnte also eine Tabelle wie folgt aufstellen: CREATE TABLE unterricht (unterricht_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, lehrer VARCHAR(30) NOT NULL, schueler VARCHAR(30), instrument VARCHAR(50) NOT NULL, raum VARCHAR(10) NOT NULL, wochentag TINYINT UNSIGNED NOT NULL, uhrzeit_von TIME NOT NULL, uhrzeit_bis TIME NOT NULL, kosten DECIMAL(6,2) DEFAULT 0.0, einzel BOOL);
Dabei wird der Wochentag als Zahl von 0 bis 6 (Montag bis Sonntag) aufgefasst. Der Grund dafür ist, dass es bei MySQL eine Funktion WEEKDAY(Datum) gibt, die diese Werte zurückgibt. Irgendwie gibt es mit dieser Definition jetzt aber ein Problem: Was, wenn mehrere Schüler teilnehmen (was bei Gruppenunterricht zum Beispiel durchaus Sinn der Sache ist)? Speichert man die Schüler dann, durch Komma getrennt, im Feld schueler ab? Das wäre schlecht, da dieses Feld nur eine begrenzte Länge hat. Zudem kann man dann später sehr schlecht herausfinden, wer wo mitmacht und vor allem ist es ein Heidenaufwand, Schüler einzufügen oder zu löschen. So kann man das also nicht machen. Die Lösung ist stattdessen also eine eigene Tabelle, die abspeichert, welcher Schüler an welchem Unterricht teilnimmt. An jedem Unterricht können (theoretisch) beliebig viele Schüler mitmachen. Solche Beziehungen nennt man 1:n-Beziehungen. D.h., an einem Unterrichtseintrag hängen n Schüler. Diese neue Tabelle wollen wir unterricht_tn (für Unterricht-Teilnehmer) nennen und sie sieht wie folgt aus: CREATE TABLE unterricht_tn (unterricht_tn_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, unterricht_id INT UNSIGNED NOT NULL, schueler VARCHAR(30));
Jetzt hat jeder Schüler seinen eigenen Tabelleneintrag und es kann beliebig viele Schüler pro Unterricht geben. In unterricht_id steht die ID des entsprechenden Eintrags der Unterrichtstabelle, so dass man (da das Feld nicht leer sein darf) immer weiß, zu welchem Unterricht dieser Eintrag gehört. Die Tabelle unterricht sähe nun wie folgt aus:
Sandini Bib
CREATE TABLE unterricht (unterricht_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, lehrer VARCHAR(30) NOT NULL, instrument VARCHAR(50) NOT NULL, raum VARCHAR(10) NOT NULL, wochentag TINYINT UNSIGNED NOT NULL, uhrzeit_von TIME NOT NULL, uhrzeit_bis TIME NOT NULL, kosten DECIMAL(6,2) DEFAULT 0.0, einzel BOOL);
Diese Tabellen und vor allem die Beziehung untereinander kann man nun wieder in einem ER-Diagramm darstellen, welches in Abbildung 2.5 zu sehen ist. Dieses Aufteilen der Daten in Tabellen ist Teil der Normalisierung, die zur Ergänzung in Kapitel 2.6.5 beschrieben ist.
Abbildung 2.5: ER-Diagramm für die Unterrichtstabellen
Die Verbindung zwischen den beiden Tabellen geschieht hier durch das Feld unterricht_id. Dieses Feld bezeichnet man als Fremdschlüssel. Solche Fremdschlüssel kann man aber noch viel mehr und sehr sinnvoll in den beiden Tabellen verwenden. Bisher haben wir nämlich nicht festgelegt, wie der Lehrer, das Instrument oder die Schüler gespeichert werden. Der Feldtyp VARCHAR(30) bzw. VARCHAR(50) legt nahe, dass der Name genutzt werden soll. Das aber ist – wie wir schon weiter oben fest-
Sandini Bib
gestellt haben – ein denkbar schlechter Identifizierer. Denn erstens können sich Namen ändern (womit diese Änderung in vielen Tabellen nachgezogen werden müsste) und zweitens ist ein Name sehr selten wirklich eindeutig. Also nutzen wir für möglichst viele Relationen die Schlüssel, die wir bisher in jeder Tabelle mit angelegt haben. Dieser Vorgang wird als „Normalisieren“ bezeichnet (wobei schon das Abtrennen der Schüler in eine eigene Tabelle ein Teil der Normalisierung war, die man recht weit treiben kann). Unsere endgültigen Tabellen für den Unterricht sehen also wie folgt aus: CREATE TABLE unterricht (unterricht_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, lehrer_id INT UNSIGNED NOT NULL, instrument_id INT UNSIGNED NOT NULL, raum_id SMALLINT UNSIGNED NOT NULL, wochentag TINYINT UNSIGNED NOT NULL, uhrzeit_von TIME NOT NULL, uhrzeit_bis TIME NOT NULL, kosten DECIMAL(6,2) DEFAULT 0.0, einzel BOOL); CREATE TABLE unterricht_tn (unterricht_tn_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, unterricht_id INT UNSIGNED NOT NULL, schueler_id INT UNSIGNED NOT NULL);
Das ER-Diagramm wird nun natürlich schnell größer, da nun auch die Lehrer-, Schüler-, Instrumenten- und Raumtabellen mit beteiligt sind. In Abbildung 2.6 sind daher für diese Tabellen nur die wichtigsten Felder mit aufgeführt. Solche Relationen über Fremdschlüssel lassen sich in anderen Datenbank-Systemen durch Regeln abbilden, so dass es zum Beispiel nicht möglich ist, in der Unterrichtstabelle eine raum_id einzutragen, die gar nicht vorhanden ist. Diese Prüfung muss in MySQL über die Applikation erfolgen. Die Syntax dazu ist zwar zum Teil implementiert, so dass das Übernehmen von CREATE TABLE-Anweisungen aus anderen Datenbanken wenig Probleme bereitet, allerdings haben diese Regeln keine Auswirkungen.
Sandini Bib
Abbildung 2.6: ER-Diagramm für die Unterrichtstabellen und deren Relationen
Beim Orchester/Chor haben wir einen ähnlichen Aufbau wie beim Unterricht, nur dass diesmal jeder Schüler/Teilnehmer ein anderes Instrument nutzen kann. Zudem haben wir einen Namen für das Orchester und diesmal keine Kosten (die Musikschule ist schließlich froh, wenn möglichst viele Schüler teilnehmen). Also sehen die Tabellen so aus: CREATE TABLE orchester (orchester_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, lehrer_id INT UNSIGNED NOT NULL, orch_name VARCHAR(50) NOT NULL, raum_id SMALLINT UNSIGNED NOT NULL, wochentag TINYINT UNSIGNED NOT NULL, uhrzeit_von TIME NOT NULL, uhrzeit_bis TIME NOT NULL); CREATE TABLE orchester_tn (orchester_tn_id INT UNSIGNED orchester_id INT UNSIGNED schueler_id INT UNSIGNED instrument_id INT UNSIGNED
NOT NOT NOT NOT
NULL AUTO_INCREMENT PRIMARY KEY, NULL, NULL, NULL);
Sandini Bib
Es sei hier noch angemerkt, dass bei einem Chor die Schüler als „Instrument“ ihre Stimme in den verschiedenen Tonlagen (Sopran, Alt, Tenor, Bass) „nutzen“. Das bedeutet, dass diese auch in der Tabelle instrument eingetragen sein müssen (was aber gar nicht so verkehrt ist, da Gesang schließlich auch unterrichtet wird). Das ER-Diagramm sieht hier ein wenig anders aus. Die Instrumente sind nun mit den Schüler verbunden (siehe Abbildung 2.7).
Abbildung 2.7: ER-Diagramm für die Orchestertabellen und deren Relationen
Für die Leihinstrumente wird es nun wieder etwas einfacher: Wir haben neben den üblichen Verdächtigen nur einen Verweis auf die Instrumententabelle: CREATE TABLE leihinstrument (leihinstrument_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, instrument_id INT UNSIGNED NOT NULL, hersteller VARCHAR(30), typ VARCHAR(30) NOT NULL, kosten DECIMAL(6,2) NOT NULL DEFAULT 0.0);
Jetzt fehlen nur noch die Tabellen für die Verwaltung der Leihinstrumente. Zunächst einmal die eigentliche Ausleihe. Wir brauchen Verweise auf das Leihinstrument und den Schüler. Dazu das Datum, an dem
Sandini Bib
das Instrument verliehen wurde und das Datum, an dem es voraussichtlich zurückkommt (das ist meist nicht so genau bekannt, deshalb haben wir es hier nicht mit NOT NULL versehen). CREATE TABLE ausleihe (ausleihe_id leihinstrument_id schueler_id datum_von datum_bis
INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, INT UNSIGNED NOT NULL, INT UNSIGNED NOT NULL, DATE NOT NULL, DATE);
Schließlich noch die Warteliste. Wir benötigen das Leihinstrument, den interessierten Schüler und das Datum, an dem er Interesse bekundet hat (um denjenigen Vorrang geben zu können, die am längsten warten): CREATE TABLE warteliste (warteliste_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, leihinstrument_id INT UNSIGNED NOT NULL, schueler_id INT UNSIGNED NOT NULL, datum DATE NOT NULL);
Jetzt sollten wir alle Tabellen zusammenhaben. Aber sind auch alle angelegt worden? Haben wir keine vergessen? Um das zu prüfen, gibt es den Befehl SHOW TABLES. Mit ihm kann man sich eine Übersicht der Tabellen in der aktuellen Datenbank anzeigen lassen:
SHOW TABLES
mysql> SHOW TABLES; +-----------------------+ | Tables_in_musikschule | +-----------------------+ | ausleihe | | instrument | | lehrer | | leihinstrument | | orchester | | orchester_tn | | raum | | schueler | | unterricht | | unterricht_tn | | warteliste | +-----------------------+
Auch hier kann man – wie bei SHOW COLUMNS (und auch bei SHOW DATABASES) – einen Filter anhängen, um nur eine Auswahl an Tabellen zu erhalten, zum Beispiel:
Sandini Bib
mysql> SHOW TABLES LIKE '%instrument'; +-------------------------------------+ | Tables_in_musikschule (%instrument) | +-------------------------------------+ | instrument | | leihinstrument | +-------------------------------------+
Zum Abschluss – bevor wir den Befehl CREATE TABLE nochmals komplett vorstellen – eine Darstellung aller Tabellen und ihrer Verknüpfungen in einem ER-Diagramm in Abbildung 2.8. Wie man sieht, wird so etwas schnell unübersichtlich. Andererseits kann man durch geschicktes Anordnen erkennen, welches die zentralen Tabellen sind. Wenn man nämlich versucht, die Verknüpfungen möglichst kreuzungsfrei zu gestalten (ganz wird das meist nie gelingen), wandern die wichtigen und am häufigsten verknüpften Tabellen meist in die Mitte. Auch hier ist dies der Fall. Die Tabellen instrument und schueler sind mit vielen anderen Tabellen verbunden.
Abbildung 2.8: ER-Diagramm mit allen Tabellen
Sandini Bib
2.6.4 CREATE TABLE – komplett Zum Abschluss dieses Kapitels wollen wir den Befehl CREATE TABLE nochmals in seiner ganzen Fülle vorstellen. Die Syntaxbeschreibung ist recht umfangreich und daher in mehrere Teile unterteilt. Zunächst der grobe Rahmen:
CREATE TABLE
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)] [table_options] [select_statement] tbl_name ist natürlich der Name der Tabelle, der einigen Einschränkungen unterliegt (siehe Abschnitt 2.5 über das Erstellen einer Datenbank). Die create_definition ist die Aufzählung der Spalten dieser Datenbank mit ihren Typen und Besonderheiten. Wir werden sie als Nächstes behandeln. table_options sind Optionen, die sich auf die gesamte Tabelle beziehen (wie zum Beispiel der Typ) und select_statement ist eine Abfrage (auf die wir im nächsten Kapitel zu sprechen kommen), deren Spalten und Daten mit in die neu zu erstellende Tabelle aufgenommen werden.
Nun zur create_definition. Sie ist recht komplex zu lesen, andererseits aber dann doch einfach: { col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [PRIMARY KEY] [reference_definition] | PRIMARY KEY (index_col_name,...) | KEY [index_name] (index_col_name,...) | INDEX [index_name] (index_col_name,...) | UNIQUE [INDEX] [index_name] (index_col_name,...) | FULLTEXT [INDEX] [index_name] (index_col_name,...) | [CONSTRAINT symbol] FOREIGN KEY index_name (index_col_name,...) [reference_definition] | CHECK (expr) }
Wie gesagt: keine Panik. Es sieht nur deshalb so komplex aus, weil es viele Möglichkeiten gibt, aus der man jeweils eine auswählen kann. Immer, wenn man am Zeilenende einen vertikalen Balken sieht, bedeutet dies, dass eine Variante zu Ende ist und die nächste beginnt. Dröseln wir das mal auf: 1. col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
[PRIMARY KEY] [reference_definition]
Dies ist die „normale“ Definition einer Spalte. Zunächst der Spaltenname col_name, dann der Typ type. Dieser ist wieder unter einer Reihe von Möglichkeiten auszuwählen: TINYINT[(length)] [UNSIGNED] [ZEROFILL] SMALLINT[(length)] [UNSIGNED] [ZEROFILL] MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
Sandini Bib
INT[(length)] [UNSIGNED] [ZEROFILL] INTEGER[(length)] [UNSIGNED] [ZEROFILL] BIGINT[(length)] [UNSIGNED] [ZEROFILL] REAL[(length,decimals)] [UNSIGNED] [ZEROFILL] DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL] FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL] DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL] NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL] [NATIONAL] CHAR[ACTER](length) [BINARY] | NCHAR(length) [BINARY] [NATIONAL] VARCHAR[ACTER](length) [BINARY] | NVARCHAR(length) [BINARY] DATE TIME TIMESTAMP DATETIME TINYBLOB BLOB MEDIUMBLOB LONGBLOB TINYTEXT TEXT MEDIUMTEXT LONGTEXT ENUM(value1,value2,value3,...) SET(value1,value2,value3,...)
Diese Liste ist uns schon von der Beschreibung der Datentypen geläufig. NOT NULL bzw. NULL (der Standard) legt fest, ob ein Feld leer bleiben darf oder nicht. DEFAULT default_value definiert, dass das Feld den Wert default_value erhalten soll, wenn es nicht befüllt wird. AUTO_INCREMENT kann für genau ein ganzzahliges Feld der Tabelle definiert werden (von Ausnahmen bei bestimmten Tabellenarten abgesehen) und sorgt dafür, dass dieses Feld automatisch einen bisher nicht genutzten Wert erhält, wenn kein eigener Wert mitgeliefert wird. PRIMARY KEY legt fest, dass dieses Feld Teil des Primärschlüssels ist (siehe Kapitel über Indizes weiter hinten). Die reference_definition dient dazu, Beziehungen dieses Feldes mit dem Inhalt anderer Tabellen herzustellen. Allerdings ist in MySQL nur die Syntax implementiert, ohne wirklich Funktionalität zu bieten und dient dazu, das Übertragen von SQL-Befehlen aus anderen Datenbanksystemen zu erleichtern. Nur der Vollständigkeit halber die Syntax der reference_definition: REFERENCES tbl_name [(index_col_name,...)] [MATCH FULL | MATCH PARTIAL] [ON DELETE reference_option] [ON UPDATE reference_option]
Sandini Bib
index_col_name ist dabei der Name einer Spalte in einem Index und reference_option ist eine der folgenden: RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT 2. PRIMARY KEY (index_col_name,...)
Wie weiter oben beschrieben, sollte jede Tabelle einen Primärschlüssel haben, der dafür sorgt, dass jeder Datensatz eindeutig identifizierbar ist. Dabei handelt es sich im Allgemeinen um ein einzelnes Feld, man kann aber auch mehrere Felder zu einem Primärschlüssel kombinieren. Bisher haben wir das Feld bei der Tabellendefinition direkt mit dem Attribut PRIMARY KEY versehen, aber man kann auch (möglichst am Ende der Definition) einen eigenen Abschnitt dafür nutzen. Statt also CREATE TABLE mytab (pfield INT UNSIGNED NOT NULL PRIMARY KEY, ...);
zu schreiben, kann man auch schreiben: CREATE TABLE mytab (pfield INT UNSIGNED NOT NULL, ..., PRIMARY KEY (pfield));
Dies bietet sich an, wenn man gleich noch weitere Indizes (siehe späteres Kapitel) definieren will und so alle Indizes an einer Stelle (am Ende der Definition) versammeln kann. Zudem ist dies die einzige Möglichkeit, mehr als ein Feld für den Primärschlüssel festzulegen. 3. KEY [index_name] (index_col_name,...)
Mit diesem Teil lässt sich direkt bei der Tabellendefinition ein Index festlegen (siehe späteres Kapitel). Dabei ist index_name der Name des Index und index_col_name,... eine Aufzählung der Spalten, die darin vorkommen. Lässt man den Namen weg, wird als Name der Name der ersten Indexspalte verwendet. Ist dieser Name schon in Verwendung, wird er um _2, _3, … erweitert. 4. INDEX [index_name] (index_col_name,...)
Dieser Teil entspricht exakt der Definition von KEY. 5. UNIQUE [INDEX] [index_name] (index_col_name,...)
Ein solchermaßen erzeugter Index muss immer eindeutig sein. Werden in der Tabelle Werte eingetragen, die dafür sorgen würden, dass der Index nicht mehr eindeutig wäre, wird die Änderung abgelehnt und eine Fehlermeldung ausgegeben. Ein solcher eindeutiger Index
Sandini Bib
unterscheidet sich von einem Primärschlüssel (per PRIMARY KEY angelegt) dadurch, dass hier durchaus auch NULL-Werte erlaubt sein können. index_name und index_col_name haben die gleiche Bedeutung wie bei KEY. 6. FULLTEXT [INDEX] [index_name] (index_col_name,...)
Mit diesem Konstrukt kann man dafür sorgen, dass eine Volltextsuche auf den angegebenen Spalten möglich ist. Wir werden darauf später im Abschnitt über Abfragen eingehen. Spalten, die abgefragt werden sollen, müssen in index_col_name,... auftauchen. 7. [CONSTRAINT
symbol] FOREIGN [reference_definition]
KEY
index_name
(index_col_name,...)
Mit diesem Teil der Syntax kann man Abhängigkeiten zu anderen Tabellen und Spalten beschreiben. Allerdings ist diese Beschreibung wirkungslos, da solche Regeln und Fremdschlüssel in MySQL nicht umgesetzt sind. Sie dienen lediglich zur leichteren Übernahme von Befehlen aus anderen Datenbanksystemen. 8. CHECK (expr)
Die CHECK-Syntax ist genauso wie CONSTRAINT und FOREIGN KEY nicht mit Funktionalität ausgestattet und dient nur der einfacheren Übernahme aus anderen Datenbanksystemen. Als Nächstes wollen wir zu den Tabellenoptionen table_options kommen. Sie bieten die folgenden Möglichkeiten: { TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM} | AUTO_INCREMENT = number | AVG_ROW_LENGTH = number | CHECKSUM = {0 | 1} | COMMENT = "string" | MAX_ROWS = number | MIN_ROWS = number | PACK_KEYS = {0 | 1} | PASSWORD = "string" | DELAY_KEY_WRITE = {0 | 1} | ROW_FORMAT = {default | dynamic | fixed | compressed} | RAID_TYPE = {1 | STRIPED | RAID0} RAID_CHUNKS = num_chunks RAID_CHUNKSIZE = num_size | UNION = (table_name,...) | INSERT_METHOD = {NO | FIRST | LAST} | DATA DIRECTORY = "directory" | INDEX DIRECTORY = "directory" }
Sandini Bib
Auch hier eine kurze Beschreibung der Optionen: 1. TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM}
Mit diesem Attribut kann man den Typ der Tabelle festlegen. MySQL unterstützt verschiedene Tabellenarten, die in den unterschiedlichen Situationen sinnvoll sind und bestimmte Features unterstützen. Zudem sind abhängig von der Distribution, dem Betriebssystem und den eventuell angepassten Kompilierungsoptionen bestimmte Tabellentypen nicht verfügbar. In solch einem Fall wird der „nächstmögliche“ Typ verwendet. Standard ist MYISAM. 2. AUTO_INCREMENT = number
der Wert, der bei Verwendung von AUTO_INCRMENT für eine Tabellenspalte als Nächstes genutzt werden soll (nur für Tabellen des Typs MyISAM). 3. AVG_ROW_LENGTH = number
eine Vorabschätzung, wie lang die einzelnen Tabellenzeilen ungefähr sein werden. Diese Angabe macht normalerweise nur Sinn, wenn es sich um eine sehr große Tabelle mit vielen Feldern variabler Länge handelt. 4. CHECKSUM = {0 | 1}
Wird dieser Parameter auf 1 gesetzt, wird für jede Zeile der Tabelle eine Prüfsumme mit abgespeichert. Dadurch werden Datenänderungen zwar etwas langsamer, kaputte Tabellen lassen sich aber leichter auffinden (nur für Tabellen des Typs MyISAM). 5. COMMENT = "string"
Angabe eines Kommentars zur Tabelle, der maximal 60 Zeichen lang sein kann. 6. MAX_ROWS = number
Angabe der geplanten maximalen Anzahl an Datensätzen für diese Tabelle. Das Produkt aus MAX_ROWS und AVG_ROW_LENGTH wird genutzt, um zum Beispiel die Größe der Zeiger in den Indexdateien festzulegen. 7. MIN_ROWS = number
Angabe der geplanten minimalen Anzahl an Datensätzen für diese Tabelle. 8. PACK_KEYS = {0 | 1}
ermöglicht das Verwalten der Indizes auch bei ganzzahligen Feldern in gepackter Form (PACK_KEYS = 1). Standard ist, dass nur Zeichenket-
Sandini Bib
ten gepackt abgelegt werden. Dadurch werden Aktualisierungen der Daten etwas verlangsamt, das Suchen von Werten aber beschleunigt und Platz gespart (nur für Tabellen des Typs MyISAM und ISAM). 9. PASSWORD = "string"
Verschlüsseln der Tabellendatei .frm mit einem Kennwort. Allerdings passiert bei der Standardversion von MySQL nichts. 10. DELAY_KEY_WRITE = {0 | 1}
Wird diese Option aktiviert (DELAY_KEY_WRITE = 1), werden die Schlüssel einer Tabelle nach dem Einfügen oder Ändern von Daten nicht sofort auf die Festplatte geschrieben, sondern erst, wenn die Tabellendatei geschlossen wird. Vorteil ist, dass deutlich weniger Schreibvorgänge für die Schlüsselfelder notwendig sind, wodurch sich die Geschwindigkeit steigern kann. Diese Option ist allerdings nur dann wirksam, wenn für den Datenbank-Server mysqld die Option delay_key_write auf ON gesetzt und die Tabelle vom Typ MyISAM ist. Nutzt man diese Tabellenoption, sollte vor dem Starten des MySQLServers immer das Tool myisamchk ausgeführt werden, um sicherzustellen, dass keine Inkonsistenzen entstanden sind, falls der Server einmal „unsanft“ beendet wurde. Allerdings ist nicht zu befürchten, dass durch die Nutzung dieser Option Daten verloren gehen, da sich die Index-Daten (um die es hier geht) immer aus den Originaldaten wiederherstellen lassen. 11. ROW_FORMAT = {default | dynamic | fixed | compressed}
Mit diesem Parameter lässt sich angeben, wie eine Tabelle gespeichert werden soll. Bisher geht dies nur für Tabellen des Typs MyISAM und zudem nur für die Optionen dynamic und fixed. 12. RAID_TYPE = {1 | STRIPED | RAID0} RAID_CHUNKS = num_chunks RAID_CHUNKSIZE
= num_size
Mit dieser Option kann man die maximale Dateigröße von MyISAMTabellen umgehen, die vom Betriebssystem vorgegeben ist. Gibt man einen RAID-Typ an (1, STRIPED und RAID0 sind Synonyme), werden num_chunks Unterverzeichnisse für die Tabelle mit den Namen 00, 01, 02, … angelegt, dort wiederum werden Tabellendateien angelegt und die Tabellendaten in Häppchen der Größe 1024·num_size auf die Dateien verteilt. Vorraussetzung dafür ist, dass der MySQL-Server mit der Option --with-raid gestartet wurde. Zudem bleibt die Indexdatei als eine einzelne Datei bestehen.
Sandini Bib
13. UNION = (table_name,...)
Diese Option ist nur für Tabellen des Typs MERGE nötig. Sie gibt an, welche Tabellen zur neuen Tabelle verbunden werden sollen. 14. INSERT_METHOD = {NO | FIRST | LAST}
Auch diese Option wird nur für Tabellen des Typs MERGE benötigt und legt fest, in welche der Tabellen, die die Merge-Tabelle darstellen, Daten eingefügt werden sollen. Wird dieser Parameter nicht mit angegeben oder auf NO gesetzt, kann man keine Daten einfügen. 15. DATA DIRECTORY = "directory"
Dieser Parameter legt fest, in welchem Verzeichnis die Tabellendatei abgelegt werden soll. Normalerweise landet sie im Verzeichnis der gewählten Datenbank. Hier ist ein absoluter Pfad anzugeben. 16. INDEX DIRECTORY = "directory"
Mit diesem Wert kann man angeben, wo die Indexdatei der Tabelle zu liegen kommen soll. Auch hier ist ein absoluter Pfad notwendig. Hiermit ist das erste „richtige“ Kapitel abgeschlossen. Wir haben ein Modell für unser Szenario entwickelt, uns mit einem MySQL-Server verbunden, eine Datenbank und Tabellen angelegt und die Beziehungen zwischen den Tabellen in einem ER-Diagramm dargestellt. Im nächsten Kapitel geht es nun um die Hauptakteure – die Daten selber. Wir werden lernen, wie man Daten einfügt, abfragt, aktualisiert und auch wieder löscht.
2.6.5 Exkurs: Normalisierung Wir haben beim Erstellen der Tabellen auf verschiedene Dinge geachtet, die mit Normalisierung zusammenhängen. Normalisieren bedeutet, die Tabellen so zu entwerfen, dass gewisse Regeln eingehalten werden, die Redundanzen vermeiden und den Verlust von Daten verhindern sollen. Gehen wir einmal davon aus, dass wir die Informationen für eine Unterrichtsgruppe in einer Form festhalten würden, wie sie vielleicht auf einer Karteikarte Sinn machen würde: Instrument
Lehrer
Termin
Raum
Max. Belegung
Schüler
Querflöte
G. Mosler, Lange Reihe 11, 20123 Hamburg
Dienstag, 17:0017:45
A5.35
6
T. Schmidt, R. Herms, A. Demmig
Es gibt hier verschiedene Punkte, die nachteilig sind, und an Hand derer wir die Normalisierung in ihren verschiedenen Stufen aufzeigen wollen.
Sandini Bib
1. Normalform Bei der ersten Normalform wird dafür gesorgt, dass in jedem Feld nur einfache („atomare“) Werte stehen. Dies ist bei unserem Beispiel an zwei Stellen definitiv verletzt:
1. Normalform
1. In der Spalte Lehrer steht neben dem Namen des Lehrers auch dessen
Adresse. Diese sollte in einzelnen Feldern gespeichert sein. (Wobei es diskussionswürdig bleibt, ob zum Beispiel die Hausnummer auch ein einzelnes Feld braucht. Dies hängt sicherlich neben dem Eifer, mit dem man „sauber“ entwerfen will, auch mit dem Einsatzzweck zusammen: Möchte man automatisch Postleitzahlen ermitteln, ist ein eigenes Feld für die Hausnummer ausgesprochen hilfreich.) 2. Die Schüler stehen als Liste in einem Feld. Das hat neben den unbe-
quemen Bearbeitungsmöglichkeiten noch den unangenehmen Effekt, dass durch die begrenzte Feldlänge die Anzahl der Einträge immer eingeschränkt ist. Auch eine Aufteilung in Schüler1, Schüler2, Schüler3 usw. hilft nicht wirklich weiter, da man immer nur eine feste Zahl an Feldern angeben kann. Was aber, wenn es dann doch einmal mehr Teilnehmer werden sollen? Daher erzeugt man eine neue Tabelle (wie wir es auch in unserem Modell mit der Tabelle unterricht_tn gemacht haben) und hat nun die Möglichkeit, so viele Schüler für einen Kurs einzutragen, wie tatsächlich vorhanden sind. Als Verweis auf die eigentliche Unterrichtstabelle benötigt man eine eindeutige Identifizierung, zum Beispiel aus Instrument, Raum und Termin. Zunächst die Tabelle Unterricht: Instrument
Lehrer
L_Strasse
L_PLZ
L_Ort
Raum
Max_ Bel
Termin
Querflöte
G. Mosler
Lange Reihe 11
2012 3
Hamburg
A5.35
6
Dienstag, 17:00-17:45
Nun die davon „abhängige“ Tabelle Unterricht_Teilnehmer: Instrument
Raum
Termin
Schüler
Querflöte
A5.35
Dienstag, 17:00-17:45
T. Schmidt
Querflöte
A5.35
Dienstag, 17:00-17:45
R. Herms
Querflöte
A5.35
Dienstag, 17:00-17:45
A. Demmig
2. Normalform Die zweite Normalform besagt, dass alle Attribute einer Tabelle nur komplett vom Primärschlüssel abhängen, und nicht nur von Teilen davon. Dies gilt hier eben nicht für die maximale Belegung des Raumes. Sie ist nur vom Raum selber abhängig, nicht von den beiden anderen
2. Normalform
Sandini Bib
Elementen des Primärschlüssels, nämlich Instrument und Termin. Es hat zwei Vorteile, eine Tabelle in die zweite Normalform zu bringen: 1. Ändert sich der Wert des abhängigen Attributs, muss er in allen Da-
tensätzen nachgezogen werden. In unserem Beispiel: Passen in den Raum mehr Personen hinein, muss das Feld Max_Bel in allen Datensätzen geändert werden, die den Raum beinhalten. Bei einer eigenen Tabelle für den Raum muss der Wert nur an dieser Stelle einmal geändert werden. 2. Werden alle Datensätze mit dem entsprechenden Attribut entfernt
(weil in unserem Beispiel in einem Halbjahr kein Unterricht in einem Raum stattfindet), verschwinden auch die Informationen aus den abhängigen Feldern. Es ist nirgendwo mehr vermerkt, dass der Raum A5.35 für sechs Personen ausgelegt ist. Auch dies wird durch eine eigene Tabelle vermieden. Wir bringen also unser Beispiel in die zweite Normalform: Instrument
Lehrer
L_Strasse
L_PLZ
L_Ort
Raum
Termin
Querflöte
G. Mosler
Lange Reihe 11
20123
Hamburg
A5.35
Dienstag, 17:00-17:45
Die davon „abhängige“ Tabelle Unterricht_Teilnehmer: Instrument
Raum
Termin
Schüler
Querflöte
A5.35
Dienstag, 17:00-17:45
T. Schmidt
Querflöte
A5.35
Dienstag, 17:00-17:45
R. Herms
Querflöte
A5.35
Dienstag, 17:00-17:45
A. Demmig
Dazu jetzt die neue Tabelle für die Raumdaten: Raum
Max_Belegung
A5.35
6
3. Normalform In der dritten Normalform gibt es auch keine Abhängigkeiten mehr zwischen Attributen, die keine Schlüsselfelder sind. Hier gelten im Prinzip die gleichen Vorteile wie bei der zweiten Normalform. In unserem Beispiel stehen noch die Felder L_Strasse, L_PLZ und L_Ort der dritten Normalform im Weg. Diese müssten auch in eine eigene Tabelle Lehrer ausgelagert werden:
3. Normalform
Instrument
Lehrer
Raum
Termin
Querflöte
G. Mosler
A5.35
Dienstag, 17:00-17:45
Sandini Bib
Die davon „abhängige“ Tabelle Unterricht_Teilnehmer: Instrument
Raum
Termin
Schüler
Querflöte
A5.35
Dienstag, 17:00-17:45
T. Schmidt
Querflöte
A5.35
Dienstag, 17:00-17:45
R. Herms
Querflöte
A5.35
Dienstag, 17:00-17:45
A. Demmig
Die Tabelle für die Raumdaten: Raum
Max_Belegung
A5.35
6
Nun die Tabelle Lehrer: Lehrer
L_Strasse
L_PLZ
L_Ort
G. Mosler
Lange Reihe 11
20123
Hamburg
In den meisten Fällen reicht die dritte Normalform für den praktischen Gebrauch. Es gibt noch drei weitere Normalformen, die Boyce-CoddNormalform (BCNF, eine etwas schärfere Version der dritten Normalform, bei der auch die Schlüssel untereinander nicht abhängig sein dürfen) sowie die vierte und fünfte Normalform. Diese sind aber eher von akademischer Natur und werden normalerweise nicht in der Praxis verwendet. Auf zwei Punkte möchte ich hier noch kurz eingehen: • Primärschlüssel sollten möglichst „sinnfrei“ sein, wie schon oben geschildert. Hier ist ein gutes Beispiel dafür, warum dies empfohlen wird. Ändert sich nämlich zum Beispiel der Raum oder der Termin, müssen auch wieder alle Werte in den abhängigen Tabellen nachgezogen werden (eine sogenannte Änderungsanomalie). • Das Normalisieren von Datenstrukturen geht fast immer auch mit einer Verschlechterung der Performance einher. Will man zum Beispiel die in die dritte Normalform gebrachten Daten wieder zusammen ausgeben (weil sie dann schließlich am lesbarsten sind), benötigt man eine Verknüpfung über vier Tabellen. Solche Verknüpfungen sind – gerade bei vielen beteiligten Tabellen – sehr speicher- und leistungsfressend. Daher kann es in bestimmten Fällen durchaus Sinn machen, Daten redundant abzulegen, wenn sie selten geändert werden. Das muss aber gut durchdacht und sauber umgesetzt werden (um Inkonsistenzen im Datenbestand zu vermeiden). Man könnte zum Beispiel die maximale Belegungszahl der Räume mit in der Unterrichtstabelle ablegen, um schnellen Zugriff darauf zu
Sandini Bib
gewährleisten (wenn beispielsweise neue Teilnehmer hinzugefügt werden sollen). Dann sollte aber weiterhin die Raum-Tabelle existieren. Erstens geht damit die Belegungszahl nicht verloren, wenn kein Unterricht in einem Raum stattfindet und zweitens kann man diese Tabelle als Quelle für das Eintragen des Werts in die Unterrichtstabelle nutzen, wenn ein Kurs neu angelegt wird. Man muss nur darauf achten, dass bei einer Änderung der Belegungszahl nicht nur die Raum-Tabelle aktualisiert wird, sondern auch die Unterrichtstabelle. Dies wird aber ausgesprochen selten vorkommen, sofern man nicht monatlich die Räume umbaut…
2.7
Fragen
1. Welche Bereiche gibt es innerhalb der Sprache SQL und wie unter-
scheiden sie sich? 2. Was für Vorteile hat es, ein normalisiertes Datenmodell zu nutzen? 3. Worin unterscheiden sich die String-Datentypen VARCHAR und VARCHAR
BINARY? 4. Wie kann man dafür sorgen, dass ein Datensatz eine eindeutige Ken-
nung erhält, ohne dass der Benutzer selber darauf achten muss? 5. Wie kann man sich mit der Datenbank musikschule auf einem MySQL-
Server verbinden, der auf dem Rechner truhe.tdemmig.local läuft? Der Benutzer soll demo sein, welcher das Kennwort geheim besitzt.
Sandini Bib
3
Daten
le
n e rn
Wofür war eine Datenbank noch mal gedacht? Richtig, sie dient dazu, Daten zu verwalten. Bisher haben wir allerdings noch gar keine Daten gesehen. Lediglich die dafür notwendigen Behältnisse – die Datenbanken und Tabellen – haben wir genauer kennen gelernt. Nun ist es an der Zeit, endlich das tatsächliche Datenhandling zu erlernen und umzusetzen.
3.1
Was Sie in diesem Kapitel lernen
Um mit den Daten umzugehen, müssen diese zunächst einmal in die Datenbank wandern. Dazu lernen wir den Befehl INSERT kennen. Um die Daten dann auch wieder auslesen zu können, verwenden wir den Befehl SELECT. Änderungen an den Daten müssen natürlich ebenso möglich sein, wie das Löschen von Informationen. Dazu dienen die Befehle UPDATE und DELETE. Wir werden zunächst Daten einfügen, abfragen, aktualisieren und löschen – ohne großen Firlefanz und ohne alle möglichen Optionen auszureizen. Danach werden wir uns detailliert mit den einzelnen Befehlen INSERT, SELECT, UPDATE und DELETE befassen. Beim Abfragen der Daten werden wir neben den nutzbaren Funktionen auch die Abfrage über mehrere Tabellen (eine Grundlage relationaler Datenbanken) behandeln – die sogenannten „Joins“.
:DV 6LH LQ GLHVHP .DSLWHO OHUQHQ
Sandini Bib
3.2
Eintragen, Abfragen, Ändern, Löschen – die Basis
Um Daten in die bisher leeren Tabellen einzutragen, nutzt man den Befehl INSERT. In seiner einfachsten Form sieht er wie folgt aus:
INSERT
INSERT INTO raum VALUES (0, 'A5.35', '5', 4);
Damit trägt man in die Tabelle raum für die Raum-Kennung 0, für den Namen „A5.35“, für die Etage 5 und für die Personenanzahl 4 ein. Diese Version setzt aber voraus, dass man die Reihenfolge der Spalten kennt. Zudem kann man keine Felder auslassen, die eventuell automatisch gefüllt werden sollen. Besser ist da schon, die Spalten mit anzugeben, die gefüllt werden sollen: INSERT INTO raum (raum_name, etage, personen) VALUES ('A5.33', '5', 4);
Damit ist man auch dann auf der sicheren Seite, wenn sich die Tabellendefinition einmal ändern sollte (zumindest soweit, wie die entsprechenden Felder dann noch vorhanden sind …). Was passiert nun mit den Feldern, die man nicht mit angibt? In diesem Fall gibt es nur das Feld raum_id, welches fehlt. Für solche Fälle haben wir in der Tabellendefinition das Attribut DEFAULT, dessen Wert dann genutzt wird. Hier handelt es sich allerdings um einen Primärschlüssel mit dem Attribut AUTO_INCREMENT. In diesem Fall wird dann der Mechanismus zum Generieren einer neuen Zahl gestartet. Man sieht noch eine weitere Regel: Zeichenketten müssen immer in Anführungszeichen gesetzt werden (ob einfach oder doppelt ist egal, sie müssen nur am Anfang und Ende des Strings gleich sein), Zahlen hingegen nicht. Wie sieht nun also das Ergebnis aus, wenn der obige Befehl gestartet wird? Um sich den Inhalt einer Tabelle ausgeben zu lassen, nutzt man den Befehl SELECT, hier in seiner einfachsten Form (später mehr):
SELECT
mysql> SELECT * FROM raum; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | +---------+-----------+-------+----------+
Wie wir sehen, wird die Raum-Kennung raum_id automatisch erstellt. Da es sich um den ersten Datensatz handelt, der in diese Tabelle eingetragen wird, erhält er den Wert 1 (hätten wir bei der Tabellendefinition einen anderen Wert als Startwert angegeben, würde dieser jetzt hier auf-
NULL
'DWHQ
Sandini Bib
tauchen). Der nächste Satz bekommt dann die 2 und so weiter. Dieser Mechanismus wird nur dann aktiv, wenn für raum_id NULL oder 0 angegeben wird. Dabei sieht man aus dem obigen Beispiel, dass ein Feld den Wert NULL erhält, wenn es bei INSERT nicht mit aufgeführt wird. Man kann auch explizit ein Feld mit dem Wert NULL versehen: INSERT INTO raum (raum_name, etage, personen) VALUES ('B2.02', NULL, 20);
Wie man sieht, gibt man einfach den Begriff „NULL“ an, ohne Anführungszeichen oder Ähnliches. Schaut man sich nun den Inhalt der Tabelle raum erneut an, sieht dieser wie folgt aus: mysql> SELECT * FROM raum; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | NULL | 20 | +---------+-----------+-------+----------+
Auch in der Ausgabe findet sich hier die Bezeichnung „NULL“. Hier gilt es, etwas aufzupassen, denn auch die Zeichenkette 'NULL' sieht genauso aus: mysql> INSERT INTO raum (raum_name, etage, personen) VALUES ('NULL', NULL, 11); Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM raum; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | NULL | 20 | | 3 | NULL | NULL | 11 | +---------+-----------+-------+----------+
Dies kann natürlich zu Verwirrung führen, aber im Gebrauch gibt es selten Probleme. Man erhält bei Abfrage der Datenbank über andere Schnittstellen wie PHP oder Perl durchaus unterscheidbare Resultate zwischen NULL und 'NULL'. Und zudem kann man bei einem SELECTBefehl filtern, ob man auch Datensätze erhalten will, die in einem bestimmten Feld den Wert NULL stehen haben.
Sandini Bib
Man kann auch mit einem INSERT-Befehl mehrere Datensätze auf einmal eintragen. Das folgende Beispiel zeigt, wie das geht: mysql> INSERT INTO raum (raum_name, etage, personen) VALUES -> ('A5.21', '5', 4), -> ('A5.23', '5', 4), -> ('CE.48', 'E', 6); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> SELECT * FROM raum; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | NULL | 20 | | 3 | NULL | NULL | 11 | | 4 | A5.21 | 5 | 4 | | 5 | A5.23 | 5 | 4 | | 6 | CE.48 | E | 6 | +---------+-----------+-------+----------+
Die Daten werden als sogenannte „Tupel“ eingetragen, deren Informationen in Klammern stehen und die durch Kommas getrennt sind. Man erhält bei der Eingabe gleich auch die Information, wie viele Datensätze erfasst wurden und ob es Probleme bei den Sätzen gab (wie zum Beispiel doppelte Datensätze). Jetzt haben wir in der Tabelle zwei Datensätze, deren Informationen nicht vollständig sind (die Etage ist nicht angegeben) bzw. deren Raumbezeichnung nicht sinnvoll ist (aus dem Beispiel NULL vs. 'NULL'). Wie findet man solche Datensätze? Nun, in der aktuellen Raum-Tabelle sieht man die unschönen Daten noch auf einen Blick, schließlich ist die Tabelle nicht sehr groß. Aber bei einer wirklich großen Tabelle (zum Beispiel wird die Schülertabelle sicher umfangreich werden) ist dies nicht mehr so einfach. Wir wünschen uns nun also, nur bestimmte Datensätze auszugeben. Dazu kann man den SELECT-Befehl um eine WHERE-Klausel erweitern, in der Filterkriterien angegeben werden: mysql> SELECT * FROM raum WHERE raum_id = 2; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 2 | B2.02 | NULL | 20 | +---------+-----------+-------+----------+
Man gibt also eine Spalte an, die gefiltert werden soll, einen Operator (hier das Gleichheitszeichen) und einen Vergleichswert. Ein anderes Beispiel ist:
'DWHQ
Sandini Bib
mysql> SELECT * FROM raum WHERE raum_id < 4; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | NULL | 20 | | 3 | NULL | NULL | 11 | +---------+-----------+-------+----------+
Hier ist der Vergleichsoperator das Kleiner-Zeichen ( (größer), >= (größer oder gleich), SELECT * FROM raum WHERE raum_id < 4 AND personen SELECT * FROM raum WHERE etage IS NULL; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 2 | B2.02 | NULL | 20 | | 3 | NULL | NULL | 11 | +---------+-----------+-------+----------+
Sandini Bib
Analog dazu kann man sich mit IS NOT NULL die Sätze ausgeben lassen, bei denen ein Feld nicht NULL ist:
IS NOT NULL
mysql> SELECT * FROM raum WHERE etage IS NOT NULL; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 4 | A5.21 | 5 | 4 | | 5 | A5.23 | 5 | 4 | | 6 | CE.48 | E | 6 | +---------+-----------+-------+----------+
Eine besondere Eigenschaft von NULL ist, dass Vergleiche mit ihm immer den Wert NULL ergeben. Dazu muss man beachten, dass unter SQL die übliche Ergebnismenge eines Vergleichs (a > b, c = d, usw.) nicht nur aus TRUE und FALSE besteht, sondern noch einen dritten Wert, eben NULL, besitzt. Ist nun einer der Vergleichswerte NULL, lautet auch das Ergebnis NULL. Man kann dies sehr schön sehen, indem man die folgenden Zeilen in mysql eingibt: mysql> SELECT 2 = 0; +-------+ | 2 = 0 | +-------+ | 0 | +-------+ mysql> SELECT 2 = 2; +-------+ | 2 = 2 | +-------+ | 1 | +-------+ mysql> SELECT 2 = NULL; +----------+ | 2 = NULL | +----------+ | NULL | +----------+
Der erste Vergleich liefert eine 0 zurück, dem SQL-Äquivalent für FALSE. Der zweite Vergleich ergibt 1, was TRUE entspricht. Der dritte Vergleich schließlich ergibt NULL, obwohl man im ersten Moment geneigt ist, auf FALSE zu tippen (denn 2 ist ja nun wirklich ungleich NULL). Dies ist nun eine Besonderheit der dreiwertigen Logik.
'DWHQ
Sandini Bib
Nun haben wir also unsere beiden Datensätze ohne Etage gefunden. Aber wie aktualisiert man diese? Dazu dient der Befehl UPDATE. Um zum Beispiel den ersten der beiden Sätze zu ändern, gibt man folgendes Kommando ein:
UPDATE
UPDATE raum SET etage = '2' WHERE raum_id = 2;
Dies bedeutet, dass man in der Tabelle raum die Etage aller Datensätze auf '2' setzen möchte, deren raum_id den Wert 2 hat. Dies kann in diesem Fall natürlich nur ein Datensatz sein, da die Spalte raum_id eindeutig ist. Es ist aber auch kein Problem, mehrere Datensätze gleichzeitig damit zu aktualisieren, wenn die WHERE-Bedingung auf mehrere Zeilen gleichzeitig zutrifft. Man kann sogar alle Datensätze einer Tabelle aktualisieren, wenn man die WHERE-Klausel ganz weglässt, allerdings ist dies nur in manchen besonderen Situationen sinnvoll. Umgekehrt kann man aber auch mehrere Felder gleichzeitig verändern, was wir für den zweiten betroffenen Datensatz vornehmen wollen: UPDATE raum SET raum_name = 'C4.16', etage = '4' WHERE raum_id = 3;
Als Ergebnis erhält man übrigens wieder eine etwas detailliertere Auskunft über die Anzahl der geänderten Datensätze: Rows matched: 1 Changed: 1 Warnings: 0
Damit kann man kontrollieren, ob eventuell zu viele oder gar kein Datensatz geändert wurde. Wenn nämlich die WHERE-Bedingung auf keinen Datensatz zutrifft, gibt es keine Fehlermeldung, da es sich dabei um keinen wirklichen Fehler handelt: mysql> UPDATE raum SET etage = '1' WHERE raum_id = 14; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0
Hintergrund dieses etwas seltsam anmutenden Verhaltens ist, dass in SQL die Datensätze als Mengen angesehen werden. Aus diesen Mengen wird mittels der WHERE-Klausel eine Teilmenge herausgeschnitten, die durchaus leer sein kann. Die Verarbeitung als Mengen ist auch der Grund dafür, dass es keine vorgegebene Ausgabereihenfolge gibt, sofern man nicht explizit (mittels ORDER BY) eine bestimmt. Dass bei ähnlichen Abfragen meist die gleiche Reihenfolge erscheint, liegt an der Implementierung im Datenbank-Server, ist aber unter SQL nicht garantiert! Bei den genutzten Mengen ist allerdings zu beachten, dass Elemente mehrfach vorkommen können (was sich durch geeignete Abfragen auch wieder ändern lässt …).
Sandini Bib
Als Ergebnis dieser Aktualisierungen sieht unsere Raumtabelle deutlich „gepflegter“ aus: mysql> SELECT * FROM raum; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | | 4 | A5.21 | 5 | 4 | | 5 | A5.23 | 5 | 4 | | 6 | CE.48 | E | 6 | +---------+-----------+-------+----------+
Nachdem wir nun Daten eingetragen, abgefragt und aktualisiert haben, wollen wir auch noch welche löschen. Der Raum CE.48 im Erdgeschoss wird leider anderweitig gebraucht und steht nicht mehr als Übungsraum zur Verfügung. Daher wollen wir ihn aus der Tabelle raum löschen. Der zugehörige Befehl ist wieder sehr einfach und „sprechend“:
DELETE
DELETE FROM raum WHERE raum_id = 6;
Hier ist wohl keine große Erklärung notwendig. Es sei nur darauf hingewiesen, dass man bei WHERE durchaus nicht immer auf den Primärschlüssel filtern muss. Aber wenn man ihn schon kennt, ist es auch besser, ihn zu nutzen, da man dann eindeutig weiß, was man bearbeitet oder löscht. In anderen Fällen kann es durchaus der Fall sein, dass der gewünschte Eintrag mehrfach existiert und man dadurch unabsichtlich auch mehr als einen Datensatz löscht. Nachdem wir nun die vier wichtigsten SQL-Befehle (INSERT, SELECT, UPDATE und DELETE) kennen gelernt haben, wollen wir uns nun intensiver mit jedem einzelnen von ihnen beschäftigen. Dies wird in den nächsten Abschnitten geschehen.
3.3
Daten eintragen
Widmen wir uns als Erstes ausführlicher dem Einfügen von Daten. Der zugehörige Befehl lautet – wie wir jetzt schon wissen – INSERT. Dessen Syntax ist nicht wirklich schwierig, allerdings gibt es drei sehr verschiedene Möglichkeiten. Alle drei beginnen mit dem folgenden Teil:
INSERT
INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] tbl_name ... tbl_name ist natürlich der Name der Tabelle, in die die Daten eingetragen
werden sollen. Es können immer nur Daten in einer einzelnen Tabelle gespeichert werden, nicht in mehreren gleichzeitig (zumindest nicht
'DWHQ
Sandini Bib
mit einem einzelnen Befehl). Die einzelnen optionalen Attribute haben folgende Bedeutung: • LOW_PRIORITY: Ruft man INSERT mit diesem Attribut auf, wird so lange mit dem Einfügen von Daten gewartet, bis niemand anderes mehr Daten aus der entsprechenden Tabelle liest. Das kann natürlich einige Zeit dauern. • DELAYED: Mit diesem Parameter geschieht eher das Gegenteil des vorher Beschriebenen. Es wird zwar auch gewartet, bis niemand sonst mehr auf die Tabelle zugreift, aber der Client, welcher den Befehl aufgerufen hat, kann sofort weiterarbeiten und muss nicht auf die Ausführung des Befehls warten. Die Daten werden derweil im Arbeitsspeicher des Datenbank-Servers gesammelt und (wenn sie von mehreren Clients kommen) gesammelt abgespeichert. Dies beschleunigt den ganzen Vorgang, aber nur dann, wenn tatsächlich stark auf die Tabelle zugegriffen wird. Bei einer „normal“ belasteten Tabelle ist DELAYED aufgrund des zusätzlichen Aufwands eher etwas langsamer. • IGNORE: Fügt man einen Datensatz mit einem Primärschlüssel oder anderen eindeutigen Spalten in eine Tabelle ein, der schon existiert, gibt es normalerweise eine Fehlermeldung. Diese kann vermieden werden, indem man den Parameter IGNORE nutzt. Solche Sätze werden dann zwar immer noch nicht eingetragen (dass würde schließlich gegen die Eindeutigkeit dieser Spalte verstoßen), aber immerhin bricht die Verarbeitung dann nicht mit einem Fehler ab – die entsprechenden Sätze werden einfach ignoriert. Dies kann besonders dann hilfreich sein, wenn man viele Sätze auf einmal einträgt. • INTO: Das Weglassen dieses Wortes ist einfach nur eine Vereinfachung der üblichen Syntax INSERT INTO... Im Weiteren unterscheiden sich die Befehlsvarianten nun doch deutlich. Die bekannteste und vermutlich am häufigsten genutzte Variante lautet wie folgt: INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] tbl_name [(col_name,...)] VALUES (expression,...),(...),...
Hiermit lassen sich ein oder mehrere Datensätze einfügen. col_name,... ist eine Liste von Spalten, für die man im Folgenden Werte angeben möchte. expression,... ist dann die Liste der Werte für einen Datensatz in der Spaltenreihenfolge, die vorher angegeben wurde. Fehlt die Angabe der betroffenen Spalten, muss man für alle Spalten der Tabelle einen Wert angeben. Die Reihenfolge muss zudem auch bekannt sein (und kann notfalls vorher mit DESCRIBE ermittelt werden). Empfehlenswerter ist allerdings, immer die Liste der Spalten mit anzugeben, um vor einer eventuellen Änderung der Spaltendefinitionen einigermaßen gefeit zu sein. Beispiele für diese Syntax sind weiter oben bereits aufgeführt.
Sandini Bib
Eine zweite, ähnliche Variante sieht so aus: INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] tbl_name SET col_name = expression, col_name = expression, ...
Diese Syntax lehnt sich stärker an die des Befehls UPDATE an, ist allerdings im SQL-Umfeld recht ungewöhnlich. Die Nähe zur UPDATE-Syntax ist in manchen Situationen natürlich durchaus hilfreich. Ein Beispiel für diese Syntax wäre: INSERT INTO raum SET raum_name = 'EE.01', etage = 'E', personen = 8;
Die letzte der drei Varianten ist sehr hilfreich, wenn man zum Beispiel Daten aus einer Tabelle in eine andere übertragen will: INSERT [LOW_PRIORITY | DELAYED] [IGNORE] [INTO] tbl_name [(col_name,...)] SELECT ...
Man nimmt als Eingabewerte also die Ausgabe einer Abfrage mit SELECT. Dadurch lassen sich beispielsweise alte Werte aus einer Tabelle in eine Archiv-Tabelle kopieren, um sie danach aus der Haupttabelle entfernen und diese damit schneller machen zu können. Dies ist für die Raumtabelle aufgrund ihrer Größe natürlich nicht sehr sinnvoll, daher hier ein Beispiel mit Tabellen, die nicht zur Musikschule gehören: INSERT IGNORE INTO bestellung_archiv (bestell_id, bestell_datum, kunden_id, liefer_datum, gesamt_preis, bezahlt) SELECT bestell_id, bestell_datum, kunden_id, liefer_datum, gesamt_preis, bezahlt FROM bestellung WHERE YEAR(bestell_datum) < YEAR(NOW()) - 1 AND bezahlt = 1;
Hier werden alle Bestellungen (aus der Tabelle bestellung) in eine Archivierungstabelle bestellung_archiv kopiert, die aus dem vorletzten Kalenderjahr oder davor sind (zu Funktionen in der WHERE-Klausel kommen wir später) und komplett bezahlt wurden (bezahlt = 1). IGNORE wurde mit aufgenommen, um Fehler bei einer eventuellen wiederholten Ausführung zu unterbinden. Nachdem dieser Befehl erfolgreich ausgeführt wurde, können diese Daten aus der Tabelle bestellung gelöscht werden. Das obige Beispiel sieht recht komplex aus. Das liegt aber nur daran, dass auf eine sichere Ausführung geachtet wurde (IGNORE, Spaltenliste) und die Formatierung wenigstens ein wenig Übersichtlichkeit verschaffen soll. Wenn man nur mal eben eine Kopie einer Tabelle benötigt (sei
'DWHQ
Sandini Bib
es, um eine Sicherung zu haben, falls man bei den kommenden Aktionen Unfug anstellt, oder um den Zustand zu einem bestimmten Zeitpunkt für einen späteren Vergleich zu haben), kann solch ein Befehl auch so aussehen: INSERT INTO backuptab SELECT * FROM tab;
Soviel zum Einfügen von Daten. Wie Sie sehen, ist dies nicht sonderlich schwierig. Damit wollen wir zum nächsten Befehl kommen: SELECT. Dabei dürfte es sich um den am häufigsten verwendeten SQL-Befehl handeln. Da er zudem auch sehr viele Möglichkeiten bietet, wird der folgende Abschnitt recht lang werden. Aber keine Bange, auch da kommt man durch und es ist eigentlich alles logisch und sinnvoll.
3.4
Daten abfragen
Wir haben weiter oben in Kapitel 3.1 den Befehl SELECT schon kennen gelernt. Er bietet im Prinzip die einzige Möglichkeit, auf Daten in der Datenbank zuzugreifen. Allerdings ist er auch so universell, dass sich fast jede Information über ihn direkt ermitteln lässt. Die Syntax lautet allgemein wie folgt:
SELECT
SELECT [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] [HIGH_PRIORITY] [DISTINCT | DISTINCTROW | ALL] select_expression,... [INTO {OUTFILE | DUMPFILE} 'file_name' export_options] [FROM table_references [WHERE where_definition] [GROUP BY {unsigned_integer | col_name | formula} [ASC | DESC], ...] [HAVING where_definition] [ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] ,...] [LIMIT [offset,] rows] [PROCEDURE procedure_name] [FOR UPDATE | LOCK IN SHARE MODE]]
Man sieht, dass es tatsächlich eine ganze Menge Elemente in diesem Befehl gibt, die wir im Folgenden nach und nach aufdröseln wollen. Das Element, welches jeder SELECT-Befehl braucht, ist die select_expression, die im nächsten Abschnitt behandelt wird. In ihr findet sich die Angabe, welche Felder ausgegeben werden sollen. Danach folgt mit FROM (in Kapitel 3.4.2) die Angabe, aus welcher Tabelle (bzw. aus welchen Tabellen) die Daten gelesen werden sollen. Schließlich kann man mit WHERE (in Kapitel 3.4.3) festlegen, ob die Tabellen in geeigneter Weise gefiltert werden sollen. Um die Ausgabe wie gewünscht zu sortieren, verwendet man ORDER BY (in Kapitel 3.4.4). Even-
Sandini Bib
tuell möchte man mehrere Datensätze zu einem zusammenfassen. Dazu eignet sich die GROUP BY-Klausel (in Kapitel 3.4.5), zu der noch zum Filtern auf solchermaßen zusammengefasste Daten HAVING gehört (in Kapitel 3.4.6). Möchte man schließlich nur einen Ausschnitt der Datensätze zu Gesicht bekommen, kann LIMIT (in Kapitel 3.4.6) manchmal sehr hilfreich sein.
3.4.1 Select-Ausdruck Zunächst geht es also um die gewünschten Felder. Man gibt sie einfach in der gewünschten Reihenfolge an, durch Komma getrennt: SELECT raum_id, raum_name ...
Interessiert man sich für alle Felder der Tabelle, kann man auch einfach ein * als Feldauswahl angeben: SELECT * ...
Es ist dabei durchaus möglich, Felder mehr als einmal anzugeben oder auch * und Feldnamen zu kombinieren: SELECT raum_name, * ...
Manchmal interessiert man sich nur dafür, welche Feldinhalte vorkommen, aber nicht, wie oft diese vorhanden sind. Möchte man zum Beispiel wissen, auf welchen Etagen Räume vorhanden sind, könnte man zunächst eingeben: SELECT etage FROM raum;
Das Ergebnis ist dann: +-------+ | etage | +-------+ | 5 | | 2 | | 4 | | 5 | | 5 | +-------+
Leider taucht die fünfte Etage mehrfach auf. Dies kann man vermeiden, indem man vor die Feldliste ein DISTINCT oder DISTINCTROW setzt. Dadurch erreicht man, dass alle Werte (oder Wertkombinationen bei mehreren Feldern) nur einmal aufgeführt werden. Mit DISTINCT sieht das Beispiel dann so aus, wie wir es uns wünschen:
'DWHQ
Sandini Bib
mysql> SELECT DISTINCT etage FROM raum; +-------+ | etage | +-------+ | 5 | | 2 | | 4 | +-------+
Das Gegenteil von DISTINCT bewirkt ALL, d.h. es werden alle Werte ausgegeben, auch wenn sie mehrfach vorkommen. Dies ist die „normale“ Vorgehensweise und ALL ist im Allgemeinen nicht notwendig. Es ist auch möglich, in der Feldliste Aliase zu vergeben, die dann als Spaltenüberschrift ausgegeben werden und die auch in den weiteren Elementen von SELECT genutzt werden können. Dazu gibt man nach dem eigentlichen Spaltennamen den gewünschten Alias an. Auf Wunsch kann man auch noch das Wort AS dazwischen schreiben. So lässt sich die Spalte raum_name in der Ausgabe (und den weiteren Elementen dieses Befehls) umbenennen in Raum: mysql> SELECT raum_name AS Raum, etage FROM raum; +-------+-------+ | Raum | etage | +-------+-------+ | A5.33 | 5 | | B2.02 | 2 | | C4.16 | 4 | | A5.21 | 5 | | A5.23 | 5 | +-------+-------+
Wenn man mit mehreren Tabellen in einer Abfrage arbeitet (siehe dazu Kapitel 3.8.2 über Joins), kann es vorkommen, dass Felder in mehreren Tabellen vorkommen. Dann ist es notwendig, festzulegen, welches Feld aus welcher Tabelle man eigentlich meint. Dies geschieht, indem man den Tabellennamen (oder dessen Alias, denn auch Tabellen können einen Aliasnamen haben, siehe Kapitel 3.4.2), gefolgt von einem Punkt vor den Spaltennamen setzt: SELECT raum.raum_id ...
Muss man auch noch die Datenbank mit angeben, kommt auch diese, durch einen Punkt getrennt, vor den Tabellennamen: SELECT musikschule.raum.raum_id ...
Sandini Bib
Man kann schon mit diesem ersten Element des SELECT-Befehls sinnvolle Aufgaben durchführen, ohne noch Tabellen anzugeben, die genutzt werden sollen. So lässt sich zum Beispiel SELECT als Taschenrechner nutzen: mysql> SELECT 1 + 2 * 3; +-----------+ | 1 + 2 * 3 | +-----------+ | 7 | +-----------+
Dieses Beispiel ist zwar nicht allzu sinnvoll (so schwer ist die Aufgabe dann doch nicht), zeigt aber, dass man auch Berechnungen mit den Feldern anstellen kann. Dies geht natürlich auch mit Tabellenfeldern: mysql> SELECT *, personen * 2 / 3 AS bequem, personen * 3 / 2 AS eng -> FROM raum; +---------+-----------+-------+----------+--------+-------+ | raum_id | raum_name | etage | personen | bequem | eng | +---------+-----------+-------+----------+--------+-------+ | 1 | A5.33 | 5 | 4 | 2.67 | 6.00 | | 2 | B2.02 | 2 | 20 | 13.33 | 30.00 | | 3 | C4.16 | 4 | 11 | 7.33 | 16.50 | | 4 | A5.21 | 5 | 4 | 2.67 | 6.00 | | 5 | A5.23 | 5 | 4 | 2.67 | 6.00 | +---------+-----------+-------+----------+--------+-------+
Dazu gibt es noch viele Funktionen, die man nutzen kann und die weiter unten in Kapitel 3.6.2 beschrieben sind. Einige davon machen auch (oder teilweise nur) dann Sinn, wenn man sie ohne Tabellenangabe aufruft: SELECT USER();
gibt den aktuellen Benutzer aus: +------------------+ | USER() | +------------------+ | demmig@localhost | +------------------+
Eine andere Funktion ist NOW(), die das aktuelle Datum und die Uhrzeit anzeigt: mysql> SELECT NOW(); +---------------------+ | NOW() | +---------------------+ | 2002-09-12 22:26:27 | +---------------------+
'DWHQ
Sandini Bib
3.4.2 FROM In der FROM-Klausel des SELECT-Befehls wird angegeben, aus welchen Tabellen die Daten ausgelesen und gefiltert werden sollen. Wir werden Abfragen über mehrere Tabellen erst weiter unten (in Kapitel 3.8.2) besprechen, daher soll hier auch nicht die komplette Syntax von table_references aufgeführt werden. Greift man nur auf eine Tabelle zu, gibt man sie hier an:
FROM
SELECT * FROM raum;
Genau wie bei den Spalten kann man auch bei Tabellen angeben, aus welcher Datenbank sie genommen werden sollen (falls man auf Tabellen anderer Datenbanken zugreifen möchte und darf): SELECT * FROM musikschule.raum;
Und auch wie bei den Spalten lassen sich Aliase für eine Tabelle definieren: SELECT * FROM raum AS zimmer;
Die Tabelle raum kann in diesem Befehl nun auch über den Alias zimmer angesprochen werden. Auch bei Tabellenaliasen ist AS optional. Bei solchen einfachen SELECT-Befehlen ist ein Tabellenalias nicht unbedingt sinnvoll oder nützlich, aber wenn es um Verknüpfungen zwischen mehreren Tabellen – womöglich noch in unterschiedlichen Datenbanken – geht, ist ein Alias häufig sehr nützlich und manchmal auch schlicht notwendig (zum Beispiel bei einem sogenannten Self-Join, d.h. einer mehrfachen Verwendung einer Tabelle in einer Abfrage).
3.4.3 WHERE In der WHERE-Klausel werden Filterkriterien angegeben. Dabei wird man vor allem Feldinhalte vergleichen:
WHERE
SELECT * FROM raum WHERE etage = '5';
Mit diesem Befehl werden alle Räume ausgegeben, die in der fünften Etage liegen. Als Vergleichsoperatoren gibt es = (gleich), < (kleiner als), > (größer als), = (größer oder gleich), (ungleich) und != (ungleich). Ist die Bedingung erfüllt, wird 1 (TRUE) zurückgeliefert, ist sie es nicht, wird 0 (FALSE) zurückgegeben. Normale Zeichenkettenfelder unterscheiden beim Vergleichen von Werten keine Groß- und Kleinschreibung. So ist der Ausdruck 'A' = 'a' wahr. Möchte man dies vermeiden, muss man die Stringfelder vom Typ [VAR]CHAR BINARY wählen.
Sandini Bib
Aufgrund der oben schon erwähnten dreiwertigen Logik mit NULL wird immer dann NULL zurückgegeben, wenn einer der beiden zu vergleichenden Werte selbst NULL ist. Um NULL-Werte zu finden, nutzt man eben nicht = NULL (weil das schließlich NULL zurückliefert), sondern IS NULL. Dies ergibt TRUE (1), wenn der entsprechende Vergleichswert NULL ist, ansonsten FALSE (0). Vergleicht man zwei verschiedene Spalten per = miteinander, kann es passieren, dass beide NULL sind. Man könnte annehmen, dass der Vergleich dann TRUE zurückgibt, da ja links und rechts das Gleiche steht. Da NULL aber besonders ist, liefert auch NULL = NULL den Wert NULL zurück. Für diese Fälle gibt es noch einen weiteren Vergleichsoperator: (NULL-sicheres gleich). Dabei ist es nicht mehr relevant, ob links oder rechts ein NULL steht, es wird tatsächlich so verglichen, als ob NULL ein „normaler“ Wert wäre. In Tabelle 3.1 ist das unterschiedliche Verhalten von = und aufgeführt. A
B
A=B
A B
0
0
1
1
0
1
0
0
0
NULL
NULL
0
1
0
0
0
1
1
1
1
1
NULL
NULL
0
NULL
0
NULL
0
NULL
1
NULL
0
NULL
NULL
NULL
1
Tabelle 3.1: Unterschiedliches Verhalten der Vergleichsoperatoren = und
Es lassen sich auch mehrere Vergleiche kombinieren. Dazu verwendet man AND und OR. Bei AND müssen die linke und die rechte Bedingung erfüllt sein, damit der gesamte Ausdruck wahr ist: mysql> SELECT * FROM raum WHERE etage = '5' AND raum_id SELECT * FROM raum WHERE raum_name LIKE 'A%'; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 4 | A5.21 | 5 | 6 | | 5 | A5.23 | 5 | 6 | +---------+-----------+-------+----------+
Der Vorteil von LIKE ist, dass man Platzhalter im Vergleichsstring nutzen kann. Gibt man diese nicht mit an, entspricht LIKE dem Vergleich mit =. Als Platzhalter kann man das Prozentzeichen (%) und den Unterstrich (_) nutzen. Das Prozentzeichen steht als Ersatz für beliebig viele Zeichen (so wie der Stern [*] bei Dateioperationen unter dem Betriebssystem), der Unterstrich für ein einzelnes Zeichen (analog zum Fragezeichen [?] auf Betriebssystem-Ebene). Man kann die Platzhalter an beliebiger Stelle im Vergleichsstring nutzen. Alle Räume mit einer „3“ am Ende findet man mit mysql> SELECT * FROM raum WHERE raum_name LIKE '%3'; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 5 | A5.23 | 5 | 6 | +---------+-----------+-------+----------+
Allerdings ist zu beachten, dass eventuelle Indizes zum Beschleunigen der Abfrage nur dann in Aktion treten können, wenn ein Platzhalter nicht am Anfang steht. Weitere Vergleichsoperatoren werden in Kapitel 3.7.2 aufgeführt.
3.4.4 ORDER BY Wie schon weiter oben geschrieben, ist die Ausgabe der gefundenen Daten mehr oder weniger zufällig. Meist möchte man aber die Datensätze in einer gewissen Reihenfolge sehen. Dazu dient die ORDER BY-Klausel. Ihr folgt eine Liste von Spalten, nach denen die gefundenen Datensätze ausgegeben werden sollen. Zunächst wird nach der ersten Spalte sortiert. Falls diese für mehrere Sätze identisch ist, werden diese nach der zweiten Spalte sortiert usw. Ein Beispiel dafür ist:
ORDER BY
Sandini Bib
mysql> SELECT * FROM raum ORDER BY etage, raum_name; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | | 4 | A5.21 | 5 | 4 | | 5 | A5.23 | 5 | 4 | | 1 | A5.33 | 5 | 4 | +---------+-----------+-------+----------+
Man kann durchaus auch nach Spalten sortieren, die nicht mit ausgegeben werden, allerdings wird dies nicht allzu häufig Sinn machen. (Es macht zum Beispiel dann Sinn, wenn man eine Auswahlliste anbieten möchte, die anders sortiert ist als nach den Auswahlwerten. So kann man mit einer Sortierspalte dafür sorgen, dass die wichtigsten Einträge ganz oben landen.) Die normale Sortierung sorgt dafür, dass „aufwärts“ sortiert wird. Sie kann auch durch Angabe von ASC erreicht werden. Für eine umgekehrte Sortierung muss man dem Spaltennamen den Begriff DESC folgen lassen: mysql> SELECT * FROM raum ORDER BY personen DESC, raum_id ASC; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | | 1 | A5.33 | 5 | 4 | | 4 | A5.21 | 5 | 4 | | 5 | A5.23 | 5 | 4 | +---------+-----------+-------+----------+
Bei ORDER BY lassen sich Aliase oder die „normalen“ Tabellen- und Feldnamen nutzen, zudem sind auch hier die MySQL-Funktionen verfügbar. Weiterhin kann man anstelle von Spalten- oder Aliasnamen auch die Position der Spalte in der Abfrage als ganzzahligen Wert angeben: SELECT * FROM raum ORDER BY 4 DESC, 1 ASC;
3.4.5 Aggregatsfunktionen und GROUP BY GROUP BY und der nächste Abschnitt, HAVING, gehören direkt zusammen.
GROUP BY
Sie dienen dazu, Datensätze zusammenzufassen. Wenn man zum Beispiel wissen möchte, wie viele Räume es in den verschiedenen Etagen gibt und wie viele Personen insgesamt darin Platz finden, kann man sich zunächst die vorhandenen Etagen ausgeben lassen:
'DWHQ
Sandini Bib
mysql> SELECT DISTINCT etage FROM raum ORDER BY etage; +-------+ | etage | +-------+ | 2 | | 4 | | 5 | +-------+
Nun kann man sich für jede Etage die vorhandenen Sätze ausgeben lassen, um die Daten selber zusammenzuzählen (hier am Beispiel der fünften Etage): mysql> SELECT personen FROM raum WHERE etage = '5'; +----------+ | personen | +----------+ | 4 | | 4 | | 4 | +----------+
Das macht zusammen zwölf Personen, die in der fünften Etage unterrichtet werden können. Es gibt aber einen deutlich einfacheren Weg: Man nutzt dazu sogenannte Aggregatsfunktionen. Diese fassen die Informationen mehrer Datensätze zusammen. Die einfachste ist COUNT. Mit dieser Funktion kann man sich angeben lassen, wie viele Werte es in einer Spalte gibt. Die bekannteste Variante davon ist COUNT(*), die einfach die Zahl der betroffenen Datensätze zählt: mysql> SELECT COUNT(*) FROM raum WHERE etage = '5'; +----------+ | COUNT(*) | +----------+ | 3 | +----------+
Diese Version von COUNT(*) wird sehr häufig genutzt, um die Anzahl der Datensätze in einer Tabelle zu ermitteln. MySQL ist sogar dahingehend optimiert, dass bei Aufruf von COUNT(*) für eine Tabelle ohne eine WHEREKlausel nicht jeder Datensatz in der Tabelle gezählt wird, sondern einfach (und viel schneller) diese Zahl aus den internen Verwaltungstabellen ermittelt wird. Man kann auch die Werte in einer Spalte zählen lassen:
Sandini Bib
mysql> SELECT COUNT(etage) FROM raum; +--------------+ | COUNT(etage) | +--------------+ | 5 | +--------------+
Das macht meist nicht sehr viel Sinn, da sich dieser Wert auch über ein COUNT(*) ermitteln ließe. Allerdings gibt es einen feinen Unterschied: COUNT(*) zählt alle Datensätze, COUNT(feld) nur die, die nicht NULL sind. Mit COUNT(DISTINCT feld) werden übrigens nur die unterschiedlichen Werte gezählt: mysql> SELECT COUNT(DISTINCT etage) FROM raum; +-----------------------+ | COUNT(DISTINCT etage) | +-----------------------+ | 3 | +-----------------------+
Es gibt also (wie wir weiter oben schon per Hand zählen konnten) drei verschiedene Etagen. Weitere Aggregatsfunktionen sind SUM (Summe der Felder), MIN und MAX (minimaler und maximaler Wert der Felder) und noch ein paar weitere Funktionen. Diese werden im Abschnitt über die Funktionen noch besprochen. Kommen wir aber nun zurück zu unserer Fragestellung am Anfang dieses Abschnitts: Wir könnten jetzt natürlich für jede Etage die Anzahl der Datensätze (entsprechend der Anzahl der Räume) und die Summe der Personen ermitteln, hier am Beispiel für die fünfte Etage: mysql> SELECT COUNT(*), SUM(personen) FROM raum WHERE etage = '5'; +----------+---------------+ | COUNT(*) | SUM(personen) | +----------+---------------+ | 3 | 12 | +----------+---------------+
Es gibt aber auch die Möglichkeit, die Datensätze von der Datenbank selber zusammenfassen zu lassen. Dazu teilen wir dem SELECT-Befehl im Abschnitt GROUP BY mit, nach welchen Spalten zusammengefasst werden soll: mysql> SELECT etage, COUNT(*), SUM(personen) FROM raum GROUP BY etage; +-------+----------+---------------+ | etage | COUNT(*) | SUM(personen) | +-------+----------+---------------+ | 2 | 1 | 20 | | 4 | 1 | 11 | | 5 | 3 | 12 | +-------+----------+---------------+
'DWHQ
Sandini Bib
Man erhält so mit einer Abfrage die Ergebnisse für alle Etagen. Die aggregierten Sätze werden automatisch nach den Gruppierungsfeldern sortiert. Die Sortierreihenfolge lässt sich auch hier mit DESC umdrehen. Die weiter oben aufgeführten Aggregatsfunktionen arbeiten auch – wie wir gesehen haben – ohne Probleme, wenn kein GROUP BY angegeben wurde. Dann werden einfach alle Datensätze als eine Gruppe angenommen. Möchte man das Ergebnis noch besonders sortiert bekommen, kann man zudem ein ORDER BY verwenden. Man muss allerdings beachten, dass dieses immer erst nach einem GROUP BY (und dem eventuell genutzten HAVING) angegeben werden darf. Das folgende Beispiel nutzt zugleich Aliase, um die Spalten etwas „sprechender“ zu benennen: mysql> SELECT etage, COUNT(*) AS zimmer, SUM(personen) AS platz -> FROM raum GROUP BY etage ORDER BY platz DESC; +-------+--------+-------+ | etage | zimmer | platz | +-------+--------+-------+ | 2 | 1 | 20 | | 5 | 3 | 12 | | 4 | 1 | 11 | +-------+--------+-------+
3.4.6 HAVING Im vorigen Abschnitt haben wir quasi neue Datensätze erzeugt, indem die vorhandenen aggregiert wurden. Möchte man nun auch aufgrund der neu berechneten Spalten filtern, kann man kein WHERE verwenden. Stattdessen gibt es HAVING, welches analog zum WHERE auf alle Spalten einer solchermaßen zusammengefassten Ausgabe angewandt werden kann. Wollen wir zum Beispiel nur die Etagen ausgegeben haben, die mindestens 12 Personen Platz bieten, kann man die folgende Abfrage starten:
HAVING
mysql> SELECT etage, COUNT(*) AS zimmer, SUM(personen) AS platz -> FROM raum GROUP BY etage HAVING platz >= 12 -> ORDER BY platz DESC; +-------+--------+-------+ | etage | zimmer | platz | +-------+--------+-------+ | 2 | 1 | 20 | | 5 | 3 | 12 | +-------+--------+-------+ HAVING kann im Prinzip auch auf die „normalen“ Spalten (wie hier etage)
angewandt werden. Dies ist allerdings nicht empfehlenswert, da dann
Sandini Bib
keinerlei Optimierung greifen kann. Wann immer möglich, sollte WHERE verwendet werden. Ein Beispiel wäre eine Ausgabe nur für die Etagen, die höher als die dritte liegen: mysql> SELECT etage, COUNT(*) AS zimmer, SUM(personen) AS platz -> FROM raum WHERE etage > '3' GROUP BY etage HAVING platz >= 12; +-------+--------+-------+ | etage | zimmer | platz | +-------+--------+-------+ | 5 | 3 | 12 | +-------+--------+-------+
3.4.7 LIMIT Mit diesem Element des SELECT-Befehls hat man die Möglichkeit, Datensätze aufgrund ihrer Position in der Ausgabe wegzufiltern. Man kann eine oder zwei (durch Komma getrennte) Ganzzahlen angeben. Bei zwei Zahlen steht die erste Zahl für die Position des ersten auszugebenden Datensatzes (beginnend bei 0), die zweite für die Anzahl der Datensätze, die auszugeben sind. Fehlt der erste Wert, wird von vorne begonnen. Das folgende Beispiel liefert nur die Datensätze an Position 2-3:
LIMIT
mysql> SELECT * FROM raum LIMIT 1,2; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | +---------+-----------+-------+----------+
Um nur die ersten drei Sätze zu erhalten, reicht dies: mysql> SELECT * FROM raum LIMIT 3; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | +---------+-----------+-------+----------+
Mit LIMIT kann man leicht nur eine „Probemenge“ ausgeben lassen. Es wird auch gerne genutzt, um zum Beispiel HTML-Seiten, die mit Daten aus der MySQL-Datenbank gefüllt werden, in ihrer Länge zu begrenzen. Man kann dann pro Seite nur 20, 50, 100, … Sätze ausgeben lassen und erst beim Blättern auf die nächste Seite die folgenden ausgeben:
'DWHQ
Sandini Bib
SELECT ... FROM ... LIMIT 0,50; SELECT ... FROM ... LIMIT 50,50; SELECT ... FROM ... LIMIT 100,50; ...
Eine andere Verwendungsmöglichkeit ist das Beschränken auf einen Satz, wenn die Möglichkeit vorhanden ist, dass mehr Sätze existieren, dies aber nicht so wichtig ist oder die entscheidenden Werte sowieso gleich sind. Man umgeht damit häufiger komplexe Abfragen, die mit anderen Datenbank-Systemen notwendig werden (allerdings bei MySQL teilweise auch nicht funktionieren würden).
3.4.8 Weitere Elemente und Optionen Es gibt noch einige Elemente von SELECT, die der Vollständigkeit halber hier aufgeführt werden sollen: • INTO {OUTFILE | DUMPFILE} 'file_name' export_options Mit diesem Element kann man die Daten in eine Datei ausgeben lassen. Die Datei wird auf dem Server angelegt und darf noch nicht existieren. Damit wird verhindert, dass bestehende wichtige Dateien (wie zum Beispiel /etc/passwd unter Unix) überschrieben werden können. Dabei kann man in den export_options festlegen, wie die Datensätze ausgegeben werden sollen. Folgende Optionen sind möglich: – FIELDS [TERMINATED BY 'fielddiv'] [[OPTIONALLY] ENCLOSED BY 'enc_char'] [ESCAPED BY 'esc_char']
Alle drei Elemente (TERMINATED BY, ENCLOSED BY und ESCAPED BY) sind optional, lässt man sie alle drei weg, darf aber auch FIELDS nicht angegeben werden. Mit TERMINATED BY kann man festlegen, wie die Werte untereinander getrennt werden sollen. Standardmäßig wird '\t' genutzt, d.h. die Felder sind durch ein Tabulatorzeichen getrennt. ENCLOSED BY gibt an, womit die Werte umschlossen sein sollen. Fehlt diese Angabe, werden die Felder nicht umschlossen. Nutzt man zusätzlich OPTIONALLY, werden nur Stringfelder mit diesem Zeichen umschlossen. Mit ESCAPED BY lässt sich festlegen, wie Sonderzeichen (Tabulator, Zeilenumbruch, …) in Werten behandelt werden sollen. Standard hierfür ist der Backslash (der allerdings selber mit dem Fluchtzeichen \ maskiert werden muss). Gibt man also nichts an, ist dies genauso, also ob man FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' angegeben hätte. – LINES TERMINATED BY 'linedev' Mit dieser Option kann man festlegen, wie die einzelnen Datensätze getrennt werden sollen. Standard ist hier '\n', d.h. jeder Datensatz steht in einer eigenen Zeile.
Sandini Bib
Normalerweise wird man INTO OUTFILE nutzen, denn dann werden alle Daten, die sonst ausgegeben würden, in die Datei geschrieben. Verwendet man stattdessen INTO DUMPFILE, wird nur ein Datensatz (die Ergebnismenge darf auch nicht größer sein, notfalls muss man mit LIMIT 1 arbeiten) ohne weitere Begrenzer oder Fluchtzeichen in die Datei geschrieben. Dies kann nützlich sein, wenn man den Inhalt eines binären Blob-Feldes in einer Datei speichern möchte (zum Beispiel Bilder oder Klänge). • PROCEDURE procedure_name Mit Hilfe dieses Elements kann man MySQL erweitern. Es lassen sich externe Prozeduren aufrufen, die die Daten des SELECT-Befehls verarbeiten und selber wiederum eine Ergebnismenge zurückgeben. • FOR UPDATE | LOCK IN SHARE MODE Gibt man FOR UPDATE mit an, werden auf die entsprechenden Datensätze in der Tabelle auch Schreib-Locks gesetzt (was sonst nur bei INSERT und UPDATE geschieht), sofern der Tabellentyp dies unterstützt. Mit LOCK IN SHARE MODE kann man bei Tabellen des Typs InnoDB sicher gehen, dass die eben gelesenen Daten auch dann noch da sind, wenn man danach Datenänderungen vornimmt. Dies ist im Zusammenhang mit Transaktionen sinnvoll. • STRAIGHT_JOIN Diese Option ist nur bei mehreren genutzten Tabellen sinnvoll. Sie sorgt dafür, dass die Verknüpfung zwischen den Tabellen anders vorgenommen wird als vom MySQL-Server geplant. • SQL_SMALL_RESULT, SQL_BIG_RESULT Diese beiden Parameter dienen dazu, den MySQL-Server bei größeren Aggregationsaktionen in Zusammenhang mit GROUP BY oder DISTINCT zu unterstützen. SQL_SMALL_RESULT teilt dem Server mit, dass es sich um nicht allzu viele Daten handeln wird und der Server sie möglichst in einer schnellen temporären Tabelle zwischenspeichern soll, anstatt sie aufwändig zu sortieren. Diese Option ist in den aktuellen Versionen nur noch sehr selten notwendig. SQL_BIG_RESULT ist das genaue Gegenteil davon. Es wird sich voraussichtlich um viele Daten handeln, so dass der MySQL-Server für diesen Befehl gleich auf temporäre Tabellen umsteigen sollte, die auf der Festplatte zwischengespeichert sind. • SQL_BUFFER_RESULT Mit dieser Option wird der Server angewiesen, die Daten aus der eigentlichen Tabelle möglichst schnell in einer temporären Tabelle zwischenzuspeichern, um die Tabelle selber wieder freigeben zu kön-
'DWHQ
Sandini Bib
nen. Dies kann dann nützlich sein, wenn es im Anschluss lange dauert, die Daten an den Client zu übertragen (zum Beispiel wegen einer langsamen Leitung), der Server aber viele Zugriffe verzeichnet. • HIGH_PRIORITY Durch diese Option wird dafür gesorgt, dass der SELECT-Befehl auch dann bevorzugt behandelt wird, wenn eigentlich ein UPDATE-Befehl schon darauf wartet, auf die Tabelle zugreifen zu können (und er im Normalfall auch eher dran käme). Diese Option sollte nur dann verwendet werden, wenn die Abfragen sehr schnell sind und möglichst hintereinander durchgeführt werden sollen.
3.5
Daten ändern
Nach dem langen Abschnitt über das Selektieren von Daten geht es jetzt wieder etwas ruhiger zur Sache. Es gibt zwei Möglichkeiten, Daten zu ändern: zum einen den bekannten Befehl UPDATE, zum anderen den Befehl REPLACE, mit dem Daten angelegt oder geändert werden können.
3.5.1 UPDATE Hier die komplette Syntax des UPDATE-Befehls:
UPDATE
UPDATE [LOW_PRIORITY] [IGNORE] tbl_name SET col_name1=expr1, [col_name2=expr2, ...] [WHERE where_definition] [LIMIT number] tbl_name ist natürlich der Name der Tabelle, in der Daten aktualisiert werden sollen. col_name1 gibt die erste Spalte an, die mit expr1 geändert wer-
den soll. Durch Komma getrennt kann man beliebig viele weitere Kombinationen aus Spaltenname und Wert angeben. In der WHERE-Klausel wird festgelegt, welche Datensätze geändert werden sollen. Fehlt diese Angabe, werden alle Datensätze aktualisiert. Weiter oben haben wir ja schon Beispiele für das Aktualisieren von Daten gesehen. Hier noch mal ein einfaches: mysql> SELECT * FROM raum WHERE raum_id = 5; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 5 | A5.23 | 5 | 4 | +---------+-----------+-------+----------+ 1 row in set (0.01 sec) mysql> UPDATE raum SET personen = 6 WHERE raum_id = 5;
Sandini Bib
Query OK, 1 row affected (0.14 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT * FROM raum WHERE raum_id = 5; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 5 | A5.23 | 5 | 6 | +---------+-----------+-------+----------+ 1 row in set (0.00 sec)
Man kann auch die bestehenden Daten als Grundlage nehmen. So lässt sich zum Beispiel die Kapazität von Raum A5.21 um 50% erhöhen: mysql> SELECT * FROM raum WHERE raum_name = 'A5.21'; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 4 | A5.21 | 5 | 4 | +---------+-----------+-------+----------+ 1 row in set (0.00 sec) mysql> UPDATE raum SET personen = personen * 1.5 -> WHERE raum_name = 'A5.21'; Query OK, 1 row affected (0.03 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT * FROM raum WHERE raum_name = 'A5.21'; +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 4 | A5.21 | 5 | 6 | +---------+-----------+-------+----------+ 1 row in set (0.00 sec)
Es gibt noch drei weitere Optionen: • Wie schon erwähnt, kann man auch mehrere Datensätze gleichzeitig aktualisieren. Mit LIMIT lässt sich dann ähnlich wie bei SELECT (allerdings ohne Startwert) begrenzen, wie viele Sätze geändert werden sollen. • LOW_PRIORITY sorgt dafür, dass die Daten erst dann aktualisiert werden, wenn kein anderer Client mehr auf die Tabelle zugreift. • Versucht man Primärschlüssel oder Felder mit eindeutigen Indizes zu ändern, kann es passieren, dass Feldinhalte mehrfach vorkommen. Da das in diesem Fall nicht vorkommen darf, wird eine Fehlermeldung ausgegeben. Mit IGNORE wird diese Fehlermeldung unterdrückt, die entsprechenden Datensätze dann aber natürlich trotzdem nicht aktualisiert.
'DWHQ
Sandini Bib
3.5.2 REPLACE REPLACE ist eine Kombination aus INSERT und UPDATE. Neue Datensätze werden eingefügt. Ist ein Satz mit dem angegebenen Primärschlüssel schon vorhanden, wird der alte Satz gelöscht und der neue eingefügt. Die Syntax gibt es analog zu INSERT mit drei Varianten:
REPLACE
REPLACE [LOW_PRIORITY | DELAYED] [INTO] tbl_name [(col_name,...)] VALUES (expression,...),(...),... REPLACE [LOW_PRIORITY | DELAYED] [INTO] tbl_name SET col_name = expression, col_name = expression, ... REPLACE [LOW_PRIORITY | DELAYED] [INTO] tbl_name [(col_name,...)] SELECT ...
Es gibt die gleichen Optionen wie bei INSERT, nur IGNORE ist nicht vorhanden (und auch nicht sinnvoll, da identische Primärschlüssel eben nicht ignoriert, sondern gesondert behandelt werden).
3.6
Daten löschen
Auch das Löschen von Daten ist nicht so kompliziert, wie wir schon weiter oben gesehen haben. Neben DELETE gibt es noch den Befehl TRUNCATE.
3.6.1 DELETE Die vollständige Syntax des DELETE-Befehls lautet: DELETE [WHERE [ORDER [LIMIT
DELETE
[LOW_PRIORITY | QUICK] FROM table_name where_definition] BY order_by_definition] number]
Die „einfache“ Form ist klar: table_name ist der Name der Tabelle, aus der gelöscht werden soll. Die where_definition legt die betroffenen Datensätze in bekannter Art und Weise fest. Gibt man sie nicht an, wird die Tabelle komplett geleert. Dies geht besonders schnell, da nicht alle Datensätze einzeln gelöscht werden, sondern die Tabelle einfach neu aufgebaut wird (was gerade bei großen Tabellen deutlich schneller geht). Normalerweise wird bei DELETE zurückgemeldet, wie viele Datensätze gelöscht wurden. Bei DELETE FROM ... ohne WHERE geschieht dies nicht. Will man diese Zahl trotzdem erfahren, muss man eine WHERE-Klausel angeben, die immer wahr ist: DELETE FROM mytable WHERE 1>0;
Es hat allerdings den Nachteil, dass es deutlich länger dauert.
Sandini Bib
Mit LIMIT kann man wieder die maximale Anzahl der zu löschenden Sätze festlegen. In diesem Zusammenhang macht auch eine Angabe von ORDER BY Sinn: Die Sätze werden in der festgelegten Reihenfolge gelöscht. Ist dies normalerweise nicht relevant, kann eine Angabe von ORDER BY mytimestamp LIMIT 1 dafür sorgen, dass nur der älteste Datensatz gelöscht wird (wenn mytimestamp ein Feld vom Typ TIMESTAMP ist). Schließlich gibt es noch die Optionen LOW_PRIORITY und QUICK: • Mit LOW_PRIORITY wartet der Befehl so lange, bis kein anderer Client mehr auf die Tabelle zugreift. • QUICK sorgt dafür, dass der Index während des Löschvorgangs nicht aktualisiert wird, was in bestimmten Fällen für einen schnelleren Ablauf sorgen kann.
3.6.2 TRUNCATE TRUNCATE TABLE
Der Befehl TRUNCATE TABLE wurde von Oracle übernommen und hat die gleiche Funktion wie ein DELETE FROM ohne WHERE-Klausel. Die Syntax lautet: TRUNCATE TABLE table_name;
3.7
Funktionen in SQL
Es gibt in MySQL viele Funktionen, die man in SELECT, WHERE, GROUP BY, HAVING und ORDER BY nutzen kann. Allen ist gemein, dass man ihnen einen oder mehrere Werte übergibt (oder auch eine Liste von Werte, wie bei den aggregierenden Funktionen) und als Ergebnis ein einzelner Wert zurückgeliefert wird. Es handelt sich also immer um skalare Funktionen. Die Funktionen lassen sich in mehrere Gruppen unterteilen: Operatoren (die wir schon weiter oben in Kapitel 3.4.3 besprochen haben), Stringfunktionen, numerische Funktionen, Datums- und Zeitfunktionen, andere Funktionen und aggregierende Funktionen.
3.7.1 Stringfunktionen In Tabelle 3.2 sind alle Stringfunktionen aufgeführt. Funktion
Beschreibung
ASCII(str)
liefert den ASCII-Wert eines Zeichens zurück
ORD(str)
liefert den Ordinalwert eines Zeichens zurück (auch bei Multi-Byte-Zeichensätzen)
Tabelle 3.2: Stringfunktionen in MySQL
,903
Sandini Bib
Funktion
Beschreibung
CONV(n, from_base, to_base)
wandelt eine Zahl von einer Basis in eine andere um
BIN(n)
liefert die Binärdarstellung einer Zahl
OCT(n)
liefert die Oktaldarstellung einer Zahl
HEX(n)
liefert die Hexadezimaldarstellung einer Zahl
CHAR(n, ...)
liefert einen String zurück, der aus den Zeichen mit den angegebenen ASCIIWerten besteht
CONCAT(str1, str2, ...)
verbindet die angegebenen Strings zu einem neuen String
CONCAT_WS(seperator, str1, str2, ...)
verbindet die angegebenen Strings zu einer durch ein Trennzeichen unterteilten Liste
LENGTH(str) OCTET_LENGTH(str) CHAR_LENGTH(str) CHARACTER_LENGTH(str)
gibt die Länge eines Strings zurück
LOCATE(substr, str[, pos]) POSITION(substr IN str) INSTR(str, substr)
liefert die Position eines Substrings in einem String zurück
LPAD(str, len, padstr)
füllt einen String von vorne mit Zeichen auf
RPAD(str, len, padstr)
füllt einen String von hinten mit Zeichen auf
LEFT(str, len)
liefert einen Teil vom Anfang eines Strings zurück
RIGHT(str, len)
liefert einen Teil vom Ende eines Strings zurück
SUBSTR(str, pos[, len]) SUBSTR(str FROM pos [FOR len]) MID(str, pos, len)
liefert einen Teil aus einem String zurück
SUBSTR_INDEX(str, delim, count)
liefert einen Teil aus einem String zurück. Der entsprechende Teil bestimmt sich über das Vorkommen eines anderen Strings.
LTRIM(str)
entfernt führende Leerzeichen aus einem String
RTRIM(str)
entfernt Leerzeichen aus einem String
TRIM([[BOTH | LEADING | TRAILING] [remstr] FROM] str)
entfernt Zeichen vom Anfang und/oder Ende eines Strings
SOUNDEX(str)
liefert einen Wert zurück, der sich an der Aussprache eines Strings orientiert
SPACE(n)
liefert Leerzeichen zurück
Tabelle 3.2: Stringfunktionen in MySQL (Forts.)
Sandini Bib
Funktion
Beschreibung
REPLACE(str, from_str, to_str)
ersetzt das Vorkommen eines Substrings in einem String durch einen anderen Text
REPEAT(str, count)
wiederholt einen String mehrfach
REVERSE(str)
dreht einen String um
INSERT(str, pos, len, newstr)
fügt einen String in einen anderen String ein
ELT(n, str1, str2, str3, ...)
liefert abhängig von einer Zahl einen der aufgeführten Strings zurück
FIELD(str, str1, str2, str3, ...)
liefert eine Zahl in Abhängigkeit vom Vorkommen eines Strings in einer Reihe von Strings zurück
FIND_IN_SET(str, strlist)
liefert eine Zahl in Abhängigkeit vom Vorkommen eines Strings in einem Set zurück
MAKE_SET(bits, str1, str2, ...)
stellt ein Set aus einer Zahl und Strings zusammen
EXPORT_SET(bits, on, off[, seperator[, number_of_bits]])
liefert eine Reihe von Wahr/Falsch-Werte zurück
LCASE(str) LOWER(str)
liefert einen String in Kleinbuchstaben zurück
UCASE(str) UPPER(str)
liefert einen String in Großbuchstaben zurück
LOAD_FILE(file_name)
liefert den Inhalt einer Datei als String zurück
Tabelle 3.2: Stringfunktionen in MySQL (Forts.)
3.7.2 Stringvergleiche Neben den „normalen“ Vergleichen (=, >,
verschiebt die Bits einer Zahl nach rechts
~
invertiert die Bits einer Zahl
Tabelle 3.8: Bit-Operatoren
3.8
Komplexe Abfragen
Die bisherigen Abfragen in den Kapiteln 3.2 und 3.4 bezogen sich immer nur auf eine einzelne Tabelle. Der Witz an relationalen DatenbankSystemen ist aber gerade, dass man die vielen Tabellen, in denen man die Daten abgelegt hat, geschickt verknüpft. Um dies auch sinnvoll demonstrieren zu können, müssen natürlich erst einmal mehr Daten vorhanden sein, als wir bisher eingegeben haben. Damit nicht jedes Mal alle Befehle erneut abgetippt werden müssen, kann man auch Dateien mit SQL-Befehlen ausführen.
3.8.1 Intermezzo – Kommentare und SQL-Dateien Kommentare
SQL-Befehle lassen sich problemlos in einer Textdatei speichern, um diese dann in oder mit MySQL aufzurufen. Um später auch noch zu wissen, wozu die entsprechende Datei eigentlich gut war, sollte man sie natürlich mit einem möglichst sinnigen Namen versehen. Meist reicht das aber nicht, um auch nach längerer Zeit den Inhalt zu verstehen. Zusätzlich hat man daher noch die Möglichkeit, Kommentare in die Befehle einzufügen. Es gibt drei verschiedene Varianten: • Man leitet einen Kommentar mit -- ein, also zwei Minus-Zeichen hintereinander. Alles, was danach bis zu Ende der Zeile geschrieben ist, wird als Kommentar angesehen und nicht weiter beachtet. Nach den zwei Strichen muss unbedingt noch ein Leerzeichen folgen. Diese Kommentar-Variante entspricht nahezu (von der Leerzeichenpflicht abgesehen) dem ANSI-SQL-Standard. • Ein Kommentar kann ebenso mit einem # eingeleitet werden. Auch solche Kommentare gelten dann bis zum Ende der Zeile. • Schließlich kann man beliebig große Bereiche mit einem /* und */ umschließen. Der Bereich kann innerhalb einer Zeile sein (so dass der Rest der Zeile dann wieder verarbeitet wird, aber auch über mehrere Zeilen gehen.
'DWHQ
Sandini Bib
Folgende Beispiele sollen das noch einmal verdeutlichen: SELECT * FROM raum; -- Ein Kommentar, der bis zum Zeilenende reicht SELECT * FROM raum; # Noch ein Kommentar bis zum Zeilenende SELECT * /* also alles */ FROM raum; /* Ein mehrzeiliger Kommentar, in dem man einiges an Informationen unterbringen kann. */
Um die Befehle aus einer solchen Datei aufrufen zu können, kann man diese entweder in mysql ausführen lassen oder mysql direkt damit starten.
SQL-Dateien
• Befindet man sich in mysql, nutzt man den Befehl SOURCE (der kein Semikolon am Ende benötigt), gefolgt von der Angabe des Dateinamens: mysql> SOURCE kap02.sql
• Man kann auch mysql direkt die Befehle mitgeben, indem man die Standardeingabe mit < aus einer Datei befüllt: prompt> mysql < kap02.sql
Natürlich kann man vor dem < noch Parameter angeben, zum Beispiel für die Datenbank (die aber auch mit USE ... in der Datei aktiviert werden kann), den Benutzer oder das Kennwort. Möchte man unter Windows Dateinamen mit einem Pfad angeben, darf man nicht den Backslash (\) nutzen. Stattdessen muss der normale Schrägstrich (/) verwendet werden! Ich gehe davon aus, dass Sie die entsprechenden Tabellen aus Kapitel 2 alle angelegt haben. Wenn nicht, sollten Sie dies jetzt nachholen. Daten haben wir bisher nur für die Tabelle raum angelegt. Nun wollen wir auch die anderen Tabellen mit Leben füllen. Die entsprechenden Dateien finden Sie auch auf der beiliegenden CDROM als kap02.sql und kap03.sql im Verzeichnis Beispiel.
Sandini Bib
3.8.2 Joins Joins
Wie werden nun mehrere Tabellen in einer Abfrage verknüpft? Dies geschieht über sogenannte Joins. Sie legen fest, wie die Tabellen verbunden sein sollen. Die einfachste Variante ist, dass man mehrere Tabellen auf einmal angibt: mysql> SELECT * FROM leihinstrument, instrument;
Leider ist die Ausgabe dabei nicht sehr sinnvoll. In Abbildung 3.1 sieht man, dass jeder Datensatz aus der Tabelle leihinstrument mit jedem Datensatz der Tabelle instrument verknüpft wurde. +-------------------+---------------+------------+--------+--------+---------------+------------+--------------+ | leihinstrument_id | instrument_id | hersteller | typ | kosten | instrument_id | instr_name | instr_gruppe | +-------------------+---------------+------------+--------+--------+---------------+------------+--------------+ | 1 | 1 | Perl | PF-501 | 10.00 | 1 | Querflöte | Holzbläser | | 2 | 1 | Yamaha | YF-311 | 15.00 | 1 | Querflöte | Holzbläser | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 1 | Querflöte | Holzbläser | | 4 | 8 | Korg | KGX2A | 14.00 | 1 | Querflöte | Holzbläser | | 1 | 1 | Perl | PF-501 | 10.00 | 2 | Klarinette | Holzbläser | | 2 | 1 | Yamaha | YF-311 | 15.00 | 2 | Klarinette | Holzbläser | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 2 | Klarinette | Holzbläser | | 4 | 8 | Korg | KGX2A | 14.00 | 2 | Klarinette | Holzbläser | | 1 | 1 | Perl | PF-501 | 10.00 | 3 | Violine | Streicher | | 2 | 1 | Yamaha | YF-311 | 15.00 | 3 | Violine | Streicher | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 3 | Violine | Streicher | | 4 | 8 | Korg | KGX2A | 14.00 | 3 | Violine | Streicher | | 1 | 1 | Perl | PF-501 | 10.00 | 4 | Viola | Streicher | | 2 | 1 | Yamaha | YF-311 | 15.00 | 4 | Viola | Streicher | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 4 | Viola | Streicher | | 4 | 8 | Korg | KGX2A | 14.00 | 4 | Viola | Streicher | | 1 | 1 | Perl | PF-501 | 10.00 | 5 | Posaune | Blechbläser | | 2 | 1 | Yamaha | YF-311 | 15.00 | 5 | Posaune | Blechbläser | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 5 | Posaune | Blechbläser | | 4 | 8 | Korg | KGX2A | 14.00 | 5 | Posaune | Blechbläser | | 1 | 1 | Perl | PF-501 | 10.00 | 6 | Trompete | Blechbläser | | 2 | 1 | Yamaha | YF-311 | 15.00 | 6 | Trompete | Blechbläser | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 6 | Trompete | Blechbläser | | 4 | 8 | Korg | KGX2A | 14.00 | 6 | Trompete | Blechbläser | | 1 | 1 | Perl | PF-501 | 10.00 | 7 | Klavier | Tasten | | 2 | 1 | Yamaha | YF-311 | 15.00 | 7 | Klavier | Tasten | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 7 | Klavier | Tasten | | 4 | 8 | Korg | KGX2A | 14.00 | 7 | Klavier | Tasten | | 1 | 1 | Perl | PF-501 | 10.00 | 8 | Keyboard | Tasten | | 2 | 1 | Yamaha | YF-311 | 15.00 | 8 | Keyboard | Tasten | | 3 | 2 | Keilwerth | KW-123 | 12.50 | 8 | Keyboard | Tasten | | 4 | 8 | Korg | KGX2A | 14.00 | 8 | Keyboard | Tasten | +-------------------+---------------+------------+--------+--------+---------------+------------+--------------+
Abbildung 3.1: Ein Join zwischen zwei Tabellen ohne Verknüpfung
Das kann zwar in manchen Fällen Sinn machen, ist aber im Allgemeinen nicht das, was man sich wünscht. Man muss also die beiden Tabellen miteinander verknüpfen. Dies kann auf die folgende Art und Weise geschehen: mysql> SELECT * FROM leihinstrument, instrument WHERE -> leihinstrument.instrument_id = instrument.instrument_id;
Da das Ergebnis für dieses Buch immer etwas breit ist, werden wir nur die wichtigsten Felder ausgeben. Zugleich nutzen wir Aliase, um nicht jedes Mal die langen Tabellennamen schreiben zu müssen:
,903
Sandini Bib
mysql> SELECT instr_name, instr_gruppe, hersteller, typ, kosten -> FROM leihinstrument l, instrument i -> WHERE l.instrument_id = i.instrument_id; +------------+--------------+------------+--------+--------+ | instr_name | instr_gruppe | hersteller | typ | kosten | +------------+--------------+------------+--------+--------+ | Querflöte | Holzbläser | Perl | PF-501 | 10.00 | | Querflöte | Holzbläser | Yamaha | YF-311 | 15.00 | | Klarinette | Holzbläser | Keilwerth | KW-123 | 12.50 | | Keyboard | Tasten | Korg | KGX2A | 14.00 | +------------+--------------+------------+--------+--------+
Die Verknüpfung zwischen den beiden Tabellen erfolgt über die WHEREKlausel. Es werden also aus der großen Liste des ersten Versuchs all die Datensätze ausgegeben, bei denen die instrument_id für beide Tabellen identisch ist. Damit MySQL weiß, woher die entsprechenden Felder genommen werden sollen (schließlich sind ihre Namen identisch), muss man ihnen den Namen der entsprechenden Tabelle (bzw. deren Alias, falls vorhanden) mitgeben. Dies geschieht, indem man den Tabellennamen vor den Spaltennamen setzt und beide mit einem Punkt trennt. Was passiert nun, wenn wir die instrument_id mit ausgeben wollen? mysql> SELECT instrument_id, instr_name, instr_gruppe, -> hersteller, typ, kosten -> FROM leihinstrument l, instrument i -> WHERE l.instrument_id = i.instrument_id; ERROR 1052: Column: 'instrument_id' in field list is ambiguous
Man erhält einen Fehler, weil MySQL nicht weiß, welches der beiden Felder mit dem Namen instrument_id genutzt werden soll. Natürlich sind bei dieser Abfrage immer beide Felder identisch, da wir die WHEREBedingung entsprechend gewählt haben. Aber dies ist nicht zwingend der Fall, und daher muss man immer angeben, aus welcher Tabelle ein Feld genommen werden soll, wenn es in mehreren Tabellen vorkommt: mysql> SELECT l.instrument_id, instr_name, hersteller, typ, kosten -> FROM leihinstrument l, instrument i -> WHERE l.instrument_id = i.instrument_id; +---------------+------------+------------+--------+--------+ | instrument_id | instr_name | hersteller | typ | kosten | +---------------+------------+------------+--------+--------+ | 1 | Querflöte | Perl | PF-501 | 10.00 | | 1 | Querflöte | Yamaha | YF-311 | 15.00 | | 2 | Klarinette | Keilwerth | KW-123 | 12.50 | | 8 | Keyboard | Korg | KGX2A | 14.00 | +---------------+------------+------------+--------+--------+
Sandini Bib
So etwas kann man natürlich auch mit mehreren Tabellen machen: mysql> SELECT CONCAT(LEFT(vorname, 1), '. ', nachname) AS lehrer, -> instr_name AS instrum, raum_name AS raum, -> CASE wochentag WHEN 0 THEN 'Mo' WHEN 1 THEN 'Di' -> WHEN 2 THEN 'Mi' WHEN 3 THEN 'Do' WHEN 4 THEN 'Fr' -> WHEN 5 THEN 'Sa' WHEN 6 THEN 'So' END AS tag, -> CONCAT(TIME_FORMAT(uhrzeit_von, '%H:%i'), '-', -> TIME_FORMAT(uhrzeit_bis, '%H:%i')) AS zeit, -> kosten AS preis, einzel -> FROM unterricht AS u, lehrer AS l, instrument AS i, raum AS r -> WHERE u.lehrer_id = l.lehrer_id -> AND u.instrument_id = i.instrument_id -> AND u.raum_id = r.raum_id; +------------+-----------+-------+------+-------------+-------+--------+ | lehrer | instrum | raum | tag | zeit | preis | einzel | +------------+-----------+-------+------+-------------+-------+--------+ | G. Mosler | Posaune | A5.23 | Mi | 19:15-20:00 | 25.00 | 0 | | H. Knarer | Querflöte | C4.16 | Fr | 17:00-17:45 | 30.00 | 1 | | K. Hansen | Violine | B2.02 | Do | 15:35-16:20 | 20.00 | 0 | | K. Hansen | Viola | B2.02 | Do | 16:30-17:15 | 20.00 | 0 | | P. Ottbein | Klavier | A5.33 | Sa | 10:00-11:00 | 35.00 | 1 | +------------+-----------+-------+------+-------------+-------+--------+
Lassen Sie sich nicht von dem ausgesprochen langen Abschnitt für die Felder verwirren. Ich zeige hier gleich die verschiedenen Möglichkeiten, Funktionen einzusetzen. An dieser Stelle sollte eher die WHERE-Klausel von Interesse sein. In ihr werden alle angegebenen Tabellen miteinander verbunden. Der Lehrer wird über die lehrer_id hinzugeholt, das Instrument über die instrument_id und der Raum über die raum_id. Man kann die Verknüpfung zwischen Tabellen auch direkt über Joins abbilden. Dazu gibt es verschiedene Syntax-Varianten: • table_reference, table_reference Dies ist die oben schon aufgeführte Variante (table_reference steht hier und in den folgenden Beispielen für eine Tabelle und einen eventuellen Alias), bei der man die Verknüpfung zwischen den Tabellen in der WHERE-Bedingung angeben muss. Tut man dies nicht, erhält man eine Ergebnismenge, in der alle möglichen Verbindungen aufgeführt sind. Dies ist ein sogenannter Cross Join. Mit vorhandener Vergleichsangabe (die hier das gewünschte Ergebnis brachte), wird es zu einem Inner Join, bei dem nur Werte ausgegeben werden, wenn es für sie Datensätze in beiden beteiligten Tabellen gibt. Diesen Inner Join kann man auch eleganter mit der folgenden Syntax festlegen:
,903
Sandini Bib
• table_reference INNER JOIN table_reference join_condition
INNER JOIN
Die Tabellen werden explizit als Inner Join verknüpft. In der join_condition wird festgelegt, wie die Tabellen verbunden werden sollen. Am bekanntesten dafür ist die Verwendung von ON condition, wobei letztere dem Vergleich in der WHERE-Klausel entspricht: SELECT ... FROM leihinstrument AS l INNER JOIN instrument AS i ON l.instrument_id = i.instrument_id
Damit erhält man das gleiche Ergebnis wie weiter oben mit WHERE. Man sollte auch beachten, dass in ON ... keine anderen Filterkriterien eingebunden werden sollten als die, die zum Verknüpfen der Tabellen notwendig sind. Alle weiteren Einschränkungen gehören in die WHERE-Klausel. Eine andere Variante für die join_condition ist USING(column1, column2, ...). Die angegebenen Spalten müssen in beiden Tabellen vorhanden sein. Sie werden dann alle auf Gleichheit geprüft. Um das selbe Ergebnis wie oben zu erhalten, lautet der Befehl dann: SELECT ... FROM leihinstrument AS l INNER JOIN instrument AS i USING (instrument_id)
• table_reference [CROSS] JOIN table_reference
CROSS JOIN
Benötigt man tatsächlich einmal einen Cross Join, kann man diese Syntax nutzen. Sie ist identisch zur Nutzung des Komma zwischen zwei Tabellen. Man kann allerdings auch hier in der WHERE-Klausel doch wieder dafür sorgen, dass nur passende Datensätze aus den beiden Tabellen angezeigt werden: SELECT ... FROM leihinstrument l JOIN instrument i WHERE l.instrument_id = i.instrument_id
• table_reference STRAIGHT_JOIN table_reference
STRAIGHT_JOIN
STRAIGHT_JOIN ist identisch zu [CROSS] JOIN, nur dass hier der Optimierer von MySQL anders vorgeht und die erste Tabelle komplett einliest, bevor er sie mit der zweiten verknüpft. Das ist in den meisten Fällen zwar eher unvorteilhaft für die Geschwindigkeit, aber es gibt durchaus Situationen, in denen ein Straight Join schneller ist als ein normaler Join.
Sandini Bib
OUTER JOIN
• table_reference {LEFT | RIGHT} [OUTER] JOIN table_reference join_condition
Kommen wir nun zu den Outer Joins. Bisher haben wir immer nur Datensätze ausgegeben, die mit Daten aus beiden beteiligten Tabellen befüllt waren. Bei den Outer Joins kann man auch Sätze aus einer Tabelle ausgeben, die kein passendes Äquivalent in der anderen Tabelle haben. Nehmen wir einmal an, wir wollen für eine Übersicht alle Räume mit ihren Belegungsdaten ausgeben lassen. Als ersten Versuch würden wir vielleicht das Folgende nehmen: mysql> SELECT raum_name, wochentag, uhrzeit_von, uhrzeit_bis -> FROM raum INNER JOIN unterricht USING (raum_id); +-----------+-----------+-------------+-------------+ | raum_name | wochentag | uhrzeit_von | uhrzeit_bis | +-----------+-----------+-------------+-------------+ | C4.16 | 4 | 17:00:00 | 17:45:00 | | B2.02 | 3 | 15:35:00 | 16:20:00 | | B2.02 | 3 | 16:30:00 | 17:15:00 | | A5.33 | 5 | 10:00:00 | 11:00:00 | | A5.23 | 2 | 19:15:00 | 20:00:00 | +-----------+-----------+-------------+-------------+
Schon ganz hübsch, aber es wäre natürlich auch schön, wenn man auch noch die Räume angezeigt bekäme, die gar nicht belegt sind. Das geht natürlich bei einem Inner Join nicht, weil dort schließlich nur Datensätze ausgegeben werden, die ihren Ursprung in beiden Tabellen haben. Eine Lösung dazu bieten Outer Joins. Finden sie in der einen der beiden Tabellen keinen passenden Datensatz, füllen sie die entsprechenden Felder mit NULL auf und geben trotzdem die Daten aus der anderen Tabelle aus. In Abhängigkeit von der Position der „optionalen“ Tabelle spricht man von eine Left Outer Join (Tabelle, deren Daten auf jeden Fall ausgegeben werden sollen, steht links) oder einem Right Outer Join (Tabelle, deren Daten auf jeden Fall ausgegeben werden sollen, steht rechts). Um nun unser oben aufgeführtes Problem zu lösen, kann man folgenden Befehl nutzen: mysql> SELECT raum_name, wochentag, uhrzeit_von, uhrzeit_bis -> FROM raum LEFT OUTER JOIN unterricht USING (raum_id); +-----------+-----------+-------------+-------------+ | raum_name | wochentag | uhrzeit_von | uhrzeit_bis | +-----------+-----------+-------------+-------------+ | A5.33 | 5 | 10:00:00 | 11:00:00 | | B2.02 | 3 | 15:35:00 | 16:20:00 | | B2.02 | 3 | 16:30:00 | 17:15:00 | | C4.16 | 4 | 17:00:00 | 17:45:00 | | A5.21 | NULL | NULL | NULL | | A5.23 | 2 | 19:15:00 | 20:00:00 | +-----------+-----------+-------------+-------------+
,903
Sandini Bib
Wie man sieht, sind alle Räume mit ihren Belegungszeiten vorhanden (Raum B2.02 auch zwei Mal, da es zwei Termine in ihm gibt), aber auch der Raum A5.21, obwohl kein Unterricht in ihm stattfindet. wochentag, uhrzeit_von und uhrzeit_bis sind dementsprechend auch mit NULL gefüllt. Das Gegenstück dazu wäre der Right Outer Join, mit dem man das gleiche Ergebnis auch erhalten kann, wenn man die beiden Tabellen umgekehrt definiert: mysql> SELECT raum_name, wochentag, uhrzeit_von, uhrzeit_bis -> FROM unterricht RIGHT OUTER JOIN raum USING (raum_id); +-----------+-----------+-------------+-------------+ | raum_name | wochentag | uhrzeit_von | uhrzeit_bis | +-----------+-----------+-------------+-------------+ | A5.33 | 5 | 10:00:00 | 11:00:00 | | B2.02 | 3 | 15:35:00 | 16:20:00 | | B2.02 | 3 | 16:30:00 | 17:15:00 | | C4.16 | 4 | 17:00:00 | 17:45:00 | | A5.21 | NULL | NULL | NULL | | A5.23 | 2 | 19:15:00 | 20:00:00 | +-----------+-----------+-------------+-------------+
Viele andere Datenbank-Server kennen keine Right Outer Join (man kann ihn ja schließlich auch genauso gut durch einen Left Outer Join ersetzen), daher sollte man auch in MySQL den Left Outer Join bevorzugen, wenn man ein wenig auf Portabilität achten möchte. Vertauscht man in unserem Fall übrigens die beiden Tabellendefinitionen, erhält man das gleiche Ergebnis wie beim Inner Join, da es keinen Unterricht in einem Raum gibt, der nicht in der Tabelle eingetragen ist: mysql> SELECT raum_name, wochentag, uhrzeit_von, uhrzeit_bis -> FROM unterricht LEFT OUTER JOIN raum USING (raum_id); +-----------+-----------+-------------+-------------+ | raum_name | wochentag | uhrzeit_von | uhrzeit_bis | +-----------+-----------+-------------+-------------+ | C4.16 | 4 | 17:00:00 | 17:45:00 | | B2.02 | 3 | 15:35:00 | 16:20:00 | | B2.02 | 3 | 16:30:00 | 17:15:00 | | A5.33 | 5 | 10:00:00 | 11:00:00 | | A5.23 | 2 | 19:15:00 | 20:00:00 | +-----------+-----------+-------------+-------------+
• table_reference NATURAL [{LEFT | RIGHT} [OUTER]] JOIN table_reference
NATURAL JOIN
Der Natural Join ist eine Vereinfachung des Joins (Inner wie Outer Join): Bei ihm muss keine Verbindung zwischen den beiden Tabellen
Sandini Bib
angegeben werden, sei es per ON ... oder per USING (...). Es werden einfach alle Felder, die in beiden Tabellen den gleichen Namen tragen, intern in die USING-Klausel eingetragen. So entspricht also in unserem Beispiel SELECT ... FROM leihinstrument AS l INNER JOIN instrument AS i ON l.instrument_id = i.instrument_id
in der Version als Natural Join: SELECT ... FROM leihinstrument AS l NATURAL JOIN instrument AS i
Auf die gleiche Art und Weise funktioniert es auch mit Left oder Right Outer Join. Dies ist natürlich sehr bequem, allerdings verliert man damit die direkte Kontrolle darüber, welche Spalten für den Join berücksichtigt werden sollen. Es kann durchaus Fälle geben, in denen es in beiden Tabellen eine Spalte mit gleichem Namen gibt, die aber für den Join nicht genutzt werden soll (weil sie zum Beispiel unterschiedliche Werte enthält). Wenn dies von Anfang an der Fall ist, wird man natürlich keinen Natural Join nutzen (der dann schließlich auf Anhieb nicht funktionieren würde). Schlimmer wäre es aber, wenn ein Natural Join zunächst funktioniert, im Rahmen einer Strukturänderung (siehe nächstes Kapitel) aber in beiden Tabellen ein Feld gleichen Namens, aber unterschiedlichen Inhalts hinzukommt. Dann klappt es plötzlich nicht mehr mit der Verbindung und es ist meist sehr schwierig, solche Fehler zu finden.
3.9
Fragen
1. Was passiert, wenn man den Wert NULL mit einem anderen Boole-
schen Wert vergleicht? 2. Wie kann man vom (ganzzahligen) Feld meinwert der Tabelle meine-
tabelle für alle Datensätze 15 abziehen. Falls der Wert des Feldes danach unter Null liegen würde, soll er auf Null gesetzt werden. 3. Was ist der Unterschied zwischen INSERT IGNORE ... und REPLACE ...? 4. Wie kann man mit einem SELECT-Befehl herausfinden, wieviele Räume
es pro Etage gibt und wieviele Personen insgesamt darin Platz finden? 5. Erzeugen Sie eine Liste mit allen Instrumenten und den Unterrichts-
terminen dazu. Dabei sollen auch die Instrumente ausgegeben werden, für die kein Unterricht gegeben wird.
,903
Sandini Bib
4
Strukturelles
le
n e rn
Wir haben bereits im zweiten Kapitel Strukturen erzeugt, nämlich Datenbanken und Tabellen. Aber bekanntlich ändert sich vieles im Laufe der Zeit, auch die Anforderungen an eine Datenbankstruktur. Daher kommt man auch bei sorgfältigster Planung nicht umhin, Strukturen in MySQL ändern zu müssen. Zudem stellt man bei längerem Betrieb häufig fest, dass bestimmte Abfragen oft aufgerufen werden, aber leider nicht die schnellsten sind. Um solche Situationen zu entschärfen, kann man Indizes verwenden.
4.1
Was Sie in diesem Kapitel lernen
Zunächst werden wir uns mit Strukturänderungen befassen. Dabei geht es um das Hinzufügen, Ändern und Löschen von Spalten in einer bestehenden Tabelle mit ALTER TABLE. Zudem werden kurz die Gegenstücke zu CREATE DATABASE und CREATE TABLE besprochen: DROP DATABASE bzw. DROP TABLE. Im zweiten Abschnitt geht es um Indizes: wofür man sie nutzen kann, wie man sie anlegt (und löscht), und was man beachten sollte, damit die Datenbank nicht langsamer wird als vorher. Dann wollen wir uns mit den Systeminformationen von MySQL befassen, die alle Informationen über die bestehenden Strukturen der Datenbanken liefern. Schließlich gibt es noch einen Abschnitt über die Möglichkeit, die Speicherung von Tabellen zu optimieren.
:DV 6LH LQ GLHVHP .DSLWHO OHUQHQ
Sandini Bib
4.2
Strukturänderungen
Nun haben wir eine wunderschöne Datenbank- und Tabellenstruktur. Aber natürlich stellen wir beim Arbeiten mit den Daten fest, dass noch die eine oder andere Unschönheit besteht. Daher möchten wir die Strukturen ändern. Dazu gibt es hauptsächlich einen Befehl: ALTER TABLE. Mit ihm kann man Tabellenfelder hinzufügen, entfernen, umbenennen, den Datentyp ändern, die Tabelle umbenennen, Indizes anlegen und einiges anderes tun. Die bisher eingegebenen Daten kann man wundervoll nutzen, um zum Beispiel eine Einladung zu einem Schulkonzert an alle Schüler zu schicken. Die Adressen erhält man mit: mysql> SELECT CONCAT(vorname, ' ', nachname) AS name, strasse, -> CONCAT(plz, ' ', ort) AS stadt FROM schueler -> ORDER BY stadt; +--------------------+--------------------+----------------+ | name | strasse | stadt | +--------------------+--------------------+----------------+ | Thomas Schmidt | Hauptstr. 16 | 28219 Bremen | | Friederike Schulze | Relaisstr. 432 | 41564 Kaarst | | Anke Mayer | Leher Heerstr. 342 | 68219 Mannheim | | Frank Meier | Waldweg 30 | 69190 Walldorf | +--------------------+--------------------+----------------+
Etwas fehlt aber noch, um daraus alle Daten für ein Anschreiben zu erhalten: die Anrede. Wir sehen zwar schon, ob es sich um einen Mann oder eine Frau handelt, aber der Computer weiß das nicht (und auch ein Mensch kann sich durchaus irren, zum Beispiel bei „Andrea“). Also sollten wir noch ein Feld aufnehmen, in dem das Geschlecht abgespeichert ist. Dabei reicht als Datentyp BOOL, da wir hier der Einfachheit halber nur zwischen weiblich und männlich unterscheiden wollen. ALTER TABLE
Um diese Spalte hinzuzufügen, nutzt man folgenden Befehl: ALTER TABLE schueler ADD COLUMN geschlecht BOOL;
Diese Syntax ist eigentlich selbsterklärend. Der Tabelle schueler wird eine Spalte geschlecht vom Typ BOOL spendiert. Diese landet als neue letzte Spalte in der Tabellendefinition. Kontrollieren wir dies mit SHOW COLUMNS, dessen Ergebnis wir in Abbildung 4.1 sehen.
6WUXNWXUHOOHV
Sandini Bib
mysql> SHOW COLUMNS FROM schueler; +--------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+----------------+ | schueler_id | int(10) unsigned | | PRI | NULL | auto_increment | | nachname | varchar(30) | | | | | | vorname | varchar(30) | YES | | NULL | | | geburtsdatum | date | YES | | NULL | | | strasse | varchar(30) | YES | | NULL | | | plz | varchar(6) | YES | | NULL | | | ort | varchar(40) | YES | | NULL | | | telefon | varchar(20) | YES | | NULL | | | mobil | varchar(20) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | | einzug | tinyint(1) | YES | | 0 | | | bankname | varchar(58) | YES | | NULL | | | blz | varchar(8) | YES | | NULL | | | kontonr | varchar(10) | YES | | NULL | | | kontoname | varchar(30) | YES | | NULL | | | geschlecht | tinyint(1) | YES | | NULL | | +--------------+------------------+------+-----+---------+----------------+
Abbildung 4.1: Spalten der Tabelle schueler
Am Ende der Definition findet sich unsere neue Spalte. Dass sie den Typ TINYINT(1) erhalten hat, liegt daran, dass BOOL darauf abgebildet wird. Wenn man sich nun den Inhalt der Spalte anschaut, stellt man fest, dass alle Werte in ihr NULL sind: mysql> SELECT schueler_id, nachname, vorname, geschlecht -> FROM schueler; +-------------+----------+------------+------------+ | schueler_id | nachname | vorname | geschlecht | +-------------+----------+------------+------------+ | 1 | Schmidt | Thomas | NULL | | 2 | Mayer | Anke | NULL | | 3 | Meier | Frank | NULL | | 4 | Schulze | Friederike | NULL | +-------------+----------+------------+------------+
Der Grund dafür ist, dass wir keinen Standardwert mit angegeben haben. Hätten wir die Spaltendefinition um DEFAULT 1 erweitert, würden wir diesen Wert in jedem Feld der Spalte wiederfinden. Aber dies ist gar nicht unser Ziel. Schließlich wollen wir das Geschlecht selber angeben. Wir legen einmal fest, dass die 0 für die Männer und die 1 für die Frauen steht. Dann brauchen wir zwei UPDATEs, um die Daten anzupassen: mysql> UPDATE schueler SET geschlecht = 0 WHERE schueler_id IN (1, 3); Query OK, 2 rows affected (0.14 sec) Rows matched: 2 Changed: 2 Warnings: 0 mysql> UPDATE schueler SET geschlecht = 1 WHERE schueler_id IN (2, 4); Query OK, 2 rows affected (0.01 sec) Rows matched: 2 Changed: 2 Warnings: 0
6WUXNWXUlQGHUXQJHQ
Sandini Bib
Nun können wir die entsprechende Ansprache gleich mit aufnehmen: mysql> SELECT schueler_id, CONCAT(IF(geschlecht = 0, -> 'Sehr geehrter Herr ', 'Sehr geehrte Frau '), nachname) AS anrede -> FROM schueler; +-------------+----------------------------+ | schueler_id | anrede | +-------------+----------------------------+ | 1 | Sehr geehrter Herr Schmidt | | 2 | Sehr geehrte Frau Mayer | | 3 | Sehr geehrter Herr Meier | | 4 | Sehr geehrte Frau Schulze | +-------------+----------------------------+
Wie schon erwähnt, hat der Befehl ALTER TABLE sehr viele Variationsmöglichkeiten. Daher werden wir die einzelnen Elemente im Folgenden aufführen.
4.2.1 ALTER TABLE ALTER TABLE
Die Syntax von ALTER TABLE lautet wie folgt: ALTER [IGNORE] TABLE tbl_name alter_spec [, alter_spec ...]
Einfach, nicht wahr? tbl_name ist logischerweise der Name der Tabelle, an der herumgeschraubt werden soll. IGNORE kann genutzt werden, um dafür zu sorgen, dass Probleme nicht zum Abbruch des Vorgangs führen, sondern ignoriert werden. Und wo liegt nun das Komplexe und Variantenreiche? Natürlich in der Definition von alter_spec. Diese ist ähnlich umfassend wie die Möglichkeiten bei CREATE TABLE: ADD [COLUMN] create_definition [FIRST | AFTER column_name] | ADD [COLUMN] (create_definition, create_definition, ...) | ADD {INDEX|KEY} [index_name] (index_col_name, ...) | ADD PRIMARY KEY (index_col_name, ...) | ADD UNIQUE [index_name] (index_col_name, ...) | ADD FULLTEXT [index_name] (index_col_name, ...) | ADD [CONSTRAINT symbol] FOREIGN KEY index_name (index_col_name, ...) [reference_definition] | ALTER [COLUMN] col_name {SET DEFAULT literal | DROP DEFAULT} | CHANGE [COLUMN] old_col_name create_definition [FIRST | AFTER column_name] | MODIFY [COLUMN] create_definition [FIRST | AFTER column_name] | DROP [COLUMN] col_name | DROP PRIMARY KEY | DROP {INDEX|KEY} index_name | RENAME [TO] new_tbl_name | ORDER BY col, ... | table_options
6WUXNWXUHOOHV
Sandini Bib
Der Reihe nach: 1. ADD [COLUMN] create_definition [FIRST | AFTER column_name]
Mit dieser Option fügen Sie der Tabelle eine Spalte hinzu. Die create_definition bietet die Möglichkeiten, die Sie auch mit CREATE TABLE besitzen. So wird zum Beispiel der Tabelle raum noch eine Spalte für die Angabe des Gebäudes hinzugefügt, die standardmäßig auf Zentrale gesetzt wird: ALTER TABLE raum ADD COLUMN gebaeude VARCHAR(10) DEFAULT 'Zentrale';
Die Angabe von COLUMN ist optional und macht den Befehl „lesbarer“, aber auch länger… Normalerweise werden neue Spalten immer hinter den bestehenden Spalten einer Tabelle angelegt. Nutzt man FIRST oder AFTER column_name, wird die neue Spalte dagegen als erste angelegt bzw. nach der bestehenden Spalte column_name eingefügt. 2. ADD [COLUMN] (create_definition, create_definition, ...)
Dies ist eine Abwandlung der vorigen Syntax. Statt einer einzigen Spalte lassen sich mehrere Spalten auf einmal anlegen. Es handelt sich eigentlich nur um eine verkürzte Version von ALTER TABLE ... ADD create_definition, ADD create_definition, ... 3. ADD {INDEX|KEY} [index_name] (index_col_name, ...)
Diese Variante fügt der Tabelle einen Index mit dem Namen index_name hinzu, der über die Spalten index_col_name, ... geht. Fehlt dieser, wird er aus dem Namen der ersten Indexspalte gebildet, eventuell ergänzt um eine Ziffer zur Unterscheidung. Siehe auch Kapitel 4.3 über Indizes. 4. ADD PRIMARY KEY (index_col_name, ...)
Hiermit lässt sich nachträglich ein Primärschlüssel für die Tabelle festlegen. Eigentlich sollte man dies ja immer direkt beim Anlegen machen, aber wenn es mal unterlassen wurde, kann man das hiermit nachholen. 5. ADD UNIQUE [index_name] (index_col_name, ...)
erstellt einen zusätzlichen eindeutigen Index. Die Syntax ist (abgesehen vom UNIQUE) identisch mit ADD INDEX. Siehe auch Kapitel 4.3 über Indizes. 6. ADD FULLTEXT [index_name] (index_col_name, ...)
legt einen Index an, über den Volltextrecherche betrieben werden kann. Die Syntax ist auch hier identisch zu ADD INDEX. Siehe auch Kapitel 4.3.3 über die Volltext-Recherche.
6WUXNWXUlQGHUXQJHQ
Sandini Bib
7. ADD [CONSTRAINT symbol] FOREIGN KEY index_name
(index_col_name,
...)
[reference_definition]
Diese Syntax ist ebenso gültig wie wirkungslos. Sie dient – wie schon ihr Äquivalent bei CREATE TABLE – nur dazu, bestehende Skripten aus anderen SQL-Servern leichter anpassen zu können. 8. ALTER [COLUMN] col_name {SET DEFAULT literal | DROP DEFAULT}
Mit dieser Variante kann man der Spalte col_name entweder mit SET DEFAULT literal einen Standardwert verpassen oder mit DROP DEFAULT einen bestehenden entziehen. Entfernt man eine Standardwertdefinition, werden dabei allerdings die schon angelegten Datensätze mit Standardwert nicht geändert. 9. CHANGE [COLUMN] old_col_name create_definition [FIRST | AFTER column_name]
benennt die alte Spalte old_col_name um und ermöglicht die Angabe einer neuen Spaltendefinition (die den neuen Spaltennamen enthält). Möchte man nur den Typ einer Spalte ändern, muss man den Spaltennamen daher zweimal angeben (wobei dann die Variante mit MODIFY sinnvoller ist): ALTER TABLE mytab CHANGE COLUMN mycol mycol VARCHAR(30); FIRST und AFTER ermöglichen wieder die genaue Angabe der Position der geänderten Spalte nach der Änderung. 10. MODIFY [COLUMN] create_definition [FIRST | AFTER column_name]
ermöglicht das Ändern der Definition einer bestehenden Spalte (ohne den Namen anzupassen) 11. DROP [COLUMN] col_name
entfernt eine Spalte aus einer Tabelle 12. DROP PRIMARY KEY
löscht die Definition des Primärschlüssels. Die entsprechenden Spalten werden dabei nicht geändert, es wird nur die Schlüsselangabe gelöscht. 13. DROP {INDEX|KEY} index_name
löscht einen bestehenden Index mit dem Namen index_name. Siehe auch Kapitel 4.3 über Indizes. 14. RENAME [TO] new_tbl_name
benennt die Tabelle um und gibt ihr den Namen new_tbl_name
6WUXNWXUHOOHV
Sandini Bib
15. ZRDER BY col, ...
sortiert die Tabelle in der angegebenen Reihenfolge. Manche Abfragen und Befehle arbeiten schneller, wenn die Tabelle vorsortiert ist. Da es sich dabei um einen einmaligen Vorgang handelt, kann sich die Sortierung durch Einfügen, Ändern und Löschen von Daten schnell wieder ändern. Zudem kann man sich auch bei einer vorsortieren Tabelle nicht darauf verlassen, dass die Daten bei einer Abfrage in dieser Reihenfolge wieder ausgegeben werden. 16. table_options
Hier lassen sich die gleichen Tabellenoptionen wie bei CREATE TABLE angeben. Sie sind hier nur der Vollständigkeit halber noch mal aufgeführt, die Beschreibung ist aber dort nachzulesen. TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM} | AUTO_INCREMENT = number | AVG_ROW_LENGTH = number | CHECKSUM = {0 | 1} | COMMENT = "string" | MAX_ROWS = number | MIN_ROWS = number | PACK_KEYS = {0 | 1} | PASSWORD = "string" | DELAY_KEY_WRITE = {0 | 1} | ROW_FORMAT = {default | dynamic | fixed | compressed} | RAID_TYPE = {1 | STRIPED | RAID0} RAID_CHUNKS = num_chunks RAID_CHUNKSIZE = num_size | UNION = (table_name,...) | INSERT_METHOD = {NO | FIRST | LAST} | DATA DIRECTORY = "directory" | INDEX DIRECTORY = "directory"
4.2.2 RENAME TABLE Mit diesem Befehl kann man eine Tabelle umbenennen. Seine Syntax lautet:
RENAME TABLE
RENAME TABLE tbl_name TO new_table_name[, tbl_name2 TO new_table_name2, ...]
Will man nur eine Tabelle umbenennen, hat dieser Befehl den gleichen Effekt wie ALTER TABLE table_name RENAME TO new_table_name;
Man kann aber mit RENAME TABLE auch mehrere Tabellen gleichzeitig umbenennen. Da diese Aktion in einem Schritt, also „atomar“ durchgeführt wird, gehen keinerlei Daten verloren, wenn man zum Beispiel die
6WUXNWXUlQGHUXQJHQ
Sandini Bib
bisherigen Daten in eine Sicherheitstabelle bringen will und die eigentliche Tabelle wieder leer sein soll: CREATE TABLE mytable_new (...); RENAME TABLE mytable TO mytable_backup, mytable_new TO mytable;
Damit kann direkt danach wieder auf die nun leere Tabelle zugegriffen werden. Es gibt noch zwei weitere Befehle, die in diesem Zusammenhang beschrieben werden sollten: DROP TABLE und DROP DATABASE.
4.2.3 DROP TABLE DROP TABLE
Mit diesem Befehl kann man eine oder mehrere komplette Tabellen entfernen. Es werden also nicht nur – wie bei TRUNCATE TABLE – alle Daten gelöscht, sondern auch die Tabellendefinition entsorgt. Man sollte also sehr vorsichtig damit umgehen, da es kein Zurück gibt (von Datensicherungen einmal abgesehen…). Die Syntax lautet: DROP TABLE [IF EXISTS] tbl_name [, tbl_name,...] [RESTRICT | CASCADE] IF EXISTS dient wieder dazu, Fehlermeldungen zu unterdrücken, wenn
die Tabellen gar nicht vorhanden sind. Danach folgt die Liste der zu löschenden Tabellen. RESTRICT oder CASCADE gehören dem Bereich der Fremdschlüssel an und sind daher wie in den anderen DDL-Befehlen ohne Funktion.
4.2.4 DROP DATABASE DROP DATABASE
Um eine komplette Datenbank zu löschen, nutzt man den Befehl DROP DATABASE. Es werden alle Tabellen, Daten und anderen Angaben zu dieser Datenbank gelöscht. Aus dem entsprechenden Verzeichnis werden zudem alle Dateien gelöscht, die von MySQL angelegt worden sind (oder auch nur sein können, also Vorsicht, falls man [aus welchem Grund auch immer] eigene Dateien in den Datenverzeichnissen erstellt hat, die eine von MySQL genutzte Endung tragen). Die Syntax lautet: DROP DATABASE [IF EXISTS] db_name
Auch hier dient IF EXISTS dazu, eventuelle Fehlermeldungen zu unterdrücken, falls die Datenbank nicht existiert. Mehr noch als bei DROP TABLE sollte man hier aufpassen, was man tut, da der Befehl nicht rückgängig zu machen ist (wie alle DDL-Befehle).
6WUXNWXUHOOHV
Sandini Bib
4.3
Indizes
Indizes sind ein wichtiger Bestandteil jedes Datenbank-Systems. Sie passen zwar nicht in die „reine“ Datenbank-Mengenlehre (auf der SQL ja eigentlich aufbaut), sind aber ein ausgesprochen wichtiger Baustein jeder Optimierungsbemühung. Nur durch sie lassen sich große Datenbestände in vernünftiger Zeit durchsuchen.
4.3.1 Sinn und Funktionsweise von Indizes Wie findet MySQL Daten in einer Tabelle? Die Daten sind (normalerweise) ungeordnet. Im Prinzip muss es die Tabelle immer vom Anfang an durchsuchen, bis es den passenden Satz findet. Selbst wenn man sie mit ALTER TABLE ... ORDER BY ... ordnet, muss die Suche für ein Feld, welches nicht in dieser Sortierreihenfolge ganz vorne steht, über die gesamte Tabelle gehen. Geht man davon aus, dass sich der gesuchte Datensatz an einer beliebigen Stelle in der Tabelle befindet (mal ganz am Anfang, mal in der Mitte, mal am Ende), muss man im Schnitt die Hälfte der Datensätze durchsuchen, bis der erste Satz gefunden wurde. Da man bei SQL aber mengenorientiert vorgeht, muss immer die komplette Tabelle durchforstet werden, um alle betroffenen Datensätze zu erhalten. Dies ist bei kleinen Datenmengen kein Problem. Aber was, wenn in einer Tabelle hunderttausende Sätze eingetragen sind, auf die auch noch häufig zugegriffen werden muss. Ohne eine Unterstützung bei der Suche würden die Abfragen sehr lange dauern und der Server wäre ziemlich ausgelastet. Nun ist es meistens so, dass bestimmte Abfragen häufig vorkommen. In einer Kundentabelle sucht man meist nach dem Namen, aber auch ab und zu nach PLZ und Ort (für Mailingaktionen). Wäre es da nicht praktisch, wenn es eine vorsortierte Liste mit den Namen gäbe, die man sehr schnell durchsuchen könnte? Dazu eine weitere Liste, die nach PLZ und Ort sortiert ist? Natürlich sind solche Listen sinnvoll, wenn sie automatisch gepflegt werden und der Geschwindigkeitssteigerung dienen. Dies ist der Hintergrund eines Indizes. Definiert man einen Index über ein Feld, wird neben der eigentlichen Tabelle eine Datenstruktur angelegt, in der nur dieses Feld (bzw. der Anfangsteil davon) sortiert abgelegt ist. Jeder Eintrag enthält einen Verweis auf den echten Datensatz in der eigentlichen Tabelle. Werden neue Daten eingefügt, bestehende geändert oder Daten gelöscht, wird auch die Indexstruktur angepasst, so dass sie immer auf dem gleichen Stand wie die Tabelle (zumindest bezüglich des Indexfeldes), aber immer auch sortiert ist.
,QGL]HV
Sandini Bib
Kommt nun eine Anfrage über dieses Feld, wird zunächst in der Indexstruktur nachgeschaut. Aufgrund der Sortierung ist das Nachschlagen sehr schnell. MySQL nimmt sich dabei das Element, welches in der Mitte der Indexstruktur liegt. Entspricht der Wert dem gesuchten, ist man fertig. Ist er zu groß, nimmt man sich die „linken“ Elemente vor, ist er zu klein, die rechten. Nun hat man die Zahl der Elemente schon halbiert und wiederholt mit diesen das gleiche Spiel: mittleres Element holen, untersuchen, und entweder gefunden haben oder den linken/ rechten Teilbereich untersuchen. So hat man nach spätestens log2 n Versuchen (bei n Datensätzen) den passenden Datensatz gefunden. Diese Zahl ist bei steigender Anzahl von Datensätzen viel kleiner (siehe Tabelle 4.1) und sorgt dafür, dass der Index so schnell ist. n
log2 n
10
3,3
100
6,6
1.000
10,0
10.000
13,3
100.000
16,6
1.000.000
19,9
Tabelle 4.1: Anzahl Suchvorgänge bei einer Binärsuche
Wie man sieht, reduziert sich die Anzahl der Vergleichsschritte massiv und macht sich besonders bei umfangreichen Datenmengen bemerkbar. Solch eine Suche wird als Binärsuche bezeichnet. MySQL selbst (wie auch alle anderen „richtigen“ Datenbank-Systeme) hat noch ausgefeiltere Techniken, die aber meist auf dem gleichen Prinzip beruhen. Nun muss man sich bei einem Index nicht auf ein einzelnes Feld beschränken. Man kann mehrere angeben, die einfach hintereinander abgespeichert werden. Sucht man zum Beispiel regelmäßig nach Nachname und Vorname, kann man einen Index mit diesen beiden Feldern anlegen. In der Indexstruktur stehen die Werte dann zusammengefügt als ein Wert. Zudem lassen sich auch für eine Tabelle mehrere Indizes anlegen, die unterschiedliche Felder enthalten. Wann wird ein solcher Index verwendet? Es gibt verschiedene Einsatzgebiete, die aber alle darauf hinauslaufen, dass Werte verglichen werden: • In einer WHERE-Klausel. Findet sich ein Feld (oder auch mehrere) aus der WHERE-Klausel in einem passenden Index, wird dieser verwendet. So lassen sich Abfragen wie die folgende massiv beschleunigen: SELECT * FROM schueler WHERE nachname = 'Schmidt';
6WUXNWXUHOOHV
Sandini Bib
Nutzt man LIKE, muss man berücksichtigen, dass ein Index nur für den Teil verwendet werden kann, der vor dem ersten Platzhalter (% oder _) steht. Auch Funktionen sorgen dafür, dass ein Index nicht wirksam werden kann. Die folgende Abfrage ist also nicht durch einen Index unterstützt, auch wenn auf dem Feld groesse ein Index liegt: SELECT * FROM kunden WHERE POWER(groesse, 2) > 1000;
Stattdessen sollte man wann immer möglich versuchen, Funktionen bei Suchen auf Literale anzuwenden (falls die Abfrage zu langsam ist), damit ein Index wirken kann. SELECT * FROM kunden WHERE groesse > SQRT(1000);
• Beim Verknüpfen von Tabellen per Join. In diesem Fall werden meistens Schlüssel miteinander verglichen. Wenn eine Tabelle einen Primärschlüssel hat, existiert dafür automatisch ein Index. Dann sollte man nur noch Indizes für die Fremdschlüssel anlegen. • Wenn man den maximalen oder minimalen Wert eines Feldes sucht. Dabei kann MySQL in bestimmten Fällen noch weiter optimieren. • Wenn man eine Abfrage sortiert oder gruppiert. Dabei ist allerdings zu beachten, dass die Sortierreihenfolge der Reihenfolge der Felder im Index entsprechen sollte, damit er komplett wirksam wird. Allerdings wird auch ein Teil eines Index genutzt. Existiert zum Beispiel ein Index über die Felder nachname, vorname und ort in dieser Reihenfolge und man hat eine Abfrage SELECT * FROM schueler ORDER BY nachname, ort, vorname;
dann wird der Index zwar verwendet, kann aber nur für das Feld nachname genutzt werden. • In bestimmten Fällen ist durch einen Index der Zugriff auf die eigentliche Tabelle vollkommen überflüssig und wird automatisch wegoptimiert. Dies ist dann der Fall, wenn im SELECT-Befehl nur Felder vorkommen, die auch im Index existieren. Bei einem angenommenen Index über nachname, vorname und ort auf die Tabelle schueler würde die folgende Abfrage diese Optimierung zulassen: SELECT nachname, vorname FROM schueler WHERE ort = 'Bremen';
• Wenn eine Spalte nur eindeutige Werte enthalten soll, wird dies durch einen Index sichergestellt. Dies kann durch UNIQUE oder PRIMARY KEY erfolgen. Nach all diesen tollen Vorteilen, die ein Index bringt, wollen wir auch nicht dessen dunkle Seite verschweigen: Er kostet Zeit, wenn man Daten anlegt, ändert oder löscht. Gibt es keinen Index, muss nur die Tabelle selber angepasst werden. Existieren aber Indizes für eine Tabelle, müssen
,QGL]HV
Sandini Bib
eventuell alle Indizes aktualisiert werden, was durchaus (bei vielen Indizes) länger dauern kann als das Eintragen in die Tabelle selber. Daher die Faustregel: nur die Indizes anlegen, die wirklich häufig gebraucht werden. Ein weiterer Tipp: falls man viele Daten auf einmal anlegen oder löschen will, kann es Sinn machen, die Indizes zunächst zu löschen, dann die Änderungen vorzunehmen und am Ende die Indizes wieder anzulegen. Denn der Aufwand für das Anlegen eines Index über 100.000 Datensätze ist meist geringer als der zusätzliche Zeitaufwand, der durch das Pflegen der Indexdaten für 100.000 einzelne Änderungen entsteht. Dies sollte man natürlich nur bei wirklich großen Aktionen machen (sogenannten Bulk-Loads). In die gleiche Richtung noch ein ähnlicher Tipp: Muss man innerhalb einer kürzeren Zeit viele ähnliche Abfragen machen, die aber nach diesem Zeitraum nicht mehr vorkommen, kann man einen Index seiner Wahl anlegen, die Abfragen machen und ihn danach wieder löschen. Auch dies ist bei entsprechenden Datenmengen und ansonsten ungünstigen bestehenden Indizes ein häufig schnellerer Weg als die vielen Abfragen ohne den Index. Nach diesem langen Vorlauf soll es nun aber endlich an das (recht unspektakuläre) Anlegen von Indizes gehen.
4.3.2 Anlegen von Indizes Es gibt mehrere Möglichkeiten, Indizes anzulegen: entweder direkt beim Erstellen der Tabelle mit CREATE TABLE, durch Nutzung einer der vielen Optionen von ALTER TABLE oder ganz klassisch mit CREATE INDEX. Ich werde sie hier der Reihe nach besprechen. Anlegen eines Index per CREATE TABLE CREATE TABLE
Will man Indizes gleich zusammen mit der Tabelle anlegen, bietet sich CREATE TABLE an. Dabei gibt man statt einer Spaltendefinition eine der folgenden Zeilen an: PRIMARY KEY (index_col_name,...) KEY [index_name] (index_col_name,...) INDEX [index_name] (index_col_name,...) UNIQUE [INDEX] [index_name] (index_col_name,...)
Mit der ersten Möglichkeit legt man einen Primärschlüssel fest. In Klammern werden die einzelnen Felder angegeben, die den Primärschlüssel bilden sollen. Handelt es sich nur um ein einzelnes Feld, kann man stattdessen auch bei der entsprechenden Spaltendefinition die Option PRIMARY KEY angeben. So bewirken also die folgenden Befehle dasselbe:
6WUXNWXUHOOHV
Sandini Bib
CREATE TABLE raum (raum_id SMALLINT UNSIGNED raum_name VARCHAR(10) etage CHAR(3), personen MEDIUMINT UNSIGNED CREATE TABLE raum (raum_id SMALLINT UNSIGNED raum_name VARCHAR(10) etage CHAR(3), personen MEDIUMINT UNSIGNED PRIMARY KEY (raum_id));
NOT NULL AUTO_INCREMENT PRIMARY KEY, NOT NULL, DEFAULT 1); NOT NULL AUTO_INCREMENT, NOT NULL, DEFAULT 1,
Beide Varianten haben ihre Vorteile: bei der ersten sieht man direkt bei der entsprechenden Spalte, dass es sich um den Primärschlüssel handelt (dort steht schließlich auch die Option AUTO_INCREMENT), bei der zweiten Variante kann man dagegen alle Schlüsseldefinitionen (und PRIMARY KEY gehört genauso dazu) an einer Stelle zusammenfassen. Man sollte allerdings daran denken, dass die erste Variante nur funktioniert, wenn ein Feld alleine den Primärschlüssel darstellt. Die zweite und dritte Version (KEY und INDEX) sind – abgesehen vom Schlüsselwort – identisch. Man kann optional einen Namen für den Index vergeben. Fehlt diese Angabe, wird der Name aus der ersten angegebenen Indexspalte gebildet, eventuell ergänzt um eine fortlaufende Ziffer, um die Eindeutigkeit des Namens zu wahren. Es ist aber immer empfehlenswert, den Indizes sinnvolle Namen zu geben, möglichst sogar ein Konzept für die Indexnamen zu haben (zum Beispiel idx_schueler_name). Es folgt die Liste der Spalten, die den Index in der angegebenen Reihenfolge bilden sollen. Ein Beispiel dafür ist: CREATE TABLE schueler (schueler_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, nachname VARCHAR(30) NOT NULL, vorname VARCHAR(30), geburtsdatum DATE, strasse VARCHAR(30), plz CHAR(6), ort VARCHAR(40), telefon VARCHAR(20), mobil VARCHAR(20), email VARCHAR(50), einzug BOOL DEFAULT 0, bankname VARCHAR(58), blz CHAR(8), kontonr CHAR(10), kontoname VARCHAR(30), INDEX idx_schueler_name (nachname, vorname), INDEX idx_schueler_wohn (plz, ort));
,QGL]HV
Sandini Bib
Möchte man bei einem String-Feld (CHAR oder VARCHAR) nur einen Teil des Feldes für den Index nutzen, kann man beim Spaltennamen in Klammern noch die gewünschte Länge angeben. Das macht zum Beispiel dann Sinn, wenn sich die meisten Daten schon in den ersten Zeichen deutlich unterscheiden und eine komplette Aufnahme in den Index nur Platz kosten würde. Möchte man zum Beispiel einen Index auf PLZ und Ort anlegen (wie schon im vorigen Beispiel geschehen), kann man vom Ort auch nur die ersten fünf Zeichen nehmen, da die PLZ meistens sowieso schon ausreicht, um einen Ort eindeutig zu finden (aber eben nicht immer…): CREATE TABLE schueler (..., INDEX idx_schueler_wohn (plz, ort(5));
Die vierte Variante ist der eindeutige Index. Er sorgt genauso wie ein Primärschlüssel dafür, dass die Spaltenkombination eindeutig ist, d.h. keine zwei Felder (oder Feldkombinationen bei mehreren Spalten) den gleichen Wert besitzen. Das kann dann von Nutzen sein, wenn man neben dem Primärschlüssel auch noch einen „sprechenden“ Schlüssel besitzt, der ebenso eindeutig sein soll. Ein Beispiel dafür wäre eine Verwaltung von Benutzern. Deren Anmeldenamen müssen auch eindeutig sein, aber trotzdem sollte man sie nicht unbedingt direkt als Primärschlüssel verwenden. Es kann schließlich sein, dass der Name einmal geändert werden soll. Hat man dann Tabellen, die von der Benutzertabelle abhängig sind, müsste man in allen den Namen ebenfalls ändern. Daher bietet sich in solchen Fällen ein eigener Primärschlüssel und ein Anmeldename mit eindeutigem Index an: CREATE TABLE benutzer (benutzer_id INT UNSIGNED AUTO_INCREMENT, benutzer_name VARCHAR(20), ..., PRIMARY KEY (benutzer_id), UNIQUE INDEX idx_benutzer_name (benutzer));
Anlegen eines Index per ALTER TABLE ALTER TABLE
Hat man die Tabelle schon definiert, möchte aber trotzdem noch zusätzlich Indizes definieren, kann man auch den Befehl ALTER TABLE nutzen. Dabei stehen einem die gleichen Optionen wie bei CREATE TABLE zur Verfügung, allerdings ergänzt durch ALTER TABLE raum ADD INDEX idx_raum_etage (etage);
6WUXNWXUHOOHV
Sandini Bib
Die einzelnen Optionen lauten also: ADD ADD ADD ADD
INDEX [index_name] (index_col_name,...) KEY [index_name] (index_col_name,...) PRIMARY KEY (index_col_name,...) UNIQUE [index_name] (index_col_name,...)
Anlegen eines Index per CREATE INDEX Schließlich bleibt einem noch die Möglichkeit, Indizes per CREATE INDEX für eine bestehende Tabelle anzulegen. Die Syntax dazu lautet:
CREATE INDEX
CREATE [UNIQUE|FULLTEXT] INDEX index_name ON tbl_name (col_name, ...)
Der Indexname ist in diesem Fall nicht optional. Allerdings sollte er – wie schon erwähnt – eigentlich immer mit angegeben werden. Eindeutige Indizes werden mit der Option UNIQUE angelegt. Primärschlüssel kann man mit diesem Befehl nicht erstellen, aber die sollten eigentlich sowieso direkt beim Anlegen der Tabelle angegeben werden. FULLTEXT dient dazu, einen Volltextindex anzulegen. Diese werden in Ka-
pitel 4.3.4 besprochen.
4.3.3 Anzeigen von Indizes Möchte man sich anzeigen lassen, welche Indizes mit welchen Feldern für eine Tabelle definiert wurden, kann man den Befehl SHOW INDEX nutzen:
SHOW INDEX
SHOW INDEX FROM table_name;
Dabei wird eine recht umfangreiche Liste mit Informationen ausgegeben (siehe Abbildung 4.2). mysql> SHOW INDEX FROM schueler; +----------+------------+-------------------+--------------+ | Table | Seq_in_index | | Non_unique | Key_name +----------+------------+-------------------+--------------+ | schueler | 0 | PRIMARY | 1 | | schueler | 1 | 1 | idx_schueler_name | | schueler | 2 | 1 | idx_schueler_name | | schueler | 1 | 1 | idx_schueler_wohn | 2 | 1 | idx_schueler_wohn | | schueler | +----------+------------+-------------------+--------------+ Fortsetzung +-----------+-------------+----------+--------+---------+ | Collation | Cardinality | Sub_part | Packed | Comment | +-----------+-------------+----------+--------+---------+ | A | 4 | NULL | NULL | | | A | | 4 | NULL | NULL | | A | | 4 | NULL | NULL | | A | 4 | NULL | NULL | | | A | 4 | 5 | NULL | | +-----------+-------------+----------+--------+---------+ Abbildung 4.2: Ausgabe des Befehls SHOW INDEX
,QGL]HV
Sandini Bib
Die Bedeutung der einzelnen Spalten ist in Tabelle 4.2 beschrieben. Spalte
Beschreibung
Table
Name der Tabelle, für die der Index definiert wurde
Non_unique
0: Index ist eindeutig, 1: Index ist nicht eindeutig
Key_name
Name des Index (PRIMARY, wenn Primärschlüssel)
Seq_in_index
Position der Spalte im Index
Column_name
Name der Spalte, die im Index aufgenommen ist
Collation
Sortierreihenfolge dieser Spalte. Kann nur A (aufsteigend) oder NULL (unsortiert) sein.
Cardinality
Anzahl der eindeutigen Werte in diesem Index. Diese Zahl basiert auf Statistiken und muss gerade bei kleinen Tabellen nicht exakt dem tatsächlichen Wert entsprechen, den man mit SELECT COUNT(DISTINCT ...) erhalten würde.
Sub_part
Anzahl der Zeichen, die bei einer nur teilweise aufgenommenen Spalte verwendet werden sollen. Ist die Spalte komplett enthalten, steht hier NULL.
Packed
Angabe, ob der Index gepackt ist oder nicht
Comment
Platz für verschiedene Angaben. Zur Zeit steht hier nur, ob es sich um einen Volltextindex handelt.
Tabelle 4.2: Bedeutung der Spalten in SHOW INDEX
Zum Thema Kardinalität noch eine kurze Anmerkung: Ist diese in einer Spalte nicht sehr groß (im Vergleich zur Gesamtzahl an Werten), ist ein Index nicht sehr effektiv. Anders herum: je unterschiedlicher die Werte in einer Spalte sind, desto besser kann ein Index arbeiten. So macht es selbst in einer Tabelle mit 10.000 Ansprechpartnern bei Kunden keinen Sinn, auf ein eventuell vorhandenes Feld geschlecht zu indizieren, da dessen Kardinalität im Allgemeinen bei 2 liegt (nur im medizinischen Bereich machen vier unterschiedliche Werte Sinn: männlich, weiblich, anderes, und unbekannt, aber selbst dann ist der Index ineffektiv).
4.3.4 Volltext-Recherche Mit MySQL lassen sich auch Volltextrecherchen durchführen. Dazu legt man für die gewünschten Spalten einen Volltextindex an und nutzt einen speziellen Suchoperator, der nicht nur die Datensätze mit den passenden Wörtern findet, sondern auch noch eine Relevanz bestimmt, um möglichst die passendsten Datensätze zuerst zu bringen. Um Beispiele für die Volltextrecherche zu bringen, müssen wir allerdings ein wenig von unserem Musikschul-Szenario abkommen, da sich dort kein richtig passendes Beispiel bietet. Wir werden stattdessen eine Tabelle mit Textinhalten erzeugen und füllen:
6WUXNWXUHOOHV
Sandini Bib
CREATE TABLE fulltexttest (chapter VARCHAR(10), title VARCHAR(50), content TEXT, FULLTEXT (title, content));
In diese Tabelle tragen wir nun noch Werte ein.
FULLTEXT
Die entsprechenden Befehle dazu finden sich auf der beiliegenden CDROM im Verzeichnis Beispiel in der Datei fulltext.sql. Um nun eine Volltextrecherche durchführen zu können, nutzen wir den Operator MATCH AGAINST. Er findet alle Vorkommen des Suchwortes in den gewünschten Spalten und gibt die Datensätze in der Reihenfolge der Relevanz aus. Die Syntax lautet: MATCH (column [, column, ...]) AGAINST ('value')
Man kann mehrere Spalten zum Durchsuchen angeben, die allerdings alle im Volltextindex aufgeführt sein müssen. Zudem müssen sie (falls man in der Abfrage Joins nutzt) aus einer Tabelle kommen. value ist ein Stringwert, der aber konstant sein muss (d.h., man kann nicht gegen den Inhalt einer anderen Spalte vergleichen lassen). Ein Beispiel: mysql> SELECT chapter, title, LEFT(content, 19) -> FROM fulltexttest -> WHERE MATCH (title, content) AGAINST ('MySQL'); +---------+--------------------------------------+---------------------+ | chapter | title | LEFT(content, 19) | +---------+--------------------------------------+---------------------+ | 1.6 | MySQL | Jetzt haben wir uns | | 1.6.4 | Lizenzen und Kosten bei MySQL | Man sagt immer, MyS | | 1.6.1 | Was ist MySQL | MySQL ist ein relat | | 1.6.2 | Die Geschichte und Idee hinter MySQL | Die Entwickler von | | 1.6.3 | Elemente von MySQL | MySQL besteht vor a | | 1 | Datenbanken | Was ist das - eine | +---------+--------------------------------------+---------------------+
Datensätze, in denen das Wort nicht vorkommt, erhalten eine Relevanz von 0 und werden daher nicht angezeigt. Wie groß ist nun die Relevanz? Dazu dient der gleiche Operator, hier als Funktion zum Ausgeben: mysql> SELECT chapter, -> MATCH (title, content) AGAINST ('MySQL') AS relevance -> FROM fulltexttest;
,QGL]HV
Sandini Bib
+---------+------------------+ | chapter | relevance | +---------+------------------+ | 1 | 0.4164290528623 | | 1.1 | 0 | | 1.2 | 0 | | 1.3 | 0 | | 1.4 | 0 | | 1.4.1 | 0 | | 1.4.2 | 0 | | 1.4.3 | 0 | | 1.4.4 | 0 | | 1.4.5 | 0 | | 1.5 | 0 | | 1.6 | 0.84725140746008 | | 1.6.1 | 0.68482611338062 | | 1.6.2 | 0.67439976592822 | | 1.6.3 | 0.66891743559618 | | 1.6.4 | 0.78651414891569 | +---------+------------------+
Wie man sieht, gibt es unterschiedlich große Relevanzen und auch genug Kapitel, die nicht relevant sind. Die Sortierung ist hier nicht vorhanden, da wir MATCH AGAINST nicht in der WHERE-Klausel verwendet haben. Man kann dies aber gleichzeitig tun: mysql> SELECT chapter, -> MATCH (title, content) AGAINST ('MySQL') AS relevance -> FROM fulltexttest -> WHERE MATCH (title, content) AGAINST ('MySQL'); +---------+------------------+ | chapter | relevance | +---------+------------------+ | 1.6 | 0.84725140746008 | | 1.6.4 | 0.78651414891569 | | 1.6.1 | 0.68482611338062 | | 1.6.2 | 0.67439976592822 | | 1.6.3 | 0.66891743559618 | | 1 | 0.4164290528623 | +---------+------------------+
Nun erhält man einerseits die Relevanz sortiert nach der Größe, andererseits aber auch nur die Datensätze, die überhaupt relevant sind. Dass im obigen SQL-Befehl MATCH AGAINST gleich zweimal vorkommt, ist nicht weiter schlimm, da der MySQL-interne Optimierer erkennt, dass es sich bei beiden um den gleichen Aufruf handelt.
6WUXNWXUHOOHV
Sandini Bib
Es gilt bei der Volltextrecherche noch ein paar Punkte zu beachten: • Man kann nicht nach Worten suchen, die kürzer als vier Buchstaben sind oder sich in der Liste mit Stopp-Wörtern befinden. Beides kann man ändern, allerdings nur, indem man MySQL neu kompiliert. • Die Relevanz berechnet sich aus der Häufigkeit der Wörter in der Menge der Datensätze. Je seltener sie vorkommen, desto größer ist ihre Wichtung, aus der sich dann die Relevanz bestimmt. • Kommt ein Wort in mehr als der Hälfte aller Datensätze vor, wird seine Wichtung auf 0 gesetzt, da es dann nicht mehr sinnvoll zur Filterung genutzt werden kann (was man allerdings auch durch ein neues Kompilieren von MySQL ändern kann). • Die Berechnung der Relevanz funktioniert am besten bei großen Datenmengen. Kleine Tabellen können zu seltsamen Ergebnissen führen.
4.3.5 Löschen von Indizes Um Indizes zu löschen, gibt es wieder zwei verschiedene Wege. Löschen eines Index mit ALTER TABLE Um einen Index mit ALTER TABLE wieder zu löschen, kann man eine der folgenden Optionen nutzen: DROP PRIMARY KEY DROP INDEX index_name DROP KEY index_name
Mit ALTER TABLE ... DROP PRIMARY KEY wird logischerweise der Primärschlüssel gelöscht. ALTER TABLE ... DROP INDEX ... oder ALTER TABLE ... DROP KEY ... löscht den entsprechenden Index, egal ob es sich um einen normalen, eindeutigen oder Volltext-Index handelt. Löschen eines Index mit DROP INDEX Die zweite Variante ist DROP INDEX:
DROP INDEX
DROP INDEX index_name ON table_name
Mit diesem Befehl kann man alle Indizes löschen, die angelegt wurden. Primärschlüssel allerdings kann man nur mit ALTER TABLE ... DROP PRIMARY KEY löschen.
,QGL]HV
Sandini Bib
4.4 SHOW
Systeminformationen
Der zentrale Befehl zum Anzeigen von Systeminformationen ist SHOW. Ihn gibt es in diversen Ausführungen, manche von ihnen haben wir schon kennengelernt. Folgende Befehle sind möglich: SHOW DATABASES [LIKE filter] SHOW TABLES [FROM db_name] [LIKE filter] SHOW [FULL] COLUMNS FROM tbl_name [FROM db_name] [LIKE filter] SHOW FIELDS FROM tbl_name [FROM db_name] [LIKE filter] DESC[RIBE] tbl_name [filter] SHOW INDEX FROM tbl_name [FROM db_name] SHOW KEYS FROM tbl_name [FROM db_name] SHOW CREATE TABLE tbl_name SHOW OPEN TABLES [FROM db_name] [LIKE filter] SHOW TABLE STATUS [FROM db_name] [LIKE filter] SHOW STATUS [LIKE filter] SHOW VARIABLES [LIKE filter] SHOW LOGS SHOW [FULL] PROCESSLIST SHOW GRANTS FOR user SHOW MASTER STATUS SHOW SLAVE STATUS SHOW MASTER LOGS
4.4.1 Anzeigen von Strukturdaten Die folgenden Befehle dienen dazu, Informationen über angelegte Strukturen auszugeben. Dabei handelt es sich um Datenbanken, Tabellen, Spalten und Indizes. SHOW DATABASES
1. SHOW DATABASES [LIKE filter]
Mit diesem Befehl kann man sich alle Datenbanken des MySQLServers ausgeben lassen, mit dem man verbunden ist. Als Ergebnis erhält man eine einfache Liste: mysql> SHOW DATABASES; +-------------+ | Database | +-------------+ | musikschule | | mysql | | test | +-------------+
6WUXNWXUHOOHV
Sandini Bib
Mit LIKE filter lässt sich diese Liste eingrenzen. Dabei sind die üblichen Platzhalter aus der LIKE-Syntax nutzbar (% und _): mysql> SHOW DATABASES LIKE 'm%'; +---------------+ | Database (m%) | +---------------+ | musikschule | | mysql | +---------------+ 2. SHOW TABLES [FROM db_name] [LIKE filter]
SHOW TABLES
Auf diesem Weg lassen sich alle Tabellen der aktuell verwendeten Datenbank anzeigen: mysql> SHOW TABLES; +-----------------------+ | Tables_in_musikschule | +-----------------------+ | ausleihe | | instrument | | lehrer | | leihinstrument | | orchester | | orchester_tn | | raum | | schueler | | unterricht | | unterricht_tn | | warteliste | +-----------------------+
Auch hier kann man mit LIKE filter die Ausgabe wieder eingrenzen: mysql> SHOW TABLES LIKE 'l%'; +----------------------------+ | Tables_in_musikschule (l%) | +----------------------------+ | lehrer | | leihinstrument | +----------------------------+
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
Nutzt man FROM db_name, kann man sich auch die Tabellen einer anderen Datenbank ausgeben lassen: mysql> SHOW TABLES FROM mysql; +-----------------+ | Tables_in_mysql | +-----------------+ | columns_priv | | db | | func | | host | | tables_priv | | user | +-----------------+ SHOW COLUMNS
3. SHOW [FULL] COLUMNS FROM tbl_name [FROM db_name] [LIKE filter]
SHOW FIELDS FROM tbl_name [FROM db_name] [LIKE filter] SHOW COLUMNS bzw. SHOW FIELDS geben Informationen über die Spalten ei-
ner Tabelle aus. Ein Beispiel dafür ist am Anfang des Kapitels in Abbildung 4.1 zu sehen. Nutzt man die Option FULL (nur bei SHOW COLUMNS möglich), werden für jede Spalte auch die Rechte mit ausgegeben, die man daran besitzt. Dies ist für eine Mehrbenutzerverwaltung notwendig und wird im übernächsten Kapitel beim Thema Datenschutz besprochen. Ein Beispiel für eine solche Ausgabe ist in Abbildung 4.3 zu sehen. mysql> SHOW FULL COLUMNS FROM schueler; +--------------+------------------+------+-----+---------+----------------+---------------------------------+ | Field | Type | Null | Key | Default | Extra | Privileges | +--------------+------------------+------+-----+---------+----------------+---------------------------------+ | schueler_id | int(10) unsigned | | PRI | NULL | auto_increment | select,insert,update,references | | nachname | varchar(30) | | MUL | | | select,insert,update,references | | vorname | varchar(30) | YES | | NULL | | select,insert,update,references | | geburtsdatum | date | YES | | NULL | | select,insert,update,references | | strasse | varchar(30) | YES | | NULL | | select,insert,update,references | | plz | varchar(6) | YES | MUL | NULL | | select,insert,update,references | | ort | varchar(40) | YES | | NULL | | select,insert,update,references | | telefon | varchar(20) | YES | | NULL | | select,insert,update,references | | mobil | varchar(20) | YES | | NULL | | select,insert,update,references | | email | varchar(50) | YES | | NULL | | select,insert,update,references | | einzug | tinyint(1) | YES | | 0 | | select,insert,update,references | | bankname | varchar(58) | YES | | NULL | | select,insert,update,references | | blz | varchar(8) | YES | | NULL | | select,insert,update,references | | kontonr | varchar(10) | YES | | NULL | | select,insert,update,references | | kontoname | varchar(30) | YES | | NULL | | select,insert,update,references | | geschlecht | tinyint(1) | YES | | NULL | | select,insert,update,references | +--------------+------------------+------+-----+---------+----------------+---------------------------------+
Abbildung 4.3: Ausgabe von SHOW FULL COLUMNS
Die Spalten Field und Type sind sicherlich klar. Null gibt an, ob in einem Feld NULL-Werte erlaubt sind oder nicht. Key zeigt an, ob es sich um einen Primärschlüssel (PRI) oder einen anderen Schlüssel handelt (MUL). In Default ist festgelegt, welcher Wert standardmäßig eingetragen wird (wobei NULL genutzt wird, wenn keine anderen Angaben gemacht wurden). Extra zeigt weitere Optionen des Feldes an (zum Beispiel, ob es sich um ein Autoincrement-Feld handelt). Die Privileges schließlich geben an, welche Berechtigungen man besitzt (siehe übernächstes Kapitel).
6WUXNWXUHOOHV
Sandini Bib
Auch bei diesen Befehlen kann man mit LIKE die auszugebenden Felder eingrenzen: mysql> SHOW FIELDS FROM schueler LIKE 'g%'; +--------------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------+------+-----+---------+-------+ | geburtsdatum | date | YES | | NULL | | | geschlecht | tinyint(1) | YES | | NULL | | +--------------+------------+------+-----+---------+-------+
Und genau wie bei SHOW TABLES lässt sich mit FROM db_name auf Tabellen in einer anderen Datenbank zugreifen: mysql> SHOW COLUMNS FROM user FROM mysql; +-----------------+-----------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------------+-----------------+------+-----+---------+-------+ | Host | char(60) binary | | PRI | | | | User | char(16) binary | | PRI | | | | Password | char(16) binary | | | | | | Select_priv | enum('N','Y') | | | N | | | Insert_priv | enum('N','Y') | | | N | | | Update_priv | enum('N','Y') | | | N | | | Delete_priv | enum('N','Y') | | | N | | | Create_priv | enum('N','Y') | | | N | | | Drop_priv | enum('N','Y') | | | N | | | Reload_priv | enum('N','Y') | | | N | | | Shutdown_priv | enum('N','Y') | | | N | | | Process_priv | enum('N','Y') | | | N | | | File_priv | enum('N','Y') | | | N | | | Grant_priv | enum('N','Y') | | | N | | | References_priv | enum('N','Y') | | | N | | | Index_priv | enum('N','Y') | | | N | | | Alter_priv | enum('N','Y') | | | N | | +-----------------+-----------------+------+-----+---------+-------+
Alternativ zu tbl_name FROM db_name kann man auch die im SELECT-Befehl übliche Syntax db_name.tbl_name nutzen: mysql> SHOW COLUMNS FROM mysql.host;
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
+-----------------+-----------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------------+-----------------+------+-----+---------+-------+ | Host | char(60) binary | | PRI | | | | Db | char(64) binary | | PRI | | | | Select_priv | enum('N','Y') | | | N | | | Insert_priv | enum('N','Y') | | | N | | | Update_priv | enum('N','Y') | | | N | | | Delete_priv | enum('N','Y') | | | N | | | Create_priv | enum('N','Y') | | | N | | | Drop_priv | enum('N','Y') | | | N | | | Grant_priv | enum('N','Y') | | | N | | | References_priv | enum('N','Y') | | | N | | | Index_priv | enum('N','Y') | | | N | | | Alter_priv | enum('N','Y') | | | N | | +-----------------+-----------------+------+-----+---------+-------+ DESCRIBE
4. DESC[RIBE] tbl_name [filter]
Dieser Befehl entspricht einem SHOW COLUMNS FROM tbl_name [LIKE filter] und wurde eingeführt, um Oracle-Nutzern die Umstellung zu erleichtern. DESC ist eine Kurzform für DESCRIBE, die Filter funktionieren bei beiden Varianten wie bei SHOW COLUMNS: mysql> DESCRIBE unterricht 'uhrzeit%'; +-------------+------+------+-----+----------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+------+------+-----+----------+-------+ | uhrzeit_von | time | | | 00:00:00 | | | uhrzeit_bis | time | | | 00:00:00 | | +-------------+------+------+-----+----------+-------+
Um auf die Tabellen einer anderen Datenbank zuzugreifen, muss man mit der Punkt-Syntax (db_name.tbl_name) arbeiten, DESCRIBE tbl_name FROM db_name funktioniert nicht. SHOW INDEX
5. SHOW INDEX FROM tbl_name [FROM db_name]
SHOW KEYS FROM tbl_name [FROM db_name]
Dieser Befehl wurde weiter oben in Kapitel 4.3.3 schon beschrieben. SHOW KEYS ist äquivalent zu SHOW INDEX und der Zugriff auf Tabellen aus anderen Datenbanken funktioniert genauso wie bei den anderen SHOW-Befehlen. SHOW CREATE TABLE
6. SHOW CREATE TABLE tbl_name
Dieser Befehl gibt einen CREATE TABLE-Befehl aus, mit dem die Tabelle tbl_name neu angelegt werden kann. Die Ausgabe enthält nur zwei Spalten: Table und Create Table. Allerdings ist die zweite Spalte für sehr lange Werte ausgelegt, so dass sie nicht sinnvoll auf dem Bild-
6WUXNWXUHOOHV
Sandini Bib
schirm zu nutzen ist. Zudem enthält diese Spalte Zeilenumbrüche. Hier macht eine Ausgabeoption vom Clientprogramm mysql Sinn: Anstatt einen Befehl mit einem Semikolon abzuschließen, gibt man am Ende \G ein (mit großen G). Das hat zur Folge, dass die einzelnen Spalten nicht mehr nebeneinander, sondern untereinander ausgegeben werden: mysql> SHOW CREATE TABLE schueler\G *************************** 1. row *************************** Table: schueler Create Table: CREATE TABLE `schueler` ( `schueler_id` int(10) unsigned NOT NULL auto_increment, `nachname` varchar(30) NOT NULL default '', `vorname` varchar(30) default NULL, `geburtsdatum` date default NULL, `strasse` varchar(30) default NULL, `plz` varchar(6) default NULL, `ort` varchar(40) default NULL, `telefon` varchar(20) default NULL, `mobil` varchar(20) default NULL, `email` varchar(50) default NULL, `einzug` tinyint(1) default '0', `bankname` varchar(58) default NULL, `blz` varchar(8) default NULL, `kontonr` varchar(10) default NULL, `kontoname` varchar(30) default NULL, `geschlecht` tinyint(1) default NULL, PRIMARY KEY (`schueler_id`), KEY `idx_schueler_name` (`nachname`,`vorname`), KEY `idx_schueler_wohn` (`plz`,`ort`(5)) ) TYPE=MyISAM
Bei dieser Form der Anzeige wird erst die Zeilennummer ausgegeben (1. row), dann der Spaltenname und Feldinhalt, jeweils pro Spalte durch einen Zeilenumbruch getrennt. In dieser Form ließe sich der CREATE TABLE-Befehl leicht übernehmen. Wie man sieht, sind die Spaltennamen durch Anführungszeichen eingeschlossen, um nicht bei „seltsamen“ Spaltennamen ins Schleudern zu kommen. Stört einen diese Ausgabe, kann man vorher folgenden Befehl eingeben: mysql> SET SQL_QUOTE_SHOW_CREATE = 0;
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
Damit werden die Anführungszeichen unterdrückt: mysql> SHOW CREATE TABLE schueler\G *************************** 1. row *************************** Table: schueler Create Table: CREATE TABLE schueler ( schueler_id int(10) unsigned NOT NULL auto_increment, nachname varchar(30) NOT NULL default '', vorname varchar(30) default NULL, geburtsdatum date default NULL, strasse varchar(30) default NULL, plz varchar(6) default NULL, ort varchar(40) default NULL, telefon varchar(20) default NULL, mobil varchar(20) default NULL, email varchar(50) default NULL, einzug tinyint(1) default '0', bankname varchar(58) default NULL, blz varchar(8) default NULL, kontonr varchar(10) default NULL, kontoname varchar(30) default NULL, geschlecht tinyint(1) default NULL, PRIMARY KEY (schueler_id), KEY idx_schueler_name (nachname,vorname), KEY idx_schueler_wohn (plz,ort(5)) ) TYPE=MyISAM
Dies kann allerdings zu Problemen führen, wenn die Spaltennamen mit definierten MySQL-Befehlen übereinstimmen. Aktiviert werden die Anführungszeichen wieder mit: mysql> SET SQL_QUOTE_SHOW_CREATE = 1;
Die Ausgabe mit \G kann man übrigens auch bei ganz normalen Tabellen nutzen: mysql> SELECT * FROM schueler\G *************************** 1. row *************************** schueler_id: 1 nachname: Schmidt vorname: Thomas geburtsdatum: 1992-06-09 strasse: Hauptstr. 16 plz: 28219 ort: Bremen telefon: 0421/1123456 mobil: NULL email: NULL
6WUXNWXUHOOHV
Sandini Bib
einzug: 0 bankname: NULL blz: NULL kontonr: NULL kontoname: NULL geschlecht: 0 *************************** 2. row *************************** schueler_id: 2 nachname: Mayer vorname: Anke geburtsdatum: 1971-04-24 strasse: Leher Heerstr. 342 plz: 68219 ort: Mannheim telefon: 0621/1129875 mobil: 0171/9998765 email: [email protected] einzug: 1 bankname: SEB AG Mannheim blz: 11122233 kontonr: 9998846 kontoname: Hans Mayer geschlecht: 1 *************************** 3. row *************************** schueler_id: 3 nachname: Meier vorname: Frank geburtsdatum: 1980-07-31 strasse: Waldweg 30 plz: 69190 ort: Walldorf telefon: 06227/1124565 mobil: NULL email: [email protected] einzug: 1 bankname: Sparkasse Rhein Neckar Nord blz: 1112234 kontonr: 9997755 kontoname: NULL geschlecht: 0 *************************** 4. row *************************** schueler_id: 4 nachname: Schulze vorname: Friederike geburtsdatum: 1990-01-07 strasse: Relaisstr. 432
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
plz: ort: telefon: mobil: email: einzug: bankname: blz: kontonr: kontoname: geschlecht:
41564 Kaarst 02131/1125464 NULL [email protected] 0 NULL NULL NULL NULL 1
4.4.2 Anzeigen weiterer Informationen Mit SHOW lassen sich neben den Strukturinformationen auch noch andere Parameter von Tabellen und dem MySQL-Server recherchieren: SHOW OPEN TABLES
1. SHOW OPEN TABLES [FROM db_name] [LIKE filter]
Mit diesem Befehl kann man sich die Tabellen der aktuellen Datenbank anzeigen lassen, die momentan im Tabellencache zwischengespeichert sind. Dieser Cache ist ein Speicherbereich im RAM, in dem Tabelleninhalte aufbewahrt werden, wenn gerade mit ihnen gearbeitet wurde. Da es sehr wahrscheinlich ist, dass die zuletzt verwendeten Tabellen erneut genutzt wird, lässt sich auf diesem Weg der Zugriff deutlich beschleunigen. Im Feld Comment ist angegeben, wie oft die entsprechende Tabelle zwischengespeichert ist und wie oft sie sich gerade in Verwendung befindet: mysql> SHOW OPEN TABLES; +----------------------------+--------------------+ | Open_tables_in_musikschule | Comment | +----------------------------+--------------------+ | lehrer | cached=1, in_use=0 | | schueler | cached=1, in_use=0 | +----------------------------+--------------------+
Mit dem Befehl FLUSH TABLES und seinen Derivaten kann man diesen Cache leeren, falls man das Gefühl hat, dass ein „Neuanfang“ aus Performancegründen besser wäre: mysql> FLUSH TABLES; Query OK, 0 rows affected (0.02 sec) mysql> SHOW OPEN TABLES; Empty set (0.00 sec)
6WUXNWXUHOOHV
Sandini Bib
Mit FROM db_name lassen sich die Tabellen der entsprechenden Datenbank anzeigen und mit LIKE filter kann man die Ausgabe wie üblich einschränken: mysql> SHOW OPEN TABLES FROM test LIKE 'my%'; +---------------------------+--------------------+ | Open_tables_in_test (my%) | Comment | +---------------------------+--------------------+ | mytable | cached=1, in_use=0 | +---------------------------+--------------------+ 2. SHOW TABLE STATUS [FROM db_name] [LIKE filter]
SHOW TABLE
Dieser Befehl zeigt Informationen über verschiedene Tabellenparameter an. Wie in Abbildung 4.4 zu sehen, handelt es sich um eine ganze Reihe von Spalten, daher nutze ich zur Demonstration noch einmal die Variante mit \G für eine einzelne Tabelle:
STATUS
mysql> SHOW TABLE STATUS LIKE 'sch%'\G *************************** 1. row *************************** Name: schueler Type: MyISAM Row_format: Dynamic Rows: 4 Avg_row_length: 117 Data_length: 468 Max_data_length: 4294967295 Index_length: 4096 Data_free: 0 Auto_increment: 5 Create_time: 2002-09-28 21:50:55 Update_time: 2002-09-28 21:50:55 Check_time: 2002-09-28 21:50:55 Create_options: Comment:
Die einzelnen Spalten sind in Tabelle 4.3 erläutert. Spalte
Beschreibung
Name
Name der Tabelle
Type
Tabellentyp (MyISAM, InnoDB, …)
Row_format
Art der Datensatzspeicherung: Fixed, Dynamic oder Compressed (abhängig von den Spaltentypen und Tabellenoptionen)
Rows
Anzahl der Datensätze
Avg_row_length
durchschnittliche Datensatzlänge
Data_length
Größe der Datendatei
Tabelle 4.3: Spalten bei der Ausgabe von SHOW TABLE STATUS
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
Spalte
Beschreibung
Max_data_length
maximal mögliche Größe der Datendatei
Index_length
Größe der Indexdatei
Data_free
Anzahl der reservierten, aber nicht genutzten Bytes
Auto_increment
nächster Wert beim Verwenden des AUTO_INCREMENT-Mechanismus
Create_time
Erstellungszeitpunkt der Tabelle
Update_time
Zeitpunkt der letzten Änderung an der Tabelle
Check_time
Zeitpunkt der letzten Tabellenprüfung
Create_options
Optionen, die bei CREATE TABLE angegeben wurden
Comment
Kommentar, der beim Erstellen der Tabelle angegeben wurde
Tabelle 4.3: Spalten bei der Ausgabe von SHOW TABLE STATUS (Forts.)
mysql> SHOW TABLE STATUS; +----------------+--------+------------+------+----------------+-------------+-----------------+--------------+-----------+ | Name | Type | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | +----------------+--------+------------+------+----------------+-------------+-----------------+--------------+-----------+ | ausleihe | MyISAM | Fixed | 0 | 0 | 0 | 81604378623 | 1024 | 0 | 8 | 29 | | instrument 236 | 4294967295 | | MyISAM | Dynamic 2048 | | 0 | | lehrer | MyISAM | Dynamic | 4 | 124 | 496 | 4294967295 | 2048 | 0 | | 4 | 34 | | leihinstrument | MyISAM | Dynamic 136 | 4294967295 | 2048 | 0 | | 0 | 0 | | orchester 0 | 4294967295 | | MyISAM | Dynamic 1024 | 0 | 1024 | 0 | 0 | 0 | 73014444031 | | 0 | | orchester_tn | MyISAM | Fixed | 5 | 20 | | raum 100 | 4294967295 | | MyISAM | Dynamic 3072 | 0 | | 4 | 117 | | schueler 468 | 4294967295 | | MyISAM | Dynamic 4096 | 0 | 2048 | | 5 | 0 | 31 | | unterricht 155 | 133143986175 | | MyISAM | Fixed 2048 | | 4 | 0 | 13 | | unterricht_tn | MyISAM | Fixed 52 | 55834574847 | 1024 | | 0 | 0 | 0 | | warteliste 0 | 68719476735 | | MyISAM | Fixed +----------------+--------+------------+------+----------------+-------------+-----------------+--------------+-----------+
Fortsetzung +----------------+---------------------+---------------------+---------------------+----------------+---------+ | Update_time | Check_time | Auto_increment | Create_time | Create_options | Comment | +----------------+---------------------+---------------------+---------------------+----------------+---------+ | | 1 | 2002-09-19 21:49:54 | 2002-09-19 21:49:54 | NULL | | | | 9 | 2002-09-19 21:49:54 | 2002-09-19 22:13:37 | NULL | | | | 5 | 2002-09-19 21:49:54 | 2002-09-21 17:00:18 | NULL | | | | 5 | 2002-09-19 21:49:54 | 2002-09-19 22:13:37 | NULL | | | | 1 | 2002-09-19 21:49:54 | 2002-09-19 21:49:54 | NULL | | | | 1 | 2002-09-19 21:49:54 | 2002-09-19 21:49:54 | NULL | | | | | 6 | 2002-09-24 21:38:22 | 2002-09-24 21:38:22 | 2002-09-24 21:38:22 | | | | 5 | 2002-09-28 21:50:55 | 2002-09-28 21:50:55 | 2002-09-28 21:50:55 | | | 6 | 2002-09-19 21:49:54 | 2002-09-19 22:13:37 | NULL | | | | 5 | 2002-09-19 21:49:54 | 2002-09-19 22:13:37 | NULL | | | | 1 | 2002-09-19 21:49:54 | 2002-09-19 21:49:54 | NULL | | +----------------+---------------------+---------------------+---------------------+----------------+---------+
Abbildung 4.4: Ausgabe des Befehls SHOW TABLE STATUS
Auch bei diesem kann man mit FROM db_name die Datenbank bestimmen, für die die Tabelleninformationen ausgegeben werden sollen (Standard ist die aktuelle Datenbank). Und mit LIKE filter lässt sich ebenso die Ausgabe auf bestimmte Tabellennamen beschränken. SHOW STATUS
3. SHOW STATUS [LIKE filter]
Dieser Befehl gibt diverse Informationen über den aktuellen Stand des MySQL-Servers aus. Die Ausgabe ist wie üblich eine einfache Tabelle, die auch noch mit LIKE filter eingeschränkt werden kann. Die einzelnen Zeilen sollen nicht genauer beschrieben werden, aber es gibt Informationen über Verbindungsabbrüche, Netzwerknutzung, verwendete temporäre Tabellen, Lese- und Schreibanforderungen, verwendete Datei- und Tabellenhandler, Joins, Sortiervorgänge und Threads:
6WUXNWXUHOOHV
Sandini Bib
mysql> SHOW STATUS; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | Aborted_clients | 0 | | Aborted_connects | 0 | | Bytes_received | 0 | | Bytes_sent | 0 | | Com_admin_commands | 0 | | Com_alter_table | 0 | | Com_analyze | 0 | | Com_backup_table | 0 | | Com_begin | 0 | | Com_change_db | 3 | | Com_change_master | 0 | | Com_check | 0 | | Com_commit | 0 | | Com_create_db | 0 | | Com_create_function | 0 | | Com_create_index | 0 | | Com_create_table | 0 | | Com_delete | 0 | | Com_drop_db | 0 | | Com_drop_function | 0 | | Com_drop_index | 0 | | Com_drop_table | 0 | | Com_flush | 1 | | Com_grant | 0 | | Com_insert | 1 | | Com_insert_select | 0 | | Com_kill | 0 | | Com_load | 0 | | Com_load_master_table | 0 | | Com_lock_tables | 0 | | Com_optimize | 0 | | Com_purge | 0 | | Com_rename_table | 0 | | Com_repair | 0 | | Com_replace | 0 | | Com_replace_select | 0 | | Com_reset | 0 | | Com_restore_table | 0 | | Com_revoke | 0 | | Com_rollback | 0 | | Com_select | 6 | | Com_set_option | 0 |
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
Com_show_binlogs Com_show_create Com_show_databases Com_show_fields Com_show_grants Com_show_keys Com_show_logs Com_show_master_status Com_show_open_tables Com_show_processlist Com_show_slave_status Com_show_status Com_show_tables Com_show_variables Com_slave_start Com_slave_stop Com_truncate Com_unlock_tables Com_update Connections Created_tmp_disk_tables Created_tmp_tables Created_tmp_files Delayed_insert_threads Delayed_writes Delayed_errors Flush_commands Handler_delete Handler_read_first Handler_read_key Handler_read_next Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_update Handler_write Key_blocks_used Key_read_requests Key_reads Key_write_requests Key_writes Max_used_connections Not_flushed_key_blocks Not_flushed_delayed_rows Open_tables Open_files
6WUXNWXUHOOHV
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
0 0 0 1 0 0 0 0 8 0 0 2 7 0 0 0 0 0 0 2 0 0 0 0 0 0 2 0 1 0 0 0 0 45 0 2 0 0 0 0 0 0 0 0 12 24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
Sandini Bib
| Open_streams | 0 | | Opened_tables | 32 | | Questions | 31 | | Select_full_join | 0 | | Select_full_range_join | 0 | | Select_range | 0 | | Select_range_check | 0 | | Select_scan | 6 | | Slave_running | OFF | | Slave_open_temp_tables | 0 | | Slow_launch_threads | 0 | | Slow_queries | 0 | | Sort_merge_passes | 0 | | Sort_range | 0 | | Sort_rows | 0 | | Sort_scan | 0 | | Table_locks_immediate | 48 | | Table_locks_waited | 0 | | Threads_cached | 0 | | Threads_created | 1 | | Threads_connected | 1 | | Threads_running | 1 | | Uptime | 12030 | +--------------------------+-------+ 4. SHOW VARIABLES [LIKE filter]
SHOW VARIABLES
Während SHOW STATUS Informationen über den aktuellen Gemütszustand des MySQL-Servers ausgibt, kann man sich mit SHOW VARIABLES die Startoptionen und Systemvariablen von MySQL anzeigen lassen. Diese Werte lassen sich im Allgemeinen per Kommandozeilenoption mysqld mitgeben oder in den entsprechenden Konfigurationsdateien angeben, falls einem die Standardwerte nicht helfen. Auch bei SHOW VARIABLES kann man mit LIKE filter die Ausgabe einschränken. In der folgenden Ausgabe wurde allerdings die Zeile character_sets etwas umformatiert, um die Ausgabe noch halbwegs im Buch unterbringen zu können… +---------------------------------+---------------------------+ | Variable_name | Value | +---------------------------------+---------------------------+ | back_log | 50 | | basedir | d:\mysql\ | | binlog_cache_size | 32768 | | character_set | german1 | | character_sets | latin1 big5 czech euc_kr | | | gb2312 gbk sjis tis620 |
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
concurrent_insert connect_timeout datadir delay_key_write delayed_insert_limit delayed_insert_timeout delayed_queue_size flush flush_time have_bdb have_gemini have_innodb have_isam have_raid have_openssl init_file interactive_timeout join_buffer_size key_buffer_size language large_files_support log log_update log_bin log_slave_updates log_long_queries long_query_time low_priority_updates lower_case_table_names max_allowed_packet max_binlog_cache_size max_binlog_size max_connections max_connect_errors max_delayed_threads max_heap_table_size max_join_size max_sort_length max_user_connections
6WUXNWXUHOOHV
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
ujis dec8 dos german1 hp8 koi8_ru latin2 swe7 usa7 cp1251 danish hebrew win1251 estonia hungarian koi8_ukr win1251ukr greek win1250 croat cp1257 latin5 ON 5 d:\mysql\data\ ON 100 300 1000 OFF 1800 NO NO NO YES NO NO 28800 131072 16773120 d:\mysql\share\english\ ON OFF OFF OFF OFF OFF 10 OFF 1 1047552 4294967295 1073741824 100 10 20 16777216 4294967295 1024 0
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
Sandini Bib
| max_tmp_tables | 32 | | max_write_lock_count | 4294967295 | | myisam_max_extra_sort_file_size | 256 | | myisam_max_sort_file_size | 2047 | | myisam_recover_options | 0 | | myisam_sort_buffer_size | 8388608 | | named_pipe | OFF | | net_buffer_length | 7168 | | net_read_timeout | 30 | | net_retry_count | 10 | | net_write_timeout | 60 | | open_files_limit | 0 | | pid_file | d:\mysql\data\kruemel.pid | | port | 3306 | | protocol_version | 10 | | record_buffer | 131072 | | record_rnd_buffer | 131072 | | query_buffer_size | 0 | | safe_show_database | OFF | | server_id | 1 | | slave_net_timeout | 3600 | | skip_locking | ON | | skip_networking | OFF | | skip_show_database | OFF | | slow_launch_time | 2 | | socket | MySQL | | sort_buffer | 524280 | | sql_mode | 0 | | table_cache | 64 | | table_type | MYISAM | | thread_cache_size | 0 | | thread_stack | 65536 | | transaction_isolation | READ-COMMITTED | | timezone | Westeuropäische Sommerzeit| | tmp_table_size | 33554432 | | tmpdir | D:\WINNT\TEMP\ | | version | 3.23.51-nt | | wait_timeout | 28800 | +---------------------------------+---------------------------+ 5. SHOW LOGS
SHOW LOGS
Dieser Befehl gibt Daten zu Protokolldateien aus, die allerdings nur bei Datenbanken vom Typ „Berkley DB“ vorhanden sind. Ansonsten wird man keine Daten erhalten.
6\VWHPLQIRUPDWLRQHQ
Sandini Bib
SHOW PROCESSLIST
6. SHOW [FULL] PROCESSLIST
Mit SHOW PROCESSLIST lassen sich die aktuell laufenden Prozesse des MySQL-Servers anzeigen. Hat man die Berechtigung process, sieht man alle Prozesse, ansonsten nur die eigenen. (Zu Berechtigungen siehe späteres Kapitel.) Normalerweise werden nur die ersten 100 Zeichen eines Prozesses ausgegeben. Will man die komplette Liste sehen, muss man SHOW FULL PROCESSLIST nutzen. Dieser Befehl kann nützlich sein, wenn der MySQL-Server keine Anfragen mehr annimmt und man sehen möchte, was gerade auf dem Server los ist. Zu diesem Zweck kann auch immer ein Client diese Abfrage ausführen. Ist gerade nichts auf dem Server los, wird man nur die eigene Abfrage sehen (siehe Abbildung 4.5) mysql> SHOW PROCESSLIST; +----+------+-----------+-------------+---------+------+-------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+-------------+---------+------+-------+------------------+ | 1 | ODBC | localhost | musikschule | Query | 0 | NULL | SHOW PROCESSLIST | +----+------+-----------+-------------+---------+------+-------+------------------+
Abbildung 4.5: Ausgabe des Befehls SHOW PROCESSLIST SHOW GRANTS
7. SHOW GRANTS FOR user
Dieser Befehl gibt die Befehle aus, die man angeben muss, um die gleichen Berechtigungen zu erhalten wie die von user. Wir werden uns im späteren Kapitel über Berechtigungen genauer damit befassen. SHOW MASTER/SLAVE
8. SHOW MASTER STATUS
SHOW SLAVE STATUS SHOW MASTER LOGS
Mit diesen Befehlen kann man sich Informationen über den Replikationsstatus ausgeben lassen, wenn man die Replikationsmechanismen von MySQL nutzt. Damit kann man Daten von einem Server auf andere verteilen, um zum Beispiel die Zugriffslast zu reduzieren oder die Zugriffsgeschwindigkeit zu erhöhen. Der Bereich Replikation soll hier aber nicht genauer beschrieben werden.
6WUXNWXUHOOHV
Sandini Bib
4.5
Optimieren von Tabellen
Es gibt zwei Befehle, die dabei helfen, schneller auf Tabelleninhalte zugreifen zu können. OPTIMIZE TABLE ordnet die Daten neu auf der Festplatte, während ANALYZE TABLE die Verteilung der Schlüsselwerte untersucht und die Statistiken über die Tabelle aktualisiert.
4.5.1 OPTIMIZE TABLE Mit diesem Befehl kann man dafür sorgen, dass die Tabelleninhalte auf der Festplatte möglichst sinnvoll und platzsparend gespeichert werden. Die Syntax ist ganz einfach:
OPTIMIZE TABLE
OPTIMIZE TABLE tbl_name [, tbl_name, ...]
Wie man sieht, kann man mehrere Tabellen gleichzeitig angeben, die dann nacheinander optimiert werden. Beim Optimieren wird zunächst der Platz von gelöschten Datensätzen freigegeben und die bestehenden Datensätze zusammenhängend abgespeichert. Aufgeteilte Daten werden wieder zusammengeführt. Sind die Speicherbereiche für die Indizes nicht sortiert abgelegt, wird dies nun durchgeführt. Wenn zudem die Statistiken der Tabellen nicht mehr aktuell sind, werden diese auf den neuesten Stand gebracht. Als Ausgabe erhält man eine Tabelle mit einer Übersicht über die bearbeiteten Tabellen und den Status: mysql> OPTIMIZE TABLE schueler; +----------------------+----------+----------+----------+ | Table | Op | Msg_type | Msg_text | +----------------------+----------+----------+----------+ | musikschule.schueler | optimize | status | OK | +----------------------+----------+----------+----------+
Man sollte OPTIMIZE TABLE dann benutzen, wenn viele Datensätze gelöscht oder in einer Tabelle mit variablen Spalten (VARCHAR usw.) eine Menge Datensätze geändert wurden. In diesem Fall kann es sein, dass viel Speicher fragmentiert ist und nicht richtig wiederverwertet werden kann. Durch die Optimierung der Tabelle wird dieser Speicher wieder zusammenhängend freigegeben.
Sandini Bib
4.5.2 ANALYZE TABLE ANALYZE TABLE
ANALYZE TABLE dient dazu, die Statistiken für eine Tabelle zu aktualisieren. Die Syntax ist ebenso einfach wie bei OPTIMIZE TABLE: ANALYZE TABLE tbl_name [, tbl_name, ...]
Auch hier können wieder mehrere Tabellen nacheinander abgearbeitet werden. Bei jeder Tabelle wird die Verteilung der Schlüsselwerte untersucht und abgespeichert. Mit diesen Informationen kann MySQL beim Verknüpfen von Tabellen feststellen, auf welche Weise es vorgehen sollte, um möglichst schnell ein Ergebnis liefern zu können. Auch ANALYZE TABLE liefert eine Tabelle mit Informationen zurück (siehe Abbildung 4.6). mysql> ANALYZE TABLE instrument, lehrer; +------------------------+---------+----------+-----------------------------+ | Table | Op | Msg_type | Msg_text | +------------------------+---------+----------+-----------------------------+ | musikschule.instrument | analyze | status | Table is already up to date | | musikschule.lehrer | analyze | status | OK | +------------------------+---------+----------+-----------------------------+
Abbildung 4.6: Ausgabe von ANALYZE TABLE
4.6
Fragen
1. Wie kann man nach dem Anlegen einer Tabelle dafür sorgen, dass
eine Spalte nur eindeutige Werte enthält? 2. Wo kann man feststellen, wieviele unterschiedliche Werte es in einer
indizierten Spalte gibt? 3. Auf welchem Weg lässt sich der CREATE TABLE-Befehl für eine Tabel-
le rekonstruieren? 4. Warum sollte man Indizes nur für die häufigsten Abfragen erstellen?
6WUXNWXUHOOHV
Sandini Bib
5
Zugriffsmöglichkeiten
le
n e rn
Man kann auf vielerlei Wegen auf die Daten eines MySQL-Servers zugreifen. Bisher haben wir immer das Client-Programm mysql genutzt. Natürlich ist das aber kein sinnvoller Weg für Endbenutzer. Für diese Gruppe schreibt man im Allgemeinen ein eigenes Programm, welches über eine der angebotenen Schnittstellen mit dem MySQL-Server kommuniziert. Zudem gibt es einige Programme, die einen Administrator bei seiner Arbeit mit dem MySQL-Server unterstützen, sei es grafisch oder textuell ausgerichtet. Um die Daten „am Stück“ aus dem Server heraus- und in einen anderen wieder hineinzubekommen, kann man auch verschiedene Wege beschreiten.
5.1
Was Sie in diesem Kapitel lernen
Zunächst wollen wir uns mit den verschiedenen Tools auseinandersetzen, die Zugriff auf den MySQL-Server bzw. die von ihm verwalteten Daten gewähren. Das wichtigste ist dabei natürlich mysql, es gibt aber noch einige weitere, die eher der Administration dienen. Danach werden wir phpMyAdmin vorstellen, ein von Dritten programmiertes Tool, das dem Benutzer eine grafische Oberfläche präsentieren kann, welche den Dialog mit dem Server hübscher verpackt als dies Textprogramme ermöglichen (welche dafür natürlich andere Vorteile haben). Zudem läuft es unter einer Web-Oberfläche und kann somit mit jedem Browser bedient werden. Zum Dritten wollen wir uns mit dem Im- und Export von Daten kümmern. Damit kann man Datenbanken von einem zum anderen Rechner verschieben, aber auch Daten aus anderen Quellen in MySQL importieren.
:DV 6LH LQ GLHVHP .DSLWHO OHUQHQ
Sandini Bib
Schließlich befassen wir uns mit den Programmierschnittstellen von MySQL. Man kann aus den verschiedensten Programmiersprachen heraus auf den Server zugreifen, aber auch sprachunabhängige Schnittstellen wie ODBC nutzen.
5.2
Admin-Tools zu MySQL
Mit MySQL werden schon viele Tools direkt mitgeliefert. Diese sollen dem Administrator die Arbeit mit dem MySQL-Server erleichtern. Das am häufigsten verwendete dürfte natürlich mysql sein. Wir haben die ganze Zeit damit gearbeitet und werden hier noch ein paar ergänzende Möglichkeiten erwähnen. Weiterhin stehen für den Zugriff auf den MySQL-Server weitere Tools bereit. Im Folgenden werden wir mysqladmin und mysqlshow besprechen. Die Tools mysqldump und mysqlimport werden wir detaillierter in Kapitel 5.4 behandeln, da sie zum Im- und Exportieren von Daten genutzt werden. mysqlhotcopy dient zum Sichern von Daten und wird im nächsten Kapitel besprochen, ebenso wie myisamchk und mysqlcheck, welche zum Prüfen und Reparieren von Tabellen und Datenbanken dienen.
5.2.1 mysql mysql ist das Tool, mit dem man vermutlich außerhalb des eigentlichen Programms für die Endbenutzer am häufigsten arbeiten wird. Wir haben schon sehr viel damit gemacht und werden hier nur noch auf einige weitere Punkte eingehen, die das Leben einfacher machen können. TEE / NOTEE TEE
Mit dem Befehl TEE kann man dafür sorgen, dass die Ausgabe neben dem Bildschirm gleichzeitig auch in eine Datei geschrieben wird. Als Parameter gibt man den Pfad und Namen einer Datei an. Diese Datei wird als Ziel genommen, um die Ein- und Ausgaben, die man auch auf dem Bildschirm sieht, abzuspeichern. Dabei wird immer an das Ende der Datei angehängt, so dass auch nach einem Neustart die alten Daten nicht verloren sind. Mit NOTEE beendet man das Protokollieren. Ein Beispiel für eine solche Log-Datei wäre: Logging to file 'd:/temp/test.txt' mysql> SELECT * FROM raum;
=XJULIIVP|JOLFKNHLWHQ
Sandini Bib
+---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | | 4 | A5.21 | 5 | 6 | | 5 | A5.23 | 5 | 6 | +---------+-----------+-------+----------+ 5 rows in set (0.00 sec) mysql> UPDATE schueler SET nachname = 'Meier' WHERE schueler_id = 11; Query OK, 0 rows affected (0.02 sec) Rows matched: 0 Changed: 0 Warnings: 0 mysql> NOTEE
Direkt vor dem „Logging to file…“ wurde der Befehl TEE d:/temp/test.txt aufgerufen (übrigens wieder ohne Semikolon). Ruft man nach einem NOTEE den Befehl TEE erneut auf, ohne aber einen Dateinamen angegeben zu haben, wird in die letzte Datei protokolliert, die verwendet wurde. STATUS Mit diesem Befehl kann man sich Statusinformationen über den Server, den Client und die Verbindung anzeigen lassen. Auch er wird ohne Semikolon aufgerufen:
STATUS
mysql> STATUS -------------D:\mysql\bin\mysql.exe Ver 11.18 Distrib 3.23.51, for Win95/Win98 (i32) Connection id: Current database: Current user: Server version: Protocol version: Connection: Client characterset: Server characterset: TCP port: Uptime:
1 musikschule ODBC@localhost 3.23.51-nt 10 localhost via TCP/IP german1 german1 3306 12 hours 37 min 35 sec
Threads: 1 Questions: 66 Slow queries: 0 Opens: 24 Flush tables: 1 Open tables: 2 Queries per second avg: 0.001 --------------
Sandini Bib
Zunächst folgt eine Angabe des aktuellen Programms mit seinem Stand und der Distribution. Die Connection id gibt dann die Kennung an, über die das Client-Programm aktuell mit dem Server verbunden ist. Sie kann genutzt werden, um unter anderem mit mysqladmin Clients „abzuschießen“, die sich aufgehängt haben oder anderen Ärger bereiten. Die Current database gibt natürlich an, welche Datenbank gerade ausgewählt ist, und der Current user zeigt, wer man gerade ist. Server version und Protocol version geben Informationen über den Stand des Servers und die Protokollversion. Connection sagt, wie man mit dem Server verbunden ist, Client characterset und Server characterset informieren über den genutzten Zeichensatz, der TCP port steht für den Port, über den der MySQL-Server kommuniziert und die Uptime steht für die Laufzeit, die der Server schon ohne Unterbrechung läuft. Am Ende finden sich noch ein paar Informationen zur Statistik. Umleiten der Ein- und Ausgabe Redirection
Hat man mehrere Befehle in einer festen Reihenfolge zu verarbeiten, kann man diese in einer Textdatei speichern (gerne mit der Endung .sql) und entweder in mysql mit dem Befehl SOURCE ausführen lassen (wie schon weiter oben behandelt), oder direkt beim Aufruf per Umleitung der Eingabe mitgeben. Speichern wir zum Beispiel die folgenden Befehle in einer Datei namens mycommand.sql: SHOW DATABASES; USE musikschule; SELECT * FROM raum; SELECT * FROM instrument;
Wenn man nun mysql diese Datei als Eingabe mitgibt, erhält man folgende Ausgabe: prompt>mysql < mycommands.sql Database musikschule mysql test raum_id raum_name etage 1 A5.33 5 4 2 B2.02 2 20 3 C4.16 4 11 4 A5.21 5 6 5 A5.23 5 6
=XJULIIVP|JOLFKNHLWHQ
personen
Sandini Bib
instrument_id instr_name instr_gruppe 1 Querflöte Holzbläser 2 Klarinette Holzbläser 3 Violine Streicher 4 Viola Streicher 5 Posaune Blechbläser 6 Trompete Blechbläser 7 Klavier Tasten 8 Keyboard Tasten
Wie man sieht, werden keine weiteren Informationen neben den Daten selber und den Spaltenüberschriften mitgegeben. Getrennt werden die Daten mit dem Tabulatorzeichen. Dieses Ausgabeformat wird dann verwendet, wenn MySQL im sogenannten Batch-Modus läuft. Das Verhalten lässt sich auch erzwingen oder verhindern, dazu benötigt man die im nächsten Abschnitt beschriebenen Kommandozeilen-Parameter. Die Ausgabe lässt sich natürlich auch direkt in eine Datei umleiten. Das erreicht man mit folgendem Aufruf: prompt>mysql < mycommands.sql > myresult.txt
Kommandozeilen-Parameter Es gibt (neben -h, -u und -p) einige Parameter, die man beim Starten von mysql angeben kann und die in manchen Situationen helfen können. Fünf möchte ich hier aufführen, es gibt aber noch diverse mehr. • -? oder --help Dieser Parameter dürfte der wichtigste sein… Er gibt die möglichen Kommandozeilen-Parameter aus und beendet das Programm dann wieder. Das Ergebnis sehen Sie hier: prompt>mysql -? mysql Ver 11.18 Distrib 3.23.51, for Win95/Win98 (i32) Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB This software comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to modify and redistribute it under the GPL license Usage: mysql [OPTIONS] [database] -?, --help Display this help and exit. -A, --no-auto-rehash No automatic rehashing. One has to use 'rehash' to get table and field completion. This gives a quicker start of mysql and disables rehashing on reconnect. -B, --batch Print results with a tab as separator, each row on a new line. Doesn't use history file.
Sandini Bib
--character-sets-dir=... Directory where character sets are located. -C, --compress Use compression in server/client protocol. -D, --database=.. Database to use. --default-character-set=... Set the default character set. -e, --execute=... Execute command and quit. (Output like with --batch) -E, --vertical Print the output of a query (rows) vertically. -f, --force Continue even if we get an sql error. -g, --no-named-commands Named commands are disabled. Use \* form only, or use named commands only in the beginning of a line ending with a semicolon (;) Since version 10.9 the client now starts with this option ENABLED by default! Disable with '-G'. Long format commands still work from the first line. -G, --enable-named-commands Named commands are enabled. Opposite to -g. -i, --ignore-spaces Ignore spaces after function names. -h, --host=... Connect to host. -H, --html Produce HTML output. --local-infile=[1|0] Enable/disable LOAD DATA LOCAL INFILE -L, --skip-line-numbers Don't write line number for errors. --no-tee Disable outfile. See interactive help (\h) also. -n, --unbuffered Flush buffer after each query. -N, --skip-column-names Don't write column names in results. -O, --set-variable var=option Give a variable an value. --help lists variables. -o, --one-database Only update the default database. This is useful for skipping updates to other database in the update log. -p[password], --password[=...] Password to use when connecting to server If password is not given it's asked from the tty. -W, --pipe Use named pipes to connect to server -P, --port=... -q, --quick
-r, --raw
Port number to use for connection. Don't cache result, print it row by row. This may slow down the server if the output is suspended. Doesn't use history file. Write fields without conversion. Used with --batch
=XJULIIVP|JOLFKNHLWHQ
Sandini Bib
-s, --silent -S --socket=... -t, --table -T, --debug-info --tee=... -u, -U, -v, -V, -w,
Be more silent. Socket file to use for connection. Output in table format. Print some debug info at exit. Append everything into outfile. See interactive help (\h) also. Does not work in batch mode. --user=# User for login if not current user. --safe-updates[=#], --i-am-a-dummy[=#] Only allow UPDATE and DELETE that uses keys. --verbose Write more. (-v -v -v gives the table output format) --version Output version information and exit. --wait Wait and retry if connection is down.
Default options are read from the following files in the given order: D:\WINNT\my.ini C:\my.cnf The following groups are read: mysql client The following options may be given as the first argument: --print-defaults Print the program argument list and exit --no-defaults Don't read default options from any options file --defaults-file=# Only read default options from the given file # --defaults-extra-file=# Read this file after the global files are read Possible variables for option connect_timeout current max_allowed_packet current net_buffer_length current select_limit current max_join_size current
--set-variable (-O) are: value: 0 value: 16777216 value: 16384 value: 1000 value: 1000000
• -e oder --execute Mit diesem Parameter kann man einzelne Befehle ausführen lassen. Das Programm wird dann direkt danach beendet. Der Befehl muss in Anführungszeichen stehen. Damit kann man sich einzelne Tabelleninhalte oder andere Informationen ausgeben lassen, ohne erst aufwändiger eine Befehlsdatei zu erstellen. Ein Beispiel dafür ist: prompt>mysql -e "SELECT * FROM raum;" musikschule +---------+-----------+-------+----------+ | raum_id | raum_name | etage | personen | +---------+-----------+-------+----------+ | 1 | A5.33 | 5 | 4 | | 2 | B2.02 | 2 | 20 | | 3 | C4.16 | 4 | 11 | | 4 | A5.21 | 5 | 6 | | 5 | A5.23 | 5 | 6 | +---------+-----------+-------+----------+
Sandini Bib
Nutzt man die lange Version des Parameters, sieht das Ganze so aus: prompt>mysql --execute="SHOW TABLES;" musikschule +-----------------------+ | Tables_in_musikschule | +-----------------------+ | ausleihe | | fulltexttest | | instrument | | lehrer | | leihinstrument | | orchester | | orchester_tn | | raum | | schueler | | unterricht | | unterricht_tn | | warteliste | +-----------------------+
• -B oder --batch Mit dieser Option kann man dafür sorgen, dass die Ausgabe wie bei umgeleiteter Eingabe geschieht: nur mit Tabulatoren getrennte Werte und keine Rahmen um die Tabellen herum. Das kann nützlich sein, wenn man mysql mit dem Parameter -e aufruft (was ansonsten zu einer „normalen“ Anzeige führt): prompt>mysql -B -e "SELECT * FROM instrument;" musikschule instrument_id instr_name instr_gruppe 1 Querflöte Holzbläser 2 Klarinette Holzbläser 3 Violine Streicher 4 Viola Streicher 5 Posaune Blechbläser 6 Trompete Blechbläser 7 Klavier Tasten 8 Keyboard Tasten
Natürlich kann man mysql mit diesem Parameter auch im interaktiven Modus starten, nur hat man dann keinerlei Anzeige und Strukturierung neben den eigentlichen Daten. Im folgenden Beispiel sind zur Verdeutlichung die eingegebenen Zeichen kursiv gedruckt:
=XJULIIVP|JOLFKNHLWHQ
Sandini Bib
prompt>mysql -B musikschule SELECT * FROM raum; raum_id raum_name etage personen 1 A5.33 5 4 2 B2.02 2 20 3 C4.16 4 11 4 A5.21 5 6 5 A5.23 5 6 SHOW FIELDS FROM raum; Field Type Null Key Default Extra raum_id smallint(5) unsigned PRI raum_name varchar(10) etage char(3) YES MUL NULL personen mediumint(8) unsigned YES EXIT
NULL
auto_increment
1
prompt>
Wie man sieht, ist dies in den meisten Fällen nicht wirklich sinnvoll. Das Gegenstück dazu ist der Parameter -t. • -H oder --html Diese Option sorgt dafür, dass die Daten anstatt mit |, + und - als HTML-Tabellen formatiert ausgegeben werden. Es wird eine Tabelle mit einer Rahmenstärke von 1 erzeugt, die Spaltennamen sind als
instrument_id | instr_name | instr_gruppe |
---|---|---|
1 | Querflöte | Holzbläser |
2 | Klarinette | Holzbläser |
3 | Violine | Streicher |
4 | Viola | Streicher |
5 | Posaune | Blechbläser |
6 | Trompete | Blechbläser |
7 | Klavier | Tasten |
8 | Keyboard | Tasten |
Vorname | Nachname | Straße | PLZ | Ort | Alter |
---|---|---|---|---|---|
Thomas | Schmidt | Ritter-Raschen-Str. 234 | 28219 | Bremen | 31 |
Annika | Herms | Sehndenstr. 431 | 29223 | Celle | 30 |
Iwipanda | von Tidenhub | Willy-Brandt-Platz 54 | 68161 | Mannheim | 38 |