The Accumulator

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
Sanofi

Problem

What did the cloud bill come to on each billing date in cloud_costs, and how much had been spent in total up to and including that date? A FinOps analyst is charting how spend builds over time and wants the series from the earliest billing date forward.

A total that builds row by row. Structure the query to match.

Tables

cloud_costs

Columns: cost_id INTEGER, bill_date TEXT, amount REAL, provider TEXT, svc_name TEXT, region TEXT, acct_id TEXT

cloud_costs: 10 of 200 rows
cost_idprovidersvc_nameregionamountacct_idbill_date
543GCPS3us-west-219.33acct-10012026-02-01
586AzureRDSeu-west-137.16acct-10022026-03-01
629awsLambdaap-southeast-154.99acct-10032026-04-01
672gcpBigQueryus-central172.82acct-10042026-05-01
715AWSCloud Runeurope-west190.65acct-10052026-06-01
758GCPBlob Storageus-east-1108.48acct-10062026-07-01
801AzureCosmos DBus-west-2126.31acct-10072026-08-01
844awsCloudFronteu-west-1144.14acct-10082026-09-01
887gcpPub/Subap-southeast-1161.97NULL2026-01-01
930AWSEC2us-central1179.8acct-10102026-02-01

Expected output

First 5 of 57 rows
bill_datedaily_spendcumulative_spend
2022-01-011108.461108.46
2022-02-011999.963108.42
2022-03-01910.834019.25
2022-04-0161.979999999999964081.23
2022-05-01732.534813.76

Worked solution and explanation

This is a two-grain problem dressed up as a FinOps chart. The cost lines in cloud_costs sit at one grain, several per bill_date. The answer sits at another, one row per date, and the cumulative only means something at that second grain. Everyone knows the windowed SUM. What separates candidates is collapsing to the date grain before accumulating. Skip that and the dashboard gets 200 rows where it wanted 57, with every date repeated once per cost line.

Two grains, two steps

Step 1: Roll each date up to one figure

GROUP BY bill_date with SUM(amount) turns the several lines on a date into a single daily_spend. Negative amounts are credits and belong in the sum. A line with a missing amount adds nothing, because SUM skips NULLs, so its date still appears.

Step 2: Accumulate over the daily figures, not the raw lines

SUM(daily_spend) OVER (ORDER BY bill_date) reads the daily CTE. With an ORDER BY and no explicit frame, the window covers every earlier date plus the current one, so each row carries spend through and including its date. That is the figure the question asks for.

Step 3: Sort the series oldest first

The ORDER BY inside OVER decides what gets summed, not how rows come back. The outer ORDER BY bill_date is a separate promise to the reader. With one row per date it is already total, so no tiebreaker is needed.

Daily totals first, then the cumulative
WITH daily AS (
    SELECT bill_date, SUM(amount) AS daily_spend
    FROM cloud_costs
    GROUP BY bill_date
)
SELECT bill_date, daily_spend, SUM(daily_spend) OVER (ORDER BY bill_date) AS cumulative_spend
FROM daily
ORDER BY bill_date

The daily CTE fixes the grain; the window only ever sees one row per bill_date.

Window over raw lines
SUM(amount) OVER (ORDER BY bill_date) straight on cloud_costs emits one row per cost line. With the default frame, peers on a date share the same cumulative, so the numbers look right while each date appears three or four times. Switch to a ROWS frame and the repeats turn into partial sums partway through the date.
Window over daily totals
GROUP BY bill_date first, then the window over daily_spend. One row per date, each carrying the full date total and the full cumulative through it. The shape the chart needs.
The one-pass version that returns the wrong grain
SELECT bill_date, amount, SUM(amount) OVER (ORDER BY bill_date) AS cumulative_spend
FROM cloud_costs
ORDER BY bill_date

200 rows, dates repeated once per cost line.

Common follow-up questions