SQL Quest › SQL Interview Questions › SQL traps › Average of averages
SQL average of averages: why AVG of group rates is wrong
The average of group averages (or of group rates) is not the overall average: every group gets one vote however many rows it has.
The task
Report the overall chargeback rate — chargebacks per transaction. A dashboard already shows the rate for each account status (active, flagged), and the analyst averages the two rates. The data is SQL Quest's synthetic card-transactions set (accounts, merchants, transactions, chargebacks), and every result on this page is what the query returns on it.
The query that looks right
SELECT ROUND(100 * AVG(rate), 2) AS chargeback_rate_pct
FROM (
SELECT a.status,
1.0 * COUNT(cb.chargeback_id) / COUNT(*) AS rate
FROM transactions t
JOIN accounts a ON a.account_id = t.account_id
LEFT JOIN chargebacks cb ON cb.txn_id = t.txn_id
GROUP BY a.status
);
Returns: 13.10
A 13.10% chargeback rate.
The two groups being averaged
SELECT a.status,
COUNT(*) AS transactions,
COUNT(cb.chargeback_id) AS chargebacks,
ROUND(100.0 * COUNT(cb.chargeback_id) / COUNT(*), 2) AS rate_pct
FROM transactions t
JOIN accounts a ON a.account_id = t.account_id
LEFT JOIN chargebacks cb ON cb.txn_id = t.txn_id
GROUP BY a.status
ORDER BY a.status;
| status | transactions | chargebacks | rate_pct |
|---|---|---|---|
| active | 2,109 | 63 | 2.99 |
| flagged | 56 | 13 | 23.21 |
Why
The flagged group has 56 transactions and the active group 2,109, but the outer AVG gives each group the same weight: (2.99% + 23.21%) / 2. The 56 flagged transactions decide half of the answer.
The overall rate is total chargebacks over total transactions: 76 / 2,165. That is one division over all the rows, not an average of divisions.
The gap depends on how uneven the groups are. On the same data, averaging resolution times across the five reason codes gives 21.30 days against a true 21.81 — close, because those groups are similar in size. The error is always there; skew decides how big it is.
The fix
Fix 1 — One division over all the rows
SELECT ROUND(100.0 * COUNT(cb.chargeback_id) / COUNT(*), 2) AS chargeback_rate_pct
FROM transactions t
LEFT JOIN chargebacks cb ON cb.txn_id = t.txn_id;
Returns: 3.51
Fix 2 — Or keep the group table, and weight it: sum the parts, then divide
SELECT ROUND(100.0 * SUM(chargebacks) / SUM(transactions), 2) AS chargeback_rate_pct
FROM (
SELECT a.status,
COUNT(*) AS transactions,
COUNT(cb.chargeback_id) AS chargebacks
FROM transactions t
JOIN accounts a ON a.account_id = t.account_id
LEFT JOIN chargebacks cb ON cb.txn_id = t.txn_id
GROUP BY a.status
);
Returns: 3.51
Right answer: The overall chargeback rate is 3.51%.
Rule of thumb. For an overall average or rate, divide totals: SUM(numerator) / SUM(denominator), or one AVG over the rows. Average the group results only when you want each group weighted equally — and say so.
Where this trap is tested
This trap is question 11 of our Capital One CodeSignal-style mock — a timed practice screen on the same kind of data. The mock asks it in a different form, so this page does not give its answer away.
Practise it
Related SQL traps
Which traps would cost you in an interview?
Ten questions, no signup: a Skillmap across nine SQL skills and the one to fix first.
Take the readiness test