Window Functions
Lerne Window Functions: ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD und mehr - für fortgeschrittene Analysen und Rankings.
Window Functions sind eines der maechtigsten Features in SQL. Sie ermoeglichen Berechnungen ueber eine Menge von Zeilen, ohne die Ergebnisse zu gruppieren. Das bedeutet: Du kannst Aggregationen durchfuehren UND gleichzeitig alle einzelnen Zeilen sehen.
Was sind Window Functions?
Vergleiche GROUP BY mit Window Functions:
-- GROUP BY: Fasst Zeilen zusammen (du siehst nur die Gruppen)
SELECT kategorie, AVG(preis) AS avg_preis
FROM produkte
GROUP BY kategorie;
kategorie | avg_preis
------------+----------
Accessoires | 27.49
Kleidung | 66.66
Schuhe | 79.99
-- Window Function: Zeigt JEDE Zeile + den Durchschnitt der Kategorie
SELECT
name,
kategorie,
preis,
AVG(preis) OVER (PARTITION BY kategorie) AS avg_kategorie
FROM produkte;
name | kategorie | preis | avg_kategorie
---------------+-------------+--------+--------------
Muetze Winter | Accessoires | 14.99 | 27.49
Rucksack Urban | Accessoires | 39.99 | 27.49
T-Shirt Basic | Kleidung | 19.99 | 66.66
Jeans Classic | Kleidung | 49.99 | 66.66
Jacke Outdoor | Kleidung | 129.99 | 66.66
Sneaker Sport | Schuhe | 79.99 | 79.99
Jede Zeile bleibt erhalten, aber du siehst trotzdem den Durchschnitt!
Die OVER-Klausel
Jede Window Function braucht eine OVER-Klausel:
funktion() OVER (
PARTITION BY spalte -- Optional: Gruppierung
ORDER BY spalte -- Optional: Sortierung innerhalb der Gruppe
)
PARTITION BY
PARTITION BY teilt die Daten in Gruppen (aehnlich wie GROUP BY, aber ohne Zusammenfassung):
SELECT
name,
kategorie,
preis,
SUM(preis) OVER (PARTITION BY kategorie) AS summe_kategorie,
COUNT(*) OVER (PARTITION BY kategorie) AS anzahl_kategorie
FROM produkte;
ORDER BY in OVER
ORDER BY innerhalb von OVER definiert die Reihenfolge fuer laufende Berechnungen:
-- Laufende Summe der Bestellbetraege nach Datum
SELECT
id,
bestelldatum,
betrag,
SUM(betrag) OVER (ORDER BY bestelldatum) AS laufende_summe
FROM bestellungen;
Ergebnis:
id | bestelldatum | betrag | laufende_summe
---+--------------+--------+---------------
1 | 2026-01-15 | 69.98 | 69.98
2 | 2026-02-20 | 129.99 | 199.97
3 | 2026-03-01 | 94.98 | 294.95
4 | 2026-03-05 | 19.99 | 314.94
5 | 2026-03-08 | 184.97 | 499.91
Ranking-Funktionen
ROW_NUMBER() - Fortlaufende Nummer
-- Produkte innerhalb jeder Kategorie nummerieren (nach Preis)
SELECT
name,
kategorie,
preis,
ROW_NUMBER() OVER (PARTITION BY kategorie ORDER BY preis) AS rang
FROM produkte;
Ergebnis:
name | kategorie | preis | rang
---------------+-------------+--------+-----
Muetze Winter | Accessoires | 14.99 | 1
Rucksack Urban | Accessoires | 39.99 | 2
T-Shirt Basic | Kleidung | 19.99 | 1
Jeans Classic | Kleidung | 49.99 | 2
Jacke Outdoor | Kleidung | 129.99 | 3
Sneaker Sport | Schuhe | 79.99 | 1
RANK() - Rang mit Luecken
-- Bei gleichen Werten: Gleicher Rang, naechster Rang wird uebersprungen
SELECT
name,
preis,
RANK() OVER (ORDER BY preis DESC) AS rang
FROM produkte;
DENSE_RANK() - Rang ohne Luecken
-- Bei gleichen Werten: Gleicher Rang, naechster Rang folgt direkt
SELECT
name,
preis,
DENSE_RANK() OVER (ORDER BY preis DESC) AS rang
FROM produkte;
Vergleich der Ranking-Funktionen
Angenommen, drei Produkte haben den gleichen Preis:
| Preis | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 100 | 1 | 1 | 1 |
| 80 | 2 | 2 | 2 |
| 80 | 3 | 2 | 2 |
| 50 | 4 | 4 | 3 |
- ROW_NUMBER: Immer eindeutig (2, 3 bei gleichen Werten)
- RANK: Gleiche Werte bekommen gleichen Rang, naechster wird uebersprungen (2, 2, 4)
- DENSE_RANK: Gleiche Werte bekommen gleichen Rang, naechster folgt direkt (2, 2, 3)
Navigations-Funktionen
LAG() - Vorherige Zeile
-- Bestellung mit dem Betrag der vorherigen Bestellung vergleichen
SELECT
id,
bestelldatum,
betrag,
LAG(betrag) OVER (ORDER BY bestelldatum) AS vorherige_bestellung,
betrag - LAG(betrag) OVER (ORDER BY bestelldatum) AS differenz
FROM bestellungen;
Ergebnis:
id | bestelldatum | betrag | vorherige_bestellung | differenz
---+--------------+--------+----------------------+----------
1 | 2026-01-15 | 69.98 | NULL | NULL
2 | 2026-02-20 | 129.99 | 69.98 | 60.01
3 | 2026-03-01 | 94.98 | 129.99 | -35.01
4 | 2026-03-05 | 19.99 | 94.98 | -74.99
5 | 2026-03-08 | 184.97 | 19.99 | 164.98
LEAD() - Naechste Zeile
-- Naechste Bestellung anzeigen
SELECT
id,
bestelldatum,
betrag,
LEAD(betrag) OVER (ORDER BY bestelldatum) AS naechste_bestellung
FROM bestellungen;
FIRST_VALUE() und LAST_VALUE()
-- Guenstigstes Produkt pro Kategorie anzeigen
SELECT
name,
kategorie,
preis,
FIRST_VALUE(name) OVER (
PARTITION BY kategorie ORDER BY preis
) AS guenstigstes_in_kategorie
FROM produkte;
Aggregat-Window-Functions
Alle Standard-Aggregatfunktionen koennen als Window Functions verwendet werden:
SELECT
name,
kategorie,
preis,
-- Aggregationen pro Kategorie (ohne GROUP BY!)
SUM(preis) OVER (PARTITION BY kategorie) AS summe_kat,
AVG(preis) OVER (PARTITION BY kategorie) AS avg_kat,
MIN(preis) OVER (PARTITION BY kategorie) AS min_kat,
MAX(preis) OVER (PARTITION BY kategorie) AS max_kat,
COUNT(*) OVER (PARTITION BY kategorie) AS anzahl_kat
FROM produkte;
Laufende Berechnungen
-- Laufende Summe und laufender Durchschnitt
SELECT
bestelldatum,
betrag,
SUM(betrag) OVER (ORDER BY bestelldatum) AS laufende_summe,
ROUND(AVG(betrag) OVER (ORDER BY bestelldatum), 2) AS laufender_avg,
COUNT(*) OVER (ORDER BY bestelldatum) AS bestellung_nr
FROM bestellungen;
NTILE() - In Gruppen aufteilen
NTILE teilt die Ergebnisse in N gleich grosse Gruppen:
-- Produkte in 3 Preisgruppen teilen
SELECT
name,
preis,
NTILE(3) OVER (ORDER BY preis) AS preisgruppe
FROM produkte;
Ergebnis:
name | preis | preisgruppe
---------------+--------+------------
Muetze Winter | 14.99 | 1
T-Shirt Basic | 19.99 | 1
Rucksack Urban | 39.99 | 2
Jeans Classic | 49.99 | 2
Sneaker Sport | 79.99 | 3
Jacke Outdoor | 129.99 | 3
PERCENT_RANK() und CUME_DIST()
-- Prozentuale Position im Ranking
SELECT
name,
preis,
ROUND(PERCENT_RANK() OVER (ORDER BY preis)::NUMERIC, 2) AS pct_rang,
ROUND(CUME_DIST() OVER (ORDER BY preis)::NUMERIC, 2) AS kum_verteilung
FROM produkte;
Praxisbeispiele
Top-N pro Kategorie
-- Die 2 teuersten Produkte jeder Kategorie
SELECT name, kategorie, preis
FROM (
SELECT
name,
kategorie,
preis,
ROW_NUMBER() OVER (PARTITION BY kategorie ORDER BY preis DESC) AS rang
FROM produkte
) AS ranked
WHERE rang <= 2;
Kunden-Ranking nach Umsatz
SELECT
k.name,
SUM(b.betrag) AS umsatz,
RANK() OVER (ORDER BY SUM(b.betrag) DESC) AS umsatz_rang
FROM kunden k
JOIN bestellungen b ON k.id = b.kunden_id
GROUP BY k.name;
Monats-ueber-Monat Vergleich
SELECT
monat,
umsatz,
LAG(umsatz) OVER (ORDER BY monat) AS vormonat_umsatz,
ROUND(
(umsatz - LAG(umsatz) OVER (ORDER BY monat)) /
LAG(umsatz) OVER (ORDER BY monat) * 100
, 1) AS veraenderung_pct
FROM (
SELECT
DATE_TRUNC('month', bestelldatum) AS monat,
SUM(betrag) AS umsatz
FROM bestellungen
GROUP BY DATE_TRUNC('month', bestelldatum)
) AS monatsumsatz;
Was kommt als Naechstes?
Im naechsten Tutorial wendest du alles Gelernte in einem praxisnahen Projekt an: Du baust eine komplette Bibliotheks-Datenbank.
Zusammenfassung
- Window Functions berechnen ueber eine Menge von Zeilen, ohne sie zusammenzufassen
- OVER() definiert das Fenster (PARTITION BY + ORDER BY)
- ROW_NUMBER, RANK, DENSE_RANK erstellen Rankings
- LAG, LEAD greifen auf vorherige/naechste Zeilen zu
- SUM, AVG, COUNT ueber OVER fuer laufende Berechnungen
- NTILE teilt Daten in gleich grosse Gruppen
- Window Functions sind ideal fuer Rankings, Vergleiche und Trendanalysen
Uebungen
- ROW_NUMBER: Nummeriere alle Bestellungen jedes Kunden nach Datum.
- RANK: Erstelle ein Produkt-Ranking nach Preis innerhalb jeder Kategorie.
- LAG: Vergleiche den Betrag jeder Bestellung mit der vorherigen desselben Kunden.
- Laufende Summe: Berechne die laufende Umsatzsumme nach Datum.
- Top-N: Finde das meistverkaufte Produkt jeder Kategorie (nutze ROW_NUMBER und eine Subquery).
-- Loesung zu Uebung 1:
SELECT
k.name,
b.bestelldatum,
b.betrag,
ROW_NUMBER() OVER (PARTITION BY k.id ORDER BY b.bestelldatum) AS bestell_nr
FROM bestellungen b
JOIN kunden k ON b.kunden_id = k.id;
-- Loesung zu Uebung 4:
SELECT
bestelldatum,
betrag,
SUM(betrag) OVER (ORDER BY bestelldatum) AS laufende_summe
FROM bestellungen;
Pro-Tipp: Window Functions werden in SQLite ab Version 3.25.0 unterstuetzt. In PostgreSQL sind sie seit vielen Versionen verfuegbar. Wenn du Window Functions beherrschst, hast du ein Werkzeug, das die meisten SQL-Anwender nicht kennen - das macht dich in Data Analytics und Reporting besonders wertvoll.