MWCodebymw.de ↗
SQL

Vergleich zur Vorzeile mit LAG() – wie stark hat sich ein Wert verändert

„Wie viele Besucher mehr als gestern?" ist in SQL erstaunlich fummelig, wenn man es mit einem Self-Join löst. Mit der Fensterfunktion LAG() greifst du direkt auf die vorherige Zeile zu – und rechnest die Differenz in einer einzigen, lesbaren Abfrage.

Eine Frage, die in fast jeder Auswertung auftaucht: „Wie viel mehr oder weniger als gestern?" Für das eigene CMS meiner Website will ich zum Beispiel sehen, wie sich die Seitenaufrufe von Tag zu Tag verändern. Der naive Weg ist ein Self-Join – die Tabelle mit sich selbst verbinden, versetzt um einen Tag. Das funktioniert, liest sich aber schnell wie ein Rätsel, und bei Lücken im Datum wird es richtig unangenehm.

Es geht viel direkter. Die Fensterfunktion LAG() holt dir den Wert aus der vorherigen Zeile – ohne Join, ohne Unterabfrage.

Die Ausgangslage

CREATE TABLE aufrufe (
    tag       TEXT PRIMARY KEY,   -- 'YYYY-MM-DD'
    besucher  INTEGER NOT NULL
);

INSERT INTO aufrufe (tag, besucher) VALUES
    ('2026-08-07', 120),
    ('2026-08-08', 138),
    ('2026-08-09',  96),
    ('2026-08-10', 145),
    ('2026-08-11', 190);

LAG() – ein Blick nach oben

SELECT
    tag,
    besucher,
    LAG(besucher) OVER (ORDER BY tag) AS gestern,
    besucher - LAG(besucher) OVER (ORDER BY tag) AS differenz
FROM aufrufe
ORDER BY tag;

Ergebnis:

tag         besucher  gestern  differenz
2026-08-07  120       NULL     NULL
2026-08-08  138       120       18
2026-08-09   96       138      -42
2026-08-10  145        96       49
2026-08-11  190       145       45

Das OVER (ORDER BY tag) ist der ganze Trick: Es sagt SQLite, in welcher Reihenfolge die Zeilen liegen. LAG(besucher) greift dann in genau dieser Reihenfolge auf die Zeile davor zu. Die erste Zeile hat keinen Vorgänger, also steht dort NULL – das ist richtig so und kein Fehler.

Prozentuale Veränderung – und der NULL-Fallstrick

Für „plus 31 %" statt „plus 45" teilst du die Differenz durch den Vorwert. Zwei Dinge sind dabei wichtig: LAG() einmal in einen Namen legen (mit einem CTE spart man sich die Wiederholung), und die Division gegen NULL und 0 absichern.

WITH mit_vortag AS (
    SELECT
        tag,
        besucher,
        LAG(besucher) OVER (ORDER BY tag) AS gestern
    FROM aufrufe
)
SELECT
    tag,
    besucher,
    gestern,
    ROUND(
        (besucher - gestern) * 100.0 / NULLIF(gestern, 0),
        1
    ) AS prozent
FROM mit_vortag
ORDER BY tag;

NULLIF(gestern, 0) macht aus einer 0 ein NULL – und eine Division durch NULL ergibt in SQL NULL statt eines Fehlers. Für die erste Zeile (ohne Vortag) bleibt prozent ebenfalls NULL. Das * 100.0 erzwingt Fließkomma, sonst schneidet die Ganzzahl-Division alles hinter dem Komma ab.

Nach vorn schauen: LEAD()

Das Gegenstück heißt LEAD() und holt die nächste Zeile – praktisch, wenn du die Dauer bis zum nächsten Ereignis brauchst:

SELECT
    tag,
    LEAD(tag) OVER (ORDER BY tag) AS naechster_tag
FROM aufrufe
ORDER BY tag;

Getrennt je Gruppe: PARTITION BY

Hast du mehrere Seiten in einer Tabelle, soll der Vergleich natürlich innerhalb einer Seite bleiben und nicht über die Grenze zur nächsten springen. Dafür ist PARTITION BY da:

LAG(besucher) OVER (PARTITION BY seite ORDER BY tag)

Damit fängt die „Vorzeile" bei jeder neuen Seite wieder von vorn an. Genau das würde ein Self-Join nur mit einer zusätzlichen Bedingung hinbekommen – und wäre wieder ein Stück schwerer zu lesen.

LAG() und LEAD() gibt es in SQLite seit Version 3.25.0 (2018), also überall, wo du heute SQLite einsetzt – und genauso in PostgreSQL und MySQL. Ich nutze das im CMS hinter bymw.de für die kleine Statistik, die mir zeigt, ob ein neuer Blog-Beitrag wirklich mehr Leute bringt. Wenn du so eine Auswertung für deine eigene Seite brauchst, melde dich gern.

Quellen

#LAG#LEAD#Fensterfunktionen#SQLite#Analytics

Du brauchst mehr als ein Snippet?

Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.

Projekt anfragen →