7 Min

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.

6 SQL-Joins, die Sie kennen sollten (mit einfachen, klaren Beispielen)

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_idname
1Ava
2Ben
3Chen
4Dia

2) orders

Primary key: order_id
Foreign key: customer_idcustomers.customer_id

order_idcustomer_idorder_total
101150
1021120
103235
104570

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_idorders.order_id

payment_idorder_idamount
900110150
900210260
900310260
900499925

Zwei wichtige Lehrpunkte hier:

  • order_id = 102 hat zwei Zahlungszeilen (eine gesplittete Zahlung). Wenn du orders mit payments joinst, erscheint diese Bestellung zweimal — hierüber stolpern viele.
  • payment_id = 9004 verweist auf order_id = 999, die nicht in orders existiert. 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 orders und payments wiederholt 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 andere orders-Spalten) sind NULL.

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 payments rechts steht).
  • Hat eine Zahlung keine passende Bestellung, sind o.order_id und o.customer_id NULL.

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

Tabellen JOIN-freundlich gestalten
Ordne Kunden, Bestellungen und Zahlungen klar zu, bevor du eine einzige Abfrage schreibst.

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 NULL für die rechte Seite.
  • Existiert eine Zeile nur in der rechten Tabelle, erscheint sie mit NULL fü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:

  1. alle Zeilen aus orders (mit passenden Zahlungen, wenn vorhanden), und
  2. 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, L
  • colors: 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

JOIN-Kenntnisse ins Team bringen
Wechsle von einem schnellen Prototyp zu einem gemeinsamen Workspace, wenn das Projekt wächst.

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:

  • e für employee (Mitarbeiter)
  • m für manager (Manager)

Praktisches Beispiel: Mitarbeiter und ihre Manager

Stell dir eine employees-Tabelle vor mit:

  • id
  • name
  • manager_id (zeigt auf die id eines 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 in einer App üben
Erstelle eine PostgreSQL-App und wende deine JOIN-Logik in einer echten Benutzeroberfläche an.

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 customers zu orders lassen Kunden mehrfach erscheinen — einmal pro Bestellung.
  • Many-to-many (die eigentliche Falle): Joins von orders zu payments, 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 × items pro 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.email oder customers.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:

  1. Wähle weniger Spalten aus. Vermeide SELECT * bei mehreren Joins — unnötige Spalten erhöhen Speicher- und Netzwerkbedarf.
  2. 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 (NULL wenn 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.

Related posts