CMD Guide
HomeDatabasesSQL Practice Problems

Monthly Transactions II

The mechanism in one line

Reshape two differently-shaped event streams into one stream that carries a synthetic state label, then let a single grouped pass count and sum each label per bucket — so chargebacks and approvals are tallied side by side without ever joining the two aggregates back together.

That synthetic-label trick has a name worth keeping: a tagged union (also called a discriminated union or a UNPIVOT-by-UNION). You normalize heterogeneous rows to a common schema, add a discriminator column, stack them with UNION ALL, and then a conditional SUM(CASE …) demultiplexes them back into separate output columns. It is the SQL equivalent of switch on a tagged enum.

The trap that makes this problem "II"

A chargeback is not bucketed by the original transaction's date — it is bucketed by the chargeback's own trans_date, but it inherits the country and amount of the original transaction. So three independent facts about one transaction can land in three different months:

This is why the chargeback branch must JOIN Transactions (to recover country and amount) while taking its month from Chargebacks.trans_date.

diagram
diagram

The corrected query

The walkthrough's query is correct as written. Two details carry the whole problem: the chargeback branch dates by Chargebacks.trans_date (not the transaction's), and HAVING — not WHERE — does the zero-row filtering because it must run after aggregation.

SELECT s.month,
       s.country,
       SUM(CASE WHEN s.state = 'approved' THEN 1 ELSE 0 END)      AS approved_count,
       SUM(CASE WHEN s.state = 'approved' THEN s.amount ELSE 0 END) AS approved_amount,
       SUM(CASE WHEN s.state = 'back'     THEN 1 ELSE 0 END)      AS chargeback_count,
       SUM(CASE WHEN s.state = 'back'     THEN s.amount ELSE 0 END) AS chargeback_amount
FROM (
    -- chargeback branch: month from the CHARGEBACK, country/amount from the TRANSACTION
    SELECT LEFT(c.trans_date, 7) AS month,
           t.country,
           'back'                AS state,   -- synthetic discriminator
           t.amount
    FROM   Chargebacks c
    JOIN   Transactions t ON c.trans_id = t.id

    UNION ALL

    -- approval branch
    SELECT LEFT(t.trans_date, 7) AS month,
           t.country,
           t.state,
           t.amount
    FROM   Transactions t
    WHERE  t.state = 'approved'
) s
GROUP BY s.month, s.country
HAVING approved_count > 0 OR chargeback_count > 0
ORDER BY s.month;

Use single quotes for string literals ('back'): double quotes are standard-SQL identifiers, and on a database with ANSI_QUOTES enabled the original "back" would be parsed as a column name and error. MySQL's default mode happens to tolerate it, which is exactly the kind of portability landmine worth removing.

Worked trace on the real example

Inputs (the canonical dataset, all US):

Transactions idstateamounttrans_date
101approved10002019-05-18
102declined20002019-05-19
103approved30002019-06-10
104declined40002019-06-13
105approved50002019-06-15
Chargebacks trans_idchargeback trans_date
1022019-05-29
1012019-06-30
1052019-09-18

Step 1 — approval branch (only approved rows survive; 102 and 104 are dropped here):

monthcountrystateamount
2019-05USapproved1000
2019-06USapproved3000
2019-06USapproved5000

Step 2 — chargeback branch (month from the chargeback date; amount/country recovered via the join — note CB on 102 survives even though 102 was declined):

month (CB date)countrystateamount (from txn)
2019-05USback2000 (txn 102)
2019-06USback1000 (txn 101)
2019-09USback5000 (txn 105)

Step 3 — UNION ALL + GROUP BY + conditional SUM. The 2019-09 bucket has zero approvals but one chargeback, so HAVING keeps it. Every output reconciles with the stacked stream:

monthcountryapproved_countapproved_amountchargeback_countchargeback_amount
2019-05US1100012000
2019-06US2800011000
2019-09US0015000

The 2019-06 approved_amount = 3000 + 5000 = 8000 confirms the conditional sum is adding only the rows whose discriminator matches.

Why the naive versions are wrong

Pitfalls

Takeaways


Problem from LeetCode 1645 "Hopper Company Queries"-family / "Monthly Transactions II"; canonical example dataset reproduced. Mechanism framing (tagged/discriminated union, conditional-aggregation pivot) draws on the SQL standard's treatment of UNION ALL and CASE, and on the conditional-aggregation pattern documented in the PostgreSQL and MySQL manuals (CASE, DATE_FORMAT/TO_CHAR) and in Itzik Ben-Gan's T-SQL Querying. Re-authored and deepened for this guide: added the mechanism statement, named the tagged-union pattern, traced the real example end-to-end, diagrammed the pipeline, fixed the string-literal quoting for portability, and documented the four naive-version failure modes.

🤖 Don't fully get this? Learn it with Claude

Stuck on Monthly Transactions II? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Monthly Transactions II** (Databases) and want to truly understand it. Explain Monthly Transactions II from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Monthly Transactions II** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Monthly Transactions II** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Monthly Transactions II** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes