Running Totals in SQL
A running total is SUM() over every row up to the current one. Add PARTITION BY and it restarts for each group.
A running total answers "how much so far?" at every row: the balance after
each transaction, revenue to date, the count of signups since launch. In SQL
it's an ordinary aggregate used as a window, with an
ORDER BY
inside
OVER.
select entry_date,
amount,
sum(amount) over (order by entry_date, entry_id) as balance
from ledger How the sum accumulates
With an
ORDER BY
in the window,
sum()
adds up the rows from the start of the window to the current one. Row 1 sees
only itself, row 2 sees rows 1 and 2, and so on, so the last row holds the
grand total. Every row stays in the result, unlike a
GROUP BY
total.
The tie-breaker in the window matters. If two rows share an
order_date
and the window orders by date alone, SQL's default frame treats them as one
step and both show the total after both. Ordering by
order_date, order_id
gives every row its own step.
Lesson 19.4 · Window frames explains
why.
Restarting for each group
Add
PARTITION BY
and the total starts again for each group. Here each customer's spend
accumulates separately:
with order_revenue as (
select o.customer_id,
o.order_id,
o.order_date,
sum(oi.quantity * oi.unit_price) as revenue
from orders as o
join order_items as oi on oi.order_id = o.order_id
where o.status = 'shipped'
group by o.customer_id, o.order_id, o.order_date
)
select customer_id,
order_date,
revenue,
sum(revenue) over (
partition by customer_id
order by order_date, order_id
) as customer_running_revenue
from order_revenue
where customer_id in (1, 10)
order by customer_id, order_date Customer 1 reaches 98 after two shipped orders, and customer 10's total starts again at 57.
Schema · Garden Shop Tables · orders, order_items 2 tables
One row per order. Unshipped orders have a null shipped_date.
One row per line item within an order.
Build a running total of shipped revenue. The starter already computes each
shipped order's revenue. Return order_date,
order_id, revenue, and running_revenue, the total of every shipped order so far. Accumulate in order of
order_date, then order_id, and sort the output the
same way.
Run your query to inspect the result.
Expected answer
- Columns: order_date, order_id, revenue, running_revenue.
- Rows: the 17 shipped orders, oldest first.
- The running total starts at 21.5 and ends at 885.5, the shop's total shipped revenue.
Related
OVER, PARTITION BY, ranking, offsets, and running totals.
Ranking, LAG/LEAD, and running totals at a glance.
Practice ranking, running totals, and top row per group.
The default RANGE frame groups tied rows; use ROWS.