Where the Money Went

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
LinkedIn

Problem

We reconcile each team's budget allocation in cost_allocs against the actual charge in cloud_costs for the same service in the same billing month, prorating every pairing to an hourly figure by multiplying the allocated amount by the actual cost and dividing by the hours in a year. Return each service with its total prorated cost, keeping only the services whose reconciled total tops 10,000.

Allocated on paper. Spent for real. Reconcile the two.

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

batch_jobs

Columns: job_id INTEGER, job_name TEXT, status TEXT, rows_done INTEGER, started TEXT, ended TEXT, priority INTEGER, retries INTEGER

batch_jobs: 10 of 200 rows
job_idjob_namestatusrows_donestartedendedpriorityretries
1047user_exportrunning972026-02-02 01:00:002026-02-02 03:11:0030
1094data_cleanupfailed1942026-03-03 02:00:002026-03-03 04:22:0060
1141email_blastqueued2912026-04-04 03:00:002026-04-04 05:33:0090
1188index_rebuildcanceled3882026-05-05 04:00:00NULL20
1235cache_warmCompleted4852026-06-06 05:00:002026-06-06 07:55:0050
1282log_archiveFAILEDNULL2026-07-07 06:00:002026-07-07 08:06:0080
1329metric_aggcompleted6792026-08-08 07:00:002026-08-08 09:17:0010
1376backup_dbrunning7762026-09-09 08:00:00NULL40
1423sync_elasticfailed8732026-01-10 09:00:002026-01-10 11:39:0075
1470daily_reportqueued9702026-02-11 10:00:002026-02-11 12:50:0000

Expected output

First 4 of 4 rows
svc_nameprorated_cost
BigQuery12913.290949178083
EC211479.357435616439
Lambda24099.861013755708
RDS16495.44568415525

Worked solution and explanation

What this really is

Underneath the cost-accounting story, this is a join on a key that does not exist yet. An allocation's period reads '2026-02' while a charge's bill_date reads '2026-02-01', so a straight equality between them matches nothing: you have to manufacture the month from the date before the join can fire at all. After that it is a plain sum of a per-row product, kept only for the services whose reconciled total clears a materiality bar. The schema also hands you batch_jobs, which shares no meaningful key with either cost table: joining it buys nothing, and if its key fanned out it would silently multiply every total. Get the month wrong and you get zero rows; drag in the wrong table and you cloud a clean aggregate.


Break down the requirements

Step 1: Derive the month, then join on service

The join key is a month that is not stored as a month. cost_allocs.period is year-month ('2026-02') while cloud_costs.bill_date is a full date ('2026-02-01'). Derive the month with strftime('%Y-%m', cc.bill_date) and match it to ca.period, together with svc_name. Leave region out of the key: the two tables disagree on it for the same service and month. A raw bill_date = period equality returns zero rows, the most common way this problem dies.

Step 2: Recognize the distractor table

The schema also offers batch_jobs, but job_id has no relationship to an allocation or a charge, and none of its columns feed the reconciliation. Joining it adds nothing, and if job_id were non-unique the extra rows would multiply into the sum and inflate every total. The disciplined move is to leave it out entirely.

Step 3: Prorate each pairing

Multiply the allocated amount by the actual cloud cost and divide by the hours in a year (8760 = 365 * 24): ca.amount * cc.amount / 8760.0. Write 8760.0 so the division is floating point on an engine that would otherwise truncate integers.

Step 4: Aggregate, then filter on the reconciled total

Sum per service with GROUP BY ca.svc_name, then keep only services whose total tops 10,000 with HAVING SUM(...) > 10000. The threshold bites on the seed: the larger services clear it and the smaller ones fall away, so dropping the HAVING returns extra services and grades wrong.


The solution

Two-table join with proration formula
SELECT
    ca.svc_name AS svc_name,
    SUM(ca.amount * cc.amount / 8760.0) AS prorated_cost
FROM cost_allocs ca
INNER JOIN cloud_costs cc
    ON ca.svc_name = cc.svc_name
    AND ca.period = strftime('%Y-%m', cc.bill_date)
GROUP BY ca.svc_name
HAVING SUM(ca.amount * cc.amount / 8760.0) > 10000;

Common follow-up questions