Skip to content
Reliable Data Engineering
Practice problem easy window-functionsrunning-totalframes
Solve it in the browser (SQL editor)

Running Total of Revenue per Customer

Difficulty: Easy · Topics: window-functions, running-total, frames · Asked at: Amazon, Stripe, Shopify

Problem

For each order, return customer_id, order_id, order_date, amount and running_total: the customer’s cumulative spend up to and including this order, processing orders by order_date then order_id. Order the output by customer_id, order_date, order_id.

Schema and sample data

CREATE TABLE orders (order_id INTEGER, customer_id INTEGER, order_date TEXT, amount INTEGER);
INSERT INTO orders VALUES
(1, 1, '2026-01-01', 100),(2, 1, '2026-01-05', 50),(3, 1, '2026-01-05', 25),
(4, 2, '2026-01-02', 300),(5, 2, '2026-01-09', 20),(6, 1, '2026-02-01', 75);

Expected output

customer_idorder_idorder_dateamountrunning_total
112026-01-01100100
122026-01-0550150
132026-01-0525175
162026-02-0175250
242026-01-02300300
252026-01-0920320

Hints

Hint 1

SUM(amount) OVER (PARTITION BY ... ORDER BY ...)

Hint 2

Customer 1 has two orders on 2026-01-05. What does the default frame do with ties?

Solution

SELECT customer_id, order_id, order_date, amount,
       SUM(amount) OVER (PARTITION BY customer_id
                         ORDER BY order_date, order_id
                         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM orders
ORDER BY customer_id, order_date, order_id;

Explanation

With only ORDER BY order_date, the default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Both 2026-01-05 orders are peers and both would show 175, which isn’t a running total per order. Adding a unique tie-breaker (order_id) and an explicit ROWS frame produces 150 then 175.

Follow-up questions

How do you reset the running total each month?

Add the month to the partition: PARTITION BY customer_id, strftime('%Y-%m', order_date).

How do you find the order where each customer crossed 150 in lifetime spend?

Compute the running total, then keep the first row per customer where running_total >= 150 (ROW_NUMBER over those rows, or MIN(order_date) with the condition).