Aufwärmen · Aufgabe 1 von 7
// B3.5 · ca. 30 Min. · Einstieg
HAVING und FILTER
Nach dieser Lektion behalten Sie nur die Gruppen, auf die es ankommt, zählen offene und zurückgegebene Ausleihen nebeneinander und listen die Titel einer Gruppe in einer Zelle.
Danach können Sie
- WHERE für einzelne Zeilen und HAVING für Gruppen wählen und ein Aggregat in WHERE reparieren
- Mit HAVING auf count oder sum nur die benötigten Gruppen behalten
- Mehrere Teilmengen in einem Durchgang mit FILTER zählen und Werte mit string_agg verbinden
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es voraus, bevor Sie weiterlesen. Die sci-fi-Bücher haben 2, 1 und 1 Exemplare; classic, fiction und fantasy haben je ein Buch, mit 1, 1 und 3 Exemplaren. Was liefert dies?
SELECT genre, count(*) FROM books WHERE copies = 1 GROUP BY genre HAVING count(*) > 1;Üben · Aufgabe 3 von 7
Ergänzen Sie das Schlüsselwort, damit die Abfrage nur die Genres mit mehr als einem Buch liefert.
SELECT genre, count(*) FROM books GROUP BY genre ____ count(*) > 1;SELECT genre, count(*) FROM books GROUP BY genre count(*) > 1;Üben · Aufgabe 4 von 7
Ordnen Sie jeder Klausel zu, was sie filtert oder ordnet.
Üben · Aufgabe 5 von 7
Fünf Ausleihen, drei davon noch offen (returned_on ist NULL). Was liefert dies?
SELECT count(*) FILTER (WHERE returned_on IS NULL) AS open, count(*) AS all_loans FROM loans;Denksport · Aufgabe 6 von 7
Knobelaufgabe. Ada und Linus wohnen in Berlin, Grace in München (Munich), Margarets Stadt ist NULL. Was liefert dies?
SELECT string_agg(city, ', ' ORDER BY city) FROM members;Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie mit der Bibliothek des Beispiels eine Abfrage über loans, verbunden mit books: für jedes Genre die Zahl der Ausleihen, die Zahl der noch offenen und die ausgeliehenen Titel alphabetisch in einer Zelle. Behalten Sie nur die Genres mit mindestens zwei Ausleihen. Sagen Sie zuerst voraus, welche Genres übrig bleiben und warum ein Titel zweimal erscheint.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
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
Gruppen filtern und Teilmengen zählen
setup.sql lädt die Bibliothek aus Modul B1: Bücher, Mitglieder und Ausleihen. main.sql behält die Genres mit mindestens drei Exemplaren, kombiniert WHERE und HAVING, zählt offene und zurückgegebene Ausleihen je Mitglied in einem Durchgang und listet die Titel jedes Genres. Auf Ihrem Computer: psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql.
main.sql
-- 1. Genres with at least three copies in all.
SELECT genre, count(*) AS books, sum(copies) AS copies
FROM books
GROUP BY genre
HAVING sum(copies) >= 3
ORDER BY genre;
-- 2. WHERE picks loans, HAVING picks members.
SELECT member_id, count(*) AS september_loans
FROM loans
WHERE loaned_on >= '2026-09-05'
GROUP BY member_id
HAVING count(*) >= 2
ORDER BY member_id;
-- 3. Several counts in one pass with FILTER.
SELECT m.name,
count(*) AS loans,
count(*) FILTER (WHERE l.returned_on IS NULL) AS open,
count(*) FILTER (WHERE l.returned_on IS NOT NULL) AS returned
FROM loans l
JOIN members m ON m.id = l.member_id
GROUP BY m.id, m.name
ORDER BY m.name;
-- 4. The titles of each genre in one cell.
SELECT genre, string_agg(title, ', ' ORDER BY title) AS titles
FROM books
GROUP BY genre
ORDER BY genre;
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);
Ausführen mit
psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sqlAusgabe
genre | books | copies
---------+-------+--------
fantasy | 1 | 3
sci-fi | 3 | 4
(2 rows)
member_id | september_loans
-----------+-----------------
2 | 2
(1 row)
name | loans | open | returned
-------+-------+------+----------
Ada | 2 | 1 | 1
Grace | 2 | 1 | 1
Linus | 1 | 1 | 0
(3 rows)
genre | titles
---------+----------------------------
classic | Emma
fantasy | The Hobbit
fiction | Beloved
sci-fi | Dune, Kindred, Neuromancer
(4 rows)- HAVING sum(copies) >= 3 behält fantasy (The Hobbit allein hat 3) und sci-fi (2 + 1 + 1); das Aggregat in HAVING ist ausgeschrieben, nicht aus dem Alias übernommen.
- WHERE verwirft zuerst Adas Ausleihe vom 2026-09-01; von den vier übrigen Ausleihen hat nur Mitglied 2 zwei, also behält HAVING eine Gruppe.
- Die drei Zählwerte der dritten Abfrage sehen jeweils andere Zeilen: FILTER schränkt nur die Eingabe seines eigenen count ein.
- ORDER BY in string_agg sortiert die Titel innerhalb jeder Zelle; das ORDER BY am Ende sortiert die Zeilen.
Ändern und ausführen
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.
Übungen
Übung 1 von 2
Nur die Gruppen, auf die es ankommt
Schreiben Sie zwei Abfragen. Erstens: die Genres mit insgesamt mindestens zwei Exemplaren, mit genre und den Exemplaren insgesamt (copies), nach Genre sortiert. Zweitens: die Mitglieder, die mindestens zwei Bücher ausgeliehen haben, mit Name und der Zahl der Ausleihen (loans), nach Namen sortiert; verbinden Sie loans mit members.
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.
Hinweise
Hinweis 1
Die Bedingung betrifft die Summe einer Gruppe, gehört also in HAVING, zwischen GROUP BY und ORDER BY.
Hinweis 2
Schreiben Sie das Aggregat in HAVING aus: HAVING sum(copies) >= 2. Der Alias copies ist dort unbekannt.
Hinweis 3
Für die Mitglieder: JOIN members m ON m.id = l.member_id, GROUP BY m.id, m.name, HAVING count(*) >= 2.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
SELECT genre, sum(copies) AS copies
FROM books
GROUP BY genre
HAVING sum(copies) >= 2
ORDER BY genre;
SELECT m.name, count(*) AS loans
FROM loans l
JOIN members m ON m.id = l.member_id
GROUP BY m.id, m.name
HAVING count(*) >= 2
ORDER BY m.name;
Auf dem eigenen Computer ausführen
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
-- setup.sql has loaded books, members and loans.
SELECT genre, sum(copies) AS copies
FROM books
GROUP BY genre
ORDER BY genre;
test.sql
-- test: fantasy und sci-fi haben mindestens zwei Exemplare, dann haben Ada und Grace je zwei Ausleihen
-- output:
-- genre | copies
-- ---------+--------
-- fantasy | 3
-- sci-fi | 4
-- (2 rows)
--
-- name | loans
-- -------+-------
-- Ada | 2
-- Grace | 2
-- (2 rows)
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);
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
Offen, zurückgegeben und welche Titel
Die Vorlage zählt die Ausleihen jedes Mitglieds. Ersetzen Sie den einen Zählwert durch zwei: open (kein returned_on) und returned, mit FILTER. Ergänzen Sie dann titles: die Titel, die das Mitglied ausgeliehen hat, alphabetisch, getrennt durch Komma und Leerzeichen. Verbinden Sie books für die Titel. Behalten Sie die Sortierung nach Namen.
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.
Hinweise
Hinweis 1
count(*) FILTER (WHERE l.returned_on IS NULL) AS open zählt nur die offenen Ausleihen jedes Mitglieds.
Hinweis 2
Die zurückgegebenen Ausleihen sind die, bei denen l.returned_on IS NOT NULL gilt.
Hinweis 3
JOIN books b ON b.id = l.book_id, dann string_agg(b.title, ', ' ORDER BY b.title) AS titles; das ORDER BY steht nach dem Trennzeichen.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
SELECT m.name,
count(*) FILTER (WHERE l.returned_on IS NULL) AS open,
count(*) FILTER (WHERE l.returned_on IS NOT NULL) AS returned,
string_agg(b.title, ', ' ORDER BY b.title) AS titles
FROM loans l
JOIN members m ON m.id = l.member_id
JOIN books b ON b.id = l.book_id
GROUP BY m.id, m.name
ORDER BY m.name;
Auf dem eigenen Computer ausführen
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 per member: one count so far.
SELECT m.name, count(*) AS loans
FROM loans l
JOIN members m ON m.id = l.member_id
GROUP BY m.id, m.name
ORDER BY m.name;
test.sql
-- test: Jedes Mitglied mit offenen und zurückgegebenen Ausleihen und den ausgeliehenen Titeln in alphabetischer Reihenfolge
-- output:
-- name | open | returned | titles
-- -------+------+----------+-------------------
-- Ada | 1 | 1 | Dune, Kindred
-- Grace | 1 | 1 | Emma, Neuromancer
-- Linus | 1 | 0 | Dune
-- (3 rows)
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);
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.
Häufige Fehler
Ein Aggregat in WHERE
SELECT genre, sum(copies)
FROM books
WHERE sum(copies) >= 3
GROUP BY genre;
Was psql ausgibt
ERROR: aggregate functions are not allowed in WHEREWarum, und die Lösung
WHERE entscheidet, welche Zeilen in die Gruppen kommen, läuft also, bevor es eine Summe gibt. Eine Bedingung über die Summe einer Gruppe gehört in HAVING, nach GROUP BY: GROUP BY genre HAVING sum(copies) >= 3. Bedingungen über einzelne Zeilen, etwa year > 1970, bleiben in WHERE.
Ein Ausgabe-Alias in HAVING
SELECT genre, count(*) AS n
FROM books
GROUP BY genre
HAVING n > 1;
Was psql ausgibt
ERROR: column "n" does not existWarum, und die Lösung
Den Alias n vergibt die SELECT-Liste, und die wird nach HAVING berechnet, also kennt HAVING ihn nicht. Wiederholen Sie das Aggregat: HAVING count(*) > 1. ORDER BY n würde funktionieren, weil ORDER BY nach der SELECT-Liste läuft. Ein Alias, der zugleich ein Tabellenname ist, ist noch tückischer: HAVING books > 1 scheitert mit operator does not exist: books > integer, weil books dann die ganze Zeile der Tabelle meint.
string_agg über Zahlen
SELECT genre, string_agg(year, ', ')
FROM books
GROUP BY genre;
Was psql ausgibt
ERROR: function string_agg(integer, unknown) does not existWarum, und die Lösung
string_agg verbindet Text (oder bytea), und year ist ein integer. Wandeln Sie die Werte zuerst in Text um: string_agg(year::text, ', ' ORDER BY year). Sortieren nach year selbst hält die Zahlen in numerischer Reihenfolge, also kommt 1965 vor 1979 und 1984.
PostgreSQL im Browser: PGlite 0.5.8 (PostgreSQL 18.3), Apache-2.0 und PostgreSQL License. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.