LEFT JOIN behält alles aus der linken Tabelle
Ein INNER JOIN liefert nur Zeilen, bei denen auf beiden Seiten ein Treffer vorhanden ist. Oft ist genau das gewünscht – aber eben nicht immer. Manchmal ist das Fehlen eines Treffers selbst die eigentliche Antwort: Nutzer ohne Bestellung, Produkte ohne einen einzigen Verkauf, Beiträge ohne Kommentare. Für solche Fälle brauchst du den LEFT JOIN (auch LEFT OUTER JOIN genannt).
Ein LEFT JOIN gibt dir jede Zeile aus der linken Tabelle zurück. Gibt es in der rechten Tabelle einen passenden Datensatz, bekommst du dessen Spalten dazu. Falls nicht, erhältst du die Zeile aus der linken Tabelle trotzdem – und die Spalten der rechten Seite kommen als NULL zurück.
Cleo hat keine Bestellungen, taucht aber trotzdem auf – mit NULL in der Spalte total. Tauschst du LEFT JOIN gegen INNER JOIN, verschwindet Cleo komplett aus dem Ergebnis.
Das mentale Modell
Lies die Abfrage von oben nach unten und stell dir die linke Tabelle als Anker vor. Jede Zeile aus users landet in jedem Fall im Ergebnis – ohne Ausnahme. Der LEFT JOIN fragt dann für jeden Nutzer: „Gibt es dazu eine passende Zeile in orders?"
- Treffer gefunden → die passenden Spalten werden an die User-Zeile drangehängt.
- Mehrere Treffer → pro Treffer entsteht eine Ergebniszeile (Ada hat zwei Bestellungen und erscheint deshalb zweimal).
- Kein Treffer → eine Zeile, in der jede Spalte aus der rechten Tabelle den Wert
NULLhat.
Genau wegen des letzten Falls gibt es den LEFT JOIN überhaupt. NULL bedeutet hier nicht „wir wissen es nicht" – sondern schlicht „auf der rechten Seite gibt es nichts zum Anhängen".
LEFT OUTER JOIN ist übrigens dieselbe Operation. Das Schlüsselwort OUTER ist in SQLite optional, und in der Praxis lässt es fast jeder weg.
Zeilen ohne Treffer finden
Der Klassiker beim LEFT JOIN: Zeilen in der linken Tabelle finden, zu denen es keinen Treffer auf der rechten Seite gibt. Der Trick dabei: Du filterst auf eine Spalte aus der rechten Tabelle, die in den echten Daten NOT NULL ist – meistens ihren Primärschlüssel – und prüfst nach dem Join auf NULL:
Nur Cleo taucht wieder auf. Der Join hängt die Bestelldaten an, wo es welche gibt, und WHERE o.id IS NULL behält dann nur noch die Zeilen, bei denen das Anhängen fehlgeschlagen ist. Diese Technik nennt man auch "Anti-Join".
ON oder WHERE: die fiese Falle
Das ist der häufigste Bug bei LEFT JOIN – und genau deshalb lohnt es sich, hier kurz innezuhalten. Bedingungen kannst du entweder in die ON-Klausel oder in die WHERE-Klausel schreiben, aber bei Outer Joins verhalten sich beide völlig unterschiedlich.
ONgreift während der Join läuft. Die Bedingungen dort legen fest, welche Zeilen aus der rechten Tabelle als Treffer gelten.WHEREgreift danach, also nachdem der Join seine Zeilen schon erzeugt hat. Es filtert das fertige Ergebnis.
Schau mal, was passiert, wenn du eine Bedingung für die rechte Tabelle in WHERE packst:
Cleo hat keine Bestellung, deshalb ist o.status in ihrer Zeile NULL – und NULL = 'shipped' ergibt eben nicht true. Damit fliegt sie raus. Bei Boris ist der Status 'pending', also auch weg. Der LEFT JOIN hat sich hier klammheimlich wie ein INNER JOIN verhalten.
Die Lösung: Pack die Bedingung in die ON-Klausel, damit sie die Treffer filtert und nicht die Ergebniszeilen:
Jetzt taucht jeder Nutzer auf: Ada bekommt ihre versandte Bestellung, Boris bekommt NULL (seine offene Bestellung zählt nicht als Treffer), und Cleo bekommt NULL (keine Bestellungen vorhanden). Genau das ist die richtige Antwort, wenn die Frage lautet: „Zeig mir alle Nutzer und – falls vorhanden – ihre versandten Bestellungen."
Faustregel: Bedingungen auf die linke Tabelle dürfen ins WHERE. Bedingungen auf die rechte Tabelle gehören dagegen fast immer ins ON – außer du willst gezielt mit IS NULL nach Zeilen ohne Treffer suchen. Genau hier liegt auch der typische Unterschied zwischen ON und WHERE beim SQLite LEFT JOIN.
Zeilen zählen mit LEFT JOIN in SQLite
Eine klassische Aufgabe: pro Eltern-Datensatz die zugehörigen Zeilen zählen – inklusive der Eltern, die null Treffer haben. Ein INNER JOIN würde die Nullen einfach unter den Tisch fallen lassen. Mit LEFT JOIN plus COUNT auf eine Spalte aus der rechten Tabelle bekommst du das korrekte Ergebnis:
Zwei Dinge, die hier wichtig sind:
COUNT(o.id)zählt nur Zeilen, bei denen die rechte Seite nichtNULList. Cleo bekommt deshalb0und nicht1— dennCOUNTignoriertNULL-Werte. Würdest du stattdessenCOUNT(*)schreiben, käme bei Cleo1heraus (die Zeile existiert ja, sie hat nurNULL-Werte drin). In den allermeisten Fällen willst duCOUNT(right.id).COALESCE(SUM(o.total), 0)verwandelt CleosNULL-Summe in eine0. Ohne diesen Kniff würde bei ihrNULLals Umsatz stehen — technisch korrekt, aber unschön anzuzeigen.
SQLite LEFT JOIN über mehrere Tabellen
LEFT JOIN lässt sich verketten. Jeder Join nimmt das bisherige Zwischenergebnis und hängt eine weitere Tabelle daran. Sobald du eine Spalte durch einen LEFT JOIN nullable gemacht hast, solltest du für alle Tabellen, die daran hängen, ebenfalls LEFT JOIN verwenden — sonst wirft dir der nächste INNER JOIN klammheimlich genau die Zeilen raus, die du eigentlich behalten wolltest.
Drei User kommen zurück. Ada hat eine Bestellung und einen Versand. Boris hat eine Bestellung, aber keinen Versand (carrier ist NULL). Cleo hat überhaupt keine Bestellung, deshalb sind sowohl o.total als auch s.carrier NULL. Die Kette aus LEFT JOINs sorgt dafür, dass jeder User erhalten bleibt – egal, an welcher Stelle der Beziehungskette die Daten ausgehen.
Wann ein SQLite LEFT JOIN die richtige Wahl ist
Greif zum LEFT JOIN, wenn es bei deiner Abfrage im Kern um die linke Tabelle geht und die rechte Tabelle nur ergänzende Informationen liefert. Formulierungen wie „alle User samt ihrer Bestellungen, falls vorhanden" oder „alle Produkte mit ihrer letzten Bewertung" lassen sich direkt auf einen LEFT JOIN abbilden.
Den INNER JOIN nimmst du, wenn beide Seiten gleichermaßen vorhanden sein müssen – „Bestellungen mit den dazugehörigen User-Daten" ergibt für eine Bestellung ohne User keinen Sinn, und genau dieses Filtern erledigt der Inner Join für dich.
Wenn du dich dabei erwischst, LEFT JOIN ... WHERE right.col IS NOT NULL zu schreiben, wolltest du eigentlich einen INNER JOIN. Schreibst du dagegen LEFT JOIN ... WHERE right.col IS NULL, dann ist das ein Anti-Join – und der ist so genau richtig.
Als Nächstes: Self-Joins
Manchmal ist die Tabelle, die du dazujoinen willst, dieselbe, die du gerade abfragst – Mitarbeiter und ihre Vorgesetzten, Kategorien und ihre Oberkategorien, Paare von Usern aus derselben Stadt. Das nennt sich Self-Join, und darum geht's auf der nächsten Seite.
Häufig gestellte Fragen
Was macht ein LEFT JOIN in SQLite?
LEFT JOIN liefert dir jede Zeile aus der linken Tabelle zurück – plus die passenden Zeilen aus der rechten Tabelle, sofern es welche gibt. Findet sich rechts kein Treffer, bekommst du die linke Zeile trotzdem, und die Spalten der rechten Seite stehen dann auf NULL. LEFT OUTER JOIN bedeutet übrigens genau dasselbe – das OUTER ist in SQLite optional.
Worin unterscheiden sich LEFT JOIN und INNER JOIN in SQLite?
Ein INNER JOIN gibt nur die Zeilen zurück, bei denen die Join-Bedingung in beiden Tabellen erfüllt ist. Ein LEFT JOIN hingegen behält alle Zeilen der linken Tabelle und füllt fehlende Werte auf der rechten Seite mit NULL auf. Nimm LEFT JOIN immer dann, wenn 'kein Treffer' selbst eine Aussage ist – zum Beispiel Nutzer, die noch keine einzige Bestellung haben.
Warum verhält sich mein LEFT JOIN wie ein INNER JOIN?
In den allermeisten Fällen liegt es an einer WHERE-Klausel, die auf eine Spalte der rechten Tabelle filtert, ohne NULL zuzulassen. Bedingungen für die rechte Tabelle gehören in die ON-Klausel, nicht in WHERE – oder du schreibst bewusst WHERE right.col IS NULL, um genau die unzugeordneten Zeilen zu finden. Ein WHERE right.col = 'x' wirft dir nämlich klammheimlich alle Zeilen ohne Match wieder raus.