Premium

Äußerer Join: OUTER JOIN

Ein innerer Join behält nur die Zeilen, für die es in der anderen Tabelle ein Gegenstück gibt. Ein äußerer Join arbeitet anders: Er liefert immer alle Zeilen einer Tabelle oder beider Tabellen und füllt die fehlende Hälfte mit NULL auf.

Es gibt drei Arten des äußeren Joins: links (LEFT), rechts (RIGHT) und vollständig (FULL). Die Art musst du angeben — ein bloßes OUTER JOIN ist ein Syntaxfehler. Das Wort OUTER selbst ist dagegen optional: LEFT JOIN und LEFT OUTER JOIN bedeuten dasselbe, und im Folgenden wird die kurze Schreibweise verwendet.

Linker äußerer Join (LEFT OUTER JOIN)

Liefert alle Zeilen der linken Tabelle. Findet eine Zeile ein Gegenstück in der rechten Tabelle, werden beide Zeilen zusammengefügt; findet sie keines, werden die Spalten der rechten Tabelle mit NULL gefüllt.

Als Beispiel holen wir aus der Datenbank den Klingelplan, verknüpft mit den passenden Einträgen aus dem Stundenplan.

Daten in der Tabelle Timepair (Klingelplan):

idstart_pairend_pair
108:30:0009:15:00
209:20:0010:05:00
310:15:0011:00:00
411:05:0011:50:00
512:50:0013:35:00
613:40:0014:25:00
714:35:0015:20:00
815:25:0016:10:00

Daten in der Tabelle Schedule (Stundenplan):

iddateclassnumber_pairteachersubjectclassroom
12019-09-01T00:00:00.000Z9111147
22019-09-01T00:00:00.000Z928213
32019-09-01T00:00:00.000Z934313
42019-09-02T00:00:00.000Z914313
52019-09-02T00:00:00.000Z922434
62019-09-02T00:00:00.000Z936535
72019-09-03T00:00:00.000Z915636
82019-09-03T00:00:00.000Z9213737
92019-09-03T00:00:00.000Z936838
102019-09-04T00:00:00.000Z919939
112019-09-04T00:00:00.000Z92101040
122019-09-04T00:00:00.000Z9331141
132019-09-05T00:00:00.000Z9131343
142019-09-05T00:00:00.000Z9211147
152019-09-05T00:00:00.000Z935636
162019-08-30T00:00:00.000Z912434
172019-08-30T00:00:00.000Z928213
182019-08-30T00:00:00.000Z936535
192019-08-30T00:00:00.000Z9410147
202019-09-03T00:00:00.000Z94101040
212019-08-30T00:00:00.000Z817953
222019-08-30T00:00:00.000Z827953
232019-08-30T00:00:00.000Z838238
242019-08-30T00:00:00.000Z8411143
252019-08-30T00:00:00.000Z858339
262019-09-01T00:00:00.000Z822434
272019-09-01T00:00:00.000Z836535
282019-09-01T00:00:00.000Z8412636
292019-09-01T00:00:00.000Z8513737
302019-09-02T00:00:00.000Z836838
312019-09-02T00:00:00.000Z847953
322019-09-03T00:00:00.000Z81101040
332019-09-03T00:00:00.000Z827953
342019-09-03T00:00:00.000Z837953
352019-09-04T00:00:00.000Z811114
362019-09-04T00:00:00.000Z8211242
372019-09-04T00:00:00.000Z8331343
382019-09-04T00:00:00.000Z848242
392019-09-04T00:00:00.000Z8511143
402019-09-05T00:00:00.000Z8211143
MySQL 8.1
SELECT Timepair.id "timepair.id", start_pair, end_pair,
    Schedule.id "schedule.id", date, class, number_pair, teacher, subject, classroom
FROM Timepair
    LEFT JOIN Schedule ON Schedule.number_pair = Timepair.id;
timepair.idstart_pairend_pairschedule.iddateclassnumber_pairteachersubjectclassroom
108:30:0009:15:00352019-09-04T00:00:00.000Z811114
108:30:0009:15:00322019-09-03T00:00:00.000Z81101040
108:30:0009:15:00212019-08-30T00:00:00.000Z817953
108:30:0009:15:00162019-08-30T00:00:00.000Z912434
108:30:0009:15:00132019-09-05T00:00:00.000Z9131343
108:30:0009:15:00102019-09-04T00:00:00.000Z919939
108:30:0009:15:0072019-09-03T00:00:00.000Z915636
108:30:0009:15:0042019-09-02T00:00:00.000Z914313
108:30:0009:15:0012019-09-01T00:00:00.000Z9111147
209:20:0010:05:00402019-09-05T00:00:00.000Z8211143
209:20:0010:05:00362019-09-04T00:00:00.000Z8211242
209:20:0010:05:00332019-09-03T00:00:00.000Z827953
209:20:0010:05:00262019-09-01T00:00:00.000Z822434
209:20:0010:05:00222019-08-30T00:00:00.000Z827953
209:20:0010:05:00172019-08-30T00:00:00.000Z928213
209:20:0010:05:00142019-09-05T00:00:00.000Z9211147
209:20:0010:05:00112019-09-04T00:00:00.000Z92101040
209:20:0010:05:0082019-09-03T00:00:00.000Z9213737
209:20:0010:05:0052019-09-02T00:00:00.000Z922434
209:20:0010:05:0022019-09-01T00:00:00.000Z928213
310:15:0011:00:00372019-09-04T00:00:00.000Z8331343
310:15:0011:00:00342019-09-03T00:00:00.000Z837953
310:15:0011:00:00302019-09-02T00:00:00.000Z836838
310:15:0011:00:00272019-09-01T00:00:00.000Z836535
310:15:0011:00:00232019-08-30T00:00:00.000Z838238
310:15:0011:00:00182019-08-30T00:00:00.000Z936535
310:15:0011:00:00152019-09-05T00:00:00.000Z935636
310:15:0011:00:00122019-09-04T00:00:00.000Z9331141
310:15:0011:00:0092019-09-03T00:00:00.000Z936838
310:15:0011:00:0062019-09-02T00:00:00.000Z936535
310:15:0011:00:0032019-09-01T00:00:00.000Z934313
411:05:0011:50:00382019-09-04T00:00:00.000Z848242
411:05:0011:50:00312019-09-02T00:00:00.000Z847953
411:05:0011:50:00282019-09-01T00:00:00.000Z8412636
411:05:0011:50:00242019-08-30T00:00:00.000Z8411143
411:05:0011:50:00202019-09-03T00:00:00.000Z94101040
411:05:0011:50:00192019-08-30T00:00:00.000Z9410147
512:50:0013:35:00392019-09-04T00:00:00.000Z8511143
512:50:0013:35:00292019-09-01T00:00:00.000Z8513737
512:50:0013:35:00252019-08-30T00:00:00.000Z858339
613:40:0014:25:00<NULL><NULL><NULL><NULL><NULL><NULL><NULL>
714:35:0015:20:00<NULL><NULL><NULL><NULL><NULL><NULL><NULL>
815:25:0016:10:00<NULL><NULL><NULL><NULL><NULL><NULL><NULL>

Alle acht Klingelzeiten sind im Ergebnis gelandet — genau das verspricht der linke Join. Zeilen sind es aber 43 und nicht 8.

Ein Join ergänzt die linke Tabelle nicht, sondern geht alle passenden Zeilenpaare durch. Dieselbe Stundennummer kommt im Stundenplan viele Male vor — an verschiedenen Tagen und in verschiedenen Klassen —, und jede Übereinstimmung ergibt eine eigene Zeile. Ist der Schlüssel in der rechten Tabelle nicht eindeutig, hat das Ergebnis mehr Zeilen als die linke Tabelle.

Am Ende der Ergebnismenge stehen Zeilen, in denen alle Spalten des Stundenplans NULL sind. Das sind die Klingelzeiten ohne jeden Unterricht: Ein Gegenstück fehlt, aber eine Zeile der linken Tabelle muss im Ergebnis auftauchen.

Zeilen ohne Gegenstück

Auf diesen NULL-Werten beruht der häufigste Praxis-Kniff — Datensätze finden, die kein Gegenstück haben. Dazu genügt es, nur die Zeilen zu behalten, in denen der Schlüssel der rechten Tabelle leer ist:

MySQL 8.1
SELECT Timepair.id, start_pair, end_pair
FROM Timepair
    LEFT JOIN Schedule ON Schedule.number_pair = Timepair.id
WHERE Schedule.number_pair IS NULL;
idstart_pairend_pair
613:40:0014:25:00
714:35:0015:20:00
815:25:0016:10:00

Übrig bleiben drei Klingelzeiten, für die kein Unterricht eingeplant ist.

Eine Verknüpfung, aus der nur die Zeilen ohne Gegenstück übrig bleiben, nennt man Anti-Join (ANTI JOIN). Einen eigenen Operator hat er nicht: Sowohl in MySQL als auch in PostgreSQL schreibt man ihn genau so — als Verknüpfung mit einer IS NULL-Bedingung.

Rechter äußerer Join (RIGHT OUTER JOIN)

Das Spiegelbild des linken Joins: Alle Zeilen der rechten Tabelle landen garantiert im Ergebnis, und die fehlenden Spalten der linken Tabelle werden mit NULL gefüllt.

MySQL 8.1
SELECT Timepair.id "timepair.id", start_pair, end_pair,
    Schedule.id "schedule.id", date, class, number_pair, teacher, subject, classroom
FROM Timepair
    RIGHT JOIN Schedule ON Schedule.number_pair = Timepair.id;

Das Ergebnis hat 40 Zeilen — genau so viele, wie Schedule Datensätze enthält — und keine einzige Zeile mit NULL. Es stimmt also vollständig mit dem inneren Join überein.

Der Grund: Jeder Unterrichtseintrag verweist auf eine vorhandene Klingelzeit, die rechte Tabelle hat schlicht keine Zeilen ohne Gegenstück. Die Art des Joins gibt die Regel vor, was am Ende im Ergebnis steht, entscheiden die Daten.

Vollständiger äußerer Join (FULL OUTER JOIN)

Liefert alle Zeilen beider Tabellen. Zeilen mit Gegenstück werden zusammengefügt, Zeilen ohne Gegenstück aus der linken und der rechten Tabelle landen mit NULL anstelle der fehlenden Hälfte im Ergebnis.

Das Ergebnis eines vollständigen Joins besteht aus drei Teilen:

  • den Zeilen des inneren Joins (INNER JOIN);
  • den Zeilen der linken Tabelle ohne Gegenstück;
  • den Zeilen der rechten Tabelle ohne Gegenstück.
MySQL 8.1
SELECT Timepair.id "timepair.id", start_pair, end_pair,
    Schedule.id "schedule.id", date, class, number_pair, teacher, subject, classroom
FROM Timepair
    FULL OUTER JOIN Schedule ON Schedule.number_pair = Timepair.id;

Mit den Daten dieser Datenbank stimmt das Ergebnis mit dem linken Join überein — dieselben 43 Zeilen: Zeilen ohne Gegenstück gibt es hier nur links.

MySQL unterstützt FULL OUTER JOIN nicht, dasselbe Ergebnis kannst du aber von Hand zusammensetzen: den linken Join nehmen und die Zeilen der rechten Tabelle ergänzen, die kein Gegenstück gefunden haben.

MySQL 8.1
SELECT tabellen_felder
FROM linke_tabelle
    LEFT JOIN rechte_tabelle ON rechte_tabelle.key = linke_tabelle.key

UNION ALL

SELECT tabellen_felder
FROM linke_tabelle
    RIGHT JOIN rechte_tabelle ON rechte_tabelle.key = linke_tabelle.key
WHERE linke_tabelle.key IS NULL;

Die Bedingung im zweiten Teil ist zwingend: ohne sie kämen die Zeilen mit Gegenstück doppelt ins Ergebnis.

Alle Arten von Tabellen-Joins

Alle Zeilen der linken Tabelle
Linke Tabelle
key
1
2
3
Rechte Tabelle
key
1
1
2
4
Ergebnis: 4 Zeilen
links.key
rechts.key
1
1
1
1
2
2
3
NULL
SELECT tabellen_felder
FROM linke_tabelle
    LEFT JOIN rechte_tabelle ON rechte_tabelle.key = linke_tabelle.key

Prüfen wir dich einmal. Die linke Tabelle hat 8 Zeilen. Wie viele Zeilen liefert ein LEFT JOIN mit der rechten Tabelle?