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 |
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 PartitionN PRECEDING– N Zeilen vor der aktuellen ZeileCURRENT ROW– die aktuelle ZeileN FOLLOWING– N Zeilen nach der aktuellen ZeileUNBOUNDED 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
- ORDER-BY-Spalten indexieren – window functions sortieren Daten intern; Indizes helfen dabei
- Partitionen minimieren – Viele Partitionen erhöhen den Rechenaufwand
- Fensterdefinitionen wiederverwenden – Viele Datenbanken unterstützen
WINDOW-Klauseln, um dieselbeOVER-Spezifikation nicht zu wiederholen - Nach dem Windowing filtern – window functions laufen vor
WHERE, daher Unterabfragen oder CTEs verwenden, um nach Rang zu filtern - 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
OVERbehalten 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!