Windows keep your rows
A window function adds a group answer to each row. No row disappears.
In chapter 3, GROUP BY gave one row for each group. The other rows disappeared. A window function gives you the group answer and keeps every row.
First, the GROUP BY way. This query adds up the amounts for each customer:
| order_id | customer_id | status | amount | order_date |
|---|---|---|---|---|
| 101 | 1 | paid | 40.00 | 2026-01-03 |
| 102 | 1 | paid | 25.00 | 2026-01-10 |
| 103 | 2 | refunded | 60.00 | 2026-01-11 |
| 104 | 3 | paid | 15.50 | 2026-01-15 |
| 105 | 3 | pending | NULL | 2026-01-20 |
| 106 | 4 | paid | 80.00 | 2026-01-21 |
SELECT customer_id,
SUM(amount) AS total
FROM orders
GROUP BY customer_id
ORDER BY customer_id;| customer_id | total |
|---|---|
| 1 | 65.00 |
| 2 | 60.00 |
| 3 | 15.50 |
| 4 | 80.00 |
Now the same total as a window function. A window is a set of related rows. The function looks at the window and writes its answer on every row:
SELECT order_id,
customer_id,
amount,
SUM(amount) OVER (PARTITION BY customer_id) AS customer_total
FROM orders
ORDER BY order_id;| order_id | customer_id | amount | customer_total |
|---|---|---|---|
| 101 | 1 | 40.00 | 65.00 |
| 102 | 1 | 25.00 | 65.00 |
| 103 | 2 | 60.00 | 60.00 |
| 104 | 3 | 15.50 | 15.50 |
| 105 | 3 | NULL | 15.50 |
| 106 | 4 | 80.00 | 80.00 |
Orders 101 and 102 both show 65.00, the total for customer 1. Order 105 shows 15.50, because SUM skips its NULL amount.
The part after OVER says which rows belong together. PARTITION BY customer_id means one group for each customer.
What you will learn
In this chapter, you learn to:
- Write the OVER clause, and number rows with ROW_NUMBER.
- Find the share of the total, and keep the latest row for each key.
- Rank rows when values tie, and take the top N for each group.
- Find a median, compare a row with the row before it, and count days since a first order.
- Build running totals, and avoid the default frame trap.
- Filter a window result, and know why QUALIFY is not in PostgreSQL.
The examples use the shop from the earlier chapters and PostgreSQL. A CTE (WITH name AS (...)) is a named step from chapter 4. We use it often here.
