Normalisierung
Kurz gesagt: Die Normalisierung zerlegt Daten in Tabellen, sodass jede Tatsache an genau einer Stelle steht. Das beseitigt Änderungs-, Einfüge- und Löschanomalien.
Problem
Excel ist in der Datenbank gelandet
Erinnerst du dich an die Bestelltabelle aus der ersten Lektion des Kurses? Jemand hat sie so, wie sie war, in die Datenbank übernommen: eine Zeile für jedes Produkt einer Bestellung, daneben das Datum, der Kunde, seine E-Mail und seine Stadt.
flat_orders
order_idinteger *ordered_attext *customertext *emailtext *citytextproducttext *pricereal *quantityinteger *
11 Zeilen und nur drei Kunden. Marias Name und E-Mail stehen 6-mal da. Sieh dir auch ihre Stadt in Bestellung 6 an.
Die Idee
Anomalien
Steht eine Tatsache an vielen Stellen, ist jede Änderung ein Risiko. Diese Probleme heißen Anomalien. Es gibt drei Arten.
Beim Ändern. Maria ändert ihre E-Mail. Ein Kollege korrigiert sie nur in ihrer letzten Bestellung:
Jetzt sagt die Datenbank zwei Dinge gleichzeitig. Welche E-Mail stimmt?
Sieh es dir an
Einfügen und Löschen
Beim Einfügen. Der Shop nimmt Haferflocken ins Sortiment. Noch hat sie niemand bestellt. Wohin schreibst du den Preis?
Eine Zeile ohne Bestellung kann nicht hinein. Das Produkt existiert erst, wenn jemand es kauft.
Beim Löschen. Elena storniert Bestellung 4, und sie wird gelöscht. Es war die einzige Bestellung mit der Bratpfanne:
Mit der Bestellung ist auch die Tatsache verschwunden, dass der Shop eine Bratpfanne für 24,90 € verkauft.
Die Idee
Normalformen
Tabellen so anzuordnen, dass jede Tatsache an einer Stelle steht, heißt Normalisierung. Die Regeln kommen in Stufen, den Normalformen. Jede schließt die vorige ein.
Erste (1NF): ein Wert pro Zelle. Keine Listen wie „vegan, bio“ und keine Spalten tag1, tag2, tag3.
Nur das Weißbrot. Das Vollkornbrot ist vegan, aber in seiner Zelle steht „vegan, bio“. Mit LIKE '%vegan%' findest du es, aber du kannst Produkte nicht nach Tag zählen, „bio“ nicht überall korrigieren und keinen Tippfehler fernhalten. Tags gehören in eine Verbindungstabelle, wie in der ersten Lektion dieses Moduls.
Zweite (2NF): jede Spalte hängt vom ganzen Schlüssel ab. Das zählt, wenn der Schlüssel aus mehreren Spalten besteht:
order_lines
- PK
order_idinteger - PK
product_idinteger product_nametext *quantityinteger *
Der Produktname hängt nur von product_id ab, nicht von der Bestellung. Er wiederholt sich in jeder Bestellung mit diesem Produkt. Er gehört in products.
Dritte (3NF): Spalten hängen nur vom Schlüssel ab, nicht von einer anderen Spalte:
products
- PK
idinteger nametext *category_idinteger *category_nametext *pricereal *
category_name wird durch category_id bestimmt, nicht durch das Produkt. Wird die Kategorie umbenannt, musst du jedes Produkt darin korrigieren. Der Name gehört in categories.
Genauer, für Neugierige
Die kurze Formel für die drei Formen: Jede Spalte hängt ab vom Schlüssel, vom ganzen Schlüssel und von nichts außer dem Schlüssel.
Es gibt auch höhere Formen (Boyce-Codd, vierte, fünfte), aber in der Praxis hat eine Tabelle in dritter Normalform meist keine dieser Anomalien mehr.
flat_orders verletzt die zweite Normalform. Der Schlüssel einer Zeile ist die Bestellung und das Produkt, während das Datum und der Kunde nur von der Bestellung abhängen. Sie verletzt auch die dritte: Stadt und E-Mail hängen vom Kunden ab, nicht von der Bestellung.
Probier es aus
Welche Form?
Die Idee
Das Zerlegen
Du zerlegst flat_orders in die Tabellen des Shops: customers, products, orders und order_items. Du beginnst mit denen, die auf nichts zeigen.
INSERT kann seine Zeilen aus einem SELECT nehmen statt aus VALUES. Mit DISTINCT kommt jedes Produkt einmal hinein:
products
- PK
idinteger nametext *pricereal *
Aus 11 Zeilen werden 6 Produkte, jedes mit einer id. Der Preis bleibt auch in den Bestellzeilen, denn das ist der Preis, zu dem gekauft wurde. Warum das keine Wiederholung ist, siehst du in der nächsten Lektion.
Sieh es dir an
Excel ist schmutzig
Dasselbe für die Kunden:
customers
- PK
idinteger nametext *emailtext *citytext
Ein Fehler. In Bestellung 6 steht bei Maria die Stadt „Sofiya“. DISTINCT vergleicht die ganze Zeile, sieht also zwei Kunden mit einer E-Mail, und UNIQUE lässt sie nicht hinein. Das ist gut: Die Regel hat den Fehler gefangen, statt ihn in die neue Datenbank mitzunehmen.
Zuerst korrigierst du die Daten, dann zerlegst du:
Fast jeder Umzug aus Excel beginnt mit einer solchen Bereinigung.
Probier es aus
Die Bestellungen
orders
- PK
idinteger - FK
customer_idinteger *→ customers ordered_attext *
Produkte und Kunden sind schon zerlegt. Fülle orders (id, customer_id, ordered_at) aus flat_orders. Die Bestellnummer ist order_id, und den Kunden findest du über die E-Mail.
Lösung
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
ordered_at TEXT NOT NULL
);
INSERT INTO orders (id, customer_id, ordered_at)
SELECT DISTINCT f.order_id, c.id, f.ordered_at
FROM flat_orders f
JOIN customers c ON c.email = f.email;INSERT INTO orders (id, customer_id, ordered_at) SELECT DISTINCT f.order_id, c.id, f.ordered_at FROM flat_orders f JOIN customers c ON c.email = f.email. Du verbindest über die E-Mail, weil sie eindeutig ist, ein Name sich aber wiederholen kann. DISTINCT ist nötig, weil eine Bestellung mit zwei Produkten in zwei Zeilen steht. 7 Bestellungen.
Übung
Zwei Aufgaben
1. Übrig bleibt die Verbindungstabelle zwischen Bestellungen und Produkten.
order_items
- PK
order_idinteger→ orders - PK
product_idinteger→ products quantityinteger *unit_pricereal *
Fülle order_items aus flat_orders: die Bestellnummer, die Produkt-ID, die Menge und den Preis.
Lösung
CREATE TABLE order_items (
order_id INTEGER NOT NULL REFERENCES orders(id),
product_id INTEGER NOT NULL REFERENCES products(id),
quantity INTEGER NOT NULL,
unit_price REAL NOT NULL,
PRIMARY KEY (order_id, product_id)
);
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT f.order_id, p.id, f.quantity, f.price
FROM flat_orders f
JOIN products p ON p.name = f.product;Jede Zeile von flat_orders wird zu einer Zeile von order_items, also gibt es hier kein DISTINCT. Das Produkt findest du über den Namen, der in products jetzt UNIQUE ist. 11 Zeilen, so viele wie am Anfang.
2. Jetzt braucht die Auswertung aus der ersten Lektion des Kurses nur noch eine Abfrage. Wie viel hat jeder Kunde ausgegeben?
Zeige den Namen jedes Kunden und die Summe seiner Einkäufe (Menge mal Preis):
Lösung
SELECT c.name, SUM(oi.quantity * oi.unit_price) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY c.id, c.name;Drei Tabellen und GROUP BY. Maria 28,20 €, Georgi 25,20 €, Elena 31,20 €. Ändert Maria morgen ihre E-Mail, ist das eine Zeile in customers.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Constraints: Regeln für die Daten · Fremdschlüssel: Verbindungen zwischen Tabellen · Beziehungstypen: 1:1, 1:N, M:N · ER-Diagramm: von der Beschreibung zum Schema
- Geht weiter in: Natürliche Schlüssel und Denormalisierung · Dokumentendatenbanken · Die Bäckerei, Etappe 1: Datenbank und Tabellen
- Aufgaben zum Thema: Eine Datenbank entwerfen