Laufende Summe in SQL – SUM() OVER statt Schleife in der Anwendung
Kontostand nach jeder Buchung, kumulierter Umsatz im Jahr, Restbestand nach jeder Entnahme: Alles dieselbe Frage. Mit einer Fensterfunktion beantwortet die Datenbank sie in einer Abfrage – inklusive gleitendem Durchschnitt.
Die Buchungsliste zeigt Beträge, aber der Kunde will den Stand nach jeder Zeile sehen. Der übliche Weg – alle Zeilen holen und in PHP mitzählen – funktioniert, bis jemand sortiert, filtert oder blättert. Dann stimmt die Summe nicht mehr, weil sie zur Sortierung im Code gehört und nicht zu den Daten.
Die laufende Summe
SELECT
gebucht,
betrag,
SUM(betrag) OVER (ORDER BY gebucht, id) AS stand
FROM buchungen
ORDER BY gebucht, id;OVER (ORDER BY …) sagt: Summiere alle Zeilen bis einschließlich dieser. Das id als zweites Sortierkriterium ist wichtig – bei zwei Buchungen zur selben Sekunde wäre die Reihenfolge sonst zufällig und die Summe je Aufruf anders.
Je Gruppe von vorn
SELECT
konto_id,
gebucht,
betrag,
SUM(betrag) OVER (PARTITION BY konto_id ORDER BY gebucht, id) AS kontostand
FROM buchungen
ORDER BY konto_id, gebucht, id;PARTITION BY setzt die Summe bei jedem neuen Konto zurück – ein Ergebnis, viele Konten, keine Schleife.
Der Rahmen: was „bis hierher" genau heißt
Steht ein ORDER BY im Fenster, gilt stillschweigend der Rahmen RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Für einen gleitenden Durchschnitt über sieben Tage schreibst du ihn selbst hin:
SELECT
tag,
umsatz,
ROUND(AVG(umsatz) OVER (
ORDER BY tag
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
), 2) AS schnitt_7_tage
FROM tagesumsatz
ORDER BY tag;ROWS zählt Zeilen, RANGE rechnet mit Werten. Bei Datumsreihen mit Lücken ist der Unterschied entscheidend: ROWS 6 PRECEDING nimmt die sechs vorhandenen Zeilen, auch wenn dazwischen Tage fehlen.
Anteil am Gesamtergebnis
SELECT
kategorie,
SUM(betrag) AS summe,
ROUND(100.0 * SUM(betrag) / SUM(SUM(betrag)) OVER (), 1) AS anteil_prozent
FROM posten
GROUP BY kategorie
ORDER BY summe DESC;Das doppelte SUM liest sich seltsam und ist doch genau richtig: Innen die Gruppensumme, außen das Fenster über alle Gruppen (leeres OVER ()). Damit steht der Prozentanteil ohne zweite Abfrage in derselben Zeile.
Fallstrick
Fensterfunktionen laufen nach WHERE und GROUP BY, aber vor ORDER BY und LIMIT. Zwei Folgen: Du kannst sie nicht im WHERE filtern (dafür brauchst du eine CTE außenrum), und ein LIMIT 20 schneidet zwar die Ausgabe ab, ändert aber die berechneten Summen nicht – bei Seitenweise-Anzeige wird die laufende Summe über den vollen Datensatz gerechnet, was meistens genau gewünscht ist. Fensterfunktionen gibt es in SQLite ab 3.25.
Auswertungen, die in der Datenbank stattfinden statt in der Anwendung, sind schneller und stimmen öfter. Wenn du Zahlen aus deinen Daten brauchst: bymw.de.
Quellen
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 →Verwandte Snippets
NULL in SQL – warum COUNT(spalte) lügt und wo die leeren Werte landen
NULL ist kein Wert, sondern die Abwesenheit eines Werts. Deshalb zählt COUNT(spalte) anders als COUNT(*), summiert SUM() an leeren Zeilen vorbei und landen beim Sortieren alle Lücken am selben Ende.
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.
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.