====== LU08a - Warum mehrere Tabellen? ======
===== Lernziele =====
* Ich kann an einem Beispiel zeigen, welche Probleme entstehen, wenn alle Daten in einer einzigen Tabelle stehen (Redundanz und Inkonsistenz).
* Ich kann begründen, warum in einer Zelle nur ein Wert stehen soll.
===== Unser Beispiel: ein Reiseblog =====
Web-Apps wie **WordPress** verteilen ihre Inhalte auf **mehrere Tabellen**: Benutzer:innen, Beiträge, Kategorien, Kommentare. WordPress läuft wie unser Kurs auf **MySQL** oder auf **MariaDB**, einer eng verwandten Abspaltung davon.
Als Beispiel nehmen wir den Reiseblog [[https://wetraveltheworld.de|WE TRAVEL THE WORLD]] von Caro und Martin. Er hat mehrere Autor:innen, viele Kategorien (Städtereise, Abenteuer, einzelne Länder) und Kommentare zu jedem Beitrag.
{{ :de:modul:m290_guko:learningunits:lu08:theorie:screenshot_2025-10-15_at_15.04.57_resized.png?nolink&600 |Startseite mit mehreren Blog-Posts und sichtbaren Kategorien}}
//Startseite mit mehreren Blog-Posts und ihren Kategorien.//
{{ :de:modul:m290_guko:learningunits:lu08:theorie:screenshot_2025-10-15_at_15.07.33_resized.png?nolink&600 |Einzelansicht eines Blog-Posts}}
//Einzelansicht mit Titel, Bild, Autor:in, Datum und Anzahl Kommentare.//
Als ERD in Chen-Notation (wie in LU04) sieht das etwa so aus:
{{ :de:modul:m290_guko:learningunits:lu08:theorie:wordpress_blog.drawio.png?nolink&900 |ERD mit den Entitäten Post, User und Category}}
//Die Kommentare lassen wir hier weg, damit der Einstieg einfach bleibt.//
===== Alles in einer Tabelle =====
Am einfachsten wäre es, alles in eine einzige Tabelle zu schreiben: Post, Autor und Kategorie zusammen.
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
);
INSERT INTO blog_posts (post_title, post_content, author_name, author_email, category_name)
VALUES
('Hasselt – 10 Highlights', 'Kurzguide …', 'Martin Merten', 'martin@wetraveltheworld.de', 'Städtereise'),
('Utrecht – 10 Sehenswürdigkeiten', 'Cafés, Grachten …', 'Martin Merten', 'martin@wetraveltheworld.de', 'Städtereise'),
('Lissabon – 8 Tipps', 'Aussichtspunkte …', 'Caro Steig', 'caro@wetraveltheworld.de', 'Portugal');
{{ :de:modul:m290_guko:learningunits:lu08:theorie:all_in_one_table_1.png?nolink&900 |Alle Posts in einer einzigen Tabelle}}
Das funktioniert. Mit der Zeit zeigen sich aber vier Probleme.
===== Problem 1: Dieselben Daten stehen mehrfach da =====
Name und E-Mail von Martin stehen bei **jedem** seiner Posts. Diese Wiederholung heisst **Redundanz**. Bei zwei Posts ist das kein Problem, bei 200 Posts schon.
===== Problem 2: Die Daten widersprechen sich =====
Martin hat eine neue E-Mail-Adresse. Wird sie nur in **einer** Zeile geändert, stehen plötzlich zwei verschiedene Adressen in der Datenbank.
UPDATE blog_posts
SET author_email = 'martin.new@wetraveltheworld.de'
WHERE id = 1;
^ 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 |
Welche Adresse stimmt? Das kann die Datenbank nicht wissen. Solche widersprüchlichen Daten nennt man **Inkonsistenz**.
===== Problem 3: Tippfehler fallen nicht auf =====
Ein vertauschter Buchstabe reicht. Hier steht //Stätdereise// statt //Städtereise//:
INSERT INTO blog_posts (post_title, post_content, author_name, author_email, category_name)
VALUES ('Maastricht an einem Tag', 'Spaziergang …', 'Caro Steig', 'caro@wetraveltheworld.de', 'Stätdereise');
Die Datenbank speichert das ohne Fehlermeldung. Die Folgen sieht man erst später:
^ Abfrage ^ Folge ^
| ''SELECT DISTINCT category_name FROM blog_posts;'' | Die Kategorienliste zeigt **zwei** Einträge: Städtereise **und** Stätdereise |
| ''SELECT post_title FROM blog_posts WHERE category_name = 'Städtereise';'' | Der Maastricht-Post **fehlt** im Ergebnis |
Das gleiche Problem haben wir in LU07 gesehen. In der Spalte //Certificate// von imdb_top_1000 stehen **UA** und **U/A**, zwei Schreibweisen für dieselbe Freigabe.
===== Problem 4: Nur eine Kategorie pro Post =====
Der Utrecht-Post gehört zu **zwei** Kategorien: Niederlande und Städtereise. Die Spalte //category_name// kann aber nur einen Wert aufnehmen.
Man könnte beides mit Komma in dieselbe Zelle schreiben (''Niederlande, Städtereise''). Das verstösst gegen eine Regel aus LU04: **Eine Zelle, ein Wert.** Was dann passiert:
* Die Datenbank kann nicht prüfen, ob die Kategorien überhaupt existieren. Tippfehler bleiben unbemerkt.
* Suchen mit LIKE werden ungenau. LIKE '%Guinea%' findet Guinea, Guinea-Bissau, Äquatorialguinea und Papua-Neuguinea, also vier verschiedene Länder.
* Wer zählen will, wie viele Posts es pro Kategorie gibt, muss jeden Text zuerst zerlegen.
===== Daten auf mehrere Tabellen verteilen =====
Für jede Entität gibt es eine eigene Tabelle: //users//, //posts// und //categories//. Jede Autorin steht dann **genau einmal** in der Datenbank. Ändert sich ihre E-Mail-Adresse, passiert das an einer einzigen Stelle.
Verbunden werden die Tabellen über **Fremdschlüssel**. Wo der Fremdschlüssel hinkommt, wissen wir aus LU04: auf die "viele"-Seite. Eine Autorin schreibt viele Posts. Deshalb bekommt //posts// eine Spalte //author_id//, die auf die Autorin verweist.
{{youtube>W4UkIK2BwS8?}}\\
//Beziehungen in relationalen Datenbanken (1:n, n:m, 1:1), einfach erklärt (9:02 min, deutsch). Quelle: Patrick Boekhoven / YouTube//
Auf der nächsten Seite bauen wir diese Tabellen. Diesmal mit Fremdschlüsseln, die von der Datenbank auch geprüft werden.
----
{{tag>M290-LU08}}
[[https://creativecommons.org/licenses/by-nc-sa/4.0/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] Guido Koch