Total Cost by Category
An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- easy
- Seniority
- L3
- Asked in
- Siemens
Problem
Every allocation in cost_allocs counts, across all of its periods, and the negative amounts are credits that net against spend rather than being dropped. Give the FinOps team's budget review the total amount per cost category.
Total spend per category.
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 | total_amount |
|---|---|
| compute | 281867.52 |
| database | 290135.47 |
| ml | 274132.65 |
| network | 287382.82 |
| other | 269813.3 |
Worked solution and explanation
Strip off the FinOps wording and this is a one-key rollup of a ledger: six cost categories, one total each, every row landing in exactly one bucket. Nobody fails it for lack of syntax. People fail it by answering a different question. One version is a grand total over cost_allocs that never mentions category. The other is a bare category next to SUM(amount) with nothing bucketing by it, which a permissive engine runs happily, returning one row: the whole table's spend glued to whichever category it read first. The second is the dangerous one, because nothing errors and the review books every dollar to one bucket.
Getting to the query
Step 1: Read the grain off the output
The answer has one row per category, so category is the key every row is bucketed by. The seed holds six values ('compute', 'storage', 'network', 'database', 'ml', 'other') across 200 rows, and the preview already shows that one-row-per-category shape. Read the grain from the shape before writing a clause.
Step 2: Total amount inside each bucket, signs and all
SUM(amount) per group, aliased total_amount so the column says what it holds. Every row keeps its sign: alloc_id 216 is a credit of -120.5 booked to 'ml', and it nets against that category's spend instead of being filtered away. No period filter either: the review covers every period in the table.
SELECT category, SUM(amount) AS total_amount
FROM cost_allocs
GROUP BY categoryCommon follow-up questions
- How would you add each category's share of total spend? (Tests a window over the grouped result: `SUM(amount) / SUM(SUM(amount)) OVER ()`.)
- What if some `amount` values were `NULL`? (`SUM` skips them; it matters once you also report `COUNT(*)` versus `COUNT(amount)`.)
- How would you keep only categories whose total clears a threshold? (The filter is on the total, so it belongs in `HAVING`, not `WHERE`.)