Bargains and Budget-Busters
A hard SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.
- Domain
- SQL
- Difficulty
- hard
- Seniority
- L4
Problem
FinOps is auditing cloud spend region by region. Two systems record line items: the provider's cost ledger and the team's internal allocations. Treat them as one combined stream of line items. For each region, name the service behind the single biggest line item and the service behind the single smallest. Return one row per region with the region, its biggest spender, and its cheapest line item.
Every region has both. Find them.
Tables
cloud_costs
Columns: cost_id INTEGER, provider TEXT, svc_name TEXT, region TEXT, amount REAL, acct_id TEXT, bill_date TEXT
| cost_id | provider | svc_name | region | amount | acct_id | bill_date |
|---|---|---|---|---|---|---|
| 543 | GCP | S3 | us-west-2 | 19.33 | acct-1001 | 2026-02-01 |
| 586 | Azure | RDS | eu-west-1 | 37.16 | acct-1002 | 2026-03-01 |
| 629 | aws | Lambda | ap-southeast-1 | 54.99 | acct-1003 | 2026-04-01 |
| 672 | gcp | BigQuery | us-central1 | 72.82 | acct-1004 | 2026-05-01 |
| 715 | AWS | Cloud Run | europe-west1 | 90.65 | acct-1005 | 2026-06-01 |
| 758 | GCP | Blob Storage | us-east-1 | 108.48 | acct-1006 | 2026-07-01 |
| 801 | Azure | Cosmos DB | us-west-2 | 126.31 | acct-1007 | 2026-08-01 |
| 844 | aws | CloudFront | eu-west-1 | 144.14 | acct-1008 | 2026-09-01 |
| 887 | gcp | Pub/Sub | ap-southeast-1 | 161.97 | NULL | 2026-01-01 |
| 930 | AWS | EC2 | us-central1 | 179.8 | acct-1010 | 2026-02-01 |
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
| region | most_expensive | cheapest |
|---|---|---|
| ap-south-1 | RDS | Lambda |
| ap-southeast-1 | CloudFront | Cloud Run |
| eu-central-1 | EKS | SQS |
| eu-west-1 | S3 | CloudFront |
| europe-west1 | BigQuery | Cloud Run |
Worked solution and explanation
Why this problem exists in real interviews
Two tables, one combined stream, two extreme picks per group. The interviewer is checking three things: that you UNION ALL the heterogeneous sources rather than treating one as canonical, that you encode the alphabetical tie-break the prompt specifies into the window's ORDER BY rather than letting the engine pick, and that you reach for FIRST_VALUE within a region partition instead of aggregating away the underlying line items.
Break down the requirements
Step 1: Combine the two sources
UNION ALL cloud_costs and cost_allocs, projecting just region, svc_name, amount from each. The two tables record line items from different sources (provider-billed vs. internally allocated), so a row in one is not a duplicate of the other; UNION ALL preserves every line item, while UNION would deduplicate identical (region, svc, amount) tuples and silently lose data.
Step 2: Compute the two extremes per region with FIRST_VALUE
Inside a CTE, attach two window-function columns: FIRST_VALUE(svc_name) OVER (PARTITION BY region ORDER BY amount DESC, svc_name ASC) and FIRST_VALUE(svc_name) OVER (PARTITION BY region ORDER BY amount ASC, svc_name ASC). The secondary svc_name ASC makes the result deterministic when two line items in a region share the extreme amount; without it, FIRST_VALUE returns whichever row the engine sees first. Each row in the CTE now carries its region's most_expensive and cheapest service names.
Step 3: Collapse to one row per region
SELECT DISTINCT region, most_expensive, cheapest collapses the row count to one per region, since the two window-derived columns are constant within a region partition.
The solution
WITH combined AS (
SELECT region, svc_name, amount FROM cloud_costs
UNION ALL
SELECT region, svc_name, amount FROM cost_allocs
),
ranked AS (
SELECT
region,
svc_name,
amount,
FIRST_VALUE(svc_name) OVER (PARTITION BY region ORDER BY amount DESC, svc_name ASC) AS most_expensive,
FIRST_VALUE(svc_name) OVER (PARTITION BY region ORDER BY amount ASC, svc_name ASC) AS cheapest
FROM combined
)
SELECT DISTINCT region, most_expensive, cheapest
FROM rankedCommon follow-up questions
- How would you sanity-check that `cloud_costs` and `cost_allocs` aren't both recording the same underlying spend before treating them as one combined stream? (Tests whether the candidate spots that the two sources cover overlapping spend. If allocations are a re-cut of the same dollars in the cost ledger, UNION ALL double-counts. Strong answer: confirm the source semantics with the data owner before unioning.)
- How would the query change if the team wanted the most and least expensive services by total spend, not by single line item? (Tests reframing to per-service totals.)
- How would you rewrite this with `ROW_NUMBER` and a WHERE filter instead of `FIRST_VALUE` plus DISTINCT? (Tests reusing the same CTE shape with `ROW_NUMBER` instead.)