Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
| de:modul:m290_guko:learningunits:lu08:theorie:c_fk-create-table [2026/10/17 13:17] – gkoch | de:modul:m290_guko:learningunits:lu08:theorie:c_fk-create-table [2026/10/17 16:40] (aktuell) – gkoch | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== LU08c: Tabellen mit Fremdschlüssel | + | ====== LU08c - Fremdschlüssel |
| - | **Ziel:** Wir verteilen die Daten wie bei WordPress auf mehrere Tabellen (**users**, **posts**, **categories** | + | ===== Lernziele ===== |
| + | | ||
| + | | ||
| + | | ||
| - | ==== ERD (Überblick) ==== | ||
| - | Wir gehen vom Schema aus dem Reiseblog-Beispiel aus: | ||
| - | {{ de: | + | ===== Wozu nachträglich? ===== |
| + | Eine Datenbank entsteht selten in einem Zug. Oft gibt es die Tabellen schon, samt Daten, | ||
| - | <WRAP tip round 80% center> | + | Bei Ihren Cases aus LU05 ist das genau so. Dort haben Sie die Spalten für die Fremdschlüssel angelegt, aber noch nicht als Fremdschlüssel gesetzt. Das holen Sie mit **ALTER TABLE** nach. |
| - | Posts können mehreren Kategorien angehören (N:M). Die saubere Lösung | + | |
| - | Das bauen wir später (s. LU08e: N: | + | |
| - | </ | + | |
| + | Wir bleiben beim Reiseblog der letzten Seite. Zur Erinnerung: Caro hat die //user_id// 1, Martin die 2, Shaolin die 3. | ||
| - | ===== Fremdschlüssel: | ||
| - | <WRAP center tip round 80%> | ||
| - | {{youtube> | ||
| - | //SQL Schlüssel einfach erklärt – SQL 3((Informatik – simpleclub / YouTube)) -> (7:49, de) Primär- und Fremdschlüssel anschaulich erklärt.// | ||
| - | </ | ||
| - | <WRAP box round center 80%> | + | ===== ALTER TABLE ===== |
| - | <code sql> | + | Zuerst kommt die neue Spalte |
| - | CREATE | + | |
| - | | + | |
| - | foreign_key_col | + | |
| - | FOREIGN KEY (foreign_key_col) REFERENCES parent_table(parent_pk) | + | |
| - | ); | + | |
| - | </ | + | |
| - | **Wichtig:** Die **referenzierte Tabelle** (parent_table) muss **bereits existieren**, | + | |
| - | </ | + | |
| - | ===== Beispiel Reiseblog ===== | + | <WRAP center box 80% round>< |
| + | ALTER TABLE kind_tabelle | ||
| + | ADD COLUMN neue_spalte DATENTYP; | ||
| + | </ | ||
| - | <WRAP center tip round 80%> | + | Danach wird genau diese Spalte zum Fremdschlüssel: |
| - | {{youtube> | + | |
| - | //SQL Tabellenerzeugung mit DDL – SQL 2((Informatik – simpleclub / YouTube)) -> (7:46, de) Kurz und klar: CREATE TABLE, Datentypen, Constraints – ideal als DDL-Auffrischung vor dem Setzen von FKs.// | + | |
| - | </ | + | |
| - | ==== 1. Tabellen anlegen ==== | + | <WRAP center box 80% round>< |
| + | ALTER TABLE kind_tabelle | ||
| + | ADD CONSTRAINT fk_name | ||
| + | FOREIGN KEY (neue_spalte) | ||
| + | REFERENCES eltern_tabelle (primaerschluessel) | ||
| + | ON DELETE referenzaktion; | ||
| + | </ | ||
| - | <WRAP box round center 80%> | + | Den //fk_name// wählen Sie selbst, zum Beispiel |
| - | === Tabelle users === | + | |
| - | <code sql> | + | |
| - | CREATE TABLE users ( | + | |
| - | user_id | + | |
| - | username | + | |
| - | email | + | |
| - | display_name | + | |
| - | registered_at DATETIME | + | |
| - | ); | + | |
| - | </code> | + | |
| - | </WRAP> | + | |
| - | <WRAP box round center 80%> | ||
| - | === Tabelle posts === | ||
| - | <code sql> | ||
| - | CREATE TABLE posts ( | ||
| - | post_id | ||
| - | title VARCHAR(200) NOT NULL, | ||
| - | featured_img | ||
| - | content | ||
| - | created_at | ||
| - | author_id | ||
| - | FOREIGN KEY (author_id) REFERENCES users (user_id) -- Foreign Key wird hier angelegt! | ||
| - | ); | ||
| - | </ | ||
| - | </ | ||
| - | <WRAP box round center 80%> | + | ===== Die drei Referenzaktionen |
| - | === Tabelle categories | + | Mit '' |
| - | <code sql> | + | |
| - | CREATE TABLE categories ( | + | <WRAP center box 80% round> |
| - | | + | ^ Referenzaktion ^ Was passiert mit den Kindzeilen? ^ Beispiel ^ |
| - | | + | | **RESTRICT** | Nichts. Das Löschen der Elternzeile wird verhindert. | Eine Autorin mit Posts kann nicht gelöscht werden | |
| - | slug VARCHAR(120) NOT NULL UNIQUE | + | | **CASCADE** | Sie werden mitgelöscht. | Wird ein Post gelöscht, verschwinden seine Kommentare | |
| - | ); | + | | **SET NULL** | Sie bleiben, der Verweis wird leer (NULL). | Eine Redaktorin wird gelöscht, ihre Posts bleiben | |
| - | </ | + | |
| </ | </ | ||
| + | Ohne Angabe gilt **RESTRICT**. Das haben wir auf der letzten Seite gesehen, als sich Martin nicht löschen liess. | ||
| - | ==== 2. Beispieldaten einfügen ==== | + | <WRAP center round info 80%> |
| - | //Für ausführliche Erklärung zum Einfügen von Daten in Tabellen via SQL schauen Sie in "LU07 - DML: Daten einfügen, ändern und löschen" | + | In MySQL heisst der Standard genau genommen |
| + | Neben '' | ||
| + | </WRAP> | ||
| - | <WRAP center box 80% round>< | + | Für die Wahl hilft eine Frage: **Ergibt die Kindzeile ohne ihre Elternzeile noch Sinn?** |
| - | -- 1) Autor:innen | + | |
| - | INSERT INTO users (username, email, display_name) VALUES | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | -- 2) Kategorien | + | * Nein, sie wäre nutzlos. Dann passt **CASCADE**. |
| - | INSERT INTO categories (name, slug) VALUES | + | * Ja, der Verweis ist nur eine Zusatzinformation. Dann passt **SET NULL**. |
| - | (' | + | * Die Elternzeile soll gar nicht gelöscht werden können, solange Kindzeilen existieren. Dann bleibt es bei **RESTRICT**. |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | (' | + | |
| - | -- 3) Posts | ||
| - | INSERT INTO posts (title, featured_img, | ||
| - | (' | ||
| - | ' | ||
| - | ' | ||
| - | ' | ||
| - | (' | + | ===== Beispiel 1: SET NULL ===== |
| - | ' | + | Jeder Post bekommt eine Redaktorin, die den Text gegenliest |
| - | ' | + | |
| - | ' | + | |
| - | (' | + | **Spalte ergänzen und befüllen:** |
| - | 'https:// | + | <WRAP center box 80% round>< |
| - | ' | + | ALTER TABLE posts |
| - | ' | + | ADD COLUMN editor_id INT NULL; |
| - | (' | + | UPDATE posts SET editor_id = 2 WHERE post_id = 1; -- Hasselt: Martin liest gegen |
| - | 'https:// | + | UPDATE posts SET editor_id = 1 WHERE post_id = 2; |
| - | ' | + | UPDATE posts SET editor_id = 3 WHERE post_id = 3; |
| - | '2025-06-12 11:05: | + | </ |
| - | ('Montenegro Roadtrip – 10 Highlights', | + | Wichtig ist das '' |
| - | 'https:// | + | |
| - | 'Bucht von Kotor, Durmitor, Tara-Schlucht …', | + | |
| - | ' | + | |
| - | ('Oman – Top 22 Highlights', | + | **Fremdschlüssel setzen:** |
| - | ' | + | <WRAP center box 80% round>< |
| - | ' | + | ALTER TABLE posts |
| - | ' | + | ADD CONSTRAINT fk_posts_editor |
| + | FOREIGN KEY (editor_id) | ||
| + | REFERENCES users (user_id) | ||
| + | ON DELETE SET NULL; | ||
| + | </code>< | ||
| - | (' | + | **Testen:** |
| - | 'https:// | + | <WRAP center box 80% round>< |
| - | ' | + | DELETE FROM users WHERE username = 'shaolin'; |
| - | '2024-10-07 07:50:00', 2); -- Martin | + | |
| + | SELECT post_id, title, editor_id FROM posts ORDER BY post_id; | ||
| </ | </ | ||
| - | **Resultate (nach dem Einfügen): | + | Shaolin ist gelöscht. Den Lissabon-Post gibt es weiterhin, seine // |
| - | === Verknüpfung Tabelle users & posts (one-to-many) === | + | <WRAP center round tip 80%> |
| + | Shaolin liess sich löschen, weil er bei keinem Post **Autor** ist. Der Fremdschlüssel // | ||
| + | </ | ||
| - | {{de: | ||
| - | === Tabelle categories (wird später mit ' | + | ===== Beispiel 2: CASCADE |
| + | Jetzt kommen Kommentare dazu. Jeder Kommentar gehört zu genau einem Post. Ohne seinen Post ergibt ein Kommentar keinen Sinn. Wird der Post gelöscht, sollen seine Kommentare deshalb automatisch mitgehen. | ||
| - | {{de:modul: | + | **Tabelle anlegen:** |
| - | + | ||
| - | ==== 3. Fremdschlüssel in Aktion (Standard: RESTRICT) ==== | + | |
| - | Beim Setzen von Fremdschlüsseln überwacht MySQL/ | + | |
| - | + | ||
| - | Bezogen auf unser Reiseblog-Beispiel: | + | |
| - | Damit ist //users// die Elterntabelle und //posts// die Kindtabelle. Die Folge von '' | + | |
| - | + | ||
| - | * Löschen eines Users ist blockiert, solange Posts auf diesen User verweisen. | + | |
| - | | + | |
| - | | + | |
| - | + | ||
| - | Probieren Sie folgende Codesnippets in Webstorm/ | + | |
| - | + | ||
| - | === Demo 1 – User ohne Posts löschen (erlaubt) === | + | |
| <WRAP center box 80% round>< | <WRAP center box 80% round>< | ||
| - | DELETE FROM users | + | CREATE TABLE comments ( |
| - | WHERE username = ' | + | |
| - | SELECT user_id, username FROM users; | + | |
| + | author | ||
| + | body TEXT NOT NULL, | ||
| + | created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| + | FOREIGN KEY (post_id) REFERENCES posts (post_id) | ||
| + | ON DELETE CASCADE | ||
| + | ); | ||
| </ | </ | ||
| - | // | ||
| - | === Demo 2 – User mit Posts löschen (blockiert) === | + | Hier steht der Fremdschlüssel direkt im CREATE TABLE, wie auf der letzten Seite. Eine Referenzaktion lässt sich auf beiden Wegen angeben. |
| - | <WRAP center box 80% round>< | + | |
| - | DELETE FROM users | + | |
| - | WHERE username = ' | + | |
| - | </ | + | |
| - | //Erwartete Fehlermeldung (sinngemäss):// | + | |
| - | <WRAP alert round 80% center> | + | |
| - | [23000][1451] Cannot delete or update a parent row: a foreign key constraint fails | + | |
| - | (travel_blog.posts, CONSTRAINT posts_ibfk_1 FOREIGN KEY (author_id) REFERENCES users(user_id)) | + | |
| - | </ | + | |
| - | //Grund: In posts.author_id gibt es Kindzeilen (z.B. " | + | |
| - | === Demo 3 – Unkritisches Attribut ändern (erlaubt) === | + | **Befüllen: |
| <WRAP center box 80% round>< | <WRAP center box 80% round>< | ||
| - | UPDATE users | + | INSERT INTO comments (post_id, author, body) VALUES |
| - | SET username = 'martin_travels' | + | |
| - | WHERE user_id = 2; -- OK: FKs verweisen auf user_id, nicht auf username | + | (2, ' |
| + | (5, ' | ||
| </ | </ | ||
| - | === Demo 4 – Primärschlüssel ändern (blockiert) === | + | **Testen:** |
| <WRAP center box 80% round>< | <WRAP center box 80% round>< | ||
| - | UPDATE users | + | DELETE FROM posts WHERE post_id |
| - | SET user_id = 5 | + | |
| - | WHERE user_id | + | |
| - | </ | + | |
| - | // | + | |
| - | === Demo 5 – Primärschlüssel ändern: Geht das? (ja) === | + | SELECT * FROM comments; |
| - | <WRAP center box 80% round>< | + | |
| - | UPDATE categories | + | |
| - | SET category_id = 11 | + | |
| - | WHERE category_id = 10; -- USA → 11: funktioniert - Primary Keys dürfen geändert werden. | + | |
| </ | </ | ||
| - | === Warum ist das so? === | + | Der Utrecht-Post |
| - | // | + | <WRAP center round important 80%> |
| + | CASCADE ist bequem, aber mit Vorsicht zu geniessen. Ein einziger DELETE kann so viele Zeilen in anderen Tabellen mitlöschen. Verwenden Sie es nur dort, wo die Kindzeilen | ||
| + | </WRAP> | ||
| - | Änderungen an nicht referenzierten Spalten (z. B. '' | ||
| - | Ob eine Änderung blockiert oder mitgezogen (CASCADE) wird, hängt von der ON DELETE/ON UPDATE-Einstellung im FK ab. | + | ===== Zusammenfassung ===== |
| - | < | + | < |
| - | Merke: Fremdschlüssel geben Datensicherheit: | + | ^ Referenzaktion ^ Wann einsetzen? ^ |
| - | * Verhindern verwaiste Daten (z. B. Posts ohne gültigen Autor), | + | | RESTRICT | Standard. Die Elternzeile soll nicht gelöscht werden, solange Kindzeilen darauf verweisen. | |
| - | * definieren klares Verhalten bei Löschen/ | + | | CASCADE | Die Kindzeilen sind ohne Elternzeile nutzlos, zum Beispiel Kommentare. | |
| - | * halten | + | | SET NULL | Der Verweis ist freiwillig und die Kindzeile soll bleiben. | |
| </ | </ | ||
| - | <wrap lo> Ausblick: In **LU08d** fügen wir FKs per **ALTER TABLE** nachträglich hinzu und testen die Referenzaktionen **RESTRICT**, **CASCADE** und **SET NULL** gezielt. </wrap> | + | |
| + | <WRAP center round box 80%> | ||
| + | {{youtube> | ||
| + | //ON DELETE: NO ACTION, SET NULL, CASCADE, SET DEFAULT (9:22 min, deutsch). Quelle: Prof. Dr. Jens Dittrich / YouTube\\ | ||
| + | Das Video zeigt zusätzlich SET DEFAULT. Diese Option brauchen wir im Modul nicht.// | ||
| + | </WRAP> | ||
| + | |||
| + | ---- | ||
| + | {{tag> | ||
| + | [[https:// | ||