SQL Tutorial · Advanced SQL · Lesson 35 of 36
SQL Window Functions
ROW_NUMBER, RANK, running totals and LAG with OVER (PARTITION BY ...).
Aggregates without collapsing rows
A window function computes a value across a set of related rows while keeping every row. The OVER (...) clause defines the window:
PARTITION BY— split rows into groups (likeGROUP BY, without collapsing).ORDER BY— order inside each partition.
SELECT name, department, salary,
ROUND(AVG(salary) OVER (PARTITION BY department)) AS dept_avg,
ROUND(salary - AVG(salary) OVER (PARTITION BY department)) AS diff
FROM employees
WHERE department IN ('Engineering', 'Sales')
ORDER BY department, salary DESC;
Output
name | department | salary | dept_avg | diff Omar Haddad | Engineering | 210000 | 148400 | 61600 Mei Chen | Engineering | 165000 | 148400 | 16600 Jonas Berg | Engineering | 140000 | 148400 | -8400 Priya Nair | Engineering | 115000 | 148400 | -33400 Lucas Martin | Engineering | 112000 | 148400 | -36400 Hannah Okafor | Sales | 150000 | 109333 | 40667 Diego Silva | Sales | 90000 | 109333 | -19333 Emma Novak | Sales | 88000 | 109333 | -21333
Ranking and top-N per group
ROW_NUMBER(), RANK() and DENSE_RANK() number rows within each partition — the standard way to get "top N per group":
SELECT department, name, salary, rnk
FROM (
SELECT department, name, salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk
FROM employees
) AS ranked
WHERE rnk <= 2
ORDER BY department, rnk;
Output
department | name | salary | rnk Engineering | Omar Haddad | 210000 | 1 Engineering | Mei Chen | 165000 | 2 Management | Sofia Reyes | 250000 | 1 Sales | Hannah Okafor | 150000 | 1 Sales | Diego Silva | 90000 | 2 Support | Ravi Kumar | 65000 | 1
Running totals and LAG
WITH monthly AS (
SELECT strftime('%Y-%m', o.order_date) AS month, SUM(oi.quantity * oi.unit_price) AS revenue
FROM orders o JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.status <> 'cancelled'
GROUP BY month
)
SELECT month, ROUND(revenue, 2) AS revenue,
ROUND(SUM(revenue) OVER (ORDER BY month), 2) AS running_total,
ROUND(revenue - LAG(revenue) OVER (ORDER BY month), 2) AS change
FROM monthly ORDER BY month;
Output
month | revenue | running_total | change 2026-01 | 167.98 | 167.98 | NULL 2026-02 | 561.38 | 729.36 | 393.4 2026-03 | 114.99 | 844.35 | -446.39 2026-04 | 747 | 1591.35 | 632.01 2026-05 | 131.45 | 1722.8 | -615.55 2026-06 | 102 | 1824.8 | -29.45
Window functions are among the most-asked SQL interview topics.
Test yourself
Which clause splits rows into groups for a window function?
GROUP BYPARTITION BYSPLIT BYWINDOW BY
Show answer
Answer: PARTITION BY