Zur Erinnerung, aktuell haben wir folgende zwei Tabellen:
Und die Struktur von "tblkunden" sieht wie folgt aus:
Was bereits gut ist (und niemals fehlen sollte), ist eine ID, die automatisch hochzählt und als Primärschlüssel definiert ist. Das haben wir mit dem Attribut "tKundenID" gemacht.
Ebenso in der Tabelle "tbllaender" haben wir einen Primärschlüssel: "tLaenderID".
Das Verknüpfen von Tabellen hat einen einfachen Grund. Schauen Sie dazu gleich mal unsere Kund*innen an:
In der letzten Spalte sehen Sie das Attribut "tLandID".
Aktuell können Sie dort reinschreiben, was Sie wollen. Ein NULL-Wert haben wir zwar nicht erlaubt.
Aber wir können da die Zahl 0 reinschreiben. Oder wir können die Zahl 250 reinschreiben.
Aufgabe 2
Was ist die grösste Zahl, die wir im Attribut "tLandID" reinschreiben können und warum?
Nun, es geht, wie wir das bereits im Einführungsmodul zu Datenbanken gesehen haben, darum, dass wir konsistente Daten haben wollen. Es macht einfach keinen Sinn, dass wir bei einer Kundin das Land 250 drin haben, wenn unsere Tabelle nur bis 239 geht:
Und es macht auch keinen Sinn, wenn ein Kunde das Land 0 drin hat, wenn die Länderliste bei 1 beginnt:
Umgekehrt sind Sie aktuell in der Lage, in der Tabelle "tbllaender" den Datensatz 43 zu löschen (Deutschland). Das führt aber wieder zu inkonsistenten Daten: unser Stefan Derrick hat dann ein Land eingetragen, das nicht mehr existiert. Das ist in einer Datenbank nicht erwünscht.
Dadurch, dass wir in MySQL (und natürlich in anderen RDBMS) einen Fremdschlüssel definieren, stellen wir eine Verbindung zwischen den beiden Tabellen her. Und das führt primär dazu, dass die beiden Zahlenwerte übereinstimmen müssen.
So, und nun definieren wir das Attribut "tLandID" in der Tabelle "tblkunden" als Fremdschlüssel. Das geht wie folgt:
ALTER TABLE tblKunden ADD FOREIGN KEY (tLandID) REFERENCES tbllaender (tLandID);
Und da kommt nun eine wunderschöne Fehlermeldung:
Also ändern wir die tLandID von Stefan Derrick wieder zurück auf 43.
Führen wir nun obigen Befehl nochmals aus, um den Fremdschlüssel zu definieren, scheint das jetzt zu funktionieren:
Schauen wir auch gleich die Tabellenstruktur an:
Nun steht in der Spalte "Key" der Wert "MUL". Das ist korrekt. Das soll zeigen, dass mehrmals der gleiche Wert vorkommen kann. Und das ist natürlich der Fall. Wir haben ja mehrere Kund*innen aus der Schweiz. Da darf überall der Wert 182 drin vorkommen. Sonst wäre es ziemlich sinnlos.
Aufgabe 5
Testen Sie selber: ändern Sie die 43 von Stefan Derrick wieder auf 250. Was passiert?
PERFEKT😎. Wir haben erreicht, was wir wollen! Wir können in der Kundentabelle keine ungültigen Werte als Land eingeben.
Das ist schon fast alles, was unsere vorher definierte Fremdschlüssel-Angabe macht.
Aufgabe 6
Löschen Sie aus der Tabelle "tbllaender" das Land mit der tLandID = 219.
Das hat funktioniert, nicht wahr?
Aufgabe 7
Jetzt löschen Sie Deutschland raus…was passiert?
Es erscheint wieder eine gefährliche Warnmeldung:
Die will nichts anderes sagen, als dass wir nicht ein Land löschen können, das in der Tabelle "tblkunden" verwendet wird. Tönt irgendwie logisch oder?
Das ist das Standardverhalten, welches aber angepasst werden kann.
Es gibt folgende Aktionen auf der 1-Seite einer Beziehung, welche die n-Seite betreffen kann:
Mit Löschen eines Datensatzes ist gemeint, dass wir in unserem Beispiel ein Land aus der Länder-Tabelle entfernen. Das kann durchaus vorkommen in der Praxis.
Mit Ändern des Primärschlüssels ist gemeint, dass wir beispielsweise die Land-ID 43 bei Deutschland ändern auf den Wert 250. Das ist in unserem Fall gar nicht möglich, weil wir ja einen Autoincrement haben. Diese Nummer können wir gar nicht festlegen.
Entfernen Sie testeshalber das Autoincrement in der Tabelle "tbllaender". Das geht wie folgt:
ALTER TABLE tbllaender MODIFY tLandID TINYINT UNSIGNED;
Die Information bezüglich Autoincrement fehlt nun:
Das erlaubt uns, nachher rasch zu testen, ob wir den Primärschlüssel ändern können resp. wir können anschauen, wie man hier das Verhalten anpassen kann.
Schauen wir aber zuerst, was beim Löschen passieren kann.
Das Verhalten kann verändert werden. Nachträglich ist das aber ein Murks. Drum besser gleich bei Erstellen des Fremdschlüssels dran denken.
Das ist grundsätzlich der Befehl, um einen Constraint bezüglich Löschen auf dem Fremdschlüssel "tLandID" zu setzen:
ALTER TABLE tblKunden ADD CONSTRAINT FOREIGN KEY (tLandID) REFERENCES tbllaender (tLandID) ON DELETE CASCADE;
Das Dumme ist aber, wenn wir das ausführen, dann wird das erledigt. Aber nicht so, wie wir uns das wünschen…
Wenn wir alle Fremdschlüssel anzeigen wollen, dann sollten wir ja folgendes kriegen:
Wir kriegen nach dem obigen ALTER TABLE aber das:
MySQL ändert also nicht den bestehenden Fremdschlüssel, sondern füg einfach einen zweiten an. Das ist grundsätzlich nicht tragisch, aber dann müssen wir den ersten halt löschen:
ALTER TABLE tblkunden DROP FOREIGN KEY tblkunden_ibfk_1;
Wie zum Geier kriegt man den Namen der Fremdschlüssel? Hehe, das ist ein Geheimnis😅. Nein, einfach den folgenden SQL-Befehl ausführen:
SELECT RefCons.constraint_schema, RefCons.table_name, RefCons.referenced_table_name, RefCons.constraint_name, KeyCol.column_name FROM information_schema.referential_constraints RefCons JOIN information_schema.key_column_usage KeyCol ON RefCons.constraint_schema = KeyCol.table_schema AND RefCons.table_name = KeyCol.table_name AND RefCons.constraint_name = KeyCol.constraint_name WHERE RefCons.constraint_schema = 'kundendb';
Der zeigt sämtliche Fremdschlüssel der ganzen Datenbank, schön aufgelistet in einer Tabelle:
Es gibt noch einen kürzeren Befehl:
SHOW CREATE TABLE kundendb.tblkunden;
Da kommt als Ausgabe noch viel mehr (zudem ein paar Bindestriche zu viel😂):
Da sehen Sie auch sämtliche Attribute plus ein paar andere Informationen bezüglich Database-Engine und Zeichensatz. Uns interessiert in diesem Fall aber nur die zweitletzte Zeile mit dem CONSTRAINT.
Weiter oben haben Sie ja das Verhalten bei Schlüsselverletzung angepasst:
ON DELETE CASCADE
Nun können Sie mal versuchen, Deutschland zu löschen:
DELETE FROM tbllaender WHERE tLandID = 43;
Wenn alles geklappt hat, sollte folgende Meldung erscheinen:
Was ist nun passiert? Listen Sie doch mal alle Kund*innen auf…
Bei mir sieht das Ergebnis so aus:
Mist, wo ist denn Stefan Derrick hin? Und Datensatz Nr. 1 fehlt bei mir auch. Was ist falsch gelaufen?
In der Theorie gar nichts…mit dem Constraint "ON DELETE CASCADE" sagen wir der Datenbank, dass sie, sobald wir ein Land löschen, auch sämtliche Datensätze auf der n-Seite der Beziehung löschen sollen, welche davon betroffen sind.
Konkret heisst das, ich habe alle deutschen Kunden gelöscht…shit😢.
Seien Sie verdammt vorsichtig mit ON DELETE CASCADE. In den allermeisten Fällen wollen Sie das nicht!
Sie haben gesehen:
ON DELETE CASCADE
ist echt nicht nett…das löscht uns ungefragt Kunden.
Es gibt bessere Varianten:
Aufgabe 8
Also, ändern Sie den Contraint auf ON DELETE SET NULL Also, löschen Sie mal das Land "Schweiz", also den Datensatz mit der tLandID = 182
Was passiert?
Aufgabe 9
Also, dann ändern Sie jetzt mal das Attribut "tLandID" in der Tabelle "tblkunden" so, dass es auch NULL-Werte enthalten darf.
Aufgabe 10
Jetzt können Sie nochmals versuchen, den Constraint auf ON DELETE SET NULL zu setzen. Das müsste jetzt gehen.
Aufgabe 11
Nun können Sie nochmals versuchen, die Schweiz zu löschen in der Länder-Tabelle.
Kleine Knacknuss: Sie müssen vorher noch den falschen FOREIGN KEY löschen (mit dem ALTER TABLE in der letzten Aufgabe habe Sie ja wieder einen zweiten erstellt, statt den ursprünglichen zu ändern).
So, hier als Vorher-Nachher-Vergleich die Tabelle "tblkunden":
Immerhin haben wir nun unsere Schweizer Kund*innen nicht gelöscht…das lässt sich noch retten.
Versuchen wir das auch noch gleich.
Aufgabe 13
Nun finden Sie heraus, wie Sie den Default-Wert der Spalte "tLandID" in der Tabelle "tblkunden" auf 182 setzen können.
Und jetzt sollte der Default-Wert ersichtlich sein:
Aufgabe 14
Wir haben immer noch NULL in den Adressen aus der Schweiz. Ändern Sie diese nun nicht auf 182, sondern auf 200 (keine Ahnung, was für ein Land das ist😂). Aber so können wir testen, ob das Löschen des Landes mit der ID = 200 bei allen Adressen automatisch den Default-Wert 182 einsetzt.
Also, zuerst einfach nur 200 überall eintragen.
Das sollte uns die folgende Kundenliste liefern:
Jetzt kommt halt wieder ein wenig die mühsame Arbeit:
Aufgabe 15
Jetzt kommt der grosse Moment. Was passiert, wenn wir das Land 200 löschen?
Aufgabe 16
Versuchen Sie es!
Wir haben vorhin das Autoincrement von der Tabelle "tbllaender" entfernt. Primärschlüssel ist das Feld "tLandID" aber immer noch.
Also können Sie testen, was passiert, wenn Sie eine tLandID in der Tabelle "tbllaender" ändern, z.B. die 182 auf 240.
Sie müssen vorgängig den Constraint anpassen auf ON UPDATE SET NULL.
Achtung, das macht wenig Sinn🙈..
Die einzigen beiden Constraints hier, die Sinn machen, sind:
Der zweite Constraint muss jedoch nur dann erstellt werden, wenn wir nicht mit Autoincrements arbeiten.
Das kommt gelegentlich vor, vor allem, wenn wir Schnittstellen zu anderen Datenbanken haben und deren Daten importieren resp. exportieren.
Wir haben in unserem Beispiel den Fremdschlüssel "tLandID" erst nachträglich erstellt.
In der Regel macht es Sinn, dies gleich von Anfang an zu erledigen, sprich, beim Erstellen der Tabelle.
Der Befehl CREATE TABLE muss lediglich durch die Zeile mit dem FOREIGN KEY ergänzt werden:
CREATE TABLE tblkunden ( tKundenID SMALLINT AUTO_INCREMENT, tVorname VARCHAR(40) NOT NULL, tNachname VARCHAR(40) NOT NULL, tStrasse VARCHAR(80), tPLZ MEDIUMINT, tOrt VARCHAR(60), tLandID TINYINT UNSIGNED NOT NULL, PRIMARY KEY (tKundenID), FOREIGN KEY (tLandID) REFERENCES tbllaender (tLandID) ON DELETE SET NULL ON UPDATE CASCADE );
Beachten Sie dementsprechend, dass Sie in einem Datenmodell immer zuerst alle Tabellen erstellen, die auf der "1-Seite" einer Beziehung stehen. Also eben in diesem Fall zuerst die Tabelle "tbllaender" und dann erst die Tabelle "tblkunden":