Was tut OUTER APPLY?
OUTER APPY
ist ein Mittel, das euch zunächst einmal die Möglichkeit gibt, Unterabfragen zu formulieren, die für jede Zeile im Ergebnis ausgeführt wird und dann an das Ergebnis gejoint wird. Damit ist es natürlich auch ein Mittel, das mit höchster Vorsicht zu genießen ist, denn da ein OUTER APPLY
für jede Zeile im Ergebnis ausgeführt wird, kann selbst eine billige (Unter-)Abfrage hier zu hohen Gesamtkosten für die Abfrage führen.
Wie könnt ihr OUTER APPLY verwenden?
Stellt euch vor, dass ihr in der Wide World Importers Datenbank zu jedem Item im Lager wissen möchtet, wann es zuletzt bestellt wurde. Würdet ihr nun die Items mit den Orders Joinen, so hättet ihr ein gewaltiges Resultset, bei dem ihr zu jedem Item jede Bestellung erfahrt. Aus diesen müsstet ihr dann die jeweils letzten heraussuchen. Dadurch würdet ihr mit einer viel größeren Datenmenge arbeiten, als ihr das eigentlich möchtet. Das bedeutet, dass ihr mehr Speicher, mehr Temp DB und mehr CPU Zeit benötigt, um die Abfrage zu bearbeiten, als es die eigentliche Datenmenge rechtfertigt.
Wenn ihr das vermeiden möchtet, könnt ihr mittels OUTER APPLY
für jede Zeile eine Unterabfrage ausführen, die diesen Wert ergänzt. Das Ganze sieht dann so aus:
SELECT si.[StockItemID] ,si.[StockItemName] ,si.[UnitPrice] ,od.[LastOrderDate] FROM [Warehouse].[StockItems] si OUTER APPLY ( SELECT TOP 1 o.[OrderDate] as LastOrderDate FROM [Sales].[Orders] as o LEFT JOIN [Sales].[OrderLines] as ol ON o.[OrderID]= ol.[OrderID] WHERE ol.StockItemID = si.StockItemID ORDER BY o.[OrderDate] desc ) od
Wenn ihr nun den Ausführungsplan für diese Abfrage anseht, dann seht ihr im Plan die Unterabfrage mit 277 Ausführungen, so vielen wie die Abfrage Zeilen zurückgibt.

Das ist natürlich nicht sonderlich effizient. In diesem Beispiel könnt ihr stattdessen auch mit einer CTE arbeiten (mehr darüber könnt ihr im ersten Teil der Serie nachlesen). Die Abfrage sähe dann so aus:
;WITH cte_lastOrder as ( SELECT MAX(o.[OrderDate]) as LastOrderDate ,ol.[StockItemID] FROM [Sales].[Orders] as o LEFT JOIN [Sales].[OrderLines] as ol ON o.[OrderID]= ol.[OrderID] GROUP BY ol.StockItemID ) SELECT si.[StockItemID] ,si.[StockItemName] ,si.[UnitPrice] ,lo.[LastOrderDate] FROM [Warehouse].[StockItems] si LEFT JOIN cte_lastOrder lo ON si.StockItemID = lo.StockItemID
Nun könnt ihr euch fragen: wenn das auch mit CTEs geht, wofür brauchen wir dann überhaupt einen APPLY
-Operator? Ganz einfach: nicht immer möchtet ihr Tabellen zusammenführen, APPLY
gibt euch darüber hinaus auch die Möglichkeit, für jede Zeile eines Abfrageergebnisses eine Table Valued Function auszuführen und das Ergebnis mit der Zeile zu joinen, ihr erhaltet hier also die Freiheit, viel mehr Operationen durchzuführen, als das bei normalen JOIN
-Operationen möglich ist.
Übrigens entspricht ein OUTER APPLY
einem LEFT JOIN
, während ein CROSS APPLY
einer INNER JOIN
Operation entspricht.