Cloud Cost Breakdown by Provider
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
- ESPN
Problem
The FinOps team wants a spend breakdown covering the two most recently completed years, so leave out anything from the current partial year. For each cost category and year in the allocation records, show the total spend, how much of it landed in the us-east-1 region, and the number of line items, from the highest total spend down.
Cloud costs, provider by provider.
Tables
cost_allocs
Columns: alloc_id INTEGER, team_name TEXT, svc_name TEXT, region TEXT, amount REAL, period TEXT, acct_id TEXT, category TEXT
| alloc_id | team_name | svc_name | region | amount | period | acct_id | category |
|---|---|---|---|---|---|---|---|
| 129 | data-eng | S3 | us-west-2 | 83.21 | 2026-02 | acct-101 | storage |
| 158 | ml-team | RDS | eu-west-1 | 156.42 | 2026-03 | acct-102 | network |
| 187 | frontend | Lambda | ap-south-1 | 229.63 | 2026-04 | acct-103 | database |
| 216 | backend | BigQuery | us-central1 | -120.5 | 2026-05 | acct-104 | ml |
| 245 | devops | CloudFront | europe-west1 | 376.05 | 2026-06 | acct-105 | other |
| 274 | security | EKS | ap-southeast-1 | 449.26 | 2026-07 | acct-106 | compute |
| 303 | mobile | SQS | eu-central-1 | 522.47 | 2026-08 | acct-107 | storage |
| 332 | Platform | EC2 | us-east-1 | 595.68 | 2026-09 | NULL | network |
| 361 | DATA-ENG | S3 | us-west-2 | 668.89 | 2026-01 | acct-109 | database |
| 390 | platform | RDS | eu-west-1 | 742.1 | 2026-02 | acct-110 | ml |
Expected output
| category | yr | total_spend | us_east_spend | entry_count |
|---|---|---|---|---|
| other | 2025 | 57289.64 | 0 | 4 |
| ml | 2025 | 56925.12 | 0 | 4 |
| database | 2025 | 56560.6 | 0 | 4 |
| other | 2024 | 52915.4 | 0 | 4 |
| ml | 2024 | 52550.88 | 25728.66 | 4 |
Worked solution and explanation
What this really is
This is a pivot hiding inside a cost report. Anyone can group by category and year and sum the spend; the skill being probed is producing the us-east-1 column in the SAME pass, as SUM(CASE WHEN region = 'us-east-1' THEN amount ELSE 0 END) beside the plain SUM(amount). Reach instead for a second query filtered to 'us-east-1' and you silently drop every category-year that spent nothing there, so your regional numbers no longer line up with your totals and the counts stop matching. The other quiet trap: period is a YYYY-MM string, so the year is its first four characters, not the output of any date function.
Working through it
Step 1: Pull the year off the period string
period is text like '2025-03', not a date. Take substr(period, 1, 4) to get the year. This is robust and dialect-free; a YEAR(period) or a cast to date is the thing that bites you here.
Step 2: Aggregate per category and year
Group by category and the extracted year. Total spend is SUM(amount); line items is COUNT(*). Keep them separate: spend and count answer different questions, and COUNT(*) still counts rows whose acct_id is null.
Step 3: Fold in the us-east-1 column
Add the regional figure as SUM(CASE WHEN region = 'us-east-1' THEN amount ELSE 0 END) in the same SELECT. Because it rides the same GROUP BY, a group with no us-east-1 rows reads 0 instead of disappearing. Match 'us-east-1' exactly: 'us-west-2' and the GCP regions are not it.
Step 4: Window to two years and order
Bound the window on both ends: substr(period, 1, 4) >= '2024' drops the older years and <= '2025' drops the current partial year, leaving exactly the two most recently completed years. Then order by total_spend descending, with category and yr as tie-breaks held in reserve.
The solution
SELECT
category,
substr(period, 1, 4) AS yr,
SUM(amount) AS total_spend,
SUM(CASE WHEN region = 'us-east-1' THEN amount ELSE 0 END) AS us_east_spend,
COUNT(*) AS entry_count
FROM cost_allocs
WHERE substr(period, 1, 4) >= '2024'
AND substr(period, 1, 4) <= '2025'
GROUP BY category, yr
ORDER BY total_spend DESC, category, yrCommon follow-up questions
- How would you add year-over-year growth as a column? (Tests a `LAG` window function partitioned by `category` ordered by `yr`.)
- What if the FinOps team wanted every region as its own column? (Tests dynamic pivoting via extensive conditional aggregation, one `CASE`-sum per region.)
- How would your query change if `period` stored a full month-end date instead of a YYYY-MM string? (Tests whether the year extraction still holds when the grain changes to a real date.)