Verwenden eigenständiger oder korrelierter Unterabfragen
Zuvor haben wir uns eigenständige Unterabfragen angesehen, bei denen die innere Abfrage unabhängig von der äußeren Abfrage ist, ein Mal ausgeführt wird und ihre Ergebnisse an die äußere Abfrage zurückgibt. T-SQL unterstützt auch korrelierte Unterabfragen, in denen die interne Abfragespalte in der äußeren Abfrage referenziert und konzeptionell einmal pro Zeile ausgeführt wird.
Arbeiten mit korrelierten Unterabfragen
Wie eigenständige Unterabfragen sind korrelierte Unterabfragen SELECT-Anweisungen, die in einer äußeren Abfrage geschachtelt sind. Korrelierte Unterabfragen können auch skalare oder mehrwertige Unterabfragen sein. Sie werden in der Regel verwendet, wenn die innere Abfrage auf einen Wert in der äußeren Abfrage verweisen muss.
Im Gegensatz zu eigenständigen Unterabfragen gibt es jedoch einige besondere Überlegungen bei der Verwendung korrelierter Unterabfragen:
- Korrelierte Unterabfragen können nicht separat von der äußeren Abfrage ausgeführt werden. Diese Einschränkung erschwert das Testen und Debuggen.
- Im Gegensatz zu eigenständigen Unterabfragen, die ein Mal verarbeitet werden, werden korrelierte Unterabfragen mehrmals ausgeführt. Logisch wird die äußere Abfrage zuerst ausgeführt, und für jede zurückgegebene Zeile wird die innere Abfrage verarbeitet.
Im folgenden Beispiel wird eine korrelierte Unterabfrage verwendet, um die letzte Bestellung für jeden Kunden zurückzugeben. Die Unterabfrage bezieht sich auf die äußere Abfrage und verweist auf den CustomerID-Wert in der WHERE-Klausel. Für jede Zeile in der äußeren Abfrage sucht die Unterabfrage die maximale Bestell-ID für den Kunden, auf den in dieser Zeile verwiesen wird, und die äußere Abfrage überprüft, ob es sich bei der untersuchten Zeile um die Zeile mit dieser Bestell-ID handelt.
SELECT SalesOrderID, CustomerID, OrderDate
FROM SalesLT.SalesOrderHeader AS o1
WHERE SalesOrderID =
(SELECT MAX(SalesOrderID)
FROM SalesLT.SalesOrderHeader AS o2
WHERE o2.CustomerID = o1.CustomerID)
ORDER BY CustomerID, OrderDate;
Schreiben korrelierter Unterabfragen
Beachten Sie die folgenden Richtlinien, um korrelierte Unterabfragen zu schreiben:
- Schreiben Sie die äußere Abfrage so, dass sie das entsprechende Rückgabeergebnis von der inneren Abfrage akzeptiert. Wenn die innere Abfrage skalar ist, können Sie Gleichheits- und Vergleichsoperatoren wie =, <, > und <> in der WHERE-Klausel verwenden. Wenn die innere Abfrage möglicherweise mehrere Werte zurückgibt, verwenden Sie ein IN-Prädikat. Planen Sie die Verarbeitung von NULL-Ergebnissen ein.
- Identifizieren Sie die Spalte aus der äußeren Abfrage, auf die von der korrelierten Unterabfrage verwiesen wird. Deklarieren Sie einen Alias für die Tabelle, die die Quelle der Spalte in der äußeren Abfrage ist.
- Identifizieren Sie die Spalte aus der inneren Tabelle, die mit der Spalte aus der äußeren Tabelle verglichen wird. Erstellen Sie wie bei der äußeren Abfrage einen Alias für die Quelltabelle.
- Schreiben Sie die innere Abfrage so, dass sie basierend auf dem Eingabewert aus der äußeren Abfrage Werte aus ihrer Quelle abruft. Verwenden Sie beispielsweise die äußere Spalte in der WHERE-Klausel der inneren Abfrage.
Die Korrelation zwischen den inneren und äußeren Abfragen tritt auf, wenn von der inneren Abfrage zum Vergleich auf den äußeren Wert verwiesen wird. Diese Korrelation gibt der Unterabfrage ihren Namen.
Arbeiten mit EXISTS
Zusätzlich zum Abrufen von Werten aus einer Unterabfrage bietet T-SQL einen Mechanismus zum Überprüfen, ob Ergebnisse von einer Abfrage zurückgegeben werden. Das EXISTS-Prädikat bestimmt, ob Zeilen vorhanden sind, die eine angegebene Bedingung erfüllen, aber anstatt diese zurückzugeben, wird TRUE oder FALSE zurückgegeben. Diese Technik ist nützlich, um Daten zu überprüfen, ohne Mehraufwand für das Abrufen und Verarbeiten der Ergebnisse zu verursachen.
Wenn eine Unterabfrage mithilfe des EXISTS-Prädikats mit der äußeren Abfrage verknüpft ist, behandelt SQL Server die Ergebnisse der Unterabfrage auf besondere Weise. Anstatt einen Skalarwert oder eine mehrwertige Liste aus der Unterabfrage abzurufen, überprüft EXISTS einfach, ob Zeilen im Ergebnis vorhanden sind.
Konzeptionell entspricht ein EXISTS-Prädikat dem Abrufen der Ergebnisse, dem Zählen der zurückgegebenen Zeilen und dem Vergleichen der Anzahl mit null. Vergleichen Sie die folgenden Abfragen, die Details zu Kunden zurückgeben, die Bestellungen platziert haben:
Die erste Beispielabfrage verwendet COUNT in einer Unterabfrage:
SELECT CustomerID, CompanyName, EmailAddress
FROM Sales.Customer AS c
WHERE
(SELECT COUNT(*)
FROM Sales.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID) > 0;
Die zweite Abfrage, die die gleichen Ergebnisse zurückgibt, verwendet EXISTS:
SELECT CustomerID, CompanyName, EmailAddress
FROM Sales.Customer AS c
WHERE EXISTS
(SELECT *
FROM Sales.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID);
Im ersten Beispiel muss die Unterabfrage jedes Vorkommen jeder custid zählen, die in der Tabelle Sales.SalesOrderHeader gefunden werden, und die Zählergebnisse mit Null vergleichen, einfach um anzugeben, dass der Kunde Bestellungen aufgegeben hat.
In der zweiten Abfrage gibt EXISTS den Wert TRUE für eine custid zurück, sobald eine relevante Bestellung in der Tabelle Sales.SalesOrderHeader gefunden wurde. Eine vollständige Auflistung jedes Vorkommens ist nicht erforderlich. Beachten Sie auch, dass die Unterabfrage mit EXISTS nicht auf die Rückgabe einer einzelnen Spalte beschränkt ist. Hier verwenden wir SELECT *. Die zurückgegebenen Spalten sind irrelevant, da wir nur überprüfen, ob überhaupt Zeilen zurückgegeben werden. Uns interessiert nicht, welche Werte in diesen Zeilen enthalten sind.
Aus Sicht der logischen Verarbeitung sind die beiden Abfrageformen gleichwertig. Aus Leistungssicht kann die Datenbank-Engine die Abfragen unterschiedlich behandeln, da sie für die Ausführung optimiert werden. Erwägen Sie, jede Form für Ihre eigene Nutzung zu testen.
Hinweis
Wenn Sie eine Unterabfrage mit COUNT(*) in eine Unterabfrage mit EXISTS konvertieren, sollten Sie sicherstellen, dass die Unterabfrage SELECT * und nicht SELECT COUNT(*) verwendet. SELECT COUNT(*) gibt immer eine Zeile zurück, sodass EXISTS immer WAHR zurückgibt .
Eine weitere nützliche Anwendung von EXISTS ist das Negieren der Unterabfrage mit NOT, wie im folgenden Beispiel gezeigt. Dabei wird jeder Kunden zurückgegeben, der noch nie eine Bestellung aufgegeben hat:
SELECT CustomerID, CompanyName, EmailAddress
FROM SalesLT.Customer AS c
WHERE NOT EXISTS
(SELECT *
FROM SalesLT.SalesOrderHeader AS o
WHERE o.CustomerID = c.CustomerID);
SQL Server muss für Kunden, die eine Bestellung aufgegeben haben, keine Daten zu den zugehörigen Bestellungen zurückgeben. Wenn ein custid in der Tabelle Sales.SalesOrderHeader gefunden wird, wird NOT EXISTS als FALSCH ausgewertet, und die Auswertung wird schnell abgeschlossen.
Beachten Sie die folgenden Richtlinien, um Abfragen zu schreiben, die EXISTS mit Unterabfragen verwenden:
- Das Schlüsselwort EXISTS folgt direkt auf WHERE. Ihm wird kein Spaltenname (oder ein anderer Ausdruck) vorangestellt, es sei denn, NOT wird ebenfalls verwendet.
- Verwenden Sie in der Unterabfrage SELECT *. Von der Unterabfrage werden keine Zeilen zurückgegeben, sodass keine Spalten angegeben werden müssen.