Window Functions¶
Window functions compute a value for each row based on a related set of rows (the window), without collapsing the result into a single row. They require an OVER clause.
Syntax¶
ROW_NUMBER()¶
Assigns a unique sequential integer to each row within the window, starting at 1.
-- Global ranking by score
SELECT name, score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM students;
-- Rank within each department
SELECT name, dept, score,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY score DESC) AS dept_rank
FROM employees;
RANK()¶
Like ROW_NUMBER(), but rows with equal values receive the same rank, and the next rank skips (1, 2, 2, 4, …).
DENSE_RANK()¶
Like RANK(), but without gaps — tied rows share a rank and the next rank is consecutive (1, 2, 2, 3, …).
SELECT name, dept, score,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY score DESC) AS dense_rank
FROM employees;
SUM, AVG, MIN, MAX over a window¶
Aggregate functions can be used as window functions to compute running totals, partition totals, and partition averages.
-- Running sum of scores
SELECT name, score,
SUM(score) OVER (ORDER BY score) AS running_sum
FROM students;
-- Department totals alongside individual rows
SELECT name, dept, salary,
SUM(salary) OVER (PARTITION BY dept) AS dept_total,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg,
MIN(salary) OVER (PARTITION BY dept) AS dept_min,
MAX(salary) OVER (PARTITION BY dept) AS dept_max
FROM employees;
LAG(col, offset)¶
Returns the value of col from offset rows before the current row within the window. Returns NULL for the first offset rows.
LEAD(col, offset)¶
Returns the value of col from offset rows after the current row. Returns NULL for the last offset rows.
LAG and LEAD together¶
SELECT name, score,
LAG(score, 1) OVER (ORDER BY score) AS prev_score,
LEAD(score, 1) OVER (ORDER BY score) AS next_score
FROM students;
Full example¶
CREATE TABLE employees (
id INT8 PRIMARY KEY AUTOINCREMENT,
name VARCHAR(64) NOT NULL,
dept VARCHAR(32) NOT NULL,
salary INT8 NOT NULL
);
INSERT INTO employees (name, dept, salary) VALUES
('Alice', 'eng', 120000),
('Bob', 'eng', 95000),
('Carol', 'sales', 80000),
('Dave', 'sales', 85000),
('Eve', 'eng', 110000);
SELECT
name,
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dense_rk,
SUM(salary) OVER (PARTITION BY dept) AS dept_total,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg
FROM employees
ORDER BY dept, dept_rank;
Notes¶
- Multiple
OVERclauses in the sameSELECTare evaluated independently. PARTITION BYis optional — omitting it treats all rows as a single partition.ORDER BYinsideOVERis independent of the query-levelORDER BY.