LAG und LEAD fur vorherige und nachste Werte
AI generated
SELECT
JOIN
SQL · LAG · LEAD · Window Functions
LAG und LEAD fur vorherige und nachste Werte
Nachbarzeilen lesen, ohne einen Self-Join zu schreiben

LAG und LEAD losen ein Problem, das fruher fast immer einen Self-Join erforderte: den Zugriff auf den Wert der vorherigen oder nachsten Zeile innerhalb einer sortierten Reihenfolge. Ob Month-over-Month-Vergleiche, Trendanalysen oder die Erkennung von Lucken in einer Zeitreihe, dieser Artikel zeigt die Syntax, die Offset- und Default-Parameter und die praktischen Anwendungsfalle von LAG und LEAD im Detail.

13 Min. Lesezeit LAG · LEAD · PARTITION BY · Offset ANSI SQL · PostgreSQL · MySQL 8+ · SQL Server

1. Das Problem: Zugriff auf Nachbarzeilen ohne Self-Join

Der Name LAG steht dabei stellvertretend fur die gesamte Klasse dieser Probleme: Immer wenn eine Berechnung den Kontext einer benachbarten Zeile benotigt, ist eine Offset-Funktion in der Regel die direkteste und wartbarste Losung, lange bevor an einen Self-Join oder eine prozedurale Schleife gedacht werden sollte.

Viele Auswertungen benotigen nicht nur den Wert der aktuellen Zeile, sondern auch den Wert der direkt vorhergehenden oder nachfolgenden Zeile innerhalb einer sortierten Reihenfolge. Klassische Beispiele sind der Vergleich des aktuellen Monatsumsatzes mit dem Vormonat, die Berechnung der Differenz zwischen zwei aufeinanderfolgenden Sensormesswerten, oder die Erkennung von Lucken in einer fortlaufenden Nummerierung. Vor der Einfuhrung von LAG und LEAD als Window Functions musste jedes dieser Probleme mit einem Self-Join gelost werden, bei dem eine Tabelle mit sich selbst verknupft wird, wobei die Verknupfungsbedingung die gewunschte Zeilenverschiebung nachbildet.

Ein Self-Join fur diesen Zweck ist fehleranfallig, schwer lesbar und bei grosseren Tabellen oft langsam, weil er die Tabelle effektiv verdoppelt und eine zusatzliche Join-Bedingung auswerten muss, die selbst bei einem passenden Index nicht immer effizient ist. LAG und LEAD losen dieses Problem elegant direkt innerhalb einer einzigen SELECT-Klausel, ohne Join, ohne Subquery und ohne die Notwendigkeit, die Tabelle ein zweites Mal zu referenzieren.

Beide Funktionen gehoren zur Gruppe der sogenannten Offset-Funktionen innerhalb der Window Functions, die neben LAG und LEAD auch FIRST_VALUE, LAST_VALUE und NTH_VALUE umfasst. Alle Offset-Funktionen haben gemeinsam, dass sie sich auf eine andere Zeile innerhalb desselben Fensters beziehen, statt nur auf die aktuelle Zeile oder eine Aggregation uber mehrere Zeilen.

2. LAG: Zugriff auf die vorherige Zeile

Wichtig ist, dass LAG sich strikt auf die durch ORDER BY definierte logische Reihenfolge bezieht, nicht auf die physische Speicherreihenfolge der Zeilen in der Tabelle. Zwei identische Abfragen mit unterschiedlichem ORDER BY innerhalb der OVER-Klausel liefern deshalb vollig unterschiedliche Ergebnisse fur dieselbe LAG-Berechnung, weil sich die Definition von "vorherige Zeile" jeweils andert.

LAG(spalte) OVER (ORDER BY sortierspalte) liefert fur jede Zeile den Wert der Spalte aus der Zeile, die in der definierten Reihenfolge unmittelbar vor der aktuellen Zeile steht. Fur die allererste Zeile innerhalb der Partition gibt es naturgemass keine vorherige Zeile, weshalb LAG dort standardmassig NULL zuruckgibt, sofern kein expliziter Default-Wert angegeben wurde. Der Name LAG kommt aus dem Englischen fur Verzogerung oder Nachlaufen und beschreibt treffend, dass der zuruckgegebene Wert der aktuellen Zeile "hinterherhinkt".

Die praktische Bedeutung von LAG liegt darin, Veranderungen zwischen aufeinanderfolgenden Zeilen zu berechnen, ohne die Tabelle zu duplizieren. Ein typisches Muster ist umsatz - LAG(umsatz) OVER (ORDER BY monat), das die absolute Veranderung des Umsatzes gegenuber dem Vormonat direkt in derselben Abfrage berechnet. Diese Art von Delta-Berechnung ist eine der haufigsten Anwendungen von LAG in Reporting- und Analytics-Abfragen.


-- LAG: access the value from the previous row, no self-join needed
SELECT
    month,
    revenue,
    LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue,
    revenue - LAG(revenue) OVER (ORDER BY month) AS revenue_change
FROM monthly_revenue
ORDER BY month;

-- Result
-- month      | revenue | prev_month_revenue | revenue_change
-- 2026-04    |   45000 |                NULL |            NULL
-- 2026-05    |   48000 |               45000 |           3000
-- 2026-06    |   46500 |               48000 |          -1500
-- 2026-07    |   51000 |               46500 |           4500

3. LEAD: Zugriff auf die nachste Zeile

Ein haufiges Missverstandnis bei LEAD ist die Annahme, es konne zukunftige, noch nicht eingetretene Ereignisse vorhersagen. Tatsachlich greift LEAD ausschliesslich auf bereits in der Ergebnismenge vorhandene Zeilen zu, die lediglich in der definierten Sortierreihenfolge nach der aktuellen Zeile kommen, nicht auf tatsachlich zukunftige, noch nicht existierende Daten.

LEAD(spalte) OVER (ORDER BY sortierspalte) ist das direkte Gegenstuck zu LAG und liefert fur jede Zeile den Wert der Spalte aus der Zeile, die unmittelbar nach der aktuellen Zeile in der definierten Reihenfolge folgt. Fur die allerletzte Zeile innerhalb der Partition existiert naturgemass keine nachste Zeile, weshalb LEAD dort ebenfalls standardmassig NULL zuruckgibt. Der Name LEAD beschreibt, dass der zuruckgegebene Wert der aktuellen Zeile "vorauseilt".

LEAD wird typischerweise eingesetzt, um vorwarts gerichtete Vergleiche zu berechnen, etwa wie lange ein Kunde bis zu seiner nachsten Bestellung braucht, oder ob ein Ereignis von einem anderen innerhalb eines bestimmten Zeitraums gefolgt wird. Ein haufiges Muster ist die Berechnung der Zeitdifferenz zwischen zwei aufeinanderfolgenden Ereignissen mit LEAD(zeitstempel) OVER (...) - zeitstempel, was zum Beispiel die Verweildauer zwischen zwei Sitzungen oder die Zeit bis zur nachsten Wartung eines Gerats liefert.


-- LEAD: access the value from the next row
SELECT
    session_id,
    started_at,
    LEAD(started_at) OVER (ORDER BY started_at) AS next_session_start,
    LEAD(started_at) OVER (ORDER BY started_at) - started_at AS gap_to_next
FROM user_sessions
ORDER BY started_at;

4. Offset-Parameter: mehr als eine Zeile zuruck oder vor

Ein negativer Offset-Wert ist bei den meisten Datenbanksystemen nicht zulassig und fuhrt zu einem Syntaxfehler. Wer stattdessen die entgegengesetzte Richtung benotigt, verwendet schlicht LEAD anstelle von LAG mit demselben positiven Offset, statt zu versuchen, die Blickrichtung uber ein negatives Vorzeichen umzukehren.

Sowohl LAG als auch LEAD akzeptieren einen optionalen zweiten Parameter, den Offset, der angibt, um wie viele Zeilen zuruck oder vor die Funktion greifen soll. Der Standardwert ist 1, was der direkt vorherigen beziehungsweise nachsten Zeile entspricht. Mit LAG(umsatz, 12) OVER (ORDER BY monat) lasst sich beispielsweise der Umsatz vor genau zwolf Monaten abrufen, was einen Year-over-Year-Vergleich innerhalb einer einzigen Spalte ermoglicht, ohne einen zweiten Join oder eine zweite Abfrage.

Dieser Offset-Parameter macht LAG und LEAD deutlich flexibler als ein simpler Self-Join mit einer festen Verschiebung, weil sich der Offset als Variable oder Parameter der Abfrage gestalten lasst. Ein Offset von 3 bei LEAD liefert den Wert drei Zeilen weiter vorn, ein Offset von 2 bei LAG den Wert zwei Zeilen zuruck. Diese Parametrisierbarkeit ist besonders wertvoll fur Analysen, die verschiedene Zeithorizonte gleichzeitig vergleichen mochten, etwa Vormonat, Vorquartal und Vorjahr in derselben Abfrage.


-- Offset parameter: compare to 12 months ago (year-over-year)
SELECT
    month,
    revenue,
    LAG(revenue, 12) OVER (ORDER BY month) AS revenue_year_ago,
    ROUND(
        100.0 * (revenue - LAG(revenue, 12) OVER (ORDER BY month))
        / NULLIF(LAG(revenue, 12) OVER (ORDER BY month), 0),
        2
    ) AS yoy_growth_pct
FROM monthly_revenue
ORDER BY month;

5. Default-Werte fur Randzeilen

Der Default-Wert-Parameter kann auch als Ausdruck angegeben werden, nicht nur als Konstante. LAG(betrag, 1, betrag) OVER (...) etwa gibt fur die erste Zeile den eigenen Betrag zuruck, was eine Differenzberechnung fur die erste Zeile automatisch auf null setzt, ohne eine separate CASE-Abfrage fur diesen Sonderfall zu benotigen.

Sowohl LAG als auch LEAD akzeptieren einen optionalen dritten Parameter, der als Ruckgabewert dient, wenn keine vorherige oder nachste Zeile existiert, statt des Standardverhaltens NULL zuruckzugeben. LAG(umsatz, 1, 0) OVER (ORDER BY monat) liefert fur die allererste Zeile eine 0 statt NULL, was in nachfolgenden Berechnungen wie Differenzen oder Prozentsatzen oft praktischer ist, weil NULL-Werte sich in arithmetischen Ausdrucken propagieren und das gesamte Ergebnis zu NULL machen konnen.

Die Wahl zwischen NULL und einem expliziten Default-Wert ist eine fachliche Entscheidung, keine rein technische. NULL signalisiert korrekt, dass fur diese Zeile schlicht keine Vergleichsbasis existiert, was fur eine korrekte Datenreprasentation oft der bessere Standard ist. Ein Default-Wert wie 0 kann dagegen fachlich irrefuhrend sein, wenn er suggeriert, der Vormonat hatte tatsachlich einen Umsatz von null gehabt, statt schlicht nicht zu existieren. Diese Entscheidung sollte bewusst und nicht aus Bequemlichkeit getroffen werden.


-- Default value instead of NULL for the first row
SELECT
    month,
    revenue,
    LAG(revenue, 1, 0) OVER (ORDER BY month) AS prev_month_revenue_or_zero,
    revenue - LAG(revenue, 1, 0) OVER (ORDER BY month) AS revenue_change
FROM monthly_revenue
ORDER BY month;

6. Month-over-Month-Vergleiche mit LAG

In Dashboards wird die prozentuale Veranderung haufig zusatzlich farblich kodiert, etwa grun fur einen Anstieg und rot fur einen Ruckgang. Diese visuelle Aufbereitung geschieht typischerweise in der Anwendungsschicht, doch die zugrundeliegende Berechnung mit LAG bleibt vollstandig in der Datenbank, was die Konsistenz der Kennzahl uber verschiedene Frontends hinweg sicherstellt.

Der Month-over-Month-Vergleich, also der Vergleich einer Kennzahl mit dem direkten Vormonat, ist eine der haufigsten Anwendungen von LAG in Business-Reporting-Systemen uberhaupt. Neben der reinen absoluten Differenz ist die prozentuale Veranderung meist die relevantere Kennzahl, weil sie unabhangig von der absoluten Grossenordnung vergleichbar bleibt. Die Formel dafur lautet (aktueller_wert - vorheriger_wert) / vorheriger_wert, wobei NULLIF eingesetzt werden sollte, um eine Division durch null zu vermeiden, falls der Vormonatswert null war.

Ein oft ubersehener Aspekt bei Month-over-Month-Berechnungen ist die korrekte Behandlung von Lucken in den Daten. Wenn fur einen Monat gar keine Zeile existiert, etwa weil in diesem Monat keine Umsatze verbucht wurden, liefert LAG den Wert des zeitlich nachsten vorhandenen Monats, nicht zwingend den unmittelbar vorherigen Kalendermonat. Wer garantiert lucklose Kalendermonate vergleichen mochte, muss die Ausgangsdaten vorab mit einer generierten Kalendertabelle auffullen, bevor LAG angewendet wird, da LAG selbst keine Kenntnis von Kalenderlucken hat.

7. LAG und LEAD kombiniert mit PARTITION BY

Fehlt PARTITION BY in einer solchen Situation, entstehen an den Ubergangen zwischen zwei Gruppen falsche Vergleiche, weil die letzte Zeile einer Gruppe faelschlich mit der ersten Zeile der naechsten Gruppe verglichen wird.

Wie die meisten Window Functions entfalten LAG und LEAD ihre volle Praxistauglichkeit erst in Kombination mit PARTITION BY, das den Vorher-Nachher-Vergleich auf einzelne Gruppen beschrankt. Ohne PARTITION BY wurde LAG in einer Abfrage mit mehreren Regionen den Vormonatswert der zeitlich vorherigen Zeile liefern, unabhangig davon, ob diese Zeile zur selben Region gehort. Mit PARTITION BY region ORDER BY monat bleibt der Vergleich sauber innerhalb jeder Region, und die erste Zeile jeder Region liefert wieder NULL oder den definierten Default-Wert, weil PARTITION BY die Gruppen strikt voneinander trennt.

Dieses Muster aus PARTITION BY plus LAG oder LEAD ist die Standardlosung fur alle Vorher-Nachher-Vergleiche, die pro Kunde, pro Produkt oder pro Region getrennt berechnet werden sollen. Es ersetzt vollstandig die fruher notwendige Kombination aus Self-Join mit zusatzlicher Bedingung auf die Gruppenzugehorigkeit, die zusatzlich zur zeitlichen Verschiebung auch noch sicherstellen musste, dass beide verknupften Zeilen zur selben Gruppe gehoren.

Aspekt Self-Join LAG / LEAD
Anzahl Tabellenreferenzen Zwei, mit Alias Eine
Lesbarkeit Join-Bedingung fur Verschiebung notig Ein Funktionsaufruf
Mehrere Offsets gleichzeitig Ein Join pro Offset Ein Aufruf pro Offset, kein Join
Performance bei grossen Tabellen Tabelle wird effektiv verdoppelt Ein sortierter Durchlauf

Ein oft ubersehener Vorteil von PARTITION BY in Kombination mit LAG und LEAD ist die Moglichkeit, mehrere unabhangige Vorher-Nachher-Vergleiche in derselben Abfrage zu berechnen, ohne dass sich die Gruppen gegenseitig beeinflussen. Eine Abfrage kann gleichzeitig LAG(umsatz) OVER (PARTITION BY region ORDER BY monat) und LAG(anzahl_bestellungen) OVER (PARTITION BY region ORDER BY monat) enthalten, wobei beide Funktionen dieselbe Partitionierung nutzen, aber unterschiedliche Spalten referenzieren. Diese Flexibilitat erlaubt es, mehrdimensionale Vorher-Nachher-Analysen in einer einzigen, gut wartbaren Abfrage abzubilden.

8. Self-Join vs. LAG/LEAD: Performance und Lesbarkeit

Der Performance-Unterschied zwischen einem Self-Join und LAG oder LEAD ist bei grosseren Tabellen erheblich. Ein Self-Join fur einen Vorher-Nachher-Vergleich verbindet die Tabelle effektiv mit sich selbst, was die Zahl der zu verarbeitenden Zeilenkombinationen vervielfacht, insbesondere wenn die Join-Bedingung nicht perfekt selektiv ist oder kein optimaler Index vorliegt. LAG und LEAD dagegen sortieren die Daten einmal und lesen sie in einem einzigen Durchlauf, wobei die Datenbank intern lediglich einen Zeiger auf die vorherige beziehungsweise nachste Zeile mitfuhrt.

Neben der reinen Performance ist die Lesbarkeit ein unterschatzter Vorteil von LAG und LEAD. Ein Self-Join mit einer Verschiebungsbedingung wie ON a.rang = b.rang + 1 ist fur andere Entwickler nicht sofort verstandlich und erfordert oft Kommentare, um die Absicht zu erklaren. LAG(spalte) OVER (ORDER BY sortierspalte) hingegen kommuniziert die Absicht direkt im Funktionsnamen und ist selbst fur SQL-Einsteiger nach kurzer Erklarung intuitiv nachvollziehbar. Dieser Lesbarkeitsvorteil reduziert Wartungsaufwand und Fehlerquote bei zukunftigen Anderungen an der Abfrage.

Mironsoft

SQL-Optimierung, Datenbankdesign und Reporting-Abfragen

Self-Joins fur Vorher-Nachher-Vergleiche, die langsam geworden sind?

Wir ersetzen bestehende Self-Join-Konstruktionen fur Month-over-Month- und Trendauswertungen durch saubere LAG- und LEAD-Abfragen und sichern das Ergebnis mit vollstandigen Regressionstests ab.

Query-Review

Analyse bestehender Self-Join-Abfragen auf Vereinfachungspotenzial

Migration

Umstellung auf LAG und LEAD mit dokumentiertem Vorher-Nachher-Vergleich

Schulung

Team-Workshop zu Offset-Funktionen und Window Functions

9. Erweiterte Szenarien: Trendanalyse mit mehreren Offsets

Solche erweiterten Trendklassifikationen lassen sich zusatzlich mit COUNT() OVER kombinieren, um beispielsweise zu zahlen, wie viele aufeinanderfolgende Perioden ein Trend bereits andauert, was fur Alarmierungslogik in Monitoring-Systemen und automatisierte Reporting-Kommentare nutzlich ist.

LAG und LEAD lassen sich in ein und derselben Abfrage mehrfach mit unterschiedlichen Offset-Werten kombinieren, um komplexere Trendanalysen zu bauen. Eine Abfrage kann gleichzeitig den Vormonat mit Offset 1, das Vorquartal mit Offset 3 und das Vorjahr mit Offset 12 abrufen, alles innerhalb desselben SELECT ohne zusatzliche Joins. Diese Technik eignet sich hervorragend fur Dashboards, die mehrere Zeithorizonte gleichzeitig gegenuberstellen mochten, etwa in einem Finanzreporting mit Monats-, Quartals- und Jahresvergleich in derselben Zeile.

Eine weitere fortgeschrittene Anwendung ist die Kombination von LAG mit einer bedingten Auswertung, um Trendrichtungen zu klassifizieren: CASE WHEN umsatz > LAG(umsatz) OVER (...) THEN 'steigend' WHEN umsatz < LAG(umsatz) OVER (...) THEN 'fallend' ELSE 'stabil' END erzeugt eine lesbare Textklassifikation der Entwicklung direkt in der Abfrage. Auch die Erkennung von Lucken in fortlaufenden Sequenzen, etwa fehlenden Rechnungsnummern, lasst sich mit LEAD(nummer) OVER (ORDER BY nummer) - nummer > 1 als Bedingung elegant losen, ohne eine externe Referenztabelle mit erwarteten Werten zu benotigen.

Ein letzter erwahnenswerter Aspekt ist die Kombination von LAG oder LEAD mit FIRST_VALUE und LAST_VALUE, den beiden weiteren Offset-Funktionen innerhalb einer Window Function. Wahrend LAG und LEAD relativ zur aktuellen Zeile arbeiten, liefern FIRST_VALUE und LAST_VALUE den Wert der ersten beziehungsweise letzten Zeile innerhalb des definierten Fensters, unabhangig von der Position der aktuellen Zeile. In Kombination lassen sich damit Analysen bauen, die sowohl den unmittelbaren Vorwert als auch den Ausgangswert einer gesamten Zeitreihe in derselben Abfrage gegenuberstellen, etwa um die Gesamtentwicklung seit Beginn der Messreihe neben der Veranderung zum Vormonat darzustellen.


-- Trend classification and gap detection in one query
SELECT
    invoice_number,
    invoice_date,
    CASE
        WHEN amount > LAG(amount) OVER (ORDER BY invoice_date) THEN 'rising'
        WHEN amount < LAG(amount) OVER (ORDER BY invoice_date) THEN 'falling'
        ELSE 'stable'
    END AS trend,
    LEAD(invoice_number) OVER (ORDER BY invoice_number) - invoice_number > 1 AS has_gap_after
FROM invoices
ORDER BY invoice_date;

10. Zusammenfassung

LAG und LEAD losen den Zugriff auf vorherige und nachste Zeilenwerte direkt innerhalb einer Window Function, ohne den Umweg uber einen Self-Join. LAG greift auf die vorhergehende Zeile zu, LEAD auf die nachfolgende, beide mit einem konfigurierbaren Offset-Parameter fur Verschiebungen um mehr als eine Zeile und einem optionalen Default-Wert fur Randzeilen ohne Vergleichsbasis. In Kombination mit PARTITION BY funktionieren beide Funktionen sauber getrennt pro Gruppe, was sie zum Standardwerkzeug fur Month-over-Month-Vergleiche, Trendanalysen und Lucken-Erkennung macht.

Gegenuber der klassischen Self-Join-Losung bieten LAG und LEAD deutliche Vorteile bei Performance und Lesbarkeit, weil die Datenbank die Daten nur einmal sortieren und in einem Durchlauf verarbeiten muss, statt eine Tabelle effektiv zu verdoppeln. Wer Vorher-Nachher-Vergleiche in SQL bislang mit Self-Joins gelost hat, sollte diese Abfragen auf LAG und LEAD umstellen, sobald die eingesetzte Datenbankversion Window Functions unterstutzt.

LAG und LEAD: das Wichtigste auf einen Blick

LAG und LEAD

LAG liefert die vorherige, LEAD die nachste Zeile innerhalb der definierten Reihenfolge.

Offset-Parameter

Zweiter Parameter steuert, wie viele Zeilen zuruck oder vor gegriffen wird, Standard ist 1.

Default-Werte

Dritter Parameter ersetzt NULL bei fehlender Vorher- oder Nachherzeile durch einen definierten Wert.

Kein Self-Join notig

Ein sortierter Durchlauf statt Tabellenverdopplung, deutlich schneller und lesbarer.

11. FAQ: LAG und LEAD in SQL

1Unterschied zwischen LAG und LEAD?
LAG liefert die vorherige Zeile, LEAD die naechste Zeile, beide bezogen auf dieselbe Reihenfolge.
2Was liefert LAG bei der ersten Zeile?
Standardmassig NULL, per drittem Parameter ist ein expliziter Default-Wert moeglich.
3Zugriff auf zwei Zeilen zurueck?
Offset-Parameter nutzen: LAG(spalte, 2), Standardwert ist 1.
4Warum falscher Kalendermonat bei Luecken?
LAG bezieht sich auf die vorhandene Vorzeile, nicht den Kalendermonat. Kalendertabelle hilft.
5Year-over-Year-Vergleich?
LAG(umsatz, 12) OVER (ORDER BY monat) bei monatlichen Daten fuer den Wert vor zwoelf Monaten.
6Warum schneller als Self-Join?
Ein sortierter Durchlauf statt Tabellenverdopplung durch den Join.
7Funktionieren sie mit PARTITION BY?
Ja, haeufigster Anwendungsfall fuer Vergleiche pro Kunde, Region oder anderer Gruppe.
8Beide gleichzeitig in einer Abfrage?
Ja, beliebig oft mit unterschiedlichen Offsets fuer verschiedene Zeithorizonte.
9Luecken in Nummerierung erkennen?
LEAD(nummer) OVER (ORDER BY nummer) - nummer > 1 zeigt eine Luecke in der Sequenz an.
10Teil des SQL-Standards?
Ja, seit SQL:2003, unterstuetzt von PostgreSQL, MySQL 8+, SQL Server, Oracle und SQLite ab 3.25.