Joins II — fan-out & grain
Concept  ·  SQL & Querying

Compare COUNT(*) to COUNT(DISTINCT pk), or row counts before/after the join — a jump means multiplication.

Before trusting any joined aggregate, interrogate the grain: count rows, count distinct keys of the side you're about to sum. Equal → the join preserved grain; unequal → the many side multiplied you, and every parent-level SUM downstream is inflated by exactly that ratio. The same probe works preventively (before writing the aggregate) and forensically (the revenue looks high — check the counts first).

See it break
grow
batchkilos
B110
B220
harvest
batchtray
B1t1
B1t2
B2t9
The trap
SELECT
  SUM(g.kilos) AS kilos
FROM
  grow g
  JOIN harvest h ON h.batch = g.batch;
The fix
SELECT
  SUM(kilos) AS kilos
FROM
  grow;
The trap
40
The fix
30

The joined SUM read 40 kilos, not 30 — batch B1 had two harvest rows, so the join multiplied it; COUNT(*) was 3 where COUNT(DISTINCT batch) was 2, and that gap is the inflation.

The rule

The two-count probe, plus GROUP BY pk HAVING COUNT(*) > 1 to see the multipliers.

Shows up elsewhere
Fan-out before aggregateDedup before vs after join
Try one →Prove it in practice →