Running Tab

A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
medium
Seniority
L4

Problem

When a customer buys more than once on the same day, each of those purchases should show the same figure: everything the customer has spent through the end of that day. A finance analyst wants every purchase in transactions listed next to that spend-to-date figure for its customer.

Every purchase adds to the total. Watch the tab grow.

Tables

transactions

Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT

transactions: 10 of 200 rows
transaction_iduser_idproduct_idquantitytotal_amounttransaction_date
10671971001223.462026-02-02
11342941050336.932026-03-03
12013911099450.42026-04-04
12684881148563.872026-05-05
13355851197002026-06-06
14026821246290.812026-07-07
146977912953104.282026-08-08
153687613444117.752026-09-09
160397313935-452026-01-10
1670107014421144.692026-02-11

Expected output

First 5 of 200 rows
user_idtransaction_datetotal_amountcumulative_spend
1002026-01-071222.291222.29
1002026-01-18616.141838.4299999999998
1002026-03-03414.092252.52
1002026-03-201020.243272.7599999999998
1002026-04-03414.093686.85

Worked solution and explanation

This is a running balance dressed up as a finance report. The skill is carrying a per-customer spend-to-date onto every purchase row without losing a single row. Anyone can total spend per customer. What separates candidates is keeping all 200 rows, resetting the tally for each customer, and noticing that two purchases on the same day share one figure. Collapse the rows and finance gets 15 totals instead of 200 balances. Step strictly row by row and one of user 1167's two same-day purchases reports a balance that leaves out the other.

Three decisions

Step 1: Keep one row per purchase

The answer has as many rows as transactions: 200. That rules out GROUP BY user_id, which folds each customer into one row and throws away transaction_date and total_amount. You need an aggregate computed per row that leaves the rows alone, SUM(total_amount) OVER (...), or an equivalent correlated sum of each customer's earlier purchases.

Step 2: Reset the tally with PARTITION BY user_id

Without the partition the sum runs across the whole table, so customer 197's balance starts with everything customer 100 spent. The partition is what makes the figure a customer's own spend.

Step 3: Order by transaction_date and keep the default frame

Once the window has an ORDER BY, the default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: every row dated on or before the current one, peers included. Both of user 1167's same-day purchases therefore get the full day's total, which is exactly 'spent through the end of that day'.

Spend to date per customer
SELECT
  user_id,
  transaction_date,
  total_amount,
  SUM(total_amount) OVER (
    PARTITION BY user_id
    ORDER BY transaction_date
  ) AS cumulative_spend
FROM transactions
The collapse to avoid
SELECT user_id, SUM(total_amount) AS total_spend
FROM transactions
GROUP BY user_id

Returns 15 rows of final totals: no transaction_date, no balance along the way.

Strictly row by row
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW gives each same-day purchase its own figure. With transaction_date as the only sort key, the engine decides which of the two comes first, so one balance omits the other and the answer can change between runs.
By date (the default)
The default RANGE frame treats same-date rows as peers. Both rows report the day's closing spend, the result is deterministic, and it matches a reconciliation done by date.

Common follow-up questions