lesson 55 Advanced product analysis · advanced
Multi-grain joins without fan-out
Combine metrics at different levels without multiplying totals.
the idea
Requests and invoices each have many rows per account. Joining raw tables creates requests × invoices and inflates sums. Aggregate each side to one row per account before joining; keep accounts with zero on either side.
tables in this lesson
The challenges use this data. Each column shows its type underneath; → marks a link to another table.
ai_accounts4 columns · 6 rows
| idinteger · key | plantext | regiontext | signed_up_attext |
|---|---|---|---|
| 1 | pro | US | 2025-01-01 |
| 2 | free | US | 2025-01-02 |
| 3 | team | EU | 2025-01-02 |
| 4 | pro | IN | 2025-01-03 |
| 5 | free | EU | 2025-01-04 |
showing 5 of 6 rows
ai_requests7 columns · 12 rows
| idinteger · key | account_idinteger | modeltext | requested_attext | input_tokensinteger | output_tokensinteger | statustext |
|---|---|---|---|---|---|---|
| 1 | 1 | swift | 2025-01-01 10:00:00 | 1000 | 200 | success |
| 2 | 1 | reason | 2025-01-02 11:00:00 | 2000 | 600 | success |
| 3 | 2 | swift | 2025-01-02 12:00:00 | 500 | 0 | error |
| 4 | 3 | reason | 2025-01-02 13:00:00 | 8000 | 2000 | success |
| 5 | 3 | swift | 2025-01-03 14:00:00 | 4000 | 1000 | success |
showing 5 of 12 rows
invoices4 columns · 6 rows
| idinteger · key | account_idinteger | periodtext | billed_amountreal |
|---|---|---|---|
| 1 | 1 | 2025-01 | 20 |
| 2 | 3 | 2025-01 | 80 |
| 3 | 4 | 2025-01 | 20 |
| 4 | 6 | 2025-01 | 80 |
| 5 | 1 | 2025-02 | 20 |
showing 5 of 6 rows
example
The idea in one query. Press Run to see what it returns; running examples never affects your progress.
WITH usage AS( SELECT account_id,COUNT(*) AS request_count FROM ai_requests GROUP BY account_id ), billing AS( SELECT account_id,SUM(billed_amount) AS billed_amount FROM invoices GROUP BY account_id ) SELECT a.id, COALESCE(u.request_count,0) AS request_count, COALESCE(b.billed_amount,0) AS billed_amount FROM ai_accounts a LEFT JOIN usage u ON u.account_id=a.id LEFT JOIN billing b ON b.account_id=a.id;
Watch for: DISTINCT cannot repair inflated SUM values after a many-to-many join.
build it step by step
Each step changes one thing. Run them in order and watch the result change.
Observe join fan-out. Every request pairs with every invoice for the same account. Watch request 1 appear twice.
step-1.sqlSELECT r.id AS request_id,i.id AS invoice_id,i.billed_amount FROM ai_requests r JOIN invoices i ON i.account_id=r.account_id WHERE r.account_id=1;
Summarize requests first. One row per account is a safe grain for the final join.
step-2.sqlSELECT account_id,COUNT(*) AS request_count FROM ai_requests GROUP BY account_id;
Summarize invoices first. This is also one row per account.
step-3.sqlSELECT account_id,SUM(billed_amount) AS billed_amount FROM invoices GROUP BY account_id;
Join summaries. Neither side can multiply the other now.
step-4.sqlWITH usage AS( SELECT account_id,COUNT(*) AS request_count FROM ai_requests GROUP BY account_id ),billing AS( SELECT account_id,SUM(billed_amount) AS billed_amount FROM invoices GROUP BY account_id ) SELECT a.id, COALESCE(u.request_count,0) AS request_count, COALESCE(b.billed_amount,0) AS billed_amount FROM ai_accounts a LEFT JOIN usage u ON u.account_id=a.id LEFT JOIN billing b ON b.account_id=a.id