Cloud Bill

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

Problem

The cost_allocs table holds one cloud-spend line item per row, tagged with a spending category, and allocations with no account ID are left out entirely. Report each category's total dollar amount, number of line items and average cost per line item, from the most expensive category to the least.

Which cost buckets are bleeding money?

Tables

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
categorytotal_amountline_itemsavg_cost_per_item
database264042.29328251.3215625
other255764.28318250.46064516129
storage254527.27327953.9771875
compute251210.53288971.804642857143
network246359.82288798.565

Worked solution and explanation

What this really is

This is a row-level exclusion dressed up as a budget report. Anyone can write SUM, COUNT and AVG per category. What separates candidates is where the missing-account rule lives: it has to drop the whole allocation in WHERE acct_id IS NOT NULL, before grouping. The shortcut is to lean on NULL-skipping, write COUNT(acct_id) for the line count, and trust the rest to follow. It does not. SUM(amount) and AVG(amount) never look at acct_id, so the 21 unattributed allocations still pour their dollars into every total, the average stops matching total over count, and the most-expensive-first order can shift.

Step 1: Drop unattributed allocations before anything is totaled

WHERE acct_id IS NOT NULL throws out the entire row, so the 21 allocations with no account never reach SUM, COUNT or AVG. Swapping COUNT(*) for COUNT(acct_id) looks like the same fix, but it repairs one of the three columns and leaves the other two wrong.

Step 2: Total, count and average per category

Every row is its own line item, so COUNT(*) is the count. Nothing repeats, and COUNT(DISTINCT ...) would answer a different question. AVG(amount) divides the same filtered sum by the same filtered count, so the three figures stay consistent with each other.

Step 3: Sort by the dollar total, biggest first

ORDER BY total_amount DESC sorts on the aggregate, not on the category name. Order is graded here, so an alphabetical list with the right numbers still fails.

Null-filtered multi-metric aggregation
SELECT
    category,
    SUM(amount) AS total_amount,
    COUNT(*) AS line_items,
    AVG(amount) AS avg_cost_per_item
FROM cost_allocs
WHERE acct_id IS NOT NULL
GROUP BY category
ORDER BY total_amount DESC
Per-column NULL skipping
COUNT(acct_id) drops the unattributed rows from the line count, but SUM(amount) and AVG(amount) still include their dollars. The total overstates spend, and the average no longer equals total divided by count.
Row-level filter
WHERE acct_id IS NOT NULL removes each unattributed row once, before grouping, so all three metrics describe the same set of allocations.

Common follow-up questions