Service Budget per Head

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
Microsoft

Problem

Team names in cost_allocs were typed by hand, so a name that differs only in letter case is still the same team. A service's budget exposure grows with its billing volume: every allocation dollar counts once for each line that service has in cloud_costs, and a service with no billing lines is left off the report entirely. An analyst wants each service's budget per head, its exposure split evenly across its unique teams, rounded to a whole number.

Budget per head. Pipeline by pipeline.

Tables

cloud_costs

Columns: cost_id INTEGER, provider TEXT, svc_name TEXT, region TEXT, amount REAL, acct_id TEXT, bill_date 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

cost_allocs

Columns: alloc_id INTEGER, team_name TEXT, svc_name TEXT, region TEXT, amount REAL, period TEXT, acct_id TEXT, category TEXT

cost_allocs: 10 of 200 rows
alloc_idteam_namesvc_nameregionamountperiodacct_idcategory
129data-engS3us-west-283.212026-02acct-101storage
158ml-teamRDSeu-west-1156.422026-03acct-102network
187frontendLambdaap-south-1229.632026-04acct-103database
216backendBigQueryus-central1-120.52026-05acct-104ml
245devopsCloudFronteurope-west1376.052026-06acct-105other
274securityEKSap-southeast-1449.262026-07acct-106compute
303mobileSQSeu-central-1522.472026-08acct-107storage
332PlatformEC2us-east-1595.682026-09NULLnetwork
361DATA-ENGS3us-west-2668.892026-01acct-109database
390platformRDSeu-west-1742.12026-02acct-110ml

Expected output

First 5 of 6 rows
svc_namebudget_per_head
BigQuery509340
CloudFront521188
EC2390893
Lambda505513
RDS546230

Worked solution and explanation

What this really is

This is a fan-out you keep on purpose, with a divisor that has to survive it. Joining cloud_costs to cost_allocs on svc_name copies every allocation row once per billing line, and the statement wants exactly that in the numerator. Anyone can write the join. What separates candidates is seeing that the same copies land in the divisor too, and that only a unique count of teams is immune to them. The second trap is quieter: team_name was typed by hand, so one team appears in two letter cases. Count the raw spellings and every affected service splits its exposure across a phantom team, landing well under the true figure.

Build it in three moves

Step 1: Match on svc_name and nothing else

An INNER JOIN on svc_name keeps only services that have both billing lines and allocated teams, which is the report's row set. Adding region or acct_id to the condition looks careful and silently shrinks every total.

Step 2: Let SUM(ca.amount) fan out

After the join each allocation dollar appears once per billing line of its service, so the plain sum already is the exposure. Deduping cloud_costs before the join removes the very multiplier the statement asks for.

Step 3: Count each team once, whatever its casing

COUNT(DISTINCT LOWER(ca.team_name)) collapses the join copies and the case variants into one entry per real team. Multiplying by 1.0 before dividing keeps an integer amount from truncating, and ROUND takes it to a whole number.

Budget per head across billed services
SELECT cc.svc_name,
       ROUND(SUM(ca.amount) * 1.0 / COUNT(DISTINCT LOWER(ca.team_name))) AS budget_per_head
FROM cloud_costs cc
INNER JOIN cost_allocs ca ON cc.svc_name = ca.svc_name
GROUP BY cc.svc_name

The join shapes the numerator; LOWER inside the unique count keeps the divisor honest.

The tempting version
SELECT cc.svc_name,
       ROUND(SUM(ca.amount) * 1.0 / COUNT(ca.team_name)) AS budget_per_head
FROM cloud_costs cc
INNER JOIN cost_allocs ca ON cc.svc_name = ca.svc_name
GROUP BY cc.svc_name

Wrong: the divisor counts fanned-out rows, not teams.

Spelling as written
COUNT(DISTINCT ca.team_name) treats 'Data Platform' and 'data platform' as two teams, so the divisor is too big wherever the casing drifted.
One team per name
COUNT(DISTINCT LOWER(ca.team_name)) folds the variants together, and every service splits its exposure across the teams that actually exist.

Common follow-up questions