MWCodebymw.de ↗
SQL

Wie viel mehr als gestern? Differenz zur Vorzeile mit LAG() in SQL

„Wie viele Besucher heute im Vergleich zu gestern?" – der Reflex ist ein Self-Join auf die Vorzeile. Mit der Fensterfunktion LAG() bekommst du den Wert der vorherigen Zeile direkt daneben, ohne die Tabelle ein zweites Mal anzufassen.

Für die anonyme Statistik auf meinen Seiten zähle ich pro Tag die Aufrufe. Die spannende Frage ist selten „wie viele?", sondern „wie viele mehr oder weniger als gestern?". Und da fangen viele an, die Tabelle per Self-Join mit sich selbst zu verbinden – Tag gegen Tag-minus-eins. Das geht, ist aber umständlich und langsam.

Dafür gibt es Fensterfunktionen. LAG() gibt dir den Wert aus der vorherigen Zeile einer sortierten Reihe direkt in dieselbe Zeile – kein zweiter Zugriff auf die Tabelle nötig.

Die Ausgangslage

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

INSERT INTO tagesstats (tag, aufrufe) VALUES
    ('2026-08-08', 120),
    ('2026-08-09', 138),
    ('2026-08-10',  95),
    ('2026-08-11', 210),
    ('2026-08-12', 244);

Die Differenz zum Vortag

SELECT
    tag,
    aufrufe,
    aufrufe - LAG(aufrufe) OVER (ORDER BY tag) AS diff_vortag
FROM tagesstats
ORDER BY tag;

Ergebnis:

tag         aufrufe  diff_vortag
2026-08-08  120
2026-08-09  138      18
2026-08-10  95       -43
2026-08-11  210      115
2026-08-12  244      34

Das OVER (ORDER BY tag) ist der Kern: Es sagt der Datenbank, in welcher Reihenfolge „die vorherige Zeile" überhaupt gemeint ist. LAG(aufrufe) greift dann eine Zeile zurück.

Beim ersten Tag ist diff_vortag leer – es gibt keinen Vortag, also liefert LAG() NULL. Das ist richtig so, nicht etwa 0. Willst du dort trotzdem einen Wert sehen, gibst du LAG() einen dritten Parameter als Vorgabe: LAG(aufrufe, 1, 0) nimmt 0, wenn keine Vorzeile da ist.

Als Prozent – und sauber gegen Null abgesichert

SELECT
    tag,
    aufrufe,
    ROUND(
        (aufrufe - LAG(aufrufe) OVER (ORDER BY tag)) * 100.0
        / NULLIF(LAG(aufrufe) OVER (ORDER BY tag), 0),
    1) AS prozent_vortag
FROM tagesstats
ORDER BY tag;

Das * 100.0 erzwingt Fließkomma-Division (sonst rechnet SQL ganzzahlig und rundet vorher ab). Das NULLIF(..., 0) fängt den Fall ab, dass der Vortag 0 Aufrufe hatte – ohne das gäbe es eine Division durch Null.

Mehrere Seiten getrennt: PARTITION BY

Zählst du pro Seite, darf die Differenz nicht über die Grenze einer Seite in die nächste „schwappen". PARTITION BY teilt die Zeilen in Gruppen, und LAG() bleibt innerhalb der Gruppe:

SELECT
    seite,
    tag,
    aufrufe - LAG(aufrufe) OVER (PARTITION BY seite ORDER BY tag) AS diff_vortag
FROM seitenstats
ORDER BY seite, tag;

Jede Seite fängt damit wieder mit NULL an – ihr eigener erster Tag hat keinen Vorgänger.

Das Gegenstück heißt übrigens LEAD(): Es schaut eine Zeile nach vorn statt zurück – praktisch, wenn du die Lücke bis zum nächsten Termin oder den Folgewert brauchst. LAG() und LEAD() gibt es in SQLite seit Version 3.25 (2018), in PostgreSQL und MySQL 8 ebenso.

Ich benutze das Muster ständig für die Auswertungen in meinem eigenen CMS – Trends erkennt man an der Veränderung, nicht am absoluten Wert. Wie ich Websites mit solcher schlanken, eigenen Statistik baue, siehst du auf bymw.de.

Quellen

#SQL#SQLite#Fensterfunktion#LAG#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 →