What this quiz covers
This quiz focuses on Join Duplication Issues, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
A customer summary joins customers to invoices and support_tickets, then groups by customer. Each customer may have many invoices and many tickets. The output must show the customer's total invoice amount and ticket count.
Which query design prevents either measure from being inflated by the other child table?
SUM(DISTINCT invoice_amount) and COUNT(*)DISTINCT to the final customer rows after calculating both grouped aggregatesSQL Quiz
Practice Join Duplication Issues in SQL with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.
This quiz focuses on Join Duplication Issues, giving you a quick way to practice the rules, question types, and explanations that matter most for SQL.
Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.
A customer summary joins customers to invoices and support_tickets, then groups by customer. Each customer may have many invoices and many tickets. The output must show the customer's total invoice amount and ticket count.
Which query design prevents either measure from being inflated by the other child table?
SUM(DISTINCT invoice_amount) and COUNT(*)DISTINCT to the final customer rows after calculating both grouped aggregatesSUM(invoice_amount) grouped by customer in one subquery, compute COUNT(*) grouped by customer in another, then join both summaries to the customers table. Each customer now has exactly one invoice total row and one ticket count row — no cross-multiplication occurs, and both measures are accurate.
A is tempting but flawed. SUM(DISTINCT invoice_amount) deduplicates by value, not by row identity. If two invoices happen to share the same amount (e.g., two $50 invoices), one gets silently dropped, undercounting revenue. COUNT(*) still counts the inflated row set.
C partially addresses the problem by counting distinct tickets, but SUM still runs over the duplicated invoice rows caused by the multi-child join, so the invoice total is wrong.
D misunderstands where the inflation happens. Applying DISTINCT to final customer rows collapses duplicates at the customer level, but the aggregates (SUM, COUNT) have already been computed over the bloated intermediate rows — the damage is done before DISTINCT runs.
Study tip: Whenever a query joins one parent to two or more child tables, always preaggregate the children first. Think of it as "flatten before you join."A fact table is expected to join to exactly one current customer-dimension row per customer_id. Revenue increased after the join, and the team suspects that some customers have multiple rows marked as current.
Which diagnostic most directly identifies dimension keys that violate the assumed join cardinality?
customer_id and retain counts greater than 1 (correct answer)SUM(revenue) with SUM(DISTINCT revenue) after completing the joincustomer_id — every join will multiply fact rows, inflating aggregates like revenue. Your diagnostic goal is to find which dimension keys violate this uniqueness constraint.
Answer A does exactly that. By grouping the dimension table on customer_id where is_current = TRUE and filtering for COUNT(*) > 1, you directly surface the offending keys — the ones that will cause fan-out during the join. This is the most targeted, efficient diagnostic because it interrogates the source of the problem before the join even runs.
Answer B is a red herring. Grouping fact rows by revenue amount has nothing to do with join cardinality; duplicate revenue values are perfectly normal and unrelated to dimension key violations.
Answer C compares SUM(revenue) with SUM(DISTINCT revenue) post-join. Even if these differ, the comparison doesn't tell you which customer IDs are duplicated — and DISTINCT on a continuous monetary value is rarely meaningful, since legitimate duplicate amounts would also be suppressed.
Answer D counts distinct customer IDs in the fact table before the join. This tells you how many unique customers exist in the fact data, not whether the dimension has duplicate current rows — it diagnoses the wrong table entirely.
Study tip: When a join produces inflated aggregates, always ask "which side is violating cardinality?" Then query that side in isolation using GROUP BY + HAVING COUNT(*) > 1 — a pattern worth memorizing for data integrity checks.Employee A has salary 100 and two bonus rows of 10 and 20. Employee B has salary 200 and no bonus rows. A report left-joins employees to bonuses, then calculates SUM(employee.salary) and SUM(COALESCE(bonus.amount, 0)) across all employees.
Which result and correction are both accurate?
COALESCE to salary prevents duplicationSUM(DISTINCT salary) for every payroll report| Employee | Salary | Bonus |
|---|---|---|
| A | 100 | 10 |
| A | 100 | 20 |
| B | 200 | NULL |
SUM(salary) = 100+100+200=400 (duplicated!), and SUM(COALESCE(bonus.amount, 0)) = 10+20+0=30. Answer B correctly identifies both values and prescribes the right fix: preaggregate bonuses per employee (e.g., in a subquery or CTE) before joining, so each employee appears exactly once.
A is wrong because it states salary = 300, missing the duplication entirely, and COALESCE on salary doesn't prevent fan-out — it only handles NULLs.
C is wrong because bonus = 60 is fabricated; dividing aggregates by a row count is a fragile hack, not a reliable fix.
D is wrong because salary = 300 is again incorrect, and SUM(DISTINCT salary) would break any report where two employees legitimately earn the same amount.
Study tip: Whenever you see a LEFT JOIN to a detail/child table followed by aggregation, immediately ask yourself: "Will parent rows be duplicated?" If yes, preaggregate the child table first.Orders are connected to categories through a many-to-many bridge. One order totaling 120 belongs to three categories, and another order totaling 80 belongs to one category. A category report joins orders through the bridge and sums order totals. Management wants every category represented while requiring category totals to add back to overall order revenue.
Which method meets both requirements?
SUM(DISTINCT order_total) independently within each category groupingSUM(DISTINCT order_total) per category, which drops duplicate values, not duplicate orders — if two different orders happened to share the same total, one would be silently excluded.
The study tip: whenever you see a many-to-many join with aggregation, immediately ask yourself "how many times does each fact row appear?" and prorate accordingly.A sales table has two rows. The first has revenue 100 and cost 60 for a product with one tag. The second has revenue 200 and cost 100 for a product with two tags. The report joins sales to product tags and calculates SUM(revenue - cost) / SUM(revenue) without grouping by tag.
Approximately what gross-margin percentage will the duplicated join report?
SUM operate on the inflated result set — not the original data. That's the core trap here.
Walk through the math carefully. The first sale (revenue 100, cost 60, one tag) appears once. The second sale (revenue 200, cost 100, two tags) appears twice after the join. So the effective dataset becomes:
| Revenue | Cost |
|---|---|
| 100 | 60 |
| 200 | 100 |
| 200 | 100 |
SUM and COUNT results, and that's a classic SQL gotcha on exams and in production.One order has two item rows with line amounts 30 and 20. The same order has three payment rows with amounts 25, 25, and 50. A query joins orders to both order_items and payments on order_id, then calculates SUM(order_items.line_amount) and SUM(payments.amount) for that order.
What values will the query return before any correction for join duplication?
SUM is calculated.
Here's how the math works for this order: joining order_items (2 rows) to payments (3 rows) produces 2×3=6 combined rows. Each item row gets duplicated 3 times (once per payment), and each payment row gets duplicated 2 times (once per item). So SUM(line_amount) becomes (30+20)×3=150, and SUM(payments.amount) becomes (25+25+50)×2=200. That confirms B is correct.
A (50 and 100) reflects the true totals with no duplication — this would be correct only if the joins didn't inflate anything, which isn't how SQL works here. C (100 and 300) inverts the pattern: 100 would be item total doubled (multiplied by 2 instead of 3) and 300 would be payment total tripled — the multipliers are swapped. D (300 and 600) applies an incorrect multiplier across the board, as if each set were multiplied by 6 rather than by the other table's row count.
As a study tip, whenever you see a query joining to multiple detail tables on the same parent key, immediately ask: "What is the cross-product row count, and which direction is each column being multiplied?" This fan-out trap is one of the most common sources of subtly wrong aggregate results in SQL.The orders table has one row per order. The order_status_history table can contain several SHIPPED events for an order because shipments may be retried or split. A report uses JOIN order_status_history h ON h.order_id = o.order_id AND h.status = 'SHIPPED' and then calculates SUM(o.order_total).
The report should total each order once if it has at least one SHIPPED event. Which approach is most reliable?
EXISTS condition for a shipped event (correct answer)SUM(DISTINCT o.order_total) after the history joinDISTINCT to the final grouped result while retaining the history joinSHIPPED rows in order_status_history, the join fans out that order into multiple rows, causing SUM(o.order_total) to count the same order total two, three, or more times.
The cleanest fix is A: replace the join with a correlated EXISTS subquery. This checks whether at least one SHIPPED event exists for each order without duplicating the order row itself. The orders table is never fanned out, so each order_total is summed exactly once — precisely what the report requires.
B is tempting but unreliable. SUM(DISTINCT o.order_total) deduplicates by value, not by order. If two different orders happen to share the same total (say, both are $49.99), only one of those totals will be counted — silently undercounting revenue. C has a similar weakness: adding DISTINCT to the result set deduplicates by the entire row, but if you're grouping only by order, the fan-out already occurred before grouping and the damage is done. D — dividing by the count of history rows — is fragile and mathematically incorrect in general; the number of history rows varies per order and includes non-SHIPPED statuses, making the divisor meaningless.
As a study habit, whenever you see a one-to-many join feeding into SUM or COUNT, immediately ask yourself: can this join duplicate my base rows? If yes, consider EXISTS or a subquery to isolate the aggregation.A transaction table contains one row per transaction, including currency_code and transaction_date. An exchange-rate table contains multiple historical rows per currency, each with valid_from, valid_to, and rate. A report joins the tables only on currency_code and then sums amount * rate. The converted total is unexpectedly high.
Which correction most directly addresses the duplication while preserving historical conversion accuracy?
SUM(DISTINCT amount * rate) after performing the existing currency joincurrency_code creates a many-to-many relationship: each transaction matches every historical rate row for that currency, so amount * rate gets summed multiple times. Option C corrects this by adding a date-range condition — something like transaction_date BETWEEN valid_from AND valid_to — while also enforcing nonoverlapping rate periods to guarantee exactly one rate per transaction. This produces the correct one-to-one match and preserves the historical accuracy the report requires.
Option A is tempting but wrong: always using the latest rate ignores the fact that a transaction from two years ago should use the rate that was valid then, not today's rate. This trades duplication for incorrect valuation. Option B, using SUM(DISTINCT amount * rate), is a flawed workaround — it would silently drop legitimate duplicate values (e.g., two transactions with the same product of amount × rate), producing an undercount rather than fixing the root cause. Option D, retaining the maximum converted amount per transaction, arbitrarily picks the highest rate rather than the historically correct one, distorting results in the opposite direction.
The study tip here: whenever you see a join to a historical or "versioned" table, immediately ask yourself whether you've constrained the join to a single valid row per record. If not, you almost certainly have a fan-out (row multiplication) problem.On one date, sales contains three rows with amounts 100, 50, and 25. On that same date, refunds contains two rows with amounts 20 and 10. A query joins the two tables on date and computes SUM(sales.amount) - SUM(refunds.amount).
What net amount does the query report for that date?
SUM is applied — this is one of the most common pitfalls in SQL aggregation.
Here's what actually happens: joining sales (3 rows) to refunds (2 rows) on date produces a Cartesian-style cross join for that date — 3×2=6 combined rows. In each of those 6 rows, every sales amount is paired with every refund amount. So SUM(sales.amount) adds each sales value twice (once per refund row): 100+50+25=175, then doubled to 350. Similarly, SUM(refunds.amount) adds each refund value three times (once per sales row): 20+10=30, then tripled to 90. The result is 350−90=260, making C correct.
A is wrong because it assumes the sums are computed independently before joining (175−30=145) — that's only true if you aggregate in subqueries first, which this query does not do. B incorrectly assumes only the sales side gets duplicated, giving 350−30=320 — but both sides are inflated by the join. D actually describes B's calculation (320) with a misleading label, so it's doubly wrong.
As a study tip: whenever you see a join combined with SUM, ask yourself whether aggregation happens before or after the join. If it's after, expect both sides to be multiplied by the opposing table's row count — and consider using subqueries or CTEs to pre-aggregate safely.A regional revenue query joins each order to order_notes because only orders having at least one note should qualify. An order may have several notes. The query groups by region, sums order_amount, and places DISTINCT immediately after SELECT.
Which revision correctly prevents note multiplicity from inflating regional revenue?
EXISTS on order_notes correlated by order_id (correct answer)SUM(DISTINCT order_amount) while retaining the inner join to notesDISTINCT to the region and aggregate outputSUM to count the same value multiple times. That's exactly the trap here — joining orders to order_notes means one order with three notes appears three times, tripling its order_amount in the regional sum.
The cleanest fix is option A: replace the join entirely with an EXISTS subquery correlated on order_id. EXISTS checks whether at least one note exists and returns a simple true/false — the order row never duplicates, so SUM(order_amount) aggregates each order exactly once. This satisfies the original business rule (only orders with at least one note qualify) without distorting revenue.
Option B attempts SUM(DISTINCT order_amount) while keeping the inner join. This only deduplicates identical amounts, so two orders that happen to share the same dollar value would be undercounted, while an order duplicated three times with a unique amount would still be overcounted. It solves the wrong problem.
Option C suggests applying DISTINCT to the region and aggregate output. DISTINCT on the SELECT level deduplicates output rows, but the SUM is already computed from the duplicated rows before DISTINCT applies — the inflated total is baked in by then.
Option D uses a left join and filters on a non-null note ID, which is functionally an inner join again, so fan-out remains.
Study tip: Whenever a join is used only to filter (not to retrieve columns from the joined table), prefer EXISTS or IN — it keeps cardinality clean and your aggregates trustworthy.