A window function computes across a set of rows but keeps every row intact. Where GROUP BY folds many rows into one summary, a window function returns 7 rows for 7 rows — each gaining a value computed over a "window" of its neighbours.
Where you meet it
- Rankings — top earner per department, the 3 newest orders per customer.
- Running totals and moving averages over a time series.
- Row-to-row deltas — this month vs. last month, with
LAG/LEAD. - Percentiles and bucketing (
NTILE) for cohorts and reporting.
What it does
It attaches a per-row result that "looks around" at related rows. You still see the detail — name, salary, date — plus an extra column: its rank, its running sum, the value one row back. The original rows are never collapsed.
A window function adds a column without ever collapsing a row — which is also why you can't filter on its result in WHERE.
How it works
The magic is the OVER (...) clause. PARTITION BY splits the table into independent groups; ORDER BY orders the rows inside each group; the function then runs per group. Drop PARTITION BY and the whole table is one window.
SELECT name, dept, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk,
SUM(salary) OVER (PARTITION BY dept ORDER BY salary DESC) AS running,
LAG(salary) OVER (PARTITION BY dept ORDER BY salary DESC) AS prev
FROM emp;
Within each dept, rows are sorted by salary. RANK gives ties the same number then skips (1, 2, 2, 4); ROW_NUMBER would force 1, 2, 3, 4. SUM ... OVER (ORDER BY ...) accumulates top-down into a running total. LAG reaches back to the previous row's salary — NULL at the top, since there is nothing before it.
Watch out
- Not allowed in
WHERE. Window functions run afterWHERE/GROUP BY/HAVING, so you cannot filter onrnk <= 3directly — wrap the query in a subquery or CTE, then filter the outer result. - Frame default is
RANGE, notROWS. With anORDER BYand ties, the defaultRANGEframe includes all peer rows with the same sort value — a runningSUMcan jump by a whole tie group at once. UseROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWfor a strict row-by-row total. ORDER BYinsideOVERorders the window; it is independent of the query's finalORDER BY, which sorts the output.
Go deeper
Eine Window-Funktion rechnet über eine Menge von Zeilen, lässt aber jede Zeile bestehen. Wo GROUP BY viele Zeilen zu einer Zusammenfassung faltet, gibt eine Window-Funktion 7 Zeilen für 7 Zeilen zurück — jede mit einem Wert, berechnet über ein "Fenster" ihrer Nachbarn.
Wo es vorkommt
- Rankings — Top-Verdiener je Abteilung, die 3 neuesten Bestellungen je Kunde.
- Laufende Summen und gleitende Mittel über eine Zeitreihe.
- Zeile-zu-Zeile-Differenzen — dieser Monat vs. letzter, mit
LAG/LEAD. - Perzentile und Eimer-Bildung (
NTILE) für Kohorten und Reporting.
Was es tut
Sie hängt jeder Zeile ein Ergebnis an, das sich bei verwandten Zeilen "umschaut". Die Detailzeile bleibt — Name, Gehalt, Datum — plus eine Zusatzspalte: ihr Rang, ihre laufende Summe, der Wert eine Zeile zurück. Die Originalzeilen werden nie kollabiert.
Eine Window-Funktion fügt eine Spalte hinzu, ohne je eine Zeile zu kollabieren — genau darum kannst du ihr Ergebnis nicht im WHERE filtern.
Wie es funktioniert
Der Kern ist die OVER (...)-Klausel. PARTITION BY teilt die Tabelle in unabhängige Gruppen; ORDER BY ordnet die Zeilen innerhalb jeder Gruppe; die Funktion läuft dann pro Gruppe. Ohne PARTITION BY ist die ganze Tabelle ein Fenster.
SELECT name, dept, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk,
SUM(salary) OVER (PARTITION BY dept ORDER BY salary DESC) AS running,
LAG(salary) OVER (PARTITION BY dept ORDER BY salary DESC) AS prev
FROM emp;
Je dept werden die Zeilen nach Gehalt sortiert. RANK gibt Gleichstand dieselbe Zahl und springt dann (1, 2, 2, 4); ROW_NUMBER würde 1, 2, 3, 4 erzwingen. SUM ... OVER (ORDER BY ...) summiert von oben nach unten zur laufenden Summe. LAG greift auf das Gehalt der vorherigen Zeile zurück — ganz oben NULL, weil davor nichts liegt.
Worauf achten
- Nicht im
WHEREnutzbar. Window-Funktionen laufen nachWHERE/GROUP BY/HAVING, du kannst also nicht direkt aufrnk <= 3filtern — pack die Query in eine Subquery oder CTE und filtere das äußere Ergebnis. - Frame-Default ist
RANGE, nichtROWS. MitORDER BYund Gleichstand umfasst der Default-RANGE-Frame alle Peer-Zeilen mit demselben Sortierwert — eine laufendeSUMkann um eine ganze Gleichstand-Gruppe auf einmal springen. Für eine strikt zeilenweise SummeROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWnutzen. ORDER BYinnerhalb vonOVERordnet das Fenster; es ist unabhängig vom finalenORDER BYder Query, das die Ausgabe sortiert.