✏️ Explanatory Question

A LEFT JOIN suddenly returns duplicate rows — why, and how do you fix it without using DISTINCT?

👁 7 Views
📘 Detailed Answer
🟢 Easy
💡

Answer with Explanation

Section 11: Tricky Query Output & Real-World Gotchas

81

A LEFT JOIN suddenly returns duplicate rows — why, and how do you fix it without DISTINCT?

Level: Hard — A classic production bug that separates people who "know joins" from those who truly understand cardinality.

Scenario: Your report showed 500 customers. After a teammate added a LEFT JOIN to the orders table to show order info, the report now shows 1,800 rows — and customer names are repeated. Marketing is complaining about inflated counts.

The cause: A JOIN multiplies rows based on matching cardinality. If one customer has 5 orders, that customer's row is repeated 5 times — one per matching order. This is a one-to-many relationship, and the JOIN correctly (but unexpectedly) fans out the rows.

Why interviewers ask this: Slapping DISTINCT on top is the junior fix — it hides the symptom, hurts performance, and can still give wrong aggregate numbers. They want to see if you understand grain/cardinality and fix it properly.

Wrong vs Right Approach

Junior Fix (band-aid)

  • Add SELECT DISTINCT
  • Hides the real problem
  • Expensive dedupe (sort)
  • Breaks if you also SUM/COUNT

Senior Fix (correct grain)

  • Pre-aggregate orders per customer
  • Join the summarized result
  • One row per customer guaranteed
  • Aggregates stay accurate

The Problem & The Proper Fix

-- THE PROBLEM: one row per ORDER, not per customer (fan-out)
SELECT c.customer_id, c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- Customer with 5 orders appears 5 times

-- JUNIOR "FIX": DISTINCT hides it but is wrong for counts/sums
SELECT DISTINCT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;

-- SENIOR FIX: aggregate to the correct grain FIRST, then join
SELECT c.customer_id, c.name,
       COALESCE(o.order_count, 0)  AS order_count,
       COALESCE(o.total_spent, 0)  AS total_spent
FROM customers c
LEFT JOIN (
    SELECT customer_id,
           COUNT(*)     AS order_count,
           SUM(amount)  AS total_spent
    FROM orders
    GROUP BY customer_id
) o ON c.customer_id = o.customer_id;
-- Exactly one row per customer, with correct totals
The mental model: Before joining, always ask "what is the grain (one row = one what?) of each side?" Joining two tables at different grains fans out rows. Fix by aggregating to a common grain first.
Interviewer follow-up: "What if you need BOTH order-level detail AND a correct customer count in one query?" → Answer: use a window function (COUNT(*) OVER (PARTITION BY customer_id)) to keep detail rows while showing the correct count — no fan-out in the aggregate.