====== LU08b: Warum Fremdschlüssel bzw. mehrere Tabellen? ======
==== Ausgangslage ====
Naheliegend ist: **Alles in eine Tabelle** (Post + Autor + Kategorie).
In echten Blogs (z. B. WordPress) führt das aber zu **Wiederholungen**, **Fehlern** und **hohem Wartungsaufwand**.
Wir bleiben beim Reiseblog-Beispiel von der letzten Seite [[https://wetraveltheworld.de|We Travel The World Blog]].
==== Beispiel: Alles in einer Tabelle (schlechte Idee) ====
-- Eine Tabelle für alles: Post + Autor + Kategorie (redundant!)
CREATE TABLE blog_posts (
id INT AUTO_INCREMENT PRIMARY KEY,
post_title VARCHAR(200) NOT NULL,
post_content TEXT,
author_name VARCHAR(100) NOT NULL,
author_email VARCHAR(200) NOT NULL,
category_name VARCHAR(100) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
**Daten einfügen (nur eine Kategorie pro Zeile möglich):**
INSERT INTO blog_posts
(post_title, post_content, author_name, author_email, category_name, created_at)
VALUES
('Hasselt – 10 Highlights',
'Kurzguide: Die schönsten Ecken von Hasselt …',
'Martin Merten', 'martin@wetraveltheworld.de', 'Städtereise', '2025-05-07 10:15:00'),
('Utrecht – 10 Sehenswürdigkeiten',
'Cafés, Grachten und Restaurant-Tipps …',
'Martin Merten', 'martin@wetraveltheworld.de', 'Städtereise', '2025-06-05 09:30:00'),
('Lissabon – 8 Tipps zu den wichtigsten Sehenswürdigkeiten',
'Aussichtspunkte, Viertel und Highlights …',
'Caro Steig', 'caro@wetraveltheworld.de', 'Portugal', '2025-03-21 08:40:00');
**Warum nur //eine// Kategorie pro Zeile?**
Komma-Listen wirken bequem, sind aber ungünstig:
* **Keine Prüfung / kein FK((Foreign Key = Fremdschlüssel)).** Die Datenbank (DB) kann bei «Belgien, Städtereise» **nicht** prüfen, ob diese Kategorien wirklich existieren. ((Wenn Kategorien und Posts in **separate Tabellen** liegen und per Fremdschlüssel verbunden sind, kontrolliert die DB beim Speichern, ob «Belgien» in der Kategorien-Tabelle vorhanden ist.)) → Tippfehler bleiben unbemerkt ((z. B. «Belgien, Städtereisen» statt «Belgien, Städtereise»)).
* **Unzuverlässiges Filtern.** Suchen mit LIKE liefern leicht Teiltreffer/Varianten. Beispiel: ''WHERE category_name LIKE '%Guinea%''' trifft auch «Equatorial Guinea», «Guinea», «Guinea-Bissau» und «Papua New Guinea» – vier verschiedene Länder.
* **Schwierig auszuwerten & langsam.** Zählen/Gruppieren erfordert Strings((Zeichenketten)) zu zerlegen; darauf kann die DB nicht sinnvoll indexieren((**Index**: Ein Index ist wie ein Inhaltsverzeichnis der Datenbank. Er beschleunigt Suchen/Sortieren auf **einzelnen** Spaltenwerten. Bei Komma-Listen stecken **mehrere** Werte in **einem** Feld – darauf lässt sich kein brauchbarer Index aufbauen; zudem können Suchmuster wie LIKE '%Wort%' einen vorhandenen Index oft nicht nutzen.)).
**Besser:** Pro Zeile **eine** Kategorie. Für mehrere Kategorien pro Post (N:M-Beziehung) verwenden wir eine **Zwischentabelle** //post_category//.
**Auszug:**
{{ :modul:m290_guko:learningunits:lu08:theorie:all_in_one_table_1.png?nolink&900 | Tabelle mit allen Posts}}
**Probleme auf einen Blick:**
* **Redundanz**: Autorname/E-Mail wiederholen sich bei mehreren Posts.
* **Fehleranfällig**: Kategorienamen können unterschiedlich geschrieben werden.
* **Aufwendig**: E-Mail-Wechsel eines Autors → alle Zeilen suchen und ändern.
* **Nur eine Kategorie möglich**: Idealerweise möchten wir aber mehrere Kategorien pro Blog-Post vergeben – z.B. beim Blog-Post «Utrecht – 10 Sehenswürdigkeiten»: Niederlande, Städtereise.
==== Tippfehler in Kategorie: sichtbare Folgen ====
Ein fehlender Buchstabe reicht: **Städtereise** vs. **Stätdereise**.
INSERT INTO blog_posts
(post_title, post_content, author_name, author_email, category_name, created_at)
VALUES
('Maastricht an einem Tag',
'Spaziergang, Restaurants, Altstadt …',
'Caro Steig', 'caro@wetraveltheworld.de', 'Stätdereise', '2025-06-12 11:05:00'); -- Tippfehler!
**Direkte Folgen in Abfragen:**
^ Abfrage ^ Zweck ^ Effekt bei Tippfehler ^
| SELECT DISTINCT category_name FROM blog_posts ORDER BY category_name; | Kategorienliste (Navigation/Filter) | Liste zeigt **zwei** Einträge: //Stätdereise// **und** //Städtereise//. |
| SELECT id, post_title FROM blog_posts WHERE category_name = 'Städtereise'; | Beiträge in „Städtereise“ | Der Datensatz mit //Stätdereise// (dt vertauscht) **fehlt** im Resultat. |
==== Teil-Update: uneinheitliche E-Mail ====
Nur **eine** von mehreren Zeilen eines Autors wird geändert → inkonsistente Daten.
UPDATE blog_posts
SET author_email = 'martin.new@wetraveltheworld.de'
WHERE id = 1;
SELECT id, post_title, author_name, author_email
FROM blog_posts
WHERE author_name = 'Martin Merten';
**Ergebnis:**
^ id ^ post_title ^ author_name ^ author_email ^
| 1 | Hasselt – 10 Highlights | Martin Merten | martin.new@wetraveltheworld.de |
| 2 | Utrecht – 10 Sehenswürdigkeiten | Martin Merten | martin@wetraveltheworld.de |
//Gleicher Autor, unterschiedliche E-Mail → Daten sind inkonsistent.//
[[https://www.youtube.com/watch?v=W4UkIK2BwS8|Beziehungen in relationalen Datenbanken (1:n, n:m, 1:1) – einfach erklärt]]((Patrick Boekhoven / YouTube)) -> (9:02, de) Grundlagen zu PK/FK und warum wir Beziehungen brauchen; zeigt, wie man 1:n modelliert und warum n:m ohne Zwischentabelle nicht direkt geht.
==== Ausblick ====
Auf der nächsten Seite bauen wir genau dieses Mehrtabellen-Schema **mit Fremdschlüsseln** auf und füllen es mit den obigen Reiseblog-Beispieldaten.