JOIN Stolperfallen Teil 2

By Frank Kalis

Posted on Aug 26, 2010 von in SQL Server

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.

Dieser Eintrag wurde eingetragen von und ist abgelegt unter SQL Server. Tags: , , , ,

Noch kein Feedback


Formular wird geladen...