Aufwärmen · Aufgabe 1 von 7
Aufwärmen aus der letzten Lektion. Persuasion wurde mit copies NULL eingefügt. Was passiert?
ALTER TABLE books ALTER COLUMN copies SET NOT NULL// B4.5 · ca. 30 Min. · Einstieg
Nach diesem Projekt legen Sie Regeln auf eine Datenbank, die schon fehlerhafte Daten enthält: die verletzenden Zeilen finden, korrigieren, die Regeln hinzufügen und zeigen, dass sie gelten.
Lektion 5 von 5 in B4 Daten und Schema ändern
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
ALTER TABLE books ALTER COLUMN copies SET NOT NULLVorhersagen · Aufgabe 2 von 7
Üben · Aufgabe 3 von 7
SELECT l.id, l.member_id
FROM loans l
LEFT JOIN members m ON m.id = l.member_id
WHERE m.id IS ____;Üben · Aufgabe 4 von 7
Üben · Aufgabe 5 von 7
Denksport · Aufgabe 6 von 7
ALTER TABLE loans ADD CHECK (returned_on >= loaned_on)Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
setup.sql lädt die Bibliothek aus Modul B1 ohne jede Regel, dazu drei Zeilen, die hineingeraten sind: eine Ausleihe für Mitglied 9, eine Ausleihe mit vertauschten Daten und ein Buch ohne Exemplare. main.sql findet sie, korrigiert sie, fügt Schlüssel, Fremdschlüssel, einen CHECK, ein NOT NULL, einen Standardwert und eine neue Spalte hinzu und listet auf, was loans jetzt durchsetzt. Auf Ihrem Computer: psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql.
main.sql
-- 1. Find the rows that would break the new rules.
SELECT l.id, l.member_id
FROM loans l
LEFT JOIN members m ON m.id = l.member_id
WHERE m.id IS NULL;
SELECT id, loaned_on, returned_on FROM loans WHERE returned_on < loaned_on;
SELECT id, title FROM books WHERE copies IS NULL;
-- 2. Fix them.
DELETE FROM loans WHERE id = 6;
UPDATE loans SET loaned_on = '2026-09-15', returned_on = '2026-09-25' WHERE id = 7;
UPDATE books SET copies = 1 WHERE id = 7;
-- 3. Keys first, then the foreign keys and rules that rely on them.
ALTER TABLE books ADD PRIMARY KEY (id);
ALTER TABLE members ADD PRIMARY KEY (id);
ALTER TABLE loans ADD PRIMARY KEY (id);
ALTER TABLE loans ADD FOREIGN KEY (book_id) REFERENCES books (id);
ALTER TABLE loans ADD FOREIGN KEY (member_id) REFERENCES members (id);
ALTER TABLE loans ADD CHECK (returned_on >= loaned_on);
ALTER TABLE books ALTER COLUMN copies SET NOT NULL;
ALTER TABLE books ALTER COLUMN copies SET DEFAULT 1;
-- 4. A new column for members.
ALTER TABLE members ADD COLUMN active boolean NOT NULL DEFAULT true;
-- 5. What loans now enforces.
SELECT contype, count(*) FROM pg_constraint
WHERE conrelid = 'loans'::regclass
GROUP BY contype
ORDER BY contype;
setup.sql
CREATE TABLE books (
id integer,
title text,
author text,
year integer,
genre text,
copies integer
);
INSERT INTO books VALUES
(1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
(2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
(3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
(4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
(5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
(6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);
CREATE TABLE members (
id integer,
name text,
email text,
city text,
joined date
);
INSERT INTO members VALUES
(1, 'Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
(2, 'Grace', 'grace@example.com', 'Munich', '2025-03-02'),
(3, 'Linus', NULL, 'Berlin', '2026-02-10'),
(4, 'Margaret', 'margaret@example.com', NULL, '2026-05-20');
CREATE TABLE loans (
id integer,
book_id integer,
member_id integer,
loaned_on date,
returned_on date
);
INSERT INTO loans VALUES
(1, 1, 1, '2026-09-01', '2026-09-10'),
(2, 3, 1, '2026-09-12', NULL),
(3, 2, 2, '2026-09-05', '2026-09-20'),
(4, 1, 3, '2026-09-15', NULL),
(5, 6, 2, '2026-09-18', NULL);
-- Rows that got in while nothing was checked.
INSERT INTO books VALUES (7, 'Persuasion', 'Jane Austen', 1817, 'classic', NULL);
INSERT INTO loans VALUES
(6, 5, 9, '2026-09-20', NULL),
(7, 4, 4, '2026-09-25', '2026-09-15');
Ausführen mit
psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sqlAusgabe
id | member_id
----+-----------
6 | 9
(1 row)
id | loaned_on | returned_on
----+------------+-------------
7 | 2026-09-25 | 2026-09-15
(1 row)
id | title
----+------------
7 | Persuasion
(1 row)
contype | count
---------+-------
c | 1
f | 2
n | 1
p | 1
(4 rows)Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.
Übung 1 von 2
Der Startcode findet die drei fehlerhaften Zeilen. Korrigieren Sie sie: Ausleihe 6 lässt sich keinem Mitglied zuordnen, also entfernen Sie sie; Ausleihe 7 ging am 2026-09-15 hinaus und kam am 2026-09-25 zurück, die Daten wurden vertauscht; Persuasion hat 1 Exemplar. Behalten Sie jede andere Zeile.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.
DELETE FROM loans WHERE id = 6; entfernt genau die Ausleihe für Mitglied 9.
Setzen Sie für Ausleihe 7 beide Daten: UPDATE loans SET loaned_on = '2026-09-15', returned_on = '2026-09-25' WHERE id = 7;
Persuasion ist Buch 7: UPDATE books SET copies = 1 WHERE id = 7;
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
DELETE FROM loans WHERE id = 6;
UPDATE loans SET loaned_on = '2026-09-15', returned_on = '2026-09-25' WHERE id = 7;
UPDATE books SET copies = 1 WHERE id = 7;
Installieren Sie PostgreSQL 18 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.sql
-- Loans whose member does not exist.
SELECT l.id, l.member_id
FROM loans l
LEFT JOIN members m ON m.id = l.member_id
WHERE m.id IS NULL;
-- Loans that came back before they went out.
SELECT id, loaned_on, returned_on FROM loans WHERE returned_on < loaned_on;
-- Books without copies.
SELECT id, title FROM books WHERE copies IS NULL;
test.sql
-- test: Jede Ausleihe nennt ein vorhandenes Mitglied
SELECT NOT EXISTS (SELECT 1 FROM loans l LEFT JOIN members m ON m.id = l.member_id WHERE m.id IS NULL);
-- test: Ausleihe 7 ging am 2026-09-15 hinaus und kam am 2026-09-25 zurück
SELECT loaned_on = '2026-09-15' AND returned_on = '2026-09-25' FROM loans WHERE id = 7;
-- test: Keine Ausleihe kommt zurück, bevor sie hinausging
SELECT NOT EXISTS (SELECT 1 FROM loans WHERE returned_on < loaned_on);
-- test: Persuasion hat 1 Exemplar
SELECT copies = 1 FROM books WHERE id = 7;
-- test: Sechs Ausleihen, sieben Bücher und vier Mitglieder bleiben
SELECT (SELECT count(*) FROM loans) = 6 AND (SELECT count(*) FROM books) = 7 AND (SELECT count(*) FROM members) = 4;
setup.sql
CREATE TABLE books (
id integer,
title text,
author text,
year integer,
genre text,
copies integer
);
INSERT INTO books VALUES
(1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
(2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
(3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
(4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
(5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
(6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);
CREATE TABLE members (
id integer,
name text,
email text,
city text,
joined date
);
INSERT INTO members VALUES
(1, 'Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
(2, 'Grace', 'grace@example.com', 'Munich', '2025-03-02'),
(3, 'Linus', NULL, 'Berlin', '2026-02-10'),
(4, 'Margaret', 'margaret@example.com', NULL, '2026-05-20');
CREATE TABLE loans (
id integer,
book_id integer,
member_id integer,
loaned_on date,
returned_on date
);
INSERT INTO loans VALUES
(1, 1, 1, '2026-09-01', '2026-09-10'),
(2, 3, 1, '2026-09-12', NULL),
(3, 2, 2, '2026-09-05', '2026-09-20'),
(4, 1, 3, '2026-09-15', NULL),
(5, 6, 2, '2026-09-18', NULL);
-- Rows that got in while nothing was checked.
INSERT INTO books VALUES (7, 'Persuasion', 'Jane Austen', 1817, 'classic', NULL);
INSERT INTO loans VALUES
(6, 5, 9, '2026-09-20', NULL),
(7, 4, 4, '2026-09-25', '2026-09-15');
psql führt die Dateien auf Ihrem eigenen PostgreSQL-Server aus. Nehmen Sie eine leere Datenbank, die Sie wegwerfen können: Hängen Sie -d und ihren Namen an den Befehl an.
Programm ausführen:
psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sqlFür die Prüfungen gibt es auf Ihrem Computer noch keinen Befehl. Sie stehen in test.sql: Jede Prüfabfrage gibt t aus, wenn sie besteht.
Übung 2 von 2
Die Daten sind jetzt sauber. Fügen Sie die Regeln mit ALTER TABLE hinzu: einen Primärschlüssel auf id in books, members und loans; Fremdschlüssel von loans.book_id zu books und von loans.member_id zu members; CHECK (returned_on >= loaned_on) auf loans; copies NOT NULL mit Standardwert 1; und eine neue Spalte active in members, boolean, NOT NULL, true für alle.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.
Zuerst die Primärschlüssel: ALTER TABLE books ADD PRIMARY KEY (id); und dasselbe für members und loans.
ALTER TABLE loans ADD FOREIGN KEY (member_id) REFERENCES members (id); und dasselbe für book_id.
ALTER COLUMN copies SET NOT NULL und SET DEFAULT 1 sind zwei Anweisungen; ADD COLUMN active boolean NOT NULL DEFAULT true füllt die vier Mitglieder.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
ALTER TABLE books ADD PRIMARY KEY (id);
ALTER TABLE members ADD PRIMARY KEY (id);
ALTER TABLE loans ADD PRIMARY KEY (id);
ALTER TABLE loans ADD FOREIGN KEY (book_id) REFERENCES books (id);
ALTER TABLE loans ADD FOREIGN KEY (member_id) REFERENCES members (id);
ALTER TABLE loans ADD CHECK (returned_on >= loaned_on);
ALTER TABLE books ALTER COLUMN copies SET NOT NULL;
ALTER TABLE books ALTER COLUMN copies SET DEFAULT 1;
ALTER TABLE members ADD COLUMN active boolean NOT NULL DEFAULT true;
Installieren Sie PostgreSQL 18 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.sql
-- The data is clean. Add the rules here, keys first.
SELECT count(*) FROM loans;
test.sql
-- test: books, members und loans haben einen Primärschlüssel auf id
SELECT EXISTS (SELECT 1 FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey) WHERE c.conrelid = 'books'::regclass AND c.contype = 'p' AND cardinality(c.conkey) = 1 AND a.attname = 'id') AND EXISTS (SELECT 1 FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey) WHERE c.conrelid = 'members'::regclass AND c.contype = 'p' AND cardinality(c.conkey) = 1 AND a.attname = 'id') AND EXISTS (SELECT 1 FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey) WHERE c.conrelid = 'loans'::regclass AND c.contype = 'p' AND cardinality(c.conkey) = 1 AND a.attname = 'id');
-- test: loans.book_id verweist auf books
SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'loans'::regclass AND contype = 'f' AND confrelid = 'books'::regclass);
-- test: loans.member_id verweist auf members
SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'loans'::regclass AND contype = 'f' AND confrelid = 'members'::regclass);
-- test: Ein CHECK-Constraint schützt die Datumswerte von loans
SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'loans'::regclass AND contype = 'c');
-- test: copies ist NOT NULL mit Standardwert 1
SELECT a.attnotnull AND pg_get_expr(d.adbin, d.adrelid) = '1' FROM pg_attribute a JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum WHERE a.attrelid = 'books'::regclass AND a.attname = 'copies';
-- test: Jedes Mitglied ist active, und active ist NOT NULL
SELECT (SELECT count(*) FROM members WHERE active) = 4 AND (SELECT attnotnull FROM pg_attribute WHERE attrelid = 'members'::regclass AND attname = 'active');
setup.sql
CREATE TABLE books (
id integer,
title text,
author text,
year integer,
genre text,
copies integer
);
INSERT INTO books VALUES
(1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
(2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
(3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
(4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
(5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
(6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);
CREATE TABLE members (
id integer,
name text,
email text,
city text,
joined date
);
INSERT INTO members VALUES
(1, 'Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
(2, 'Grace', 'grace@example.com', 'Munich', '2025-03-02'),
(3, 'Linus', NULL, 'Berlin', '2026-02-10'),
(4, 'Margaret', 'margaret@example.com', NULL, '2026-05-20');
CREATE TABLE loans (
id integer,
book_id integer,
member_id integer,
loaned_on date,
returned_on date
);
INSERT INTO loans VALUES
(1, 1, 1, '2026-09-01', '2026-09-10'),
(2, 3, 1, '2026-09-12', NULL),
(3, 2, 2, '2026-09-05', '2026-09-20'),
(4, 1, 3, '2026-09-15', NULL),
(5, 6, 2, '2026-09-18', NULL);
-- Rows that got in while nothing was checked.
INSERT INTO books VALUES (7, 'Persuasion', 'Jane Austen', 1817, 'classic', NULL);
INSERT INTO loans VALUES
(6, 5, 9, '2026-09-20', NULL),
(7, 4, 4, '2026-09-25', '2026-09-15');
-- Step 1: the bad rows, fixed.
DELETE FROM loans WHERE id = 6;
UPDATE loans SET loaned_on = '2026-09-15', returned_on = '2026-09-25' WHERE id = 7;
UPDATE books SET copies = 1 WHERE id = 7;
psql führt die Dateien auf Ihrem eigenen PostgreSQL-Server aus. Nehmen Sie eine leere Datenbank, die Sie wegwerfen können: Hängen Sie -d und ihren Namen an den Befehl an.
Programm ausführen:
psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sqlFür die Prüfungen gibt es auf Ihrem Computer noch keinen Befehl. Sie stehen in test.sql: Jede Prüfabfrage gibt t aus, wenn sie besteht.
ALTER TABLE members ADD PRIMARY KEY (id);
ALTER TABLE loans ADD FOREIGN KEY (member_id) REFERENCES members (id);
Was psql ausgibt
ERROR: insert or update on table "loans" violates foreign key constraint "loans_member_id_fkey"Warum, und die Lösung
Der neue Fremdschlüssel wird an jeder Ausleihe geprüft, und Ausleihe 6 nennt Mitglied 9, das es nicht gibt; die DETAIL-Zeile nennt den Schlüssel. Finden Sie solche Zeilen mit LEFT JOIN members … WHERE m.id IS NULL, korrigieren oder löschen Sie sie und fügen Sie dann den Fremdschlüssel hinzu.
ALTER TABLE loans ADD FOREIGN KEY (book_id) REFERENCES books (id);
Was psql ausgibt
ERROR: there is no unique constraint matching given keys for referenced table "books"Warum, und die Lösung
Ein Fremdschlüssel muss auf einen Primärschlüssel oder eine UNIQUE-Spalte verweisen, damit jeder Wert genau eine Zeile findet. Fügen Sie zuerst ALTER TABLE books ADD PRIMARY KEY (id); hinzu.
ALTER TABLE loans ADD CHECK (returned_on >= loaned_on);
Was psql ausgibt
ERROR: check constraint "loans_check" of relation "loans" is violated by some rowWarum, und die Lösung
Ausleihe 7 kam zurück, bevor sie hinausging. Listen Sie solche Zeilen mit WHERE returned_on < loaned_on auf, korrigieren Sie die Daten mit UPDATE und fügen Sie den CHECK dann erneut hinzu.
PostgreSQL im Browser: PGlite 0.5.8 (PostgreSQL 18.3), Apache-2.0 und PostgreSQL License. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.