T-SQL Ninja #07

OUTER APPLY

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.

Ausführungsplan der Abfrage

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.

Referenzen

Ninja-Notebooks @ GitHub

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.