Two Sides of the Ledger

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
L5
Asked in
Uber

Problem

Reconciling each region's budget, an analyst keeps one monthly ledger where every charge in cloud_costs draws the region's balance down and every allocation in cost_allocs builds it back up, each booked in whole dollars with the cents dropped. Charges are stamped with a full billing date while allocations are recorded by month, so each charge counts toward its billing month. For every region and month, show the net movement and the balance standing after it, regions alphabetically with each region's months earliest first.

Two cost tables, one region. Which way does the balance tip?

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 158 rows
regionevent_dateamtrunning_balance
ap-south-12023-043210732107
ap-south-12023-122481656923
ap-south-12024-082627483197
ap-south-12025-0427732110929
ap-south-12025-1229190140119

Worked solution and explanation

What this really is

Strip the FinOps costume and this is a signed running total over two differently shaped tables, reconciled on a monthly grid. Everyone writes the UNION ALL and the window; what separates candidates is the sign. cloud_costs.amount is stored positive but is money leaving the business, so it is negated before it enters the sum, while cost_allocs.amount stays positive. Miss that and the balance only climbs, quietly hiding every regional deficit. The second silent killer is the partition: drop PARTITION BY region and one region's balance bleeds into the next, collapsing every timeline into one meaningless line.

Build it in four moves

Step 1: Book each movement as signed whole dollars

From cloud_costs take -CAST(amount AS INTEGER), and from cost_allocs take CAST(amount AS INTEGER). The cast drops the cents toward zero, so '19.33' books as 19 and the '-120.5' clawback books as -120. Do it per row, before any sum: the ledger books each charge in whole dollars, not each month.

Step 2: Put charges on the allocation calendar

SUBSTR(bill_date, 1, 7) turns '2026-02-01' into '2026-02', the same shape as period, so a charge and an allocation in the same month share one key. Stack the two sources with UNION ALL, never UNION: two equal movements in one region and month are both real money.

Step 3: Net each region and month

GROUP BY region, event_date with SUM(amt) collapses every booking into one net movement per region and month. That makes each output row unique on its keys, which is what gives the running balance a well-defined order.

Step 4: Carry the balance forward per region

SUM(amt) OVER (PARTITION BY region ORDER BY event_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) restarts at each region and adds every month up to the current one. Sort by region, then event_date, so each region's timeline reads top to bottom.

Signed whole-dollar events, netted per month, balanced per region
WITH ledger_events AS (
    SELECT region,
           -CAST(amount AS INTEGER) AS amt,
           SUBSTR(bill_date, 1, 7) AS event_date
    FROM cloud_costs
    UNION ALL
    SELECT region,
           CAST(amount AS INTEGER) AS amt,
           period AS event_date
    FROM cost_allocs
),
monthly_net AS (
    SELECT region, event_date, SUM(amt) AS amt
    FROM ledger_events
    GROUP BY region, event_date
)
SELECT region,
       event_date,
       amt,
       SUM(amt) OVER (
           PARTITION BY region
           ORDER BY event_date
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS running_balance
FROM monthly_net
ORDER BY region, event_date
Drop cents per booking
SUM(CAST(amount AS INTEGER)): every charge loses its own cents, which is how the ledger books it. Three charges of '10.60' net to 30.
Drop cents per month
CAST(SUM(amount) AS INTEGER): the cents pile up before the cut. The same three charges net to 31, and the error compounds month over month in the balance.

Common follow-up questions