The Slow Build
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
- Asked in
- Audible
Problem
Each row of transactions is one sale with its total_amount and transaction_date, and a month's revenue is the sum of its sales, refunds included. List every month of 2026 from the first onward with its revenue next to the average monthly revenue of all months so far, that month included, as a whole-dollar integer with the cents dropped.
Month over month, the number grows. Track how the average moves with it.
Tables
transactions
Columns: transaction_id INTEGER, user_id INTEGER, product_id INTEGER, quantity INTEGER, total_amount REAL, transaction_date TEXT
| transaction_id | user_id | product_id | quantity | total_amount | transaction_date |
|---|---|---|---|---|---|
| 1067 | 197 | 1001 | 2 | 23.46 | 2026-02-02 |
| 1134 | 294 | 1050 | 3 | 36.93 | 2026-03-03 |
| 1201 | 391 | 1099 | 4 | 50.4 | 2026-04-04 |
| 1268 | 488 | 1148 | 5 | 63.87 | 2026-05-05 |
| 1335 | 585 | 1197 | 0 | 0 | 2026-06-06 |
| 1402 | 682 | 1246 | 2 | 90.81 | 2026-07-07 |
| 1469 | 779 | 1295 | 3 | 104.28 | 2026-08-08 |
| 1536 | 876 | 1344 | 4 | 117.75 | 2026-09-09 |
| 1603 | 973 | 1393 | 5 | -45 | 2026-01-10 |
| 1670 | 1070 | 1442 | 1 | 144.69 | 2026-02-11 |
products
Columns: product_id INTEGER, product_name TEXT, category TEXT, price REAL, rating REAL, in_stock INTEGER
| product_id | product_name | category | price | rating | in_stock |
|---|---|---|---|---|---|
| 1001 | Basic Device 1X | Books | 17.52 | 2.7 | 1 |
| 1050 | Deluxe Bundle 2X | Clothing | 25.05 | 4.4 | 1 |
| 1099 | Pro Unit 3X | Home & Kitchen | 32.58 | 2.1 | 1 |
| 1148 | Ultra Tool 4X | Collectibles | 40.11 | 3.8 | 1 |
| 1197 | Essential Set 5X | Toys | 47.64 | 1.5 | 1 |
| 1246 | Classic System 6X | Beauty | 0 | 3.2 | 1 |
| 1295 | Smart Gadget 7X | Automotive | 62.7 | 4.9 | 0 |
| 1344 | Eco Kit 8X | Garden | 70.23 | 2.6 | 1 |
| 1393 | Turbo Pack 9X | Music | 77.76 | 4.3 | 0 |
| 1442 | Premium Widget 10X | Electronics | 85.29 | 2 | 1 |
Expected output
| month | monthly_revenue | cumulative_avg |
|---|---|---|
| 2026-01 | 10187.37 | 10187 |
| 2026-02 | 9447.63 | 9817 |
| 2026-03 | 8508.21 | 9381 |
| 2026-04 | 8925.78 | 9267 |
| 2026-05 | 9420.69 | 9297 |
Worked solution and explanation
What this problem really is
Under the revenue-tracker costume, this is a two-level aggregation: an average of monthly totals, scoped to every month so far, then cut to whole dollars. Anyone can sum a month. What separates candidates is putting the cumulative average on top of the grouped totals instead of the raw total_amount rows, and noticing that 'cents dropped' is not 'rounded'. Average the raw rows and every figure collapses to a per-sale number in the tens of dollars. Round instead of truncating and February reads 9818 where the books say 9817.
Build it in two levels
Step 1: Total each month of the year
Keep only 2026 rows with strftime('%Y', transaction_date) = '2026', bucket by strftime('%Y-%m', transaction_date), and take SUM(total_amount). The refund and the voided zero sale stay in: revenue is net. No products join is needed, because revenue lives entirely on transactions.
Step 2: Make the monthly totals the rows
Wrap that aggregate in a subquery so each month is one row with one monthly_revenue. This is the crux: the average must read nine monthly figures, not two hundred sales.
Step 3: Scope the average to the months so far
AVG(monthly_revenue) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) averages January through the current month. Without the ordered frame, every row would show the same full-year figure.
Step 4: Drop the cents with an integer cast
CAST(... AS INTEGER) truncates toward zero, which is what dropping the cents means. ROUND would push 9817.5 up to 9818, so it is the wrong tool here.
The solution
SELECT month, monthly_revenue,
CAST(AVG(monthly_revenue) OVER (ORDER BY month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS INTEGER) AS cumulative_avg
FROM (
SELECT strftime('%Y-%m', transaction_date) AS month,
SUM(total_amount) AS monthly_revenue
FROM transactions
WHERE strftime('%Y', transaction_date) = '2026'
GROUP BY strftime('%Y-%m', transaction_date)
)
ORDER BY month- Rounded average
ROUND(AVG(...))gives February 9818 and May 9298. Both are a dollar more than the year has actually averaged.- Truncated average
CAST(AVG(...) AS INTEGER)gives February 9817 and May 9297: the cents are dropped, never carried up.
Common follow-up questions
- How would this change for a 3-month rolling average instead of a cumulative one? (The frame becomes `ROWS BETWEEN 2 PRECEDING AND CURRENT ROW`, and the first two months average fewer values.)
- What if the average should span every year but reset each January? (Drop the year filter and add `PARTITION BY substr(month, 1, 4)` to the window.)
- What if the cents must be kept to two places instead? (The integer cast goes away and `ROUND(..., 2)` takes its place, which changes the type the output carries.)