MWCodebymw.de ↗
SQL

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

#SQL#SQLite#Fensterfunktionen#Auswertung#Kennzahlen

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 →