23. Diagnosing wrong row counts

📖 Reading · 4 min
💡 Every code box below is live — edit it and hit Run.

The most common join bug isn't an error — it's a silently wrong row count. Two causes account for almost all of it: dropped rows (from inner joins) and fan-out (from duplicate keys).

Fan-out: one row becomes many

If the key isn't unique on the side you're joining to, each left row matches several right rows and multiplies. Our orders has that duplicated row (order 1021 appears twice). Join without cleaning it and the duplicate rides along:

print("raw orders joined  :", orders.join(customers, "customer_id").count())
print("deduped orders join:", orders.dropDuplicates().join(customers, "customer_id").count())

Now imagine aggregating after that join — the duplicated order would be counted twice in revenue. Fan-out corrupts sums, not just counts.

The habit: know your key's grain

Before joining, ask "is the key unique on each side?" Check it:

# how many customer_ids appear more than once in customers? (should be 0)
customers.groupBy("customer_id").count().filter(F.col("count") > 1).show()

If a key that should be unique isn't, dedup or aggregate to the right grain before the join — never after.

A quick checklist when counts look wrong

  • More rows than expected? Fan-out — a duplicate/non-unique join key. Dedup or pre-aggregate the many-side.
  • Fewer rows than expected? An inner join dropped non-matches. Switch to a left join and inspect the nulls (lesson 4.1).
  • Sums too high? Fan-out again — you aggregated after a fanned-out join.

Your turn

Count each customer's orders correctly — deduplicate orders first so the double-counted order 1021 doesn't inflate anyone's total — then group by customer_id and count. Return customer_id and the count. Assign to result.

result = orders  # <- dropDuplicates, groupBy customer_id, count()
result.show()
result = orders.dropDuplicates().groupBy("customer_id").count()