Window Functions
A window function computes a value for each row from a set of related rows — its window — without
collapsing the rows the way GROUP BY does: a rank within a category, a running total, a moving
average, the previous order of the same customer.
SELECT o_orderkey,
o_orderpriority,
ROW_NUMBER() OVER (PARTITION BY o_orderpriority ORDER BY o_totalprice DESC) AS rank_in_priority,
SUM(o_totalprice) OVER (PARTITION BY o_custkey ORDER BY o_orderdate) AS running_total,
AVG(o_totalprice) OVER (ORDER BY o_orderdate
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7,
LAG(o_orderdate) OVER (PARTITION BY o_custkey ORDER BY o_orderdate) AS previous_order
FROM ice.sales.orders;
Ontul's end-to-end suite runs these queries, and the other shapes on this page, on Ontul and on Trino
479 over the same Iceberg tables and compares the answers (tests/e2e-window-offset.sh).
Functions
| Function | Result |
|---|---|
ROW_NUMBER() |
1, 2, 3, … in the window's order |
RANK() |
rank with gaps: peers (rows with equal ORDER BY values) share a rank, and the next rank skips |
DENSE_RANK() |
rank without gaps |
PERCENT_RANK() |
(rank - 1) / (rows in partition - 1), 0 for a one-row partition |
CUME_DIST() |
fraction of the partition's rows that sort at or before the row, peers included |
NTILE(n) |
bucket 1 … n; the first rows % n buckets get one extra row |
LAG(x [, offset [, default]]), LEAD(…) |
x from the row offset rows before / after (default 1) in the partition, or default (default NULL) |
FIRST_VALUE(x), LAST_VALUE(x), NTH_VALUE(x, n) |
x from the first / last / n-th row of the frame |
COUNT(*), COUNT(x), SUM, AVG, MIN, MAX |
the aggregate over the frame |
IGNORE NULLS is honored by LAG, LEAD, FIRST_VALUE, LAST_VALUE and NTH_VALUE.
Result types follow the aggregates': SUM over integers is an exact BIGINT (overflow fails the query),
over a DECIMAL(p, s) an exact DECIMAL(38, s); AVG over integers is a DOUBLE; MIN, MAX,
FIRST_VALUE, LAG, … keep the column's type, including DATE, TIMESTAMP and text.
Partitions, order and peers
PARTITION BYsplits the rows; each partition is computed on its own. Without it the whole result is one partition.ORDER BYorders the rows inside the partition.NULLS FIRST/NULLS LASTare honored; without either, NULLs sort last in both directions, as in Trino.- Rows whose
ORDER BYvalues are all equal are peers.RANK,DENSE_RANK,PERCENT_RANK,CUME_DISTandRANGEframes treat peers as one position.
Frames
The aggregate functions and FIRST_VALUE / LAST_VALUE / NTH_VALUE work over a frame — a range of
rows around the current one:
SUM(x) OVER (ORDER BY d ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)
SUM(x) OVER (ORDER BY d RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW)
SUM(x) OVER (ORDER BY n RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING)
| Default | Frame |
|---|---|
no ORDER BY |
the whole partition |
ORDER BY without a frame |
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — a running total that includes the row's peers |
ROWScounts rows.RANGEwith an offset moves by a value of the singleORDER BYcolumn: a number, or an interval for aDATE/TIMESTAMPcolumn. A row whose key isNULLhas itsNULLpeers as its frame.EXCLUDE CURRENT ROW,EXCLUDE GROUPandEXCLUDE TIESare supported.- Over an empty frame
COUNTis0and the other aggregates areNULL.
Frames that start at the partition's first row, frames that end at its last row, and sliding frames are computed incrementally, so a running total over a partition of n rows is O(n), not O(n²).
How it runs
A window needs every row of a partition in one place. A plan with a window runs its scan on one worker, which computes the window over all the rows — see Splitting a Query Across Workers. The operator holds the window's input in memory.
A window can rank the result of a GROUP BY — the aggregate is computed first, then the window over
the groups:
SELECT o_orderpriority, SUM(o_totalprice) AS revenue,
RANK() OVER (ORDER BY SUM(o_totalprice) DESC) AS revenue_rank
FROM ice.sales.orders
GROUP BY o_orderpriority;
Several OVER clauses in one query are computed by one operator, one sort per distinct window. Rows come
out in the order of the last window; add ORDER BY to the query when the output order matters.
Not supported
| Construct | Error | Rewrite |
|---|---|---|
DISTINCT inside a window function, e.g. COUNT(DISTINCT x) OVER (…) |
DISTINCT inside window function COUNT() |
GROUP BY first, then the window |
Other aggregates over a window (e.g. STDDEV_POP(x) OVER (…)) |
Window function STDDEV_POP() |
— |
RANGE with an offset over a non-numeric, non-temporal key, or several keys |
RANGE frame with an offset over this ORDER BY |
ROWS |