CiAgICA8IS0tIExpbmtlZEluIC0tPgogICAgPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPgogICAgICAgIF9saW5rZWRpbl9wYXJ0bmVyX2lkID0gIjEyMzUwNzMiOwogICAgICAgIHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyA9IHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyB8fCBbXTsKICAgICAgICB3aW5kb3cuX2xpbmtlZGluX2RhdGFfcGFydG5lcl9pZHMucHVzaChfbGlua2VkaW5fcGFydG5lcl9pZCk7CiAgICA8L3NjcmlwdD48c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgICAgICAgKGZ1bmN0aW9uKCl7dmFyIHMgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgic2NyaXB0IilbMF07CiAgICAgICAgICAgIHZhciBiID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7CiAgICAgICAgICAgIGIudHlwZSA9ICJ0ZXh0L2phdmFzY3JpcHQiO2IuYXN5bmMgPSB0cnVlOwogICAgICAgICAgICBiLnNyYyA9ICJodHRwczovL3NuYXAubGljZG4uY29tL2xpLmxtcy1hbmFseXRpY3MvaW5zaWdodC5taW4uanMiOwogICAgICAgICAgICBzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGIsIHMpO30pKCk7CiAgICA8L3NjcmlwdD4KICAgIDxub3NjcmlwdD4KICAgICAgICA8aW1nIGhlaWdodD0iMSIgd2lkdGg9IjEiIHN0eWxlPSJkaXNwbGF5Om5vbmU7IiBhbHQ9IiIgc3JjPSJodHRwczovL3B4LmFkcy5saW5rZWRpbi5jb20vY29sbGVjdC8/cGlkPTEyMzUwNzMmZm10PWdpZiIgLz4KICAgIDwvbm9zY3JpcHQ+CiAgICA8IS0tIEVuZCBMaW5rZWRJbiAtLT4KICAgIA==
Generic filters
Exact matches only
Search in title
Search in excerpt
Search in content

LEAD und LAG mit T-SQL

Die Funktionen LEAD() und LAG() kennen viele bereits als MDX-Funktionen und wissen, die Möglichkeiten zu nutzen, die diese Funktionen bieten. Seit SQL-Server 2012 stehen diese Funktionen auch in T-SQL für relationale Datenbankabfragen zur Verfügung. Im Folgenden wird die Verwendung dieser Funktionen anhand von Anwendungsbeispielen erläutert.

Seit der Version SQL-Server 2012 stehen die Funktionen LEAD() und LAG() als analytische Funktionen auch in T-SQL zur Verfügung. Ähnlich wie in MDX, wo sich die nachfolgenden bzw. vorausgehenden Elemente in einem Set ermitteln lassen, kann in T-SQL mit LEAD() und LAG() auf nachfolgende bzw. vorausgehende Zeilen einer SELECT-Anweisung zugegriffen werden.

Im Folgenden wird die Verwendung am Beispiel der LAG()-Funktion erläutert, mit der auf den Inhalt von vorausgehenden Zeilen einer SELECT-Anweisung zugegriffen werden kann. Dabei ist die Verwendung der LEAD()-Funktion analog, nur eben in die entgegengesetzte Richtung.

Syntax

Die Syntax der LAG()-Funktion ist recht überschaubar:

LAG (scalar_expression [,offset] [,default])
	OVER ( [partition_by_clause] order_by_clause )
scalar_expression

Der Rückgabewert auf Basis des angegebenen Offsets – z.B. eine Spalte oder Berechnung aus mehreren Spalteninhalten aus der über das Offset angegebenen vorausgegangenen Zeile.

offset

Abstand der vorausgehenden Zeile, aus der ein Wert abgerufen werden soll. Wenn die vorausgehende Zeile nicht existiert, wird der angegebene default-Wert zurückgegeben. Der Wert für offset muss eine positive ganze Zahl sein. Wird kein offset angegeben, dann wird ein Abstand von 1 verwendet.

default

Gibt den Wert an, der zurückgegeben wird, falls scalar_expression am angegebenen offset NULL ist. Wenn kein Standardwert angegeben ist, wird NULL zurückgegeben. default muss mit scalar_expression typkompatibel sein.

OVER ( [partition_by_clause] order_by_clause )

Die partition_by_clause unterteilt das Resultset in Partitionen, auf die die LAG()-Funktion angewendet wird. Ohne Angabe der partition_by_clause wird das gesamte Abfrageergebnis als einzelne Partition verarbeitet. Mit der order_by_clause wird ein Sortierkriterium angegeben, nach dem die Daten innerhalb der Partition(en) sortiert werden. Die Angabe der order_by_clause ist nicht optional!

Beispiele

Es folgen Beispiele auf der Basis der Datenbank AdventureWorks2012. Die Datenbank kann kostenlos bei GitHub heruntergeladen werden:

https://github.com/Microsoft/sql-server-samples/releases/tag/adventureworks

Werte aus verschiedenen Quartalen vergleichen

Mit der folgenden Abfrage werden die Quartals-Verkaufszahlen für einen bestimmten Mitarbeiter ermittelt und der jeweils vorausgehende Wert mit Hilfe der LAG()-Funktion in einer eigenen Spalte ausgegeben.

SELECT 
	BusinessEntityID, 
	QuotaDate AS SalesQuarter, 
	SalesQuota AS CurrentQuota,   
	LAG(SalesQuota, 1,0) OVER (ORDER BY QuotaDate) AS PreviousQuota  
FROM Sales.SalesPersonQuotaHistory 
WHERE BusinessEntityID = 275;

Im Resultset lässt sich die Anwendung der LAG()-Funktion klar nachvollziehen:


Abbildung 1: Anwendung der LAG()-Funktion

Werte aus verschiedenen Jahren vergleichen mit LAG() und SUM()

Auf der gleichen Datenbasis wie im vorherigen Beispiel, sollen nun die Jahreswerte des Mitarbeiters ermittelt und zusammen mit dem entsprechenden Vorjahreswert und der Abweichung zum Vorjahreswert ausgegeben werden.

SELECT 
	BusinessEntityID, 
	YEAR(QuotaDate) AS SalesYear, 
	SUM(SalesQuota) AS CurrentQuota,   
	LAG(SUM(SalesQuota), 1,0) OVER (ORDER BY YEAR(QuotaDate)) AS PreviousQuota,
	SUM(SalesQuota) 
           - LAG(SUM(SalesQuota), 1,0) OVER (ORDER BY YEAR(QuotaDate)) AS Deviation
FROM Sales.SalesPersonQuotaHistory  
WHERE BusinessEntityID = 275 
GROUP BY BusinessEntityID, YEAR(QuotaDate);  

An diesem Beispiel lässt sich gut nachvollziehen, dass auch die Verwendung von Aggregat-Funktionen innerhalb der LAG()-Funktion keinerlei Probleme bereitet:


Abbildung 2: Verwendung von Aggregat-Funktionen innerhalb der LAG()-Funktion

Gruppierter Wertevergleich mit PARTITION_BY

Nun sollen die Jahreswerte mit dem Vergleichswert des Vorjahres für alle Mitarbeiter ermittelt werden. Dafür wird der PARTITION_BY Parameter verwendet.

SELECT 
	 BusinessEntityID, 
	 YEAR(QuotaDate) AS SalesYear, 
	 SUM(SalesQuota) AS CurrentQuota,   
	 LAG(SUM(SalesQuota), 1,0) 
	   OVER (PARTITION BY BusinessEntityID ORDER BY YEAR(QuotaDate)) AS PreviousQuota
FROM Sales.SalesPersonQuotaHistory  
GROUP BY BusinessEntityID, YEAR(QuotaDate);


Abbildung 3: Verwendung von PARTITION_BY Parameter

Fazit

Die mit SQL-Server 2012 neu eingeführten analytischen Funktionen LEAD() und LAG() lassen sich an vielen Stellen verwenden, an denen sonst eine Tabelle mit sich selbst über JOIN verbunden werden müsste. Neben der Vereinfachung bringt die Verwendung von LEAD() bzw. LAG() auch Performance-Vorteile mit sich.

In unseren Projekten lässt sich die LAG()-Funktion immer dann einsetzen, wenn wir mit Bestandsdaten aus einem Vorsystem versorgt werden und die Daten im ETL-Prozess dekumulieren müssen. Eine einfachere Variante als die Verwendung von LAG() gibt es nicht.

Auch in bestehenden Projekten sollten wir prüfen, ob Datenbankabfragen zur Dekumulation bestehen. Es wäre interessant zu wissen, wie hoch der Performance-Gewinn ist, wenn man die self-joins durch Verwendung der LAG()-Funktion ersetzt. Schneller wird es auf alle Fälle sein.