SQL Window Functions: Rankings, laufende Summen und Analysen

Published: 2026-03-24

Window functions führen Berechnungen über eine Menge von Zeilen aus, die mit der aktuellen Zeile in Beziehung stehen – ohne das Ergebnis wie bei GROUP BY auf eine einzelne Zeile zu verdichten. Sie gehören zu den mächtigsten Funktionen in modernem SQL und werden in SQLite (3.25+), PostgreSQL, MySQL (8.0+), SQL Server und Oracle unterstützt.

Beispieldaten

Wir verwenden in allen Beispielen zwei Tabellen:

employees-Tabelle:

id name department hire_date salary
1 Alice Engineering 2023-01-15 95000
2 Bob Engineering 2023-06-01 85000
3 Carol Engineering 2024-03-10 85000
4 Dave Sales 2023-02-20 72000
5 Eve Sales 2023-09-05 68000
6 Frank Sales 2024-01-15 65000
7 Grace Marketing 2023-04-01 82000
8 Henry Marketing 2024-06-01 70000

monthly_sales-Tabelle:

id employee_id month revenue
1 1 2026-01 22000
2 1 2026-02 25000
3 1 2026-03 28000
4 2 2026-01 19000
5 2 2026-02 21000
6 2 2026-03 20000
7 3 2026-01 15000
8 3 2026-02 17000
9 3 2026-03 19000
10 4 2026-01 18000
11 4 2026-02 22000
12 4 2026-03 17000
13 5 2026-01 14000
14 5 2026-02 16000
15 5 2026-03 19000
16 6 2026-01 11000
17 6 2026-02 13000
18 6 2026-03 15000
19 7 2026-01 9000
20 7 2026-02 12000
21 7 2026-03 14000
22 8 2026-01 7000
23 8 2026-02 9000
24 8 2026-03 11000
Datensatz in DbGate Lite erkunden

Syntax von window functions

Jede window function verwendet die OVER-Klausel, um zu definieren, auf welche Zeilen sie angewendet wird:

function_name(...) OVER (
  [PARTITION BY column]
  [ORDER BY column]
  [frame_clause]
)
  • PARTITION BY – teilt Zeilen in Gruppen auf (ähnlich wie GROUP BY, aber alle Zeilen bleiben erhalten)
  • ORDER BY – definiert die Reihenfolge der Zeilen innerhalb jeder Partition
  • Frame-Klausel – begrenzt das Fenster auf einen Bereich von Zeilen relativ zur aktuellen Zeile

ROW_NUMBER()

ROW_NUMBER() weist jeder Zeile innerhalb einer Partition eine eindeutige fortlaufende Nummer zu. Die Nummerierung beginnt immer bei 1, ohne Lücken oder Gleichstände.

Beispiel: Alle Mitarbeitenden nach Gehalt nummerieren

Beispiel: Mitarbeitende innerhalb jeder Abteilung nummerieren

Mit PARTITION BY beginnt die Nummerierung für jede Abteilung wieder bei 1:

RANK() und DENSE_RANK()

RANK() und DENSE_RANK() gehen mit Gleichständen anders um als ROW_NUMBER():

  • ROW_NUMBER() – immer eindeutig, Gleichstände werden willkürlich aufgelöst
  • RANK() – gleicher Wert erhält den gleichen Rang, danach werden Nummern übersprungen (1, 2, 2, 4)
  • DENSE_RANK() – gleicher Wert erhält den gleichen Rang, keine Lücken (1, 2, 2, 3)

Beispiel: Alle drei Ranking-Funktionen vergleichen

Bob und Carol verdienen beide 85000 – beachte, wie jede Funktion den Gleichstand behandelt:

Beispiel: Mitarbeitende innerhalb der Abteilung ranken

NTILE()

NTILE(n) verteilt Zeilen auf n ungefähr gleich große Gruppen (Buckets), nummeriert von 1 bis n.

Beispiel: Mitarbeitende in Gehaltsquartile einteilen

Beispiel: Mitarbeitende pro Abteilung in zwei Stufen aufteilen

LAG() und LEAD()

LAG(column, offset) greift auf einen Wert aus einer vorherigen Zeile zu. LEAD(column, offset) greift auf einen Wert aus einer folgenden Zeile zu. Beide verwenden standardmäßig den Versatz 1 und geben NULL zurück, wenn es keine entsprechende Zeile gibt.

Beispiel: Umsatzvergleich von Monat zu Monat

Vergleiche den Umsatz jedes Monats mit dem Vormonat für jede Mitarbeiterin bzw. jeden Mitarbeiter:

Beispiel: Umsatz des nächsten Monats vorab anzeigen

Aggregierte window functions

Standard-Aggregatfunktionen (SUM, AVG, COUNT, MIN, MAX) können mit OVER als window functions verwendet werden. Im Gegensatz zu GROUP BY bleibt jede Zeile in der Ausgabe erhalten.

Beispiel: Gehalt jeder Person vs. Abteilungsdurchschnitt

Beispiel: Anteil am Gesamtgehalt

Berechne, welchen Anteil am gesamten Gehaltsvolumen jede Person hat:

Beispiel: Laufende Summe und kumulative Anzahl

Laufende Summen pro Partition

Kombiniere PARTITION BY und ORDER BY, um laufende Summen innerhalb von Gruppen zu berechnen.

Beispiel: Kumulativer Umsatz pro Mitarbeiter:in

Beispiel: Monatlicher Umsatzrang über alle Mitarbeitenden

Finde heraus, welchen Rang jede Person in jedem Monat hatte:

Fensterrahmen (Window Frames)

Ein Fensterrahmen definiert genau, welche Zeilen relativ zur aktuellen Zeile in die Berechnung einbezogen werden. Die Frame-Klausel steht innerhalb von OVER nach ORDER BY.

Syntax

ROWS BETWEEN <start> AND <end>

Gängige Grenzen:

  • UNBOUNDED PRECEDING – ab der ersten Zeile der Partition
  • N PRECEDING – N Zeilen vor der aktuellen Zeile
  • CURRENT ROW – die aktuelle Zeile
  • N FOLLOWING – N Zeilen nach der aktuellen Zeile
  • UNBOUNDED FOLLOWING – bis zur letzten Zeile der Partition

Beispiel: Gleitender 2-Monats-Durchschnitt

Beispiel: Laufendes Minimum und Maximum des Umsatzes

FIRST_VALUE und LAST_VALUE

FIRST_VALUE(column) gibt den ersten Wert im Fensterrahmen zurück. LAST_VALUE(column) gibt den letzten Wert zurück. Vorsicht: LAST_VALUE sieht mit dem Standardrahmen (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) nur bis zur aktuellen Zeile.

Beispiel: Jeden Monat mit dem ersten Monat vergleichen

Beispiel: Jeden Monat mit dem besten Monat vergleichen

Verwende ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, um die gesamte Partition zu sehen:

Praxisbeispiel: Top-Verdiener:in pro Abteilung

Ein häufiges Muster: Eine window function in einer Unterabfrage verwenden und dann nach dem Rang filtern.

Beispiel: Bestbezahlte Person in jeder Abteilung

Beispiel: Monat mit dem höchsten Umsatz pro Mitarbeiter:in

window functions vs. GROUP BY

Ein zentraler Unterschied: GROUP BY fasst Zeilen zu Summenzeilen zusammen, während window functions jede Zeile beibehalten und eine berechnete Spalte hinzufügen.

Beispiel: Vergleich nebeneinander

GROUP BY gibt eine Zeile pro Abteilung zurück:

Window functions behalten alle Zeilen:

Häufige Anwendungsfälle für window functions

  • Rankings und Bestenlisten – ROW_NUMBER, RANK, DENSE_RANK für Top-N-Abfragen
  • Laufende Summen – kumulative SUM für Finanz- und Bestandsverfolgung
  • Periodenvergleiche – LAG/LEAD für Analysen von Monat zu Monat oder Jahr zu Jahr
  • Gleitende Durchschnitte – Glättung von Daten mit rahmenbasiertem AVG
  • Perzentil-Analysen – NTILE zum Aufteilen von Daten in gleich große Gruppen
  • Deduplizierung – ROW_NUMBER, um eine Zeile pro Gruppe auszuwählen
  • Lückenanalyse – LAG, um fehlende Sequenzen oder ungewöhnliche Änderungen zu erkennen

Performance-Tipps

  1. ORDER-BY-Spalten indexieren – window functions sortieren Daten intern; Indizes helfen dabei
  2. Partitionen minimieren – Viele Partitionen erhöhen den Rechenaufwand
  3. Fensterdefinitionen wiederverwenden – Viele Datenbanken unterstützen WINDOW-Klauseln, um dieselbe OVER-Spezifikation nicht zu wiederholen
  4. Nach dem Windowing filtern – window functions laufen vor WHERE, daher Unterabfragen oder CTEs verwenden, um nach Rang zu filtern
  5. Unnötige Frames vermeiden – Frame-Klauseln nur angeben, wenn du ein vom Standard abweichendes Verhalten brauchst

Zusammenfassung

  • Window functions berechnen Werte über verwandte Zeilen, ohne das Resultset zu verdichten
  • ROW_NUMBER, RANK, DENSE_RANK weisen Zeilen Ordnungspositionen zu
  • LAG und LEAD greifen auf Werte aus vorherigen bzw. folgenden Zeilen zu
  • NTILE verteilt Zeilen auf gleich große Buckets
  • FIRST_VALUE und LAST_VALUE liefern Randwerte aus dem Fenster
  • Aggregatfunktionen (SUM, AVG, COUNT, MIN, MAX) mit OVER behalten alle Zeilen und berechnen gleichzeitig Kennzahlen auf Gruppenebene
  • PARTITION BY bildet unabhängige Gruppen; ORDER BY definiert die Reihenfolge der Zeilen
  • Fensterrahmen (ROWS BETWEEN ... AND ...) steuern fein, welche Zeilen einbezogen werden
  • Verwende Unterabfragen, um nach Ergebnissen von window functions zu filtern (z. B. Top-N pro Gruppe)
  • Unterstützt in SQLite 3.25+, PostgreSQL, MySQL 8.0+, SQL Server und Oracle

Probiere aus, die Beispiele zu verändern – mit unterschiedlichen Partitionen, Sortierungen und Frame-Klauseln –, um zu sehen, wie window functions deine analytischen Abfragen vereinfachen können!