Arrays vs. normalisierte Tabellen
Wann Arrays die richtige Wahl sind
Arrays vs. normalisierte Tabellen ist eine kostenlose SQL Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des SQL Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der SQL Academy-Kurs umfasst insgesamt 4 Lektionen.
Zwei Möglichkeiten zum Speichern mehrerer Werte
Wenn eine einzelne Zeile mehrere zusammengehörige Werte enthalten muss, bietet PostgreSQL zwei wesentliche Ansätze: Sie speichern sie als Array-Spalte in derselben Zeile oder erstellen eine separate Kindtabelle, in der jeder Wert eine eigene Zeile erhält.
Zu verstehen, wann welcher Ansatz geeignet ist, gehört zu den wichtigsten Fähigkeiten beim Entwurf effizienter und wartbarer Datenbanken.
Der normalisierte Ansatz
In einem vollständig normalisierten Schema befindet sich jedes Datenelement in einer eigenen Zeile. Wenn ein Benutzer mehrere Telefonnummern haben kann, erstellen Sie eine Tabelle user_phones mit einem Fremdschlüssel zurück auf users.
Dies ist das klassische relationale Modell und in den meisten Situationen die Standardwahl.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
INSERT INTO user_phones (user_id, phone) VALUES
(1, '+1-555-0101'),
(1, '+1-555-0102'),
(2, '+1-555-0200');Der Array-Ansatz
Mit PostgreSQLs TEXT[] (oder einem beliebigen anderen Datentyp gefolgt von []) können Sie mehrere Werte direkt in einer einzigen Spalte speichern. Eine zusätzliche Tabelle ist nicht erforderlich.
Dieselben Telefonnummerndaten können in einer kompakten Zeile pro Benutzer gespeichert werden.
CREATE TABLE users_with_phones (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
phones TEXT[]
);
INSERT INTO users_with_phones (name, phones) VALUES
('Alice', ARRAY['+1-555-0101', '+1-555-0102']),
('Bob', ARRAY['+1-555-0200']);Arrays lassen sich leicht abfragen
Die Suche in einer Array-Spalte ist mit dem Operator ANY oder dem Operator @> (enthält) unkompliziert. Mit einer einfachen WHERE-Klausel können Sie alle Benutzer finden, die eine bestimmte Telefonnummer haben.
-- Find users who have a specific phone number
SELECT name
FROM users_with_phones
WHERE '+1-555-0101' = ANY(phones);
-- Or using the array-contains operator
SELECT name
FROM users_with_phones
WHERE phones @> ARRAY['+1-555-0101'];Wann Arrays die bessere Wahl sind: einfache Suchen
Arrays sind eine gute Wahl, wenn:
- die Werteliste als Einheit gemeinsam gelesen wird (Tags, Bezeichnungen, Kategorien)
- Sie einzelne Elemente niemals per JOIN verknüpfen müssen
- die Liste eine natürliche Obergrenze hat und nur selten teilweise aktualisiert wird
Ein klassisches Beispiel ist das Speichern von Tags zu einem Blogbeitrag. Sie rufen immer alle Tags gemeinsam ab und fragen Beiträge nur selten anhand eines einzelnen Tags in einer komplexen Verknüpfung ab.
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[]
);
INSERT INTO posts (title, tags) VALUES
('Intro to SQL', ARRAY['sql', 'beginner', 'database']),
('Advanced Indexes', ARRAY['sql', 'performance', 'indexes']),
('NoSQL Overview', ARRAY['nosql', 'beginner']);
-- Get all posts tagged 'beginner'
SELECT title FROM posts
WHERE 'beginner' = ANY(tags);Wann normalisierte Tabellen die bessere Wahl sind: Beziehungen
Normalisierte Tabellen sind die bessere Wahl, wenn:
- einzelne Werte eigene Attribute benötigen (z. B. hat eine Telefonnummer einen Typ: privat/geschäftlich)
- Sie einzelne Werte per JOIN verknüpfen müssen
- sich Werte unabhängig voneinander und häufig ändern
- Sie referenzielle Integrität über Fremdschlüssel benötigen
-- Phone numbers need a 'type' attribute — array can't do this cleanly
CREATE TABLE user_phones (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
phone TEXT NOT NULL,
type TEXT CHECK (type IN ('home', 'work', 'mobile'))
);
INSERT INTO user_phones (user_id, phone, type) VALUES
(1, '+1-555-0101', 'home'),
(1, '+1-555-0102', 'work');Der Unterschied bei der Indexierung
Bei einer normalisierten Tabelle können Sie einen herkömmlichen B-Tree-Index auf der Fremdschlüssel- oder Wertespalte anlegen. Bei Arrays benötigen Sie einen GIN-Index (Generalized Inverted Index), um schnelle Suchen innerhalb des Arrays zu ermöglichen.
GIN-Indizes funktionieren gut, sind aber größer und langsamer zu aktualisieren als B-Tree-Indizes.
-- Index for fast array element lookups
CREATE INDEX idx_posts_tags ON posts USING GIN (tags);
-- Now this query uses the index efficiently
EXPLAIN SELECT title FROM posts
WHERE tags @> ARRAY['sql'];Aggregation über mehrere Zeilen: Normalisierte Tabellen sind überlegen
Wenn Sie einzelne Werte zählen, gruppieren oder aggregieren müssen, sind normalisierte Tabellen wesentlich natürlicher. Für eine Aggregation innerhalb von Arrays benötigen Sie unnest(), das das Array zunächst in Zeilen erweitert — damit wird die normalisierte Struktur im Grunde zum Abfragezeitpunkt wiederhergestellt.
-- Count posts per tag (array approach — needs unnest)
SELECT tag, COUNT(*) AS post_count
FROM posts, unnest(tags) AS tag
GROUP BY tag
ORDER BY post_count DESC;
-- With a normalized post_tags table this would be simpler:
-- SELECT tag, COUNT(*) FROM post_tags GROUP BY tag;Array-Elemente ändern
Das Aktualisieren oder Löschen eines einzelnen Elements innerhalb eines Arrays erfordert eine umständliche Syntax — Sie müssen das gesamte Array ersetzen oder array_remove() verwenden. In einer normalisierten Tabelle löschen oder aktualisieren Sie einfach die betreffende Zeile mit DELETE oder UPDATE.
-- Remove a single tag from an array column
UPDATE posts
SET tags = array_remove(tags, 'beginner')
WHERE id = 1;
-- Append a new tag
UPDATE posts
SET tags = array_append(tags, 'tutorial')
WHERE id = 1;
SELECT title, tags FROM posts WHERE id = 1;Gültige Werte erzwingen
In einer normalisierten Tabelle können Sie mit einem Fremdschlüssel erzwingen, dass jeder Wert aus einer bekannten Menge stammt. Arrays können nicht auf eine andere Tabelle verweisen — sie unterstützen keine Fremdschlüssel.
Wenn Sie garantierte referenzielle Integrität für jedes Element benötigen, ist eine Kindtabelle die einzige Möglichkeit.
-- Normalized: only valid category IDs allowed (FK enforced)
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE post_categories (
post_id INT REFERENCES posts(id),
category_id INT REFERENCES categories(id),
PRIMARY KEY (post_id, category_id)
);
-- Array: no constraint possible — any text value is accepted
-- UPDATE posts SET tags = ARRAY['totally_invalid_tag'] WHERE id = 1;Ein praktischer Entscheidungsleitfaden
Verwenden Sie ein Array, wenn die Daten eine einfache flache Liste bilden, immer als Einheit gelesen werden, keine zusätzlichen Attribute pro Element besitzen und keine referenzielle Integrität erforderlich ist (z. B. Tags, Bezeichnungen, Suchbegriffe).
Verwenden Sie eine normalisierte Kindtabelle, wenn jedes Element eigene Attribute besitzt, Sie einzelne Werte verknüpfen oder aggregieren, Fremdschlüssel benötigen oder einzelne Elemente häufig aktualisiert oder gelöscht werden.
-- Summary example: tags as array (good fit)
SELECT title, tags
FROM posts
WHERE tags @> ARRAY['sql']
ORDER BY title;
-- Unnest when you need row-level processing
SELECT title, unnest(tags) AS tag
FROM posts
ORDER BY title, tag;Kurzer Wissenscheck
Welches Szenario eignet sich am besten dafür, Daten als PostgreSQL-Array statt in einer normalisierten Kindtabelle zu speichern?
Zusammenfassung der Lektion
In dieser Lektion haben Sie die wichtigsten Abwägungen zwischen Arrays und normalisierten Tabellen in PostgreSQL kennengelernt.
- Arrays sind kompakt und praktisch für flache Listen, die als Einheit gelesen werden, etwa Tags — ihnen fehlen jedoch Fremdschlüssel, Aktualisierungen einzelner Elemente sind umständlich, und für schnelle Suchen werden GIN-Indizes benötigt.
- Normalisierte Tabellen unterstützen Attribute pro Element, Fremdschlüssel-Integrität, effiziente Aggregationen und einfache Aktualisierungen auf Zeilenebene — zum Preis eines zusätzlichen JOINs.
- Die richtige Wahl hängt davon ab, wie Sie die Daten abfragen, aktualisieren und in Beziehung setzen — nicht nur davon, wie Sie sie speichern.
Häufig gestellte Fragen
Ist die Lektion „Arrays vs. normalisierte Tabellen“ kostenlos?
Ja — der vollständige Text von „Arrays vs. normalisierte Tabellen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des SQL Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der SQL Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Arrays vs. normalisierte Tabellen“?
Wann Arrays die richtige Wahl sind Du übst SQL Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um SQL Academy zu starten?
Keine Vorkenntnisse erforderlich. SQL Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Arrays vs. normalisierte Tabellen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser SQL Academy-Lektion Code schreiben und ausführen?
Ja. Jede SQL Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Grundlagen von Array-Spalten
- In Arrays suchen
- UNNEST und Aggregation
- Arrays vs. normalisierte Tabellen