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

Joining one-to-many multiplies the one side's rows; aggregating after the join counts the money once per match — aggregate first, dedupe, or use EXISTS.

A one-to-many join repeats each left row once per matching right row. An aggregate over the joined rows therefore sums the left value once per match, not once per entity.

See it break
orders
idamount
1100
250
shipments
order_id
1
1
2
The trap
SELECT
  SUM(o.amount) AS total
FROM
  orders o
  JOIN shipments s ON s.order_id = o.id
  ;
The fix
SELECT
  SUM(o.amount) AS total
FROM
  orders o
WHERE
  EXISTS (
    SELECT
      1
    FROM
      shipments s
    WHERE
      s.order_id = o.id
  );
The trap
250
The fix
150

$250 is a believable revenue number — the timebomb that passes review, ships, and misstates revenue by the fan-out ratio.

The rule

Semi-join with WHERE EXISTS to count each entity once, or pre-aggregate to the parent grain before joining the child table.

WHERE EXISTS (SELECT 1 FROM child c WHERE c.fk = base.id)
Shows up elsewhere
Many-to-many through a bridgeDetecting fan-out
Try one →Prove it in practice →