runtime
56 / 88

lesson 56 Advanced product analysis · advanced

Revenue & payment reconciliation

Reconcile revenue with payments and identify differences.

the idea

Reconciliation compares independent totals. Sum payments and refunds by invoice before joining. Net collected is payments minus refunds; gap is billed minus net collected. A refund creates a gap under this definition, even when the original bill was paid.

tables in this lesson

The challenges use this data. Each column shows its type underneath; → marks a link to another table.

invoices4 columns · 6 rows
idinteger · keyaccount_idintegerperiodtextbilled_amountreal
112025-0120
232025-0180
342025-0120
462025-0180
512025-0220

showing 5 of 6 rows

payments3 columns · 7 rows
idinteger · keyinvoice_idintegeramountreal
1110
2110
3280
4315
5480

showing 5 of 7 rows

refunds3 columns · 4 rows
idinteger · keyinvoice_idintegeramountreal
112
213
3210
445

all 4 rows

example

The idea in one query. Press Run to see what it returns; running examples never affects your progress.

example.sql
WITH paid AS(
  SELECT invoice_id,SUM(amount) AS paid_amount
  FROM payments
  GROUP BY invoice_id
),returned AS(
  SELECT invoice_id,SUM(amount) AS refund_amount
  FROM refunds
  GROUP BY invoice_id
)
SELECT
  i.id,
  i.billed_amount,
  COALESCE(p.paid_amount,0)-COALESCE(r.refund_amount,0) AS net_collected,
  i.billed_amount-COALESCE(p.paid_amount,0)+COALESCE(r.refund_amount,0) AS gap
FROM invoices i
LEFT JOIN paid p ON p.invoice_id=i.id
LEFT JOIN returned r ON r.invoice_id=i.id;

Watch for: Unmatched payments need a separate investigation; they are not assigned to an invoice here.

build it step by step

Each step changes one thing. Run them in order and watch the result change.

  1. Inspect split payments. Invoice 1 has two payments. Refunds also have multiple rows.

    step-1.sql
    SELECT *
    FROM payments
    WHERE invoice_id=1;
  2. Aggregate payments. One summary row per invoice.

    step-2.sql
    SELECT invoice_id,SUM(amount) AS paid_amount
    FROM payments
    GROUP BY invoice_id;
  3. Aggregate refunds. Join this summary, not individual refund rows.

    step-3.sql
    SELECT invoice_id,SUM(amount) AS refund_amount
    FROM refunds
    GROUP BY invoice_id;
  4. Calculate the reconciliation gap. Unmatched payments need a separate investigation; they are not assigned to an invoice here.

    step-4.sql
    WITH paid AS(
      SELECT invoice_id,SUM(amount) AS paid_amount
      FROM payments
      GROUP BY invoice_id
    ),returned AS(
      SELECT invoice_id,SUM(amount) AS refund_amount
      FROM refunds
      GROUP BY invoice_id
    )
    SELECT
      i.id,
      i.billed_amount,
      COALESCE(p.paid_amount,0)-COALESCE(r.refund_amount,0) AS net_collected,
      i.billed_amount-COALESCE(p.paid_amount,0)+COALESCE(r.refund_amount,0) AS gap
    FROM invoices i
    LEFT JOIN paid p ON p.invoice_id=i.id
    LEFT JOIN returned r ON r.invoice_id=i.id

challenge 1

Return id, billed_amount, net_collected and gap for every invoice across all periods. Net collected = payments - refunds; gap = billed - net collected. Treat missing totals as zero.

Return idbilled_amountnet_collectedgapany row order

challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.