runtime
55 / 88

lesson 55 Advanced product analysis · advanced

Multi-grain joins without fan-out

Combine metrics at different levels without multiplying totals.

the idea

Joining a parent to two independent child tables multiplies rows: each settlement row is repeated once per refund, so summing settlement amounts after the join double-counts. Aggregate each child table to the shared grain first, then join the summaries.

Dataset: Finance / Payments

The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.

accounts7 columns · 2,109 rows
account_idnumber · unique idcustomer_idnumber · links to customersaccount_typetextopened_atdateclosed_attextcredit_limit_centsnumberopening_balance_centsnumber
11checking2022-02-24NULLNULL43125
22checking2025-06-11NULLNULL0
33checking2024-07-07NULLNULL216925
43savings2024-10-07NULLNULL475941
53credit_card2024-09-26NULL1020600-339148
64checking2023-07-18NULLNULL155995
account_idnumber · unique id
1
customer_idnumber · links to customers
1
account_typetext
checking
opened_atdate
2022-02-24
closed_attext
NULL
credit_limit_centsnumber
NULL
opening_balance_centsnumber
43125
account_idnumber · unique id
2
customer_idnumber · links to customers
2
account_typetext
checking
opened_atdate
2025-06-11
closed_attext
NULL
credit_limit_centsnumber
NULL
opening_balance_centsnumber
0
account_idnumber · unique id
3
customer_idnumber · links to customers
3
account_typetext
checking
opened_atdate
2024-07-07
closed_attext
NULL
credit_limit_centsnumber
NULL
opening_balance_centsnumber
216925
account_idnumber · unique id
4
customer_idnumber · links to customers
3
account_typetext
savings
opened_atdate
2024-10-07
closed_attext
NULL
credit_limit_centsnumber
NULL
opening_balance_centsnumber
475941
account_idnumber · unique id
5
customer_idnumber · links to customers
3
account_typetext
credit_card
opened_atdate
2024-09-26
closed_attext
NULL
credit_limit_centsnumber
1020600
opening_balance_centsnumber
-339148
account_idnumber · unique id
6
customer_idnumber · links to customers
4
account_typetext
checking
opened_atdate
2023-07-18
closed_attext
NULL
credit_limit_centsnumber
NULL
opening_balance_centsnumber
155995

first 6 of 2,109 rows · your queries use all of them

cards8 columns · 1,774 rows
card_idnumber · unique idaccount_idnumber · links to accountscard_typetextnetworktextlast4textissued_atdateexpires_ondatestatustext
11debitvisa19952024-09-062028-09-05active
22debitvisa32522025-06-112029-06-10active
33debitvisa12412024-07-132028-07-12active
45creditvisa30442024-09-262028-09-25active
56debitmastercard99452023-07-182027-07-17active
68debitvisa74432023-08-232027-08-22active
card_idnumber · unique id
1
account_idnumber · links to accounts
1
card_typetext
debit
networktext
visa
last4text
1995
issued_atdate
2024-09-06
expires_ondate
2028-09-05
statustext
active
card_idnumber · unique id
2
account_idnumber · links to accounts
2
card_typetext
debit
networktext
visa
last4text
3252
issued_atdate
2025-06-11
expires_ondate
2029-06-10
statustext
active
card_idnumber · unique id
3
account_idnumber · links to accounts
3
card_typetext
debit
networktext
visa
last4text
1241
issued_atdate
2024-07-13
expires_ondate
2028-07-12
statustext
active
card_idnumber · unique id
4
account_idnumber · links to accounts
5
card_typetext
credit
networktext
visa
last4text
3044
issued_atdate
2024-09-26
expires_ondate
2028-09-25
statustext
active
card_idnumber · unique id
5
account_idnumber · links to accounts
6
card_typetext
debit
networktext
mastercard
last4text
9945
issued_atdate
2023-07-18
expires_ondate
2027-07-17
statustext
active
card_idnumber · unique id
6
account_idnumber · links to accounts
8
card_typetext
debit
networktext
visa
last4text
7443
issued_atdate
2023-08-23
expires_ondate
2027-08-22
statustext
active

first 6 of 1,774 rows · your queries use all of them

authorizations10 columns · 40,508 rows
authorization_idnumber · unique idrequest_keytextcard_idnumber · links to cardsmerchant_idnumber · links to merchantsrequested_atdate & timeamountdecimalcurrencytextchanneltextstatustextdecline_reasontext
1req_000000114652025-01-06 18:49:43100USDin_storedeclinedinsufficient_funds
2req_000000214202025-01-08 20:45:556.9USDin_storedeclinedinsufficient_funds
3req_00000031322025-01-21 15:28:5376.55USDin_storeapprovedNULL
4req_000000415162025-01-27 11:09:51175USDin_storeapprovedNULL
5req_000000511592025-01-29 15:06:2157.71USDin_storeapprovedNULL
6req_000000612012025-02-09 23:15:3437.17USDonlineapprovedNULL
authorization_idnumber · unique id
1
request_keytext
req_0000001
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
465
requested_atdate & time
2025-01-06 18:49:43
amountdecimal
100
currencytext
USD
channeltext
in_store
statustext
declined
decline_reasontext
insufficient_funds
authorization_idnumber · unique id
2
request_keytext
req_0000002
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
420
requested_atdate & time
2025-01-08 20:45:55
amountdecimal
6.9
currencytext
USD
channeltext
in_store
statustext
declined
decline_reasontext
insufficient_funds
authorization_idnumber · unique id
3
request_keytext
req_0000003
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
32
requested_atdate & time
2025-01-21 15:28:53
amountdecimal
76.55
currencytext
USD
channeltext
in_store
statustext
approved
decline_reasontext
NULL
authorization_idnumber · unique id
4
request_keytext
req_0000004
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
516
requested_atdate & time
2025-01-27 11:09:51
amountdecimal
175
currencytext
USD
channeltext
in_store
statustext
approved
decline_reasontext
NULL
authorization_idnumber · unique id
5
request_keytext
req_0000005
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
159
requested_atdate & time
2025-01-29 15:06:21
amountdecimal
57.71
currencytext
USD
channeltext
in_store
statustext
approved
decline_reasontext
NULL
authorization_idnumber · unique id
6
request_keytext
req_0000006
card_idnumber · links to cards
1
merchant_idnumber · links to merchants
201
requested_atdate & time
2025-02-09 23:15:34
amountdecimal
37.17
currencytext
USD
channeltext
online
statustext
approved
decline_reasontext
NULL

first 6 of 40,508 rows · your queries use all of them

settlements4 columns · 37,071 rows
settlement_idnumber · unique idauthorization_idnumber · links to authorizationssettled_atdate & timeamount_centsnumber
132025-01-23 01:12:537655
242025-01-30 13:04:516852
352025-01-30 17:41:215771
462025-02-11 06:57:343717
572025-02-11 19:25:534401
682025-02-20 03:08:189288
settlement_idnumber · unique id
1
authorization_idnumber · links to authorizations
3
settled_atdate & time
2025-01-23 01:12:53
amount_centsnumber
7655
settlement_idnumber · unique id
2
authorization_idnumber · links to authorizations
4
settled_atdate & time
2025-01-30 13:04:51
amount_centsnumber
6852
settlement_idnumber · unique id
3
authorization_idnumber · links to authorizations
5
settled_atdate & time
2025-01-30 17:41:21
amount_centsnumber
5771
settlement_idnumber · unique id
4
authorization_idnumber · links to authorizations
6
settled_atdate & time
2025-02-11 06:57:34
amount_centsnumber
3717
settlement_idnumber · unique id
5
authorization_idnumber · links to authorizations
7
settled_atdate & time
2025-02-11 19:25:53
amount_centsnumber
4401
settlement_idnumber · unique id
6
authorization_idnumber · links to authorizations
8
settled_atdate & time
2025-02-20 03:08:18
amount_centsnumber
9288

first 6 of 37,071 rows · your queries use all of them

refunds5 columns · 1,230 rows
refund_idnumber · unique idsettlement_idnumber · links to settlementsrefunded_atdate & timeamount_centsnumberreasontext
1252025-02-12 02:56:322820returned_item
2272025-03-01 03:02:5211883returned_item
32682025-03-01 07:28:00367canceled_service
43132025-05-30 14:18:371601returned_item
53132025-06-15 14:18:372440canceled_service
63482025-05-11 19:21:1719843price_adjustment
refund_idnumber · unique id
1
settlement_idnumber · links to settlements
25
refunded_atdate & time
2025-02-12 02:56:32
amount_centsnumber
2820
reasontext
returned_item
refund_idnumber · unique id
2
settlement_idnumber · links to settlements
27
refunded_atdate & time
2025-03-01 03:02:52
amount_centsnumber
11883
reasontext
returned_item
refund_idnumber · unique id
3
settlement_idnumber · links to settlements
268
refunded_atdate & time
2025-03-01 07:28:00
amount_centsnumber
367
reasontext
canceled_service
refund_idnumber · unique id
4
settlement_idnumber · links to settlements
313
refunded_atdate & time
2025-05-30 14:18:37
amount_centsnumber
1601
reasontext
returned_item
refund_idnumber · unique id
5
settlement_idnumber · links to settlements
313
refunded_atdate & time
2025-06-15 14:18:37
amount_centsnumber
2440
reasontext
canceled_service
refund_idnumber · unique id
6
settlement_idnumber · links to settlements
348
refunded_atdate & time
2025-05-11 19:21:17
amount_centsnumber
19843
reasontext
price_adjustment

first 6 of 1,230 rows · your queries use all of them

worked example

Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.

  1. Find settlements with many refunds. Run this. refunds > 1 means one charge spawned several partial refund rows—the pattern that breaks naive joins.the pattern

    example.sql
    SELECT s.settlement_id, COUNT(r.refund_id) AS refunds
    FROM settlements s
    LEFT JOIN refunds r ON r.settlement_id = s.settlement_id
    GROUP BY s.settlement_id
    HAVING COUNT(r.refund_id) > 1
    LIMIT 10;
  2. Watch totals double-count. Run this. joined_sum adds settlement amounts once per refund row; true_sum is the correct ledger total.

    step-2.sql
    SELECT
      SUM(s.amount_cents) AS joined_sum,
      (
        SELECT SUM(amount_cents)
        FROM settlements
      ) AS true_sum
    FROM settlements s
    LEFT JOIN refunds r ON r.settlement_id = s.settlement_id;
  3. Roll refunds up first. Run this. One row per settlement_id with total refunded cents—safe to join without multiplying settlements.

    step-3.sql
    SELECT
      settlement_id,
      COUNT(*) AS refunds,
      SUM(amount_cents) AS refunded_cents
    FROM refunds
    GROUP BY settlement_id
    ORDER BY refunds DESC
    LIMIT 10;
  4. Join summaries, not raw refunds. Run this. settled_cents and refunded_cents match because each settlement appears once on the left.

    step-4.sql
    SELECT
      SUM(s.amount_cents) AS settled_cents,
      SUM(COALESCE(r.refunded_cents, 0)) AS refunded_cents
    FROM settlements s
    LEFT JOIN (
      SELECT settlement_id, SUM(amount_cents) AS refunded_cents
      FROM refunds
      GROUP BY settlement_id
    ) r ON r.settlement_id = s.settlement_id;

common mistake

SUM(s.amount_cents) after joining refunds counts a settlement with two refunds twice.

challenge 1

For accounts 1–200, return account_id, settled_cents and refunded_cents: total card settlements and total refunds for cards on that account, each counted once, 0 when none.

what to return

Columns
account_idsettled_centsrefunded_cents in this order
Rows
any order
challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.
Multi-grain joins without fan-out — Advanced product analysis · Runtime