Zum Inhalt springen
aviral gupta

// B3.2 · ca. 30 Min. · Einstieg

Inner Joins

Nach dieser Lektion verbinden Sie die Zeilen von zwei oder drei Tabellen mit JOIN, benennen jede Spalte eindeutig und erkennen einen Join, der jede Zeile mit jeder anderen paart.

Lektion 2 von 6 in B3 Joins und Aggregate

Danach können Sie

  • Zwei Tabellen mit JOIN ... ON verbinden und vorhersagen, welche Zeilen zurückkommen
  • Spalten in einem Join mit Tabellenaliasen und qualifizierten Namen benennen, oder mit USING, wenn die Spaltennamen gleich sind
  • Drei Tabellen verketten und eine fehlende Join-Bedingung an der Zeilenzahl erkennen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Ausleihe 3 hat book_id 2 und member_id 2. Welches Buch hat welches Mitglied ausgeliehen? Die Tabellen stehen unten.

    -- books: 1 Dune (sci-fi), 2 Emma (classic), 3 Kindred (sci-fi),
    --        4 Beloved (fiction), 5 The Hobbit (fantasy), 6 Neuromancer (sci-fi)
    -- members: 1 Ada (Berlin), 2 Grace (Munich), 3 Linus (Berlin), 4 Margaret (no city)
    -- loans (id, book_id, member_id): (1, 1, 1), (2, 3, 1), (3, 2, 2), (4, 1, 3), (5, 6, 2)
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es voraus, bevor Sie weiterlesen. members hat 4 Zeilen und loans 5. Ada hat zwei Ausleihen, Grace zwei, Linus eine und Margaret keine. Wie viele Zeilen liefert dies?

    SELECT m.name, l.id
    FROM members m
    JOIN loans l ON l.member_id = m.id;
  3. Üben · Aufgabe 3 von 7

    Füllen Sie die Lücke, damit die Abfrage läuft: Die ON-Bedingung und die Select-Liste nennen die Tabelle books b.

    SELECT b.title, l.loaned_on
    FROM loans l
    JOIN books ____ ON b.id = l.book_id
    ORDER BY l.id;
    SELECT b.title, l.loaned_on FROM loans l JOIN books ON b.id = l.book_id ORDER BY l.id;
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Teil eines Joins zu, was er tut.

  5. Üben · Aufgabe 5 von 7

    Bringen Sie die Zeilen in die richtige Reihenfolge, um die gerade ausgeliehenen Bücher mit dem Ausleihdatum aufzulisten, die älteste Ausleihe zuerst.

    1. 1.FROM loans AS l
    2. 2.JOIN books AS b
    3. 3.ORDER BY l.loaned_on
    4. 4.WHERE l.returned_on IS NULL
    5. 5.ON b.id = l.book_id
    6. 6.SELECT b.title, l.loaned_on
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Eine Tabelle lässt sich unter zwei Aliasen mit sich selbst verbinden. Ada und Linus wohnen in Berlin, Grace in München, und Margarets Stadt ist NULL. Wie viele Zeilen liefert dies?

    SELECT m1.name, m2.name
    FROM members m1
    JOIN members m2 ON m1.city = m2.city;
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Listen Sie im Editor des Beispiels jede Ausleihe eines Sci-Fi-Buchs mit dem Namen des Mitglieds, dem Titel und dem Ausleihdatum auf, die älteste Ausleihe zuerst. Schreiben Sie dieselbe Abfrage dann in der älteren Form, mit durch Kommas getrennten Tabellen in FROM und den Join-Bedingungen in WHERE, und prüfen Sie, dass beide dieselben vier Zeilen liefern.

    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

Ausleihen mit Titeln, Namen und Regalen

setup.sql lädt die Bibliothek aus Modul B1 (books, members, loans) und eine kleine Tabelle shelves: das Stockwerk, auf dem jedes Genre steht. main.sql verbindet loans mit books, dann zusätzlich mit members, verbindet books per USING mit shelves und zählt zuletzt die Paare eines Joins ohne Bedingung. Auf Ihrem Rechner: psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql.

main.sql

-- 1. Each loan with the title of its book.
SELECT l.id, b.title, l.loaned_on
FROM loans AS l
JOIN books AS b ON b.id = l.book_id
ORDER BY l.id;

-- 2. Open loans: who has which book (three tables).
SELECT l.id, m.name, b.title
FROM loans l
JOIN members m ON m.id = l.member_id
JOIN books b ON b.id = l.book_id
WHERE l.returned_on IS NULL
ORDER BY l.id;

-- 3. The floor of each book's shelf, joined on genre.
SELECT genre, b.title, s.floor
FROM books b
JOIN shelves s USING (genre)
ORDER BY s.floor, b.title;

-- 4. No join condition: every loan with every book.
SELECT count(*) AS pairs
FROM loans
CROSS JOIN books;

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);

CREATE TABLE shelves (
  genre text,
  floor integer
);
INSERT INTO shelves VALUES
  ('classic', 1),
  ('fantasy', 2),
  ('sci-fi', 2),
  ('poetry', 3);

Ausführen mit

psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql

Ausgabe

 id |    title    | loaned_on
----+-------------+------------
  1 | Dune        | 2026-09-01
  2 | Kindred     | 2026-09-12
  3 | Emma        | 2026-09-05
  4 | Dune        | 2026-09-15
  5 | Neuromancer | 2026-09-18
(5 rows)

 id | name  |    title
----+-------+-------------
  2 | Ada   | Kindred
  4 | Linus | Dune
  5 | Grace | Neuromancer
(3 rows)

  genre  |    title    | floor
---------+-------------+-------
 classic | Emma        |     1
 sci-fi  | Dune        |     2
 sci-fi  | Kindred     |     2
 sci-fi  | Neuromancer |     2
 fantasy | The Hobbit  |     2
(5 rows)

 pairs
-------
    30
(1 row)
  • Fünf Ausleihen ergeben fünf Zeilen, und Dune erscheint zweimal, weil es zweimal ausgeliehen wurde. Beloved und The Hobbit passen zu keiner Ausleihe, also lässt der Inner Join sie weg.
  • Das zweite JOIN fügt members zu Zeilen hinzu, die schon eine Ausleihe und ihr Buch enthalten; WHERE behält dann die drei offenen Ausleihen.
  • USING (genre) verbindet über gleiche Genres und zeigt genre einmal, unqualifiziert. Beloved fehlt: Kein Regal führt fiction. Das Regal poetry fehlt ebenfalls: Kein Buch ist poetry.
  • Ohne Bedingung wird jede der 5 Ausleihen mit jedem der 6 Bücher gepaart: 30 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

Ausleihen der Berliner Mitglieder

Listen Sie die Ausleihen der Mitglieder auf, die in Berlin wohnen: den Namen des Mitglieds, den Titel des Buchs und das Ausleihdatum, die älteste Ausleihe zuerst. Der Starter zeigt nur die Nummern, die in loans stehen. Verbinden Sie members und books, und filtern Sie nach der Stadt des Mitglieds.

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
  1. Hinweis 1

    Gehen Sie von loans aus und fügen Sie ein JOIN pro benötigter Tabelle hinzu: members für Name und Stadt, books für den Titel.

  2. Hinweis 2

    Jedes JOIN braucht sein eigenes ON: m.id = l.member_id und b.id = l.book_id.

  3. Hinweis 3

    Die Stadt gehört zum Mitglied, der Filter lautet also WHERE m.city = 'Berlin'.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

SELECT m.name, b.title, l.loaned_on
FROM loans l
JOIN members m ON m.id = l.member_id
JOIN books b ON b.id = l.book_id
WHERE m.city = 'Berlin'
ORDER BY l.loaned_on;
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, loans and shelves.
SELECT l.member_id, l.book_id, l.loaned_on
FROM loans l
ORDER BY l.loaned_on;

test.sql

-- test: Die Abfrage listet die drei Ausleihen von Ada und Linus mit Titeln, die älteste zuerst
-- output:
--  name  |  title  | loaned_on
-- -------+---------+------------
--  Ada   | Dune    | 2026-09-01
--  Ada   | Kindred | 2026-09-12
--  Linus | Dune    | 2026-09-15
-- (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);

CREATE TABLE shelves (
  genre text,
  floor integer
);
INSERT INTO shelves VALUES
  ('classic', 1),
  ('fantasy', 2),
  ('sci-fi', 2),
  ('poetry', 3);

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.sql

Fü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

Fünfzehn Zeilen für vier Ausleihen

Die erste Abfrage soll zeigen, wer welches Sci-Fi-Buch ausgeliehen hat, liefert aber 15 Zeilen für 4 Ausleihen: books wird ohne Bedingung verbunden. Geben Sie ihr die richtige Join-Bedingung. Fügen Sie dann eine zweite Abfrage hinzu: Titel und Stockwerk jedes Buchs auf Stockwerk 2, books und shelves per USING verbunden, nach Titel sortiert.

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
  1. Hinweis 1

    CROSS JOIN paart jede Ausleihe mit jedem Sci-Fi-Buch: 5 × 3 = 15 Zeilen.

  2. Hinweis 2

    Ersetzen Sie CROSS JOIN books b durch JOIN books b ON b.id = l.book_id.

  3. Hinweis 3

    books und shelves haben beide eine Spalte genre, also kann die zweite Abfrage JOIN shelves s USING (genre) verwenden.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

-- Every loan of a sci-fi book: who borrowed which title.
SELECT m.name, b.title
FROM loans l
JOIN members m ON m.id = l.member_id
JOIN books b ON b.id = l.book_id
WHERE b.genre = 'sci-fi'
ORDER BY m.name, b.title;

SELECT b.title, s.floor
FROM books b
JOIN shelves s USING (genre)
WHERE s.floor = 2
ORDER BY b.title;
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

-- Every loan of a sci-fi book: who borrowed which title.
SELECT m.name, b.title
FROM loans l
JOIN members m ON m.id = l.member_id
CROSS JOIN books b
WHERE b.genre = 'sci-fi'
ORDER BY m.name, b.title;

test.sql

-- test: Die erste Abfrage listet die vier Sci-Fi-Ausleihen, die zweite die vier Bücher auf Stockwerk 2
-- output:
--  name  |    title
-- -------+-------------
--  Ada   | Dune
--  Ada   | Kindred
--  Grace | Neuromancer
--  Linus | Dune
-- (4 rows)
--
--     title    | floor
-- -------------+-------
--  Dune        |     2
--  Kindred     |     2
--  Neuromancer |     2
--  The Hobbit  |     2
-- (4 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);

CREATE TABLE shelves (
  genre text,
  floor integer
);
INSERT INTO shelves VALUES
  ('classic', 1),
  ('fantasy', 2),
  ('sci-fi', 2),
  ('poetry', 3);

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.sql

Fü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 Spaltenname, den beide Tabellen haben

SELECT id, title
FROM books b
JOIN loans l ON l.book_id = b.id;

Was psql ausgibt

ERROR:  column reference "id" is ambiguous

Warum, und die Lösung

books und loans haben beide eine Spalte id, und PostgreSQL rät nicht, welche Sie meinen. Qualifizieren Sie sie mit dem Alias: b.id für das Buch, l.id für die Ausleihe. Wer in einem Join jede Spalte qualifiziert (auch b.title), hält die Abfrage lauffähig, wenn eine Tabelle später eine gleichnamige Spalte bekommt.

Der Tabellenname, nachdem sie einen Alias hat

SELECT books.title
FROM books AS b
JOIN loans AS l ON l.book_id = b.id;

Was psql ausgibt

ERROR:  invalid reference to FROM-clause entry for table "books"

Warum, und die Lösung

Ein Alias ersetzt den Tabellennamen für die ganze Abfrage, also funktioniert books.title nicht mehr, sobald FROM books AS b sagt. PostgreSQL fügt den Hinweis Perhaps you meant to reference the table alias "b". hinzu. Schreiben Sie b.title, oder lassen Sie den Alias weg.

USING mit Spalten verschiedenen Namens

SELECT m.name, l.loaned_on
FROM loans l
JOIN members m USING (member_id);

Was psql ausgibt

ERROR:  column "member_id" specified in USING clause does not exist in right table

Warum, und die Lösung

USING (member_id) braucht eine Spalte member_id in beiden Tabellen, doch in members heißt die Spalte id. USING funktioniert nur, wenn die Namen übereinstimmen. Schreiben Sie die Bedingung aus: JOIN members m ON m.id = l.member_id.

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.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Ein Join paart Zeilen, die eine Bedingung erfüllen

loans speichert nur Nummern: book_id 1, member_id 3. Um den Titel zu sehen, paart FROM loans JOIN books ON books.id = loans.book_id jede Ausleihe mit jedem Buch, für das die ON-Bedingung wahr ist, und jedes Paar wird eine Ergebniszeile. Eine Ausleihe passt hier zu genau einem Buch, also gibt es 5 Zeilen. Dune wurde zweimal ausgeliehen und erscheint zweimal. Beloved und The Hobbit wurden nie ausgeliehen: Keine Ausleihe passt zu ihnen, und ein Inner Join lässt sie weg. JOIN allein bedeutet INNER JOIN. Alles nach FROM (WHERE, ORDER BY, LIMIT) arbeitet dann mit den verbundenen Zeilen.

Aliase, qualifizierte Namen und USING

FROM loans AS l JOIN books AS b gibt jeder Tabelle für diese Abfrage einen kurzen Namen (AS ist optional). Hat eine Tabelle einen Alias, gilt nur noch er: books.title scheitert dann. Schreiben Sie l.id und b.id, wenn beide Tabellen eine id haben; ein nacktes id scheitert mit column reference "id" is ambiguous. Jede Spalte zu qualifizieren ist guter Stil, damit die Abfrage eine spätere neue Spalte übersteht. Heißt die verbindende Spalte in beiden Tabellen gleich, ist USING (genre) die Kurzform von ON b.genre = s.genre, und SELECT * zeigt genre einmal. loans.member_id und members.id heißen verschieden, USING kann sie also nicht verbinden.

Drei Tabellen, und der Join ohne Bedingung

Jedes JOIN fügt eine Tabelle mit eigenem ON hinzu: FROM loans l JOIN members m ON m.id = l.member_id JOIN books b ON b.id = l.book_id liefert eine Zeile pro Ausleihe, mit dem Namen des Mitglieds und dem Titel. Die Joins laufen von links nach rechts, und jedes ON darf die Tabellen davor verwenden. Ohne Bedingung erhalten Sie jede Kombination: loans CROSS JOIN books paart 5 Ausleihen mit 6 Büchern, 5 × 6 = 30 Zeilen. FROM loans, books ohne WHERE tut dasselbe. Ein Ergebnis, das viel größer ist als die Ausgangstabelle, bedeutet also eine fehlende oder falsche Join-Bedingung. JOIN ohne ON oder USING ist ein Syntaxfehler.

Quellen

Zuletzt geprüft am 30. September 2026