Skip to content
/ Chapter 19 · Window Functions in Depth
Lesson 19.3 · garden_shop
Lesson 19.3

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.

Pattern
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:

Running spend per customer
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
Table · orders

One row per order. Unshipped orders have a null shipped_date.

6 columns · 24 rows
order_id int customer_id int order_date date shipped_date date status text coupon_code text
Table · order_items

One row per line item within an order.

5 columns · 48 rows
order_item_id int order_id int product_id int quantity int unit_price decimal
Your task

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.