More PostgreSQL Window Functions: NTILE, FIRST_VALUE, LAST_VALUE & Frame Clauses
Learn advanced PostgreSQL window function features — NTILE for bucketing rows, FIRST_VALUE/LAST_VALUE, and the ROWS/RANGE frame clause that controls moving averages.
More Window Functions: NTILE, FIRST_VALUE, Frame Clauses
This builds on the Window Functions lesson — covering NTILE, FIRST_VALUE/LAST_VALUE, and the frame clause that controls exactly which rows a window function looks at.
NTILE — Split Rows Into N Equal Buckets
SELECT name, salary,
NTILE(4) OVER (ORDER BY salary DESC) AS salary_quartile
FROM employees;
-- splits employees into 4 roughly equal groups by salary rank — quartile 1 is the top earners
FIRST_VALUE and LAST_VALUE
Return a value from the first/last row of the current window frame.
SELECT name, department, salary,
FIRST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS top_earner
FROM employees;
-- every row in a department shows that department's highest earner's name
LAST_VALUE needs an explicit frame (see below) to behave as most people expect — by default it only sees up to the current row, not the whole partition.
The Frame Clause: ROWS / RANGE BETWEEN
The frame clause defines exactly which rows, relative to the current one, a window function considers — this is what makes moving averages and running totals possible.
SELECT month, revenue,
AVG(revenue) OVER (
ORDER BY month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3mo
FROM monthly_sales;
-- a 3-month moving average: current row + the 2 rows before it
SELECT month, revenue,
SUM(revenue) OVER (
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM monthly_sales;
-- explicit version of the running total pattern from the earlier Window Functions lesson
Fixing LAST_VALUE
SELECT name, department, salary,
LAST_VALUE(name) OVER (
PARTITION BY department
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_earner
FROM employees;
-- extending the frame to the WHOLE partition makes LAST_VALUE see the true last row
Advertisement728 x 90
Common Errors & Troubleshooting
LAST_VALUEreturning the same value as the current row — this is the default frame behavior (only sees up to the current row); you must widen the frame toROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGto make it see the true last row of the partition.NTILE(4)producing unevenly-sized buckets — this is expected when the row count isn't evenly divisible by 4; PostgreSQL distributes the remainder across the first few buckets rather than erroring.- Moving average including too many/too few rows — double-check the frame bounds (
ROWS BETWEEN N PRECEDING AND CURRENT ROW) match the window size you actually intend — an off-by-one here is easy to make.
Key Takeaways
NTILE(n)buckets ranked rows intonroughly equal-sized groups — handy for quartiles/percentile buckets.LAST_VALUEis commonly misunderstood: without widening the frame toUNBOUNDED FOLLOWING, it only sees up to the current row.- The frame clause (
ROWS BETWEEN ... AND ...) is what actually defines a moving average window —ORDER BYalone isn't enough.
Advertisement320 x 100