🧚 Hör mal! Beekeeper Studio ist eine schnelle, moderne Open-Source-Datenbank-GUI Download
2. Juli 2025 Von Matthew Rathbone

SQL-JOINs verbinden Zeilen aus zwei Tabellen über eine gemeinsame Spalte, zum Beispiel die author_id eines Buchs und die id eines Autors. Die Art des Joins entscheidet, was mit den Zeilen ohne Treffer passiert. Die Beispiele funktionieren in PostgreSQL, MySQL, SQLite und SQL Server.

So verwendest du SQL-JOINs

Schreib die zweite Tabelle hinter JOIN und gib in ON an, wie ihre Zeilen zu denen der ersten Tabelle passen:

SELECT columns
FROM table_a
JOIN table_b ON table_a.key = table_b.key;

Alle Beispiele in diesem Abschnitt verbinden dieselben zwei Tabellen: Butler hat zwei der Bücher geschrieben, Asimov keines davon, und wer Beowulf geschrieben hat, weiß niemand.

INNER JOIN

Diagramm: Inner Join

Ein INNER JOIN liefert nur die Zeilen, die in beiden Tabellen einen Treffer haben. JOIN allein bedeutet INNER JOIN.

-- Input: authors
-- id | name
-- ---+--------
-- 1  | Le Guin
-- 2  | Butler
-- 3  | Asimov

-- Input: books
-- title                | author_id
-- ---------------------+----------
-- The Dispossessed     | 1
-- Kindred              | 2
-- Parable of the Sower | 2
-- Beowulf              | NULL

SELECT a.name, b.title
FROM authors a
INNER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

Asimov hat keine Bücher und Beowulf keinen Autor, also taucht keiner von beiden auf.

LEFT JOIN

Diagramm: Left Join

Ein LEFT JOIN (oder LEFT OUTER JOIN) liefert jede Zeile der linken Tabelle, also der hinter FROM. Wo die rechte Tabelle keinen Treffer hat, sind ihre Spalten NULL.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- Asimov  | NULL

RIGHT JOIN

Diagramm: Right Join

Ein RIGHT JOIN (oder RIGHT OUTER JOIN) ist das Spiegelbild: jede Zeile der rechten Tabelle, mit NULLs, wo die linke Tabelle keinen Treffer hat.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf

FULL OUTER JOIN

Diagramm: Full Outer Join

Ein FULL OUTER JOIN liefert jede Zeile beider Tabellen, verbunden, wo es geht, und mit NULLs aufgefüllt, wo nicht. MySQL kennt ihn nicht: siehe „Alternative Syntax“ weiter unten.

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a
FULL OUTER JOIN books b ON a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower
-- NULL    | Beowulf
-- Asimov  | NULL

CROSS JOIN

Diagramm: Cross Join

Ein CROSS JOIN kombiniert jede Zeile der einen Tabelle mit jeder Zeile der anderen, deshalb hat er kein ON. Zwei Größen und zwei Farben ergeben vier Kombinationen; 1.000 mal 1.000 Zeilen ergeben eine Million, also nutz ihn nur bei kleinen Tabellen.

-- Input: sizes
-- size
-- ----
-- S
-- M

-- Input: colors
-- color
-- -----
-- Red
-- Blue

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

-- Output:
-- size | color
-- -----+------
-- S    | Red
-- S    | Blue
-- M    | Red
-- M    | Blue

LEFT ANTI JOIN

Diagramm: Left Anti Join

Ein Left Anti Join liefert die Zeilen der linken Tabelle, die in der rechten Tabelle keinen Treffer haben. Ein eigenes Schlüsselwort gibt es nicht: Mach einen LEFT JOIN und behalte die Zeilen, bei denen die rechte Seite NULL ist.

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.author_id IS NULL;

-- Output:
-- name
-- ------
-- Asimov

RIGHT ANTI JOIN

Diagramm: Right Anti Join

Ein Right Anti Join liefert die Zeilen der rechten Tabelle, die in der linken Tabelle keinen Treffer haben.

-- Input: the authors and books tables above

SELECT b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- title
-- -------
-- Beowulf

Wichtige Regeln

  • NULL passt zu nichts, nicht einmal zu einem anderen NULL. Beowulfs NULL-author_id passt zu keinem Autor, daher liefern ihn nur die Joins, die Bücher ohne Treffer behalten.
  • Eine Zeile erscheint einmal pro passender Zeile. Butler erscheint oben zweimal, weil sie zu zwei Büchern passt. Summierst du dann eine Spalte aus der Autorentabelle, zählen ihre Werte doppelt.
  • Bei einem Outer Join gehört eine Bedingung auf die andere Tabelle in ON. In WHERE greift sie erst nach dem Join und wirft die NULL-Zeilen weg, die der Join gerade behalten hat:
-- Input: the authors and books tables above

-- Only Butler is left: the WHERE drops every author whose title is NULL
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.title LIKE 'K%';

-- Output:
-- name   | title
-- -------+--------
-- Butler | Kindred

-- Every author is kept, with a book only where it starts with K
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id AND b.title LIKE 'K%';

-- Output:
-- name    | title
-- --------+--------
-- Le Guin | NULL
-- Butler  | Kindred
-- Asimov  | NULL

Alternative Syntax

MySQL hat keinen FULL OUTER JOIN. Kombinier stattdessen einen LEFT JOIN mit den Zeilen ohne Treffer aus einem RIGHT JOIN:

-- MySQL
-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a LEFT JOIN books b ON a.id = b.author_id
UNION ALL
SELECT a.name, b.title
FROM authors a RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Parable of the Sower
-- Butler  | Kindred
-- Asimov  | NULL
-- NULL    | Beowulf

SQLite vor Version 3.39 kennt weder RIGHT JOIN noch FULL OUTER JOIN: Tausch die Tabellen und nimm einen LEFT JOIN.

Einen Anti Join kannst du auch mit NOT EXISTS schreiben. Verzichte dafür auf NOT IN: Liefert die Unterabfrage auch nur ein einziges NULL, wie hier Beowulfs author_id, gibt NOT IN überhaupt nichts zurück:

-- Input: the authors and books tables above

SELECT a.name
FROM authors a
WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);

-- Output:
-- name
-- ------
-- Asimov

SELECT a.name
FROM authors a
WHERE a.id NOT IN (SELECT author_id FROM books);

-- Output:
-- name
-- ----
-- (0 rows)

Wenn die verbindenden Spalten in beiden Tabellen gleich heißen, kannst du statt der ON-Bedingung JOIN books USING (author_id) schreiben, außer in SQL Server.

Älterer Code listet die Tabellen oft mit Kommas auf und schreibt die Verknüpfung in WHERE. Das ist ein INNER JOIN in anderer Schreibweise. Lässt du das WHERE weg, wird daraus ein CROSS JOIN:

-- Input: the authors and books tables above

SELECT a.name, b.title
FROM authors a, books b
WHERE a.id = b.author_id;

-- Output:
-- name    | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler  | Kindred
-- Butler  | Parable of the Sower

Wann solltest du SQL-JOINs verwenden?

Nutz einen Join, wann immer die benötigten Daten auf mehrere Tabellen verteilt sind. Welcher Join der richtige ist, hängt davon ab, was mit den Zeilen ohne Treffer passieren soll – und die meisten Aufgaben fallen in einen dieser fünf Fälle.

Eine Mitarbeiterzeile enthält eine Abteilungs-ID, der Bericht braucht aber den Namen der Abteilung. Ein INNER JOIN holt ihn und lässt jede Zeile weg, zu der es nichts nachzuschlagen gibt.

Zeilen ohne Treffer behalten

Wenn die verknüpften Daten optional sind, etwa bei Mitarbeitenden ohne zugewiesene Abteilung, würde ein INNER JOIN sie stillschweigend ausblenden. Ein LEFT JOIN behält jede Zeile der Haupttabelle und füllt die Lücken mit NULL.

Alle Einträge auflisten, auch leere

Berichte brauchen oft jede Abteilung, Kategorie oder jeden Kunden, auch die, zu denen es noch nichts gibt. Mach diese Tabelle zu der, deren Zeilen alle erhalten bleiben: an erster Stelle bei einem LEFT JOIN oder an zweiter bei einem RIGHT JOIN.

Zeilen ohne Treffer finden

Kunden, die nie bestellt haben, Produkte, die nie verkauft wurden, Mitarbeitende ohne Abteilung: Ein Anti Join findet, was fehlt – und eignet sich damit auch gut zur Prüfung der Datenqualität.

Alle Kombinationen erzeugen

Ein CROSS JOIN erzeugt Zeilen, die es noch nicht gibt: jede Größe in jeder Farbe, jeder Mitarbeitende mit jeder Schulung oder jede Filiale an jedem Tag eines Berichts.

Praxisbeispiele für SQL-JOINs

Studierende und ihre Kurse

Eine Many-to-many-Beziehung braucht zwei INNER JOINs über die Verknüpfungstabelle, die Studierende mit Kursen verbindet. Mei ist in keinem Kurs eingeschrieben, also taucht sie nicht auf:

-- Input: students
-- id | name
-- ---+------
-- 1  | Amara
-- 2  | Lucas
-- 3  | Mei

-- Input: enrollments
-- student_id | course_id | grade
-- -----------+-----------+------
-- 1          | 10        | A
-- 1          | 20        | B
-- 2          | 10        | B

-- Input: courses
-- id | course_name
-- ---+------------
-- 10 | Databases
-- 20 | Statistics

SELECT s.name, c.course_name, e.grade
FROM students s
INNER JOIN enrollments e ON s.id = e.student_id
INNER JOIN courses c ON e.course_id = c.id;

-- Output:
-- name  | course_name | grade
-- ------+-------------+------
-- Amara | Databases   | A
-- Amara | Statistics  | B
-- Lucas | Databases   | B

Nutzer mit optionalem Profil

Nicht jeder Nutzer hat ein Profil ausgefüllt. Ein LEFT JOIN behält sie alle:

-- Input: users
-- id | username
-- ---+---------
-- 1  | rosa_m
-- 2  | devon
-- 3  | kai_l

-- Input: user_profiles
-- user_id | bio
-- --------+------------------------
-- 1       | Data engineer in Lisbon
-- 3       | Loves SQL puzzles

SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;

-- Output:
-- username | bio
-- ---------+------------------------
-- rosa_m   | Data engineer in Lisbon
-- devon    | NULL
-- kai_l    | Loves SQL puzzles

Anzahl der Bestellungen pro Kunde

Um Kunden ohne Bestellungen einzubeziehen, mach einen LEFT JOIN ausgehend von customers. Zähl dann eine Spalte aus der Bestelltabelle: COUNT(*) zählt die Zeile mit, die der LEFT JOIN für Omar erzeugt hat, sodass er 1 statt 0 bekäme:

-- Input: customers
-- id | name
-- ---+------------
-- 1  | Harper Lee
-- 2  | Omar Haddad
-- 3  | Yuki Tanaka

-- Input: orders
-- id  | customer_id | total
-- ----+-------------+-------
-- 101 | 1           | 59.90
-- 102 | 3           | 120.00
-- 103 | 1           | 14.50

-- Wrong: COUNT(*) counts Omar's NULL row
SELECT c.name, COUNT(*) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 1
-- Yuki Tanaka | 1

-- Right: COUNT(o.id) skips it
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;

-- Output:
-- name        | order_count
-- ------------+------------
-- Harper Lee  | 2
-- Omar Haddad | 0
-- Yuki Tanaka | 1

Rechnungen und Zahlungen, die nicht zusammenpassen

Ein FULL OUTER JOIN, der nur die Zeilen ohne Treffer behält, gleicht zwei Listen in einer Abfrage ab: Rechnung 502 ist unbezahlt, und Zahlung 9003 gehört zu einer Rechnung, die es nicht gibt:

-- Input: invoices
-- invoice_id | amount
-- -----------+-------
-- 501        | 120.00
-- 502        | 75.50
-- 503        | 300.00

-- Input: payments
-- payment_id | invoice_id | paid
-- -----------+------------+-------
-- 9001       | 501        | 120.00
-- 9002       | 503        | 300.00
-- 9003       | 504        | 42.00

-- MySQL has no FULL OUTER JOIN: use the UNION ALL form under "Alternative syntax"
SELECT i.invoice_id, i.amount, p.payment_id, p.invoice_id AS paid_invoice
FROM invoices i
FULL OUTER JOIN payments p ON p.invoice_id = i.invoice_id
WHERE i.invoice_id IS NULL OR p.payment_id IS NULL;

-- Output:
-- invoice_id | amount | payment_id | paid_invoice
-- -----------+--------+------------+-------------
-- 502        | 75.50  | NULL       | NULL
-- NULL       | NULL   | 9003       | 504

Tagesumsatz jeder Filiale, auch an Tagen ohne Verkäufe

Verbinde die Filialen per CROSS JOIN mit den Tagen, um eine Zeile pro Filiale und Tag zu bekommen, und hänge dann die Verkäufe per LEFT JOIN an dieses Raster. York hat am 6. Oktober nichts verkauft und bekommt trotzdem eine Zeile:

-- Input: stores
-- store
-- -----
-- Leeds
-- York

-- Input: days
-- day
-- ----------
-- 2026-10-05
-- 2026-10-06

-- Input: sales
-- store | day        | amount
-- ------+------------+-------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00

SELECT st.store, d.day, COALESCE(SUM(s.amount), 0.00) AS takings
FROM stores st
CROSS JOIN days d
LEFT JOIN sales s ON s.store = st.store AND s.day = d.day
GROUP BY st.store, d.day
ORDER BY st.store, d.day;

-- Output:
-- store | day        | takings
-- ------+------------+--------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York  | 2026-10-05 | 220.00
-- York  | 2026-10-06 | 0.00

Weiterführende Artikel zu SQL-JOINs

In diesem Blog:

Offizielle Dokumentation:

Beekeeper Studio ist eine kostenlose & quelloffene Datenbank-GUI

Das beste SQL-Abfrage- & Editor-Tool, das ich je benutzt habe. Es bietet alles, was ich zum Verwalten meiner Datenbank brauche. - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studio ist schnell, intuitiv und einfach zu bedienen. Beekeeper unterstützt jede Menge Datenbanken und läuft hervorragend auf Windows, Mac und Linux.

Die Linux-Version von Beekeeper hat 100% des Funktionsumfangs, ohne Abstriche und ohne Kompromisse.

Was Nutzer über Beekeeper Studio sagen

★★★★★
„Beekeeper Studio hat meinen alten SQL-Workflow komplett ersetzt. Es ist schnell, intuitiv und macht die Arbeit mit Datenbanken wieder zum Vergnügen.“
— Alex K., Datenbankentwickler
★★★★★
„Ich habe viele Datenbank-GUIs ausprobiert, aber Beekeeper trifft die perfekte Balance zwischen Funktionsumfang und Einfachheit. Es funktioniert einfach.“
— Sarah M., Full Stack Engineer

Bereit, deinen SQL-Workflow zu verbessern?

download Kostenlos herunterladen