LeetCode 1205 - Monthly Transactions II
This problem asks us to generate a monthly financial summary for each country using information from two database tables: Transactions and Chargebacks. The Transactions table stores incoming transactions.
Difficulty: 🟡 Medium
Topics: Database
Solution
LeetCode 1205 - Monthly Transactions II
Problem Understanding
This problem asks us to generate a monthly financial summary for each country using information from two database tables: Transactions and Chargebacks.
The Transactions table stores incoming transactions. Each transaction has a unique ID, a country, a state (approved or declined), an amount, and the transaction date.
The Chargebacks table stores chargeback events. Each chargeback references a transaction from the Transactions table through the trans_id foreign key. A chargeback also has its own date, which represents when the chargeback occurred.
The goal is to produce, for every (month, country) combination, the following aggregated values:
- Number of approved transactions
- Total amount of approved transactions
- Number of chargebacks
- Total amount of chargebacks
The month must be formatted as YYYY-MM.
An important detail is that chargebacks are grouped by the chargeback date, not the original transaction date. However, the country and amount information must still come from the original transaction in the Transactions table.
The note about ignoring rows with all zeros means that we should only return month-country combinations that contain at least one approved transaction or at least one chargeback.
The problem guarantees that every chargeback references a valid transaction. This means we can safely join Chargebacks with Transactions using the transaction ID.
Even though explicit constraints are not shown, database problems on LeetCode are generally designed for efficient set-based SQL solutions. A naive procedural approach that repeatedly scans tables would not scale well. Instead, aggregation and grouping operations should be pushed into SQL itself.
Several edge cases are important:
- A month may contain approved transactions but no chargebacks.
- A month may contain chargebacks but no approved transactions.
- Transactions with state
declineddo not contribute to approved totals, but they can still appear in chargebacks. - Multiple chargebacks may exist across different months.
- Some months may contain data for multiple countries.
The solution must correctly combine these cases into a single aggregated result.
Approaches
Brute Force Approach
A brute force strategy would iterate through every possible (month, country) pair and repeatedly scan the tables to compute the required aggregates.
For approved transactions, we would search the Transactions table for matching rows with state approved. For chargebacks, we would join each chargeback back to its original transaction and aggregate matching values manually.
This approach is logically correct because it explicitly computes every required metric. However, it is inefficient because it repeatedly scans large tables and performs redundant computations.
In procedural terms, this could become extremely expensive if the number of months, countries, transactions, and chargebacks grows large.
Optimal Approach
The key insight is that SQL databases are optimized for grouping and aggregation operations.
Instead of manually iterating through rows, we can:
- Aggregate approved transactions by
(month, country) - Aggregate chargebacks by
(month, country) - Combine the two result sets
- Perform a final grouping to merge overlapping rows
The cleanest way to achieve this is to use UNION ALL.
We create one query that produces approved transaction statistics and another query that produces chargeback statistics. Each query fills in the fields it does not use with zeros.
After combining the datasets with UNION ALL, we group again by (month, country) and sum the columns.
This approach is efficient because each table is scanned only once, and aggregation work is delegated to the database engine.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(T × C) or worse | O(1) | Repeated scans over tables |
| Optimal | O(T + C) | O(T + C) | Uses SQL aggregation and grouping efficiently |
Here:
T= number of transaction rowsC= number of chargeback rows
Algorithm Walkthrough
- First, process approved transactions from the
Transactionstable.
For each transaction with state approved, extract:
- The month using date formatting
- The country
- A count of approved transactions
- The approved transaction amount
Since this query only handles approved transactions, set the chargeback fields to zero. 2. Next, process chargebacks.
Join the Chargebacks table with the Transactions table using:
Chargebacks.trans_id = Transactions.id
This join is necessary because the Chargebacks table does not contain country or amount information.
3. For each chargeback row:
- Extract the chargeback month from
Chargebacks.trans_date - Use the country from
Transactions - Count the chargeback
- Sum the original transaction amount
Since this query only handles chargebacks, set the approved transaction fields to zero.
4. Combine the two result sets using UNION ALL.
UNION ALL is preferred over UNION because we do not want duplicate elimination. We want all aggregated rows preserved.
5. Perform a final grouping by (month, country).
Sum all four metric columns:
approved_countapproved_amountchargeback_countchargeback_amount
- Return the final aggregated table.
Why it works
The algorithm works because every approved transaction contributes exactly once to the approved aggregates, and every chargeback contributes exactly once to the chargeback aggregates.
By converting both datasets into the same schema and combining them with UNION ALL, the final grouping step naturally merges rows belonging to the same (month, country) pair.
The aggregation is mathematically correct because summation is associative and independent across categories.
SQL Solution
# MySQL solution
SELECT
month,
country,
SUM(approved_count) AS approved_count,
SUM(approved_amount) AS approved_amount,
SUM(chargeback_count) AS chargeback_count,
SUM(chargeback_amount) AS chargeback_amount
FROM
(
SELECT
DATE_FORMAT(trans_date, '%Y-%m') AS month,
country,
COUNT(*) AS approved_count,
SUM(amount) AS approved_amount,
0 AS chargeback_count,
0 AS chargeback_amount
FROM Transactions
WHERE state = 'approved'
GROUP BY DATE_FORMAT(trans_date, '%Y-%m'), country
UNION ALL
SELECT
DATE_FORMAT(c.trans_date, '%Y-%m') AS month,
t.country,
0 AS approved_count,
0 AS approved_amount,
COUNT(*) AS chargeback_count,
SUM(t.amount) AS chargeback_amount
FROM Chargebacks c
JOIN Transactions t
ON c.trans_id = t.id
GROUP BY DATE_FORMAT(c.trans_date, '%Y-%m'), t.country
) AS combined
GROUP BY month, country;
The solution is divided into two major aggregation queries.
The first query handles approved transactions only. It filters rows using:
WHERE state = 'approved'
It then groups by month and country and computes the approved counts and amounts.
The second query handles chargebacks. Since the Chargebacks table does not contain country or amount information, we join it with Transactions.
The grouping for chargebacks is based on the chargeback date because the problem statement asks for chargeback statistics by the month the chargeback occurred.
The two result sets are combined using UNION ALL. Both queries return identical column structures, which allows them to be merged seamlessly.
Finally, the outer query groups again by (month, country) and sums the metric columns to produce the final result.
Worked Example
Example 1
Input Tables
Transactions
| id | country | state | amount | trans_date |
|---|---|---|---|---|
| 101 | US | approved | 1000 | 2019-05-18 |
| 102 | US | declined | 2000 | 2019-05-19 |
| 103 | US | approved | 3000 | 2019-06-10 |
| 104 | US | declined | 4000 | 2019-06-13 |
| 105 | US | approved | 5000 | 2019-06-15 |
Chargebacks
| trans_id | trans_date |
|---|---|
| 102 | 2019-05-29 |
| 101 | 2019-06-30 |
| 105 | 2019-09-18 |
Step 1, Approved Transaction Aggregation
| month | country | approved_count | approved_amount | chargeback_count | chargeback_amount |
|---|---|---|---|---|---|
| 2019-05 | US | 1 | 1000 | 0 | 0 |
| 2019-06 | US | 2 | 8000 | 0 | 0 |
Explanation:
- Transaction 101 contributes to May
- Transactions 103 and 105 contribute to June
- Declined transactions are ignored
Step 2, Chargeback Aggregation
| month | country | approved_count | approved_amount | chargeback_count | chargeback_amount |
|---|---|---|---|---|---|
| 2019-05 | US | 0 | 0 | 1 | 2000 |
| 2019-06 | US | 0 | 0 | 1 | 1000 |
| 2019-09 | US | 0 | 0 | 1 | 5000 |
Explanation:
- Chargeback for transaction 102 occurs in May
- Chargeback for transaction 101 occurs in June
- Chargeback for transaction 105 occurs in September
Step 3, Final Aggregation
After combining and regrouping:
| month | country | approved_count | approved_amount | chargeback_count | chargeback_amount |
|---|---|---|---|---|---|
| 2019-05 | US | 1 | 1000 | 1 | 2000 |
| 2019-06 | US | 2 | 8000 | 1 | 1000 |
| 2019-09 | US | 0 | 0 | 1 | 5000 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(T + C) | Each table is scanned once |
| Space | O(T + C) | Intermediate grouped result sets |
The database engine performs grouping and aggregation internally using optimized execution plans. The transaction table is scanned once for approved transactions, and the chargeback table is scanned once for chargeback aggregation.
The temporary grouped result sets require additional memory proportional to the number of distinct (month, country) combinations.
Test Cases
# Example 1
# Standard mixed case with approved transactions and chargebacks
# Expected:
# 2019-05 US -> approved(1,1000), chargeback(1,2000)
# 2019-06 US -> approved(2,8000), chargeback(1,1000)
# 2019-09 US -> approved(0,0), chargeback(1,5000)
# Only approved transactions
# Verifies rows appear even without chargebacks
# Transactions:
# approved: 100, 200
# Expected approved_count = 2
# Expected approved_amount = 300
# Only chargebacks
# Verifies months with zero approved transactions are still included
# Chargeback exists for declined transaction
# Expected approved_count = 0
# Expected chargeback_count = 1
# Multiple countries
# Ensures grouping separates countries correctly
# US and CA transactions should produce separate rows
# Multiple months
# Ensures monthly grouping works correctly
# January and February should appear separately
# Declined transactions without chargebacks
# Ensures they do not contribute to output
# Expected no row generated
# Multiple chargebacks in same month
# Verifies aggregation sums correctly
# Expected chargeback_count > 1
# Expected chargeback_amount summed correctly
| Test | Why |
|---|---|
| Example input | Validates standard mixed aggregation |
| Only approved transactions | Ensures approved-only rows appear |
| Only chargebacks | Ensures chargeback-only rows appear |
| Multiple countries | Verifies country grouping |
| Multiple months | Verifies monthly grouping |
| Declined without chargeback | Ensures invalid rows excluded |
| Multiple chargebacks | Verifies aggregation correctness |
Edge Cases
Chargebacks for Declined Transactions
A common mistake is assuming chargebacks only apply to approved transactions. The problem explicitly states that chargebacks may reference declined transactions as well.
The implementation handles this correctly because the chargeback aggregation joins directly with all transactions, without filtering on state.
Months with Only Chargebacks
Some months may contain chargebacks but no approved transactions. A naive inner join between approved transactions and chargebacks would accidentally exclude these rows.
The solution avoids this issue by aggregating approved transactions and chargebacks independently before combining them with UNION ALL.
Declined Transactions Without Chargebacks
Declined transactions should not contribute to approved statistics, and if they have no chargebacks, they should not appear in the result at all.
The implementation naturally handles this because:
- The approved query filters them out
- The chargeback query only includes transactions referenced by chargebacks
As a result, irrelevant rows never enter the combined dataset.