6 SQL-Joins, die Sie kennen sollten (mit einfachen, klaren Beispielen)
Lerne die 6 SQL-Joins, die jeder Analyst kennen sollte — INNER, LEFT, RIGHT, FULL OUTER, CROSS und SELF — mit praktischen Beispielen und typischen Fallen.

Was SQL JOINs sind und warum du sie benutzt
Ein SQL JOIN erlaubt es dir, Zeilen aus zwei (oder mehr) Tabellen zu einer Ergebnismenge zusammenzuführen, indem sie anhand einer verwandten Spalte — meist einer ID — abgeglichen werden.
Warum JOINs wichtig sind
Die meisten echten Datenbanken sind bewusst auf mehrere Tabellen verteilt, damit sich Informationen nicht wiederholen. Zum Beispiel steht der Name eines Kunden in der Tabelle customers, während seine Käufe in der Tabelle orders liegen. JOINs verbinden diese Teile wieder, wenn du Antworten brauchst.
Deshalb sind JOINs überall in Reporting und Analyse zu finden:
- Erstellen eines Verkaufsberichts mit Kundennamen, Bestellsummen und Zahlungsstatus
- Finden von Kunden, die noch nichts bestellt haben
- Prüfen von Unstimmigkeiten, z. B. Bestellungen ohne Zahlungen
- Erzeugen von „eine Zeile pro Kunde“-Zusammenfassungen aus vielen verwandten Zeilen
Ohne JOINs müsstest du separate Abfragen ausführen und Ergebnisse manuell zusammenfügen — langsam, fehleranfällig und schwer wiederholbar.
Wenn du Produkte über eine relationale Datenbank baust (Dashboards, Admin-Tools, Kundenportale), sind JOINs auch das Mittel, mit dem „rohe Tabellen“ zu benutzerfreundlichen Ansichten werden. Plattformen wie Koder.ai (die React + Go + PostgreSQL-Apps aus Chats generiert) setzen weiterhin auf solide JOIN-Grundlagen, wenn du genaue Listen, Berichte oder Abgleiche brauchst — die Datenbanklogik verschwindet nicht, nur weil die Entwicklung schneller wird.
Die 6 JOIN-Typen, die du am häufigsten verwenden wirst
Diese Anleitung fokussiert sich auf sechs JOINs, die den Großteil des Alltags abdecken:
- INNER JOIN: liefert nur Zeilen, die in beiden Tabellen übereinstimmen (gut für „zeige mir bestätigte Beziehungen“).
- LEFT JOIN: behält jede Zeile aus der linken Tabelle und ergänzt, was sich aus der rechten finden lässt (gut für „inkludiere fehlende zugehörige Daten“).
- RIGHT JOIN: Spiegelung des LEFT JOIN (seltener, aber je nach Stil nützlich).
- FULL OUTER JOIN: behält alle Zeilen aus beiden Tabellen und kombiniert, wo möglich (ideal für Reconciliation und Lückenfinden).
- CROSS JOIN: erzeugt alle Kombinationen von Zeilen (nützlich für Kalender, Szenarien oder Testdaten — aber leicht falsch zu verwenden).
- SELF JOIN: eine Tabelle wird mit sich selbst verbunden (praktisch für Hierarchien wie Mitarbeiter/Manager).
Eine kurze Anmerkung zur Syntax
Die JOIN-Syntax ist in den meisten SQL-Datenbanken ähnlich (PostgreSQL, MySQL, SQL Server, SQLite). Es gibt ein paar Unterschiede — besonders bei FULL OUTER JOIN-Support und einigen Randfällen — aber Konzepte und Kernmuster übertragen sich gut.
Die Beispieltabellen, die wir verwenden (customers, orders, payments)
Um die JOIN-Beispiele einfach zu halten, nutzen wir drei kleine Tabellen, die eine gängige reale Struktur widerspiegeln: Kunden tätigen Bestellungen, und Bestellungen können (müssen aber nicht) Zahlungen haben.
Ein Hinweis vorab: die Beispieltabellen zeigen nur wenige Spalten, aber einige Abfragen später referenzieren zusätzliche Felder (wie order_date, created_at, status oder paid_at) zur Demonstration typischer Muster. Betrachte diese Spalten als übliche Felder, die du in Produktionsschemata oft findest.
1) customers
Primary key: customer_id
| customer_id | name |
|---|---|
| 1 | Ava |
| 2 | Ben |
| 3 | Chen |
| 4 | Dia |
2) orders
Primary key: order_id
Foreign key: customer_id → customers.customer_id
| order_id | customer_id | order_total |
|---|---|---|
| 101 | 1 | 50 |
| 102 | 1 | 120 |
| 103 | 2 | 35 |
| 104 | 5 | 70 |
Beachte, dass order_id = 104 auf customer_id = 5 verweist, die in customers nicht existiert. Diese „fehlende Übereinstimmung“ ist nützlich, um das Verhalten von LEFT JOIN, RIGHT JOIN und FULL OUTER JOIN zu sehen.
3) payments
Primary key: payment_id
Foreign key: order_id → orders.order_id
| payment_id | order_id | amount |
|---|---|---|
| 9001 | 101 | 50 |
| 9002 | 102 | 60 |
| 9003 | 102 | 60 |
| 9004 | 999 | 25 |
Zwei wichtige Lehrpunkte hier:
order_id = 102hat zwei Zahlungszeilen (eine gesplittete Zahlung). Wenn duordersmitpaymentsjoinst, erscheint diese Bestellung zweimal — hierüber stolpern viele.payment_id = 9004verweist auforder_id = 999, die nicht inordersexistiert. Das ist ein weiterer „nicht übereinstimmender“ Fall.
Was du beim Join dieser Tabellen erwarten kannst
- Übereinstimmende Zeilen: z. B. Kunde 1 ↔ Bestellungen 101/102; Bestellung 101 ↔ Zahlung 9001.
- Nicht übereinstimmende Zeilen: z. B. Kunde 3 und 4 haben keine Bestellungen; Bestellung 104 hat keinen Kunden; Zahlung 9004 hat keine Bestellung.
- Duplikate: das Verbinden von
ordersundpaymentswiederholt Bestellung 102, weil sie zwei zugehörige Zahlungen hat.
INNER JOIN: Nur übereinstimmende Zeilen behalten
Ein INNER JOIN gibt nur die Zeilen zurück, bei denen es in beiden Tabellen eine Übereinstimmung gibt. Hat ein Kunde keine Bestellungen, erscheint er nicht. Verweist eine Bestellung auf einen nicht existierenden Kunden (schlechte Daten), erscheint diese Bestellung ebenfalls nicht.
Das Grundmuster
Du wählst eine „linke“ Tabelle, joinst eine „rechte“ Tabelle und verbindest sie mit einer Bedingung in der ON-Klausel.
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id;
Der Schlüssel ist die Zeile ON o.customer_id = c.customer_id: sie sagt SQL, wie Zeilen zusammengehören.
Anwendungsfall: Kunden, die Bestellungen aufgegeben haben
Wenn du nur eine Liste der Kunden willst, die tatsächlich mindestens eine Bestellung aufgegeben haben (inklusive Bestelldetails), ist INNER JOIN die natürliche Wahl:
SELECT
c.name,
o.order_id,
o.total_amount
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY o.order_id;
Das ist nützlich für Dinge wie „sende Follow-up-E-Mails zu Bestellungen“ oder „berechne Umsatz pro Kunde“ (wenn du nur Kunden mit Käufen betrachtest).
Häufiger Fehler: fehlende oder unklare Join-Bedingungen
Wenn du einen Join schreibst, aber die ON-Bedingung vergisst (oder auf die falschen Spalten joinst), kannst du unbeabsichtigt ein kartesisches Produkt erzeugen (jeder Kunde kombiniert mit jeder Bestellung) oder falsche Übereinstimmungen erhalten.
Schlecht (nicht machen):
SELECT c.name, o.order_id
FROM customers c
JOIN orders o;
Stelle immer sicher, dass du eine klare Join-Bedingung in ON (oder USING in den speziellen Fällen) angibst.
LEFT JOIN: Alles von der linken Tabelle behalten
Ein LEFT JOIN gibt alle Zeilen aus der linken Tabelle zurück und ergänzt passende Daten aus der rechten Tabelle sofern vorhanden. Gibt es keine Übereinstimmung, erscheinen die rechten Spalten als NULL.
Wann du es verwendest
Verwende LEFT JOIN, wenn du eine vollständige Liste aus deiner Primärtabelle willst, plus optionale zugehörige Daten.
Beispiel: „Zeige mir alle Kunden und füge ihre Bestellungen falls vorhanden hinzu."
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY c.customer_id;
- Kunden mit Bestellungen erscheinen mit Bestelldetails.
- Kunden ohne Bestellungen erscheinen trotzdem, aber
o.order_id(und andereorders-Spalten) sindNULL.
Die klassischen „keine Übereinstimmung“-Zeilen finden
Ein sehr häufiger Grund für LEFT JOIN ist das Finden von Elementen, die keine zugehörigen Datensätze haben.
Beispiel: "Welche Kunden haben noch nie bestellt?"
SELECT
c.customer_id,
c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;
Die Bedingung WHERE ... IS NULL behält nur die linken Tabellenzeilen, für die kein Treffer gefunden wurde.
Aufpassen: Mehrfache Treffer vervielfachen Zeilen
LEFT JOIN kann linke Zeilen „duplizieren“, wenn es mehrere passende rechte Zeilen gibt.
Hat ein Kunde drei Bestellungen, erscheint dieser Kunde dreimal — einmal pro Bestellung. Das ist erwartetes Verhalten, kann aber überraschen, wenn du Kunden zählen willst.
Beispiel, das Bestellungen zählt (nicht Kunden):
SELECT COUNT(*)
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id;
Wenn du Kunden zählen willst, zählst du typischerweise den Kunden-Schlüssel (oft mit COUNT(DISTINCT c.customer_id)), je nachdem, was du messen willst.
RIGHT JOIN: Alles von der rechten Tabelle behalten
Ein RIGHT JOIN behält alle Zeilen der rechten Tabelle und nur die passenden Zeilen der linken Tabelle. Gibt es keine Übereinstimmung, erscheinen die linken Spalten als NULL. Es ist im Grunde die Spiegelung eines LEFT JOIN.
Ein einfaches Beispiel
Mit unseren Beispieltabellen willst du vielleicht jede Zahlung auflisten, selbst wenn sie keiner Bestellung zugeordnet werden kann (vielleicht wurde die Bestellung gelöscht oder die Zahlungsdaten sind fehlerhaft):
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM orders o
RIGHT JOIN payments p
ON o.order_id = p.order_id;
Ergebnis:
- Alle Zahlungen sind enthalten (weil
paymentsrechts steht). - Hat eine Zahlung keine passende Bestellung, sind
o.order_idundo.customer_idNULL.
Dieselbe Abfrage als LEFT JOIN (oft lesbarer)
In den meisten Fällen kannst du ein RIGHT JOIN als LEFT JOIN umschreiben, indem du die Tabellenreihenfolge tauschst:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM payments p
LEFT JOIN orders o
ON o.order_id = p.order_id;
Das liefert dasselbe Ergebnis, aber viele finden es lesbarer: du beginnst mit der „Haupt“-Tabelle, die dich interessiert (hier payments) und ziehst optional verwandte Daten hinzu.
Lesbarkeit: warum Teams RIGHT JOIN oft meiden
Viele SQL-Styleguides raten von RIGHT JOIN ab, weil Leser die Reihenfolge mental umkehren müssen:
- „Beginne mit der Haupttabelle“
- „LEFT JOIN zusätzliche Tabellen"
Wenn optionale Beziehungen konsistent als LEFT JOIN geschrieben werden, sind Abfragen leichter zu überfliegen.
Wann RIGHT JOIN trotzdem praktisch ist
Ein RIGHT JOIN kann praktisch sein, wenn du eine bestehende, lange Abfrage bearbeitest und bemerkst, dass die „beizubehaltende“ Tabelle auf der rechten Seite steht. Anstatt die ganze Abfrage umzuschreiben, kann das Umschalten einer JOIN-Art auf RIGHT JOIN eine schnelle, risikoarme Änderung sein.
FULL OUTER JOIN: Alle Zeilen aus beiden Tabellen behalten
Ein FULL OUTER JOIN gibt jede Zeile aus beiden Tabellen zurück.
- Wenn eine Zeile auf dem Join-Key übereinstimmt, erhältst du eine kombinierte Zeile (wie bei INNER JOIN).
- Existiert eine Zeile nur in der linken Tabelle, erscheint sie mit
NULLfür die rechte Seite. - Existiert eine Zeile nur in der rechten Tabelle, erscheint sie mit
NULLfür die linke Seite.
Wann es nützlich ist
Ein klassischer Business-Use-Case ist die Abstimmung von Bestellungen und Zahlungen:
- bezahlte Bestellungen (Treffer)
- unbezahlte Bestellungen (Bestellung existiert, Zahlung fehlt)
- fremde Zahlungen (Zahlung existiert, aber keine Bestellung — Datenfehler, Rückerstattung, falsche Referenz)
Beispiel:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount
FROM orders o
FULL OUTER JOIN payments p
ON p.order_id = o.order_id;
Datenbankunterstützung (wer kann es direkt ausführen)
FULL OUTER JOIN wird in PostgreSQL, SQL Server und Oracle unterstützt.
Es ist nicht verfügbar in MySQL und SQLite (dort brauchst du eine Umgehung).
Portable Alternative: LEFT JOIN und RIGHT JOIN mit UNION
Wenn deine Datenbank FULL OUTER JOIN nicht unterstützt, kannst du es simulieren, indem du kombinierst:
- alle Zeilen aus
orders(mit passenden Zahlungen, wenn vorhanden), und - alle Zeilen aus
payments, die keine Bestellung matched haben.
Ein gängiges Muster:
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.order_id
UNION
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
RIGHT JOIN payments p
ON p.order_id = o.order_id;
Tipp: Wenn du NULLs auf einer Seite siehst, ist das dein Signal, dass die Zeile in der anderen Tabelle fehlte — genau das, was du für Prüfungen und Reconciliation suchst.
CROSS JOIN: Alle Kombinationen erzeugen (mit Bedacht benutzen)
Ein CROSS JOIN liefert jede mögliche Paarung von Zeilen aus zwei Tabellen. Hat Tabelle A 3 Zeilen und Tabelle B 4 Zeilen, enthält das Ergebnis 3 × 4 = 12 Zeilen. Das nennt man auch ein kartesisches Produkt.
Das klingt beängstigend — und kann es sein — aber es ist wirklich nützlich, wenn du Kombinationen erzeugen willst.
Ein kleines, sicheres Beispiel: Größen × Farben (SKUs erzeugen)
Angenommen, du hältst Produktoptionen in separaten Tabellen:
sizes: S, M, Lcolors: Red, Blue
Ein CROSS JOIN kann alle Varianten erzeugen (praktisch zum Erzeugen von SKUs, Vorbefüllen eines Katalogs oder zum Testen):
SELECT
s.size,
c.color
FROM sizes AS s
CROSS JOIN colors AS c;
Ergebnis (3 × 2 = 6 Zeilen):
- S / Red
- S / Blue
- M / Red
- M / Blue
- L / Red
- L / Blue
Große Warnung: Ergebnisse wachsen schnell
Da sich Zeilenzahlen multiplizieren, kann ein CROSS JOIN schnell explodieren:
- 10.000 Kunden × 50 Produkte = 500.000 Zeilen
- 100.000 × 100.000 = 10.000.000.000 Zeilen
Das kann Abfragen verlangsamen, Speicher überfordern und Ausgaben erzeugen, die niemand nutzen kann. Wenn du Kombinationen brauchst, halte die Eingabetabellen klein und erwäge Beschränkungen oder gezielte Filter.
SELF JOIN: Eine Tabelle mit sich selbst verbinden
Ein SELF JOIN ist genau das: du verbindest eine Tabelle mit sich selbst. Das ist nützlich, wenn eine Zeile in einer Tabelle zu einer anderen Zeile derselben Tabelle in Beziehung steht — typischerweise für Parent/Child-Beziehungen wie Mitarbeiter und deren Manager.
Warum Aliase nötig sind (und wie sie helfen)
Da du dieselbe Tabelle zweimal verwendest, musst du jeder „Kopie“ einen eigenen Alias geben. Aliase machen die Abfrage lesbar und zeigen SQL, welche Seite du meinst.
Übliche Konventionen sind:
efür employee (Mitarbeiter)mfür manager (Manager)
Praktisches Beispiel: Mitarbeiter und ihre Manager
Stell dir eine employees-Tabelle vor mit:
idnamemanager_id(zeigt auf dieideines anderen Mitarbeiters)
Um jeden Mitarbeiter mit dem Namen seines Managers aufzulisten:
SELECT
e.id,
e.name AS employee_name,
m.name AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
Top-Level-Mitarbeiter (NULL manager_id) behandeln
Beachte, dass die Abfrage einen LEFT JOIN verwendet, nicht einen INNER JOIN. Das ist wichtig, weil einige Mitarbeiter keinen Manager haben (z. B. der CEO). In solchen Fällen ist manager_id oft NULL, und ein LEFT JOIN behält die Mitarbeiterzeile bei und zeigt manager_name als NULL.
Verwendest du stattdessen INNER JOIN, würden diese Top-Level-Mitarbeiter aus dem Ergebnis verschwinden, weil es keine passende Managerzeile gibt.
Join-Bedingungen: ON vs USING (und warum das wichtig ist)
Ein JOIN weiß nicht „magisch“, wie zwei Tabellen zusammengehören — du musst es sagen. Diese Beziehung wird in der Join-Bedingung definiert, und sie gehört direkt neben das JOIN, weil sie erklärt, wie die Tabellen gematcht werden, nicht wie du das finale Ergebnis filtern willst.
ON: flexibel und am gebräuchlichsten
Verwende ON, wenn du volle Kontrolle über die Matching-Logik brauchst — unterschiedliche Spaltennamen, mehrere Bedingungen oder zusätzliche Regeln.
SELECT
c.customer_id,
c.name,
o.order_id,
o.created_at
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.customer_id;
ON ist auch der richtige Ort für komplexere Matches (z. B. Matching auf zwei Spalten), ohne die Abfrage unübersichtlich zu machen.
USING: kürzer, aber nur für gleichnamige Spalten
Einige Datenbanken (z. B. PostgreSQL und MySQL) unterstützen USING. Es ist eine bequeme Kurzform, wenn beide Tabellen eine Spalte mit dem gleichen Namen haben und du genau auf dieser Spalte joinen willst.
SELECT
customer_id,
name,
order_id
FROM customers
JOIN orders
USING (customer_id);
Ein Vorteil: USING gibt typischerweise nur eine customer_id-Spalte im Ergebnis zurück (statt zwei Duplikate).
Vermeide mehrdeutige Spaltennamen: immer qualifizieren
Nach einem Join überlappen Spaltennamen oft (id, created_at, status). Schreibst du SELECT id, kann die Datenbank einen „ambiguous column“-Fehler werfen — oder noch schlimmer, du liest aus Versehen das falsche id.
Nutze Tabellenpräfixe (oder Aliase) zur Klarheit:
SELECT c.customer_id, o.order_id
FROM customers AS c
JOIN orders AS o
ON o.customer_id = c.customer_id;
Verzichte auf SELECT * in Join-Abfragen
SELECT * wird bei Joins schnell unübersichtlich: du ziehst unnötige Spalten, riskierst Namenskonflikte und verschlechterst die Lesbarkeit. Wähle stattdessen genau die Spalten, die du brauchst. Das Ergebnis ist sauberer, wartbarer und oft effizienter — besonders bei breiten Tabellen.
Join-Daten filtern: WHERE vs ON
Bei Joins „filtern“ sowohl WHERE als auch ON, aber zu unterschiedlichen Zeitpunkten:
- ON entscheidet, welche Zeilen beim Join zusammenpassen.
- WHERE filtert das endgültige Ergebnis, nachdem der Join gebildet wurde.
Dieser Zeitunterschied ist der Grund, warum Leute unbeabsichtigt einen LEFT JOIN in einen INNER JOIN verwandeln.
Wie WHERE einen LEFT JOIN unbeabsichtigt kaputtmachen kann
Angenommen, du willst alle Kunden, selbst diejenigen ohne kürzliche bezahlte Bestellungen:
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
Problem: Für Kunden ohne passende Bestellung sind o.status und o.order_date NULL. Die WHERE-Klausel verwirft diese Zeilen, sodass die nicht übereinstimmenden Kunden verschwinden — dein LEFT JOIN verhält sich wie ein INNER JOIN.
Join-bezogene Bedingungen in ON verschieben, um linke Zeilen zu behalten
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
Jetzt erscheinen Kunden ohne passende Bestellungen weiterhin (mit NULL-Bestellspalten), was normalerweise der Zweck eines LEFT JOIN ist.
Schnell-Checkliste: was gehört in ON vs WHERE?
- Bedingungen in ON gehören, wenn sie beschreiben, welche rechten Tabellenzeilen zugelassen sind (Status/Datum/Typ-Filter auf der verbundenen Tabelle).
- Bedingungen in WHERE gehören, wenn sie beschreiben, welche finalen Zeilen du behalten willst (Filter auf der linken Tabelle oder wenn du explizit einen Treffer verlangst).
- Willst du alle linken Zeilen behalten, aber die rechten einschränken, bevor sie matchen, nutze LEFT JOIN + Bedingungen in ON.
- Willst du nur Zeilen mit Treffer, nutze INNER JOIN (oder
WHERE o.order_id IS NOT NULL).
Duplikate und Many-to-Many-Überraschungen vermeiden
Joins fügen nicht nur Spalten hinzu — sie können auch Zeilen multiplizieren. Das ist häufig richtig, überrascht aber oft, wenn Summen plötzlich doppelt so groß sind.
Warum Zeilen multiplizieren
Ein Join gibt eine Ausgabezeile für jedes Paar passender Zeilen zurück.
- One-to-many: Ein Kunde kann viele Bestellungen haben. Joins von
customerszuorderslassen Kunden mehrfach erscheinen — einmal pro Bestellung. - Many-to-many (die eigentliche Falle): Joins von
orderszupayments, wenn jede Bestellung mehrere Zahlungen hat, ergeben mehrere Zeilen pro Bestellung. Joins zu einer weiteren Many-Tabelle (z. B.order_items) können eine Multiplikationseffekte erzeugen:payments × itemspro Bestellung.
Vor dem Join aggregieren
Wenn du „eine Zeile pro Kunde“ oder „eine Zeile pro Bestellung“ willst, fasse zuerst die Many-Seite zusammen und joine dann:
-- Eine Zeile pro Bestellung aus payments
WITH payment_totals AS (
SELECT
order_id,
SUM(amount) AS total_paid,
COUNT(*) AS payment_count
FROM payments
GROUP BY order_id
)
SELECT
o.order_id,
o.customer_id,
COALESCE(pt.total_paid, 0) AS total_paid,
COALESCE(pt.payment_count, 0) AS payment_count
FROM orders o
LEFT JOIN payment_totals pt
ON pt.order_id = o.order_id;
Das hält die Join-Form überschaubar: eine Bestellung bleibt eine Bestellung.
DISTINCT nur als letzte Maßnahme
SELECT DISTINCT kann Duplikate scheinbar beheben, aber es kann das eigentliche Problem verbergen:
- Es kann legitime Zeilen stillschweigend entfernen.
- Es kann eine fehlerhafte Join-Bedingung maskieren.
- Es kann Summen und Zählungen verfälschen.
Verwende es nur, wenn du sicher bist, dass Duplikate rein zufällig sind und du verstehst, warum sie aufgetreten sind.
Schnelle Sicherheitsprüfung: Zähle zur Validierung
Bevor du Ergebnissen vertraust, vergleiche Zeilenzahlen:
- Zähle Zeilen in deiner Haupttabelle (z. B. orders).
- Zähle Zeilen nach dem Join.
- Springt die Zahl unerwartet hoch, prüfe, welche Schlüssel mehrere Treffer verursachen und entscheide, ob du voraggregieren oder einen anderen Join-Pfad benötigst.
Performance-Grundlagen und ein kurzer JOIN-Spickzettel
JOINs werden oft für „langsame Abfragen“ verantwortlich gemacht, aber die echte Ursache ist meist, wie viele Daten du kombinieren willst und wie leicht die Datenbank passende Zeilen finden kann.
Indizierung (konzeptionell) und warum sie JOINs hilft
Denk an einen Index wie an das Inhaltsverzeichnis eines Buches. Ohne Index muss die Datenbank möglicherweise viele Zeilen scannen, um passende Join-Zeilen zu finden. Mit einem Index auf dem Join-Key (z. B. customers.customer_id und orders.customer_id) kann die Datenbank schneller zu relevanten Zeilen springen.
Du musst die Interna nicht kennen: Wenn eine Spalte oft zum Matchen verwendet wird (ON a.id = b.a_id), ist sie ein guter Kandidat für einen Index.
Auf stabilen Keys joinen (statt Namen oder E-Mails)
Soweit möglich, join auf stabilen, eindeutigen Identifikatoren:
- Gut:
customers.customer_id = orders.customer_id - Riskant:
customers.email = orders.emailodercustomers.name = orders.name
Namen ändern sich und sind nicht eindeutig. E-Mails können sich ändern oder unterschiedlich formatiert sein. IDs sind dafür gedacht, konsistent zu matchen und sind oft indiziert.
Arbeit früh reduzieren
Zwei Gewohnheiten machen JOINs deutlich schneller:
- Wähle weniger Spalten aus. Vermeide
SELECT *bei mehreren Joins — unnötige Spalten erhöhen Speicher- und Netzwerkbedarf. - Beschränke Zeilen vor oder während des JOIN. Filtere so früh wie möglich.
Beispiel: Bestellungen zuerst einschränken, dann joinen:
SELECT c.customer_id, c.name, o.order_id, o.created_at
FROM customers c
JOIN (
SELECT order_id, customer_id, created_at
FROM orders
WHERE created_at >= DATE '2025-01-01'
) o
ON o.customer_id = c.customer_id;
Wenn du diese Abfragen in einer App erstellst (z. B. für eine Reporting-Seite mit PostgreSQL), können Tools wie Koder.ai das Scaffolding beschleunigen — Schema, Endpunkte, UI — während du die JOIN-Logik kontrollierst, die die Korrektheit bestimmt.
Kurzer JOIN-Spickzettel
- INNER JOIN → nur Zeilen, die in beiden Tabellen matchen
- LEFT JOIN → alle Zeilen aus der linken Tabelle, plus Treffer aus der rechten (Nicht-Treffer werden
NULL) - RIGHT JOIN → alle Zeilen aus der rechten Tabelle, plus Treffer aus der linken (
NULLwenn fehlend) - FULL OUTER JOIN → alle Zeilen aus beiden Tabellen; Treffer werden kombiniert, Nicht-Treffer zeigen
NULL - CROSS JOIN → jede Kombination von Zeilen (Anzahl multipliziert sich; mit Vorsicht nutzen)
- SELF JOIN → eine Tabelle mit sich selbst verbinden (nützlich für Hierarchien und Vergleiche)
FAQ
Was ist ein SQL JOIN in einfachen Worten?
Ein SQL-JOIN kombiniert Zeilen aus zwei (oder mehr) Tabellen zu einem Ergebnis, indem verwandte Spalten abgeglichen werden — meist ein Primary Key zu einem Foreign Key (zum Beispiel customers.customer_id = orders.customer_id). So „verknüpfst“ du normalisierte Tabellen wieder, wenn du Berichte, Prüfungen oder Analysen brauchst.
Wann sollte ich INNER JOIN verwenden?
Verwende INNER JOIN, wenn du nur die Zeilen möchtest, bei denen die Beziehung in beiden Tabellen vorhanden ist.
- Kunden ohne Bestellungen tauchen nicht auf.
- Bestellungen, die auf einen fehlenden Kunden verweisen, tauchen nicht auf.
Ideal für „bestätigte Beziehungen“, zum Beispiel eine Liste nur der Kunden, die tatsächlich Bestellungen getätigt haben.
Wie finde ich Zeilen, die in einer anderen Tabelle keine Übereinstimmung haben?
Verwende LEFT JOIN, wenn du alle Zeilen aus deiner Haupttabelle (links) brauchst und zusätzlich optionale zugehörige Daten aus der rechten Tabelle.
Um „fehlende Übereinstimmungen“ zu finden, joine und filtere die rechte Seite auf NULL:
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;
Brauche ich wirklich RIGHT JOIN, und wie ersetze ich ihn durch LEFT JOIN?
RIGHT JOIN behält jede Zeile aus der rechten Tabelle und füllt linke Tabellenspalten mit NULL, wenn keine Übereinstimmung existiert. Viele Teams vermeiden es, weil es „rückwärts“ zu lesen ist.
In den meisten Fällen kannst du es als LEFT JOIN schreiben, indem du die Tabellenreihenfolge tauschst:
FROM payments p
LEFT JOIN orders o ON o.order_id = p.order_id
Wofür ist FULL OUTER JOIN am besten geeignet?
Verwende FULL OUTER JOIN zur Abstimmung/Abstimmung (Reconciliation): du willst Treffer, nur-links-Zeilen und nur-rechts-Zeilen in einer Ausgabe.
Das ist ideal für Prüfungen wie „Bestellungen ohne Zahlungen“ und „Zahlungen ohne Bestellungen“, weil die nicht übereinstimmenden Seiten als NULL erscheinen.
Was tun, wenn meine Datenbank FULL OUTER JOIN nicht unterstützt?
Einige Datenbanken (insbesondere MySQL und SQLite) unterstützen FULL OUTER JOIN nicht direkt. Eine gängige Umgehung ist, zwei Abfragen zu kombinieren:
orders LEFT JOIN payments- plus die Zeilen, die nur auf der payments-Seite zu finden sind
Dies wird typischerweise mit UNION (oder UNION ALL plus gezielter Filterung) gemacht, damit du sowohl „nur-links“ als auch „nur-rechts“ behältst.
Was ist ein CROSS JOIN und wann ist er wirklich nützlich?
CROSS JOIN liefert jede Kombination von Zeilen zwischen zwei Tabellen (kartesisches Produkt). Nützlich, um Szenarien zu erzeugen (z. B. Größen × Farben) oder ein Kalendergitter zu bauen.
Vorsicht: Die Anzahl der Zeilen multipliziert sich schnell, also kann es die Ausgabegröße explodieren lassen und Abfragen verlangsamen, wenn die Eingaben nicht klein und kontrolliert sind.
Was ist ein SELF JOIN und warum brauche ich Tabellen-Aliase?
Ein Self-Join ist das Zusammenführen einer Tabelle mit sich selbst, um Zeilen innerhalb derselben Tabelle zu verknüpfen (häufig bei Hierarchien wie Mitarbeiter → Manager).
Du musst Aliase verwenden, um die beiden „Kopien“ zu unterscheiden:
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id
Was ist der Unterschied zwischen Filtern in ON vs WHERE bei Joins?
ON definiert, wie Zeilen während des Joins zusammenpassen; WHERE filtert das finale Ergebnis, nachdem der Join gebildet wurde. Bei einem LEFT JOIN kann eine WHERE-Bedingung auf die rechte Tabelle die NULL-gekennzeichneten Zeilen ausschließen und damit unbeabsichtigt aus dem LEFT JOIN ein INNER JOIN machen.
Wenn du alle linken Zeilen behalten, aber die rechten Zeilen einschränken willst, setze die Bedingungen für die rechte Tabelle in ON.
Warum erzeugen Joins Duplikate und wie vermeide ich doppelte Zählungen?
Joins können Zeilen vervielfachen, wenn Beziehungen One-to-Many oder Many-to-Many sind. Zum Beispiel erscheint eine Bestellung mit zwei Zahlungen zweimal, wenn du orders mit payments verbindest.
Um „Double-Counting“ zu vermeiden, aggregiere zuerst die Many-Seite (z. B. SUM(amount) gruppiert nach order_id) und joine dann. DISTINCT ist nur die letzte Option, weil es echte Probleme verschleiern kann und Summen/Counts verfälschen könnte.