Running Totals mit Window Functions berechnen
AI generated
SELECT
JOIN
SQL · Running Total · SUM OVER · Window Functions
Running Totals mit Window Functions berechnen
Kumulative Summen ohne korrelierte Subquery

Ein Running Total, also eine kumulative Summe, die mit jeder Zeile wachst, gehort zu den haufigsten Anforderungen in Kontoauszugen, Umsatzberichten und Bestandsverwaltungen. Mit SUM() OVER lasst sich diese Berechnung direkt in SQL losen, ohne den langsamen Umweg uber eine korrelierte Subquery. Dieser Artikel zeigt die Syntax, den Reset pro Gruppe und den konkreten Performance-Unterschied.

13 Min. Lesezeit SUM() OVER · PARTITION BY · ROWS BETWEEN ANSI SQL · PostgreSQL · MySQL 8+ · SQL Server

1. Was ein Running Total ist und wo es gebraucht wird

Ein Running Total ist eine kumulative Summe, die fur jede Zeile einer sortierten Ergebnismenge die Summe aller Werte von der ersten Zeile bis zur aktuellen Zeile enthalt. Anders als eine einfache Gesamtsumme, die auf jeder Zeile denselben Wert zeigt, wachst ein Running Total mit jeder weiteren Zeile. Klassische Anwendungsfalle sind Kontoauszuge, bei denen jede Buchung den aktuellen Kontostand nach dieser Buchung zeigt, kumulative Umsatzberichte pro Monat, oder die laufende Bestandsentwicklung eines Lagers uber die Zeit.

Vor der Einfuhrung von Window Functions in relationalen Datenbanken war die Berechnung eines Running Totals in reinem SQL umstandlich und meist auf korrelierte Subqueries oder prozedurale Cursor-Logik angewiesen. Beide Ansatze sind fehleranfallig, schwer lesbar und bei grosseren Datenmengen langsam. Mit SUM() OVER lasst sich ein Running Total heute in wenigen Zeilen lesbarem SQL berechnen, ohne die Datenbank mit ineffizienten wiederholten Teilberechnungen zu belasten.

Der Begriff Running Total wird oft synonym mit kumulativer Summe verwendet, und beide Begriffe meinen dasselbe Konzept: eine fortlaufend akkumulierende Berechnung uber eine definierte Reihenfolge von Zeilen. Wichtig fur ein korrektes Running Total ist immer eine eindeutige, stabile Sortierreihenfolge, denn ohne sie ist nicht klar, was mit "bis zur aktuellen Zeile" uberhaupt gemeint ist.

2. SUM() OVER fur kumulative Summen: Grundsyntax

Die Grundsyntax fur ein Running Total ist denkbar einfach: SUM(spalte) OVER (ORDER BY sortierspalte). Die Aggregatfunktion SUM wird durch OVER zu einer Window Function, und das ORDER BY innerhalb der Klammern definiert die Reihenfolge, in der die Werte akkumuliert werden. Wichtig ist, dass dieses ORDER BY sich von einem eventuellen abschliessenden ORDER BY der gesamten Abfrage unterscheidet, auch wenn beide oft dieselbe Spalte verwenden.

Sobald ORDER BY innerhalb von OVER angegeben wird, andert sich automatisch das implizite Fensterverhalten: Statt der gesamten Ergebnismenge wird nur der Bereich von der ersten Zeile bis zur aktuellen Zeile fur die Summe herangezogen. Das ist genau das Verhalten, das ein Running Total ausmacht, und es geschieht ohne zusatzliche Frame-Angabe automatisch, sobald ein ORDER BY vorhanden ist.


-- Basic running total: cumulative sum of transactions by date
SELECT
    transaction_date,
    amount,
    SUM(amount) OVER (ORDER BY transaction_date) AS running_balance
FROM transactions
ORDER BY transaction_date;

-- Result
-- transaction_date | amount | running_balance
-- 2026-07-01       |    500 |             500
-- 2026-07-03       |    250 |             750
-- 2026-07-05       |   -100 |             650
-- 2026-07-08       |    400 |            1050

3. Der Standard-Frame: RANGE UNBOUNDED PRECEDING

Hinter dem scheinbar einfachen Verhalten von SUM() OVER (ORDER BY ...) steckt eine implizite Frame-Definition, die man kennen sollte, um Running Totals wirklich zu verstehen. Sobald ORDER BY innerhalb der OVER-Klausel steht, aber kein expliziter Frame angegeben wird, greift standardmassig RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. UNBOUNDED PRECEDING bedeutet "vom Anfang der Partition an", CURRENT ROW bedeutet "bis einschliesslich der aktuellen Zeile". Genau dieser Bereich wird fur die Summenberechnung herangezogen.

Der Standard-Frame verwendet RANGE statt ROWS, was bei Werten mit Gleichstand im ORDER BY zu einem Verhalten fuhren kann, das man nicht sofort erwartet: Alle Zeilen mit demselben Sortierwert erhalten denselben Running-Total-Wert, namlich die Summe bis einschliesslich aller gleichwertigen Zeilen, nicht nur bis zur eigenen Zeile. Wer ein garantiert zeilenweises Verhalten mochte, auch bei Gleichstand im Sortierwert, sollte den Frame explizit auf ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW setzen, um dieses RANGE-Verhalten zu umgehen.

4. Running Totals pro Gruppe mit PARTITION BY zurucksetzen

In der Praxis wird ein Running Total selten global uber die gesamte Tabelle benotigt, sondern meist pro Kunde, pro Konto oder pro Kategorie. Mit PARTITION BY innerhalb derselben OVER-Klausel lasst sich dieses Verhalten exakt abbilden: SUM(betrag) OVER (PARTITION BY kunde_id ORDER BY buchungsdatum) berechnet fur jeden Kunden eine eigene, unabhangige laufende Summe, die bei jedem neuen Kunden automatisch wieder bei null beginnt.

Dieses automatische Zurucksetzen ist keine zusatzliche Logik, die explizit programmiert werden muss, sondern eine direkte Konsequenz davon, dass PARTITION BY jede Gruppe als eigenstandige Berechnungseinheit fur die Window Function behandelt. Vor Window Functions musste dieses Zurucksetzen mit einer WHERE-Bedingung in einer korrelierten Subquery nachgebildet werden, die zusatzlich zur Sortierbedingung auch die Kundenzugehorigkeit prufen musste, was die Abfrage deutlich komplexer und langsamer machte.


-- Running total per customer, resets automatically for each new customer
SELECT
    customer_id,
    order_date,
    order_amount,
    SUM(order_amount) OVER (
        PARTITION BY customer_id
        ORDER BY order_date
    ) AS customer_running_total
FROM orders
ORDER BY customer_id, order_date;

-- Result
-- customer_id | order_date | order_amount | customer_running_total
-- 101         | 2026-06-01 |          200 |                    200
-- 101         | 2026-06-15 |          150 |                    350
-- 102         | 2026-06-02 |          300 |                    300   -- reset for new customer
-- 102         | 2026-06-20 |          120 |                    420

5. Der klassische Ansatz: korrelierte Subquery

Vor der breiten Verfugbarkeit von Window Functions wurde ein Running Total typischerweise mit einer korrelierten Subquery berechnet: Fur jede Zeile der ausseren Abfrage summiert eine innere Subquery alle Zeilen, deren Sortierspalte kleiner oder gleich dem Wert der aktuellen Zeile ist. Dieser Ansatz funktioniert korrekt, ist aber strukturell fundamental anders als eine Window Function, weil die Subquery fur jede einzelne Zeile der ausseren Abfrage komplett neu ausgefuhrt wird.

Bei n Zeilen bedeutet das im schlechtesten Fall n separate Aggregationen uber im Schnitt n/2 Zeilen, was zu einer quadratischen Komplexitat fuhrt. Bei kleinen Tabellen mit wenigen hundert Zeilen ist dieser Unterschied kaum messbar, bei Tabellen mit hunderttausenden oder Millionen Zeilen wird die korrelierte Subquery jedoch schnell zum Performance-Engpass, der eine Abfrage von Millisekunden auf mehrere Sekunden oder Minuten verlangsamen kann.


-- Classic correlated subquery approach (avoid for large tables)
SELECT
    t1.transaction_date,
    t1.amount,
    (
        SELECT SUM(t2.amount)
        FROM transactions t2
        WHERE t2.transaction_date <= t1.transaction_date
    ) AS running_balance
FROM transactions t1
ORDER BY t1.transaction_date;

-- Equivalent, much faster window function version
SELECT
    transaction_date,
    amount,
    SUM(amount) OVER (ORDER BY transaction_date) AS running_balance
FROM transactions
ORDER BY transaction_date;

6. Performance-Vergleich: Window Function vs. Subquery

Der Performance-Unterschied zwischen einer korrelierten Subquery und einer Window Function fur Running Totals ist einer der deutlichsten und am besten dokumentierten in der SQL-Optimierung. Wahrend die korrelierte Subquery die Daten fur jede Zeile neu liest und neu summiert, sortiert die Datenbank fur die Window Function die Daten einmalig und akkumuliert die Summe in einem einzigen Durchlauf uber die sortierten Daten. Diese algorithmische Differenz entspricht dem Unterschied zwischen O(n²) und O(n log n) beziehungsweise O(n) bei bereits vorliegender Sortierung durch einen passenden Index.

In der Praxis bedeutet das: Bei einer Tabelle mit 100000 Zeilen kann eine korrelierte Subquery mehrere Sekunden oder sogar Minuten benotigen, wahrend dieselbe Berechnung mit SUM() OVER typischerweise in einem Bruchteil einer Sekunde abgeschlossen ist. Dieser Unterschied ist einer der starksten praktischen Argumente dafur, bestehenden Legacy-SQL-Code mit korrelierten Subqueries fur Running Totals systematisch auf Window Functions zu migrieren, sobald die eingesetzte Datenbankversion sie unterstutzt.

Kriterium Korrelierte Subquery SUM() OVER (Window Function)
Algorithmische Komplexitat Quadratisch, O(n²) Linear bis linearithmisch
Datenzugriffe pro Zeile Erneute Aggregation je Zeile Einmaliger sortierter Durchlauf
Lesbarkeit Verschachtelt, schwerer nachzuvollziehen Kompakt, ein SELECT
Skalierung bei grossen Tabellen Schlecht, oft Sekunden bis Minuten Gut, meist Millisekunden

7. Gleitende Summen mit ROWS BETWEEN

Neben dem klassischen Running Total, das immer vom Anfang der Partition an summiert, gibt es die gleitende Summe, auch Moving Sum genannt, die nur eine feste Anzahl vorheriger Zeilen einbezieht. Mit ROWS BETWEEN n PRECEDING AND CURRENT ROW lasst sich beispielsweise eine Summe uber die letzten sieben Tage berechnen, die mit jeder neuen Zeile die alteste Zeile aus dem Fenster entfernt. Diese Technik wird haufig fur gleitende Durchschnitte und kurzfristige Trendanalysen eingesetzt, bei denen ein zu weit zuruckliegender Wert die Aktualitat der Kennzahl verwassern wurde.

Der Unterschied zum klassischen Running Total liegt einzig im Frame: Wahrend der Standard-Frame UNBOUNDED PRECEDING verwendet und damit unbegrenzt weit zuruckreicht, begrenzt ROWS n PRECEDING das Fenster auf eine feste Anzahl von Zeilen. Beide Varianten nutzen dieselbe SUM() OVER-Syntax, unterscheiden sich aber in der Frage, wie weit das Fenster in die Vergangenheit reicht, was die Wahl zwischen kumulativer Gesamtperspektive und kurzfristigem, gleitendem Trend ermoglicht.


-- Moving sum: last 7 rows only, not the entire history
SELECT
    sale_date,
    daily_revenue,
    SUM(daily_revenue) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS revenue_last_7_days
FROM daily_sales
ORDER BY sale_date;

8. Running Totals mit zusatzlichen Bedingungen

Ein haufiger praktischer Bedarf ist ein Running Total, das nur bestimmte Zeilen einbezieht, etwa nur bestatigte Bestellungen oder nur Zeilen mit einem positiven Betrag. Da eine Window Function selbst keine WHERE-Klausel enthalten kann, lost man dieses Problem entweder durch einen bedingten Ausdruck innerhalb von SUM, etwa SUM(CASE WHEN status = 'confirmed' THEN betrag ELSE 0 END) OVER (...), oder durch eine vorgeschaltete WHERE-Klausel, die die Ausgangsdaten bereits vor der Window Function filtert.

Der Unterschied zwischen beiden Ansatzen ist wichtig: Eine vorgeschaltete WHERE-Klausel entfernt die Zeilen komplett aus der Ergebnismenge, bevor die Window Function sie sieht, wahrend der CASE-Ausdruck innerhalb von SUM alle Zeilen in der Ausgabe behalt, aber nur bestimmte Werte zur Summe beitragen lasst. Welcher Ansatz richtig ist, hangt davon ab, ob die ausgeschlossenen Zeilen im Ergebnis sichtbar bleiben sollen oder nicht.

Mironsoft

SQL-Optimierung, Datenbankdesign und Reporting-Abfragen

Langsame Running-Total-Abfragen mit korrelierten Subqueries?

Wir migrieren bestehende Legacy-Abfragen mit korrelierten Subqueries auf Window Functions und beschleunigen damit Kontoauszuge, Umsatzberichte und Bestandsauswertungen spurbar.

Performance-Audit

Identifikation langsamer korrelierter Subqueries im Bestandscode

Migration

Umstellung auf SUM() OVER mit vollstandiger Regressionsprufung

Schulung

Team-Workshop zu Running Totals und Window Functions


-- Running total that only counts confirmed orders
SELECT
    order_date,
    status,
    amount,
    SUM(
        CASE WHEN status = 'confirmed' THEN amount ELSE 0 END
    ) OVER (ORDER BY order_date) AS confirmed_running_total
FROM orders
ORDER BY order_date;

9. Typische Fallstricke bei Running Totals

Der haufigste Fehler bei Running Totals ist eine nicht eindeutige Sortierreihenfolge im ORDER BY der OVER-Klausel. Wenn mehrere Zeilen denselben Sortierwert haben, etwa dasselbe Datum ohne Uhrzeit, kann der Standard-RANGE-Frame dazu fuhren, dass alle diese Zeilen denselben Running-Total-Wert erhalten, was oft nicht der fachlichen Erwartung entspricht. Eine zusatzliche eindeutige Spalte im ORDER BY, etwa eine fortlaufende ID als zweites Sortierkriterium, oder ein expliziter ROWS-Frame lost dieses Problem zuverlassig.

Ein zweiter haufiger Fehler ist das Vergessen von PARTITION BY, wenn ein Running Total eigentlich pro Gruppe berechnet werden soll. Ohne PARTITION BY summiert die Window Function uber die gesamte Ergebnismenge hinweg, was bei einer Auswertung, die eigentlich pro Kunde oder pro Konto laufen soll, zu vollig falschen, viel zu hohen Werten fuhrt. Ein dritter Fallstrick betrifft NULL-Werte: SUM ignoriert NULL-Werte automatisch, was bei sparsamen Datenqualitatsprufungen dazu fuhren kann, dass fehlende Werte stillschweigend als null behandelt werden, statt als fehlerhafte oder unvollstandige Daten aufzufallen.

10. Zusammenfassung

Running Totals lassen sich mit SUM() OVER direkt in SQL berechnen, ohne auf Anwendungslogik oder langsame korrelierte Subqueries zuruckzugreifen. Die Grundsyntax SUM(spalte) OVER (ORDER BY sortierspalte) nutzt den impliziten RANGE-UNBOUNDED-PRECEDING-Frame, um eine kumulative Summe von der ersten bis zur aktuellen Zeile zu berechnen. PARTITION BY erganzt diese Berechnung um automatisches Zurucksetzen pro Gruppe, wahrend ROWS BETWEEN gleitende Summen uber ein festes Fenster ermoglicht.

Der Performance-Unterschied zur korrelierten Subquery ist bei grosseren Tabellen erheblich und oft der entscheidende Grund, bestehenden Legacy-Code zu modernisieren. Wer die typischen Fallstricke kennt, insbesondere die Notwendigkeit einer eindeutigen Sortierreihenfolge und PARTITION BY bei gruppierten Berechnungen, kann Running Totals sicher und performant fur Kontoauszuge, Umsatzberichte und ahnliche kumulative Auswertungen einsetzen.

Running Totals mit Window Functions: das Wichtigste auf einen Blick

Grundsyntax

SUM(spalte) OVER (ORDER BY sortierspalte) summiert automatisch vom Anfang bis zur aktuellen Zeile.

Reset pro Gruppe

PARTITION BY setzt die laufende Summe bei jeder neuen Gruppe automatisch auf null zuruck.

Performance

Deutlich schneller als korrelierte Subqueries, besonders bei grossen Tabellen mit vielen Zeilen.

Gleitende Summen

ROWS BETWEEN n PRECEDING AND CURRENT ROW begrenzt das Fenster auf feste Anzahl Zeilen.

11. FAQ: Running Totals mit Window Functions

1Einfaches Running Total berechnen?
SUM(spalte) OVER (ORDER BY sortierspalte), die Datenbank summiert automatisch bis zur aktuellen Zeile.
2Warum Reset pro Kunde?
PARTITION BY behandelt jede Gruppe eigenstaendig, neue Gruppe bedeutet automatischer Neustart bei null.
3Schneller als korrelierte Subquery?
Deutlich, Subquery ist quadratisch, SUM() OVER sortiert einmal und akkumuliert in einem Durchlauf.
4Was ist der implizite Frame?
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, sobald ein ORDER BY vorhanden ist.
5Gleiches Datum, gleicher Wert?
RANGE-Frame gibt bei Gleichstand denselben Wert. ROWS-Frame oder eindeutige Sortierung lost das.
6Gleitende Summe uber 7 Tage?
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW deckt aktuelle Zeile plus sechs vorherige ab.
7Zeilen von Summe ausschliessen?
Vorgeschaltete WHERE-Klausel oder SUM(CASE WHEN ... THEN ... ELSE 0 END) OVER.
8Running Total vs. gleitende Summe?
Running Total ist unbegrenzt, gleitende Summe nutzt ein festes Fenster mit ROWS n PRECEDING.
9Ignoriert SUM() OVER NULL?
Ja, genau wie die klassische SUM-Funktion werden NULL-Werte automatisch ignoriert.
10Funktioniert das uberall?
Ja, Standard seit SQL:2003, unterstutzt von PostgreSQL, MySQL 8+, SQL Server, Oracle und SQLite ab 3.25.