SQL JOINs verstehen: Tabellen richtig verknüpfen

INNER, LEFT, RIGHT und FULL JOIN endlich verstehen: Wie du Daten aus mehreren Tabellen sauber zusammenführst – mit anschaulichen Beispielen und typischen Fallstricken.

Teilen

Sobald deine Datenbank aus mehr als einer Tabelle besteht, kommst du an einer Sache nicht vorbei: Du musst diese Tabellen miteinander verknüpfen. Genau dafür gibt es JOINs. Kundendaten in der einen Tabelle, Bestellungen in der anderen – ein JOIN bringt beide in einer Abfrage zusammen. In diesem Beitrag schauen wir uns die vier wichtigsten JOIN-Typen an, wann du welchen brauchst und welche Fehler dir dabei am häufigsten passieren.

Warum überhaupt mehrere Tabellen?

In einer gut gestalteten Datenbank speicherst du zusammengehörige Daten getrennt, um Redundanz zu vermeiden – das nennt man Normalisierung. Statt bei jeder Bestellung den kompletten Kundennamen und die Adresse zu wiederholen, legst du Kunden einmal an und verweist per kunden_id darauf. Nehmen wir zwei einfache Tabellen als Grundlage für alle Beispiele:

CREATE TABLE kunden (
    id    INTEGER PRIMARY KEY,
    name  TEXT NOT NULL
);

CREATE TABLE bestellungen (
    id         INTEGER PRIMARY KEY,
    kunden_id  INTEGER,
    betrag     NUMERIC
);

INSERT INTO kunden (id, name) VALUES
    (1, 'Anna'),
    (2, 'Ben'),
    (3, 'Clara');

INSERT INTO bestellungen (id, kunden_id, betrag) VALUES
    (100, 1, 49.90),
    (101, 1, 12.50),
    (102, 2, 99.00),
    (103, 4, 5.00);

Beachte: Clara (id 3) hat noch keine Bestellung, und Bestellung 103 verweist auf kunden_id = 4 – einen Kunden, den es in unserer Tabelle gar nicht gibt. Diese beiden Sonderfälle machen die Unterschiede zwischen den JOIN-Typen gleich schön sichtbar.

INNER JOIN: Nur die Treffer

Der INNER JOIN ist der häufigste JOIN. Er liefert nur Zeilen, für die in beiden Tabellen ein passender Partner existiert. Die Bedingung, nach der verknüpft wird, steht hinter ON:

SELECT k.name, b.id AS bestell_id, b.betrag
FROM kunden AS k
INNER JOIN bestellungen AS b
    ON k.id = b.kunden_id;

Das Ergebnis enthält nur Anna und Ben, denn nur ihre IDs kommen in beiden Tabellen vor:

name | bestell_id | betrag
-----+------------+-------
Anna | 100        | 49.90
Anna | 101        | 12.50
Ben  | 102        | 99.00

Clara fehlt (keine Bestellung), und Bestellung 103 fehlt ebenfalls (kein passender Kunde). Merke dir: Was in einer der beiden Tabellen keinen Partner findet, fällt beim INNER JOIN raus. Das Schlüsselwort INNER ist übrigens optional – ein reines JOIN bedeutet in SQL immer einen INNER JOIN.

Oft willst du aber gerade die Zeilen sehen, die keinen Partner haben – etwa alle Kunden, auch die ohne Bestellung. Dafür gibt es den LEFT JOIN (ausführlich LEFT OUTER JOIN). Er behält alle Zeilen der linken Tabelle und füllt fehlende Werte der rechten Seite mit NULL:

SELECT k.name, b.id AS bestell_id, b.betrag
FROM kunden AS k
LEFT JOIN bestellungen AS b
    ON k.id = b.kunden_id;

Jetzt taucht auch Clara auf – mit leeren Spalten für die Bestellung:

name  | bestell_id | betrag
------+------------+-------
Anna  | 100        | 49.90
Anna  | 101        | 12.50
Ben   | 102        | 99.00
Clara | NULL       | NULL

Der LEFT JOIN ist perfekt, um fehlende Zuordnungen aufzuspüren. Willst du gezielt nur die Kunden ohne Bestellung, filterst du auf die NULL-Werte der rechten Tabelle:

SELECT k.name
FROM kunden AS k
LEFT JOIN bestellungen AS b
    ON k.id = b.kunden_id
WHERE b.id IS NULL;

RIGHT JOIN und FULL JOIN

Der RIGHT JOIN ist das Spiegelbild des LEFT JOIN: Er behält alle Zeilen der rechten Tabelle. In unserem Beispiel liefert er also auch Bestellung 103, die auf den nicht existierenden Kunden 4 zeigt:

SELECT k.name, b.id AS bestell_id, b.betrag
FROM kunden AS k
RIGHT JOIN bestellungen AS b
    ON k.id = b.kunden_id;
name | bestell_id | betrag
-----+------------+-------
Anna | 100        | 49.90
Anna | 101        | 12.50
Ben  | 102        | 99.00
NULL | 103        | 5.00

In der Praxis wird RIGHT JOIN selten benutzt, weil man den gleichen Effekt erreicht, indem man die Tabellen vertauscht und einen LEFT JOIN schreibt – das liest sich für die meisten intuitiver. Der FULL JOIN (bzw. FULL OUTER JOIN) kombiniert schließlich beide Seiten: Er behält alle Zeilen aus beiden Tabellen und setzt überall dort NULL, wo ein Partner fehlt.

SELECT k.name, b.id AS bestell_id, b.betrag
FROM kunden AS k
FULL JOIN bestellungen AS b
    ON k.id = b.kunden_id;

Hier erscheinen sowohl Clara (ohne Bestellung) als auch Bestellung 103 (ohne Kunde). Ein kleiner Hinweis: MySQL kennt FULL JOIN nicht direkt – dort simuliert man ihn mit einer Kombination aus LEFT und RIGHT JOIN via UNION. PostgreSQL und SQLite (ab Version 3.39) unterstützen ihn dagegen nativ.

Der häufigste Fehler: ON vs. WHERE beim LEFT JOIN

Ein Stolperstein, der auch Fortgeschrittene erwischt: Wo du eine Bedingung hinschreibst, ändert beim OUTER JOIN das Ergebnis komplett. Angenommen, du willst zu jedem Kunden nur Bestellungen über 20 Euro sehen, aber Kunden ohne solche Bestellung trotzdem behalten. Diese Variante tut nicht, was du erwartest:

-- FALSCH: filtert Kunden ohne passende Bestellung wieder heraus
SELECT k.name, b.betrag
FROM kunden AS k
LEFT JOIN bestellungen AS b ON k.id = b.kunden_id
WHERE b.betrag > 20;

Das WHERE wird erst nach dem JOIN ausgewertet und wirft alle Zeilen mit betrag IS NULL raus – aus deinem LEFT JOIN wird faktisch ein INNER JOIN. Richtig ist es, die Filterbedingung in die ON-Klausel zu ziehen, denn die wird während des Verknüpfens angewendet:

-- RICHTIG: Kunden bleiben erhalten, auch ohne Bestellung > 20
SELECT k.name, b.betrag
FROM kunden AS k
LEFT JOIN bestellungen AS b
    ON k.id = b.kunden_id AND b.betrag > 20;

Die Faustregel: Bedingungen an die rechte (optionale) Tabelle gehören beim OUTER JOIN in die ON-Klausel. Bedingungen, die echte Zeilen herausfiltern sollen, gehören ins WHERE.

Mehr als zwei Tabellen und Aliase

Du kannst beliebig viele JOINs aneinanderreihen. In der Praxis nutzt du dabei fast immer Tabellen-Aliase (das AS k und AS b oben), damit die Abfrage kurz und lesbar bleibt. Ein Beispiel mit einer zusätzlichen Produkttabelle:

SELECT k.name, p.titel, b.betrag
FROM bestellungen AS b
JOIN kunden AS k       ON k.id = b.kunden_id
JOIN produkte AS p     ON p.id = b.produkt_id
WHERE b.betrag > 10
ORDER BY b.betrag DESC;

SQL arbeitet die JOINs von oben nach unten ab und verknüpft das Zwischenergebnis Schritt für Schritt mit der nächsten Tabelle. Solange jede ON-Bedingung sauber die passenden Schlüssel verbindet, bleibt das Ergebnis korrekt und effizient – vorausgesetzt, die Spalten in den ON-Bedingungen sind indiziert.

Fazit

JOINs sind das Herzstück relationaler Datenbanken. Merke dir die vier Grundtypen so: INNER JOIN liefert nur beidseitige Treffer, LEFT JOIN behält alles von links, RIGHT JOIN alles von rechts und FULL JOIN alles aus beiden Tabellen. Der mit Abstand wichtigste Praxis-Tipp: Achte beim OUTER JOIN genau darauf, ob eine Bedingung ins ON oder ins WHERE gehört – das entscheidet, ob deine fehlenden Partner erhalten bleiben oder verschwinden. Am besten legst du dir die beiden kleinen Beispieltabellen selbst an und probierst jeden JOIN-Typ einmal durch. Sobald du das Muster einmal siehst, wird es zur Routine.