Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung | |||
| de:modul:m290_guko:learningunits:lu08:theorie:d_fk-alter-table [2026/10/17 13:14] – gkoch | de:modul:m290_guko:learningunits:lu08:theorie:d_fk-alter-table [2026/10/17 16:42] (aktuell) – gkoch | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== LU08d: | + | ====== LU08d - n:m-Beziehungen mit einer Zwischentabelle |
| - | **Ziel:** Bereits bestehende Tabellen | + | ===== Lernziele ===== |
| + | | ||
| + | | ||
| - | **Voraussetzung: | ||
| + | ===== Ausgangslage ===== | ||
| + | Ein Post kann zu **mehreren Kategorien** gehören. Der Utrecht-Post zum Beispiel zu Niederlande und Städtereise. Gleichzeitig enthält eine Kategorie **mehrere Posts**. Das ist eine **n: | ||
| - | ===== 0) ALTER TABLE – Spalte in bestehende Tabelle hinzufügen ===== | + | <WRAP center round tip 80%> |
| - | Mit **ALTER TABLE** können bestehende Tabellen geändert werden – also auch Fremdschlüssel | + | Im ÖV-Case aus LU05 gab es das schon einmal. Zwischen //linie// und // |
| + | </ | ||
| - | <WRAP box round center 80%> | + | Zuerst braucht der Reiseblog eine Tabelle für die Kategorien: |
| - | **Spalte hinzufügen (generelle Syntax)** | + | |
| - | <code sql> | + | |
| - | ALTER TABLE table_name | + | |
| - | ADD COLUMN neue_spalte DATENTYP [AFTER bestehende_spalte]; | + | |
| - | </ | + | |
| - | **Fremdschlüssel hinzufügen** | + | <WRAP center box 80% round><code sql> |
| - | <code sql> | + | CREATE |
| - | ALTER TABLE table_name | + | |
| - | ADD CONSTRAINT fk_name | + | name VARCHAR(100) NOT NULL, |
| - | FOREIGN | + | |
| - | REFERENCES parent_table(parent_pk) | + | ); |
| - | ON DELETE RESTRICT|CASCADE|SET | + | |
| - | ON UPDATE RESTRICT|CASCADE|SET NULL; | + | INSERT INTO categories (name, slug) VALUES |
| - | </ | + | (' |
| - | </ | + | (' |
| + | (' | ||
| + | (' | ||
| + | (' | ||
| + | </ | ||
| - | <WRAP center | + | <WRAP center round info 80%> |
| - | {{youtube> | + | Der **Slug** ist die Version eines Namens, die sich für eine Webadresse eignet: ohne Leerzeichen, Umlaute und Grossbuchstaben. Aus " |
| - | //ON DELETE: NO ACTION, SET NULL, CASCADE, SET DEFAULT((Prof. Dr. Jens Dittrich – Big Data Analytics / YouTube)) -> (9:22, de) Referenzaktionen kompakt: was bei Löschen/Ändern passiert und wann welche Option sinnvoll ist.// | + | |
| </ | </ | ||
| - | ===== 1) SET NULL: Redaktor:in (editor_id) in posts ===== | + | {{ :de:modul:m290_guko: |
| - | Wir ergänzen in **posts** eine **optionale** verantwortliche Redaktor:in (**editor_id**) und verknüpfen sie mit **users**. | + | |
| - | **1.1 Spalte ergänzen und Beispielwerte setzen** | ||
| - | <WRAP box round center 80%> | ||
| - | <code sql> | ||
| - | -- Spalte hinzufügen | ||
| - | ALTER TABLE posts | ||
| - | ADD COLUMN editor_id INT NULL AFTER author_id; | ||
| - | -- Shaolin wieder hinzufügen, | + | ===== Warum ein Fremdschlüssel nicht reicht ===== |
| - | INSERT INTO users (username, email, display_name) | + | Aus LU04 kennen Sie die Regel **Eine Zelle, ein Wert.** Ein Fremdschlüssel kann deshalb immer nur auf **eine** Zeile verweisen. Bei n:m bräuchten aber **beide** Seiten mehrere Verweise. Jeder naheliegende Versuch scheitert: |
| - | VALUES(' | + | |
| - | -- Beispielwerte passend zu user_id: 1=caro, 2=martin, 3=shaolin | + | <WRAP center box 80% round> |
| - | UPDATE posts SET editor_id = 2 WHERE post_id = 1; -- Post #1: Editor = martin | + | ^ Versuch ^ Problem ^ |
| - | UPDATE | + | | // |
| - | UPDATE posts SET editor_id = 4 WHERE post_id | + | | //post_id// in // |
| - | </code> | + | | Kommaliste '' |
| + | | Spalten | ||
| </ | </ | ||
| - | **1.2 Fremdschlüssel setzen – SET NULL beim Löschen** | + | {{ : |
| - | <WRAP box round center 80%> | + | |
| - | <code sql> | + | |
| - | ALTER TABLE posts | + | |
| - | ADD CONSTRAINT fk_posts_editor | + | |
| - | FOREIGN KEY (editor_id) | + | |
| - | REFERENCES users (user_id) | + | |
| - | ON DELETE SET NULL -- User gelöscht → Post bleibt, Verweis wird NULL | + | |
| - | ON UPDATE RESTRICT; | + | |
| - | </ | + | |
| - | </ | + | |
| - | <WRAP tip round 80% center> | ||
| - | **Warum SET NULL?** Die Redaktor:in ist **optional**. Wird der zugehörige User gelöscht, soll der Post **nicht** verschwinden – der optionale Verweis fällt auf **NULL**. | ||
| - | </ | ||
| - | **Test:** | + | ===== Die Zwischentabelle ===== |
| - | <WRAP center box 80% round> | + | //posts// und // |
| - | <code sql> | + | |
| - | -- Lösche User ' | + | |
| - | DELETE FROM users WHERE username = ' | + | |
| - | -- Kontrolle: editor_id des betroffenen Posts ist jetzt NULL | + | Gehört der Utrecht-Post zu zwei Kategorien, gibt es dafür zwei Zeilen. In jeder Zelle steht so wieder genau ein Wert. |
| - | SELECT post_id, title, editor_id FROM posts ORDER BY post_id; | + | |
| - | </ | + | |
| - | </ | + | |
| + | {{ : | ||
| + | Aus der n: | ||
| - | ===== 2) CASCADE: Kommentare zur posts (comments → posts) ===== | ||
| - | Wir fügen eine Kindtabelle **comments** hinzu. Jeder Kommentar gehört zu **genau einem** Post. Wird ein Post gelöscht, sollen die zugehörigen Kommentare **automatisch verschwinden**. | ||
| - | **2.1 Tabelle | + | ===== Zwischentabelle |
| - | <WRAP box round center | + | |
| - | <code sql> | + | < |
| - | CREATE TABLE comments | + | CREATE TABLE post_category |
| - | comment_id | + | post_id |
| - | post_id | + | |
| - | author | + | |
| - | body TEXT NOT NULL, | + | FOREIGN KEY (post_id) REFERENCES posts (post_id) |
| - | created_at | + | ON DELETE CASCADE, |
| - | FOREIGN KEY (post_id) | + | |
| - | REFERENCES posts (post_id) | + | ON DELETE CASCADE |
| - | ON DELETE CASCADE | + | |
| - | ON UPDATE CASCADE | + | |
| ); | ); | ||
| - | </ | + | </ |
| - | </ | + | |
| - | **2.2 Kurz befüllen** | + | Zwei Dinge sind neu. |
| - | <WRAP box round center 80%> | + | |
| - | <code sql> | + | |
| - | INSERT INTO comments (post_id, author, body, created_at) VALUES | + | |
| - | (2, 'Leser A', | + | |
| - | (2, ' | + | |
| - | (5, ' | + | |
| - | </ | + | |
| - | </ | + | |
| - | **2.3 Test (CASCADE in Aktion)** | + | **Der zusammengesetzte Primärschlüssel.** '' |
| - | <WRAP box round center 80%> <code sql> | + | |
| - | -- Post #2 löschen … | + | |
| - | DELETE FROM posts WHERE post_id = 2; | + | |
| - | -- … die zugehörigen Kommentare sind automatisch | + | **CASCADE auf beiden Seiten.** Das kennen Sie von der letzten Seite. Eine Zuordnung ohne Post oder ohne Kategorie ist nutzlos. Wird eines der beiden gelöscht, verschwindet |
| - | SELECT * FROM comments WHERE post_id = 2; -- → keine Zeilen | + | |
| - | </ | + | |
| - | </ | + | |
| - | < | + | |
| - | **Warum hier CASCADE?** Kommentare ohne zugehörigen | + | ===== Zuordnungen einfügen ===== |
| - | Mit **ON DELETE CASCADE** bleibt die Datenbank **konsistent** und **aufräumen** passiert automatisch. | + | |
| + | < | ||
| + | INSERT INTO post_category (post_id, category_id) VALUES | ||
| + | (1, 4), -- Hasselt → Belgien | ||
| + | (1, 1), -- Hasselt → Städtereise | ||
| + | (2, 5), -- Utrecht → Niederlande | ||
| + | (2, 1), -- Utrecht → Städtereise | ||
| + | (3, 6), -- Lissabon → Portugal | ||
| + | (3, 1), -- Lissabon → Städtereise | ||
| + | (4, 5), -- Maastricht → Niederlande | ||
| + | (4, 1), -- Maastricht → Städtereise | ||
| + | (5, 8), -- Montenegro → Montenegro | ||
| + | (5, 2), -- Montenegro → Abenteuer | ||
| + | (5, 3), -- Montenegro → Roadtrip | ||
| + | (6, 9), -- Oman → Oman | ||
| + | (6, 2), -- Oman → Abenteuer | ||
| + | (6, 3), -- Oman → Roadtrip | ||
| + | (7, 10), -- Chicago → USA | ||
| + | (7, 1); -- Chicago → Städtereise | ||
| + | </ | ||
| + | |||
| + | Der Montenegro-Post hat drei Kategorien und deshalb drei Zeilen. Wie viele Kategorien ein Post haben kann, ist nicht begrenzt. | ||
| + | |||
| + | Die Zwischentabelle schützt sich selbst gegen Fehler: | ||
| + | |||
| + | <WRAP center box 80% round> | ||
| + | ^ Befehl ^ Ergebnis ^ Warum ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| </ | </ | ||
| + | ===== Zusammenfassung ===== | ||
| + | Eine n: | ||
| - | ===== 3) Zusammenfassung ===== | + | <WRAP center round box 80%> |
| + | //Zur Vertiefung: [[https:// | ||
| + | </ | ||
| + | |||
| + | <WRAP center round info 80%> | ||
| + | Die Daten liegen jetzt sauber verteilt in mehreren Tabellen. Mit **JOIN** führen wir sie in einer einzigen Abfrage wieder zusammen, zum Beispiel für "alle Posts mit Autorin und Kategorien" | ||
| + | </ | ||
| - | * **RESTRICT**: Löschen/Ändern der Elternzeile **nur** möglich, wenn **keine** Kindzeilen verweisen (Standardverhalten; | + | ---- |
| - | * **CASCADE**: | + | {{tag> |
| - | * **SET NULL**: Kindzeile bleibt, der **optionale** Verweis wird **NULL**. | + | [[https:// |