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

State 'one row per X' before writing — the grain is a decision, not an accident (bridge to data modeling).

Fan-out bugs, dirty-dimension bugs, and duplicate-source MERGE bugs share one root: an assumed grain that nothing wrote down and nothing checked. The contract habit closes the root: every table and every join-feeding CTE gets a one-line grain sentence, and load-bearing grains get the probe as a test — the detect_fanout sibling pointed at the source instead of the join result. Cheap enough to be habit: one comment, one query.

See it break
slips
slipamount
s15
s23
s15
The trap
SELECT
  COUNT(*) AS slips
FROM
  slips;
The fix
SELECT
  COUNT(DISTINCT slip) AS slips
FROM
  slips;
The trap
3
The fix
2

The depot counted 3 deposit slips where the grain is one-row-per-slip and only 2 slips exist — a double-scanned slip broke the assumed grain; the GROUP BY slip HAVING COUNT(*) > 1 probe catches the violator before it multiplies anything.

The rule

Grain sentence on every table/CTE; the HAVING probe on every assumed-unique key; uniqueness tests where the stack offers them.

Shows up elsewhere
Detecting fan-outDedup before vs after join
Try one →Prove it in practice →