By Frank Kalis
Immer wieder kann man Leute beobachten, die fragen, warum ihr LEFT JOIN Statement nicht das gewünschte Resultset zurückbringt.
Beispiel:
CREATE TABLE #Table1 (tid INT, c1 CHAR) CREATE TABLE #Table2 (tid INT, c2 CHAR) INSERT INTO #Table1 SELECT 1, 'a' UNION ALL SELECT 2, 'b' UNION ALL SELECT 3, 'c' UNION ALL SELECT 4, 'd' INSERT INTO #Table2 SELECT 1, 'x' UNION ALL SELECT 2, 'y' UNION ALL SELECT 3, 'z'
Zu diesem Ausgangsszenarion wird nun folgende Abfrage gestellt:
SELECT t1.*, t2.c2 FROM #Table1 t1 LEFT OUTER JOIN #Table2 t2 ON t1.tid=t2.tid tid c1 c2 ----------- ---- ---- 1 a x 2 b y 3 c z 4 d NULL (4 row(s) affected)
Erwartungsgemäß werden alle Zeilen der Tabelle #Table1 und die korrespondierenden Zeilen aus #Table2 zurückgegeben. Sofern kein korrespondierender Wert in #Table2 existiert, wird dies im Resultset durch NULL repräsentiert.
Jetzt wird die Abfrage etwas verändert.
SELECT t1.*, t2.c2 FROM #Table1 t1 LEFT OUTER JOIN #Table2 t2 ON t1.tid=t2.tid WHERE t2.c2='x' tid c1 c2 ----------- ---- ---- 1 a x (1 row(s) affected)
Nanu? Wo sind denn unsere drei anderen Zeilen aus #Table1 geblieben?
Was passiert ist, ist folgendes. Das Hinzufügen der WHERE Klausel
WHERE t2.c2='x'
hat unseren LEFT JOIN effektiv in einen INNER JOIN umgewandelt, indem wir auf eine Spalte der Tabelle #Table2 Bezug genommen haben. Das gleiche Ergebnis erhält man auch durch:
SELECT t1.*, t2.c2 FROM #Table1 t1 INNER JOIN #Table2 t2 ON t1.tid=t2.tid WHERE t2.c2='x' tid c1 c2 ----------- ---- ---- 1 a x (1 row(s) affected)
SQL Server produziert für beide Abfragen identische Ausführungspläne; ist also smart genug, das LEFT JOIN Statement zu einem INNER JOIN zu optimieren.
Warum aber wird aus dem LEFT JOIN ein INNER JOIN? Hier muß man sich vor Augen führen, was eigentlich bei einem LEFT JOIN passieren soll. In unserem Beispiel nennt man #Table1 die "preserved" Tabelle, #Table2 die "unpreserved" Tabelle. Was bedeutet das? Nun, vereinfacht gesagt, nicht anderes als das jede Zeile aus #Table1 wenigstens ein Mal im Resultset auftaucht (siehe unsere erste Abfrage), egal, ob nun ein entsprechendes Gegenstück in #Table2 existiert oder nicht. Suche ich hingegen durch meine WHERE Klausel nach einem bestimmten Wert der unpreserved Tabelle, werden entsprechend der JOIN Bedingung nur diejenigen Zeilen der preserved Tabelle zurückgegeben, die der WHERE Klausel genügen und einen Match in #Table1 haben, respektive ein leeres Resultset, falls keine entsprechenden Daten gefunden werden. Anders verhält es sich, wenn man in der WHERE Klausel Bezug auf die preserved Tabelle nimmt:
SELECT t1.*, t2.c2 FROM #Table1 t1 LEFT OUTER JOIN #Table2 t2 ON t1.tid=t2.tid WHERE t1.c1='d' tid c1 c2 ----------- ---- ---- 4 d NULL (1 row(s) affected)
Diese Abfrage liefert exakt das erwartete Ergebnis. Die entsprechende Spalte der preserved Tabelle erscheint mindestens einmal in Resultset, und, da kein Match in der unpreserved Tabelle vorliegt, werden die entsprechenden Spalte(n) von #Table2 kurzerhand mit NULL aufgefüllt.
Fazit: Wenn man über ein unerwartetes Resultset bei einem LEFT JOIN stolpert, lieber einmal genauer hinschauen, ob nicht vielleicht ein Denkfehler vorliegt und man unbeabsichtigt, einen INNER JOIN produziert hat.