concept · concepts/One Fact Per Grain.md
One Fact Per Grain
A dimensional-modeling discipline: every fact or dimension table is defined by a single grain (the level its key identifies), and you never widen it with attributes that don't share that key. A corollary: a table can only answer a question if it actually carries the rows that question needs.
Grounded 2026-06-26 by Kimball — Declaring the Grain (Ralph Kimball, 2003), the foundational source — fetched under the auto-ground convention. It confirms the heuristic precisely ("the facts must be true to the grain") and hands the wiki a connective gift: Kimball calls the grain declaration "like a contract!", tying this concept directly to Data Contract.
Explanation
Grain is the meaning of one row. Once you fix the grain (one order line, one device-day, one shipment), every column on the table must be a property of that key. The source (Working Heuristics — Data, Matching, Product, Org, Tooling) states the rule as: one fact per grain — never widen a dimension or fact with attributes that don't share its key. Bolting on a finer- or coarser-grained attribute creates a silent fan-out or duplication that corrupts every aggregate computed downstream.
Ralph Kimball (Kimball — Declaring the Grain, 2003) is the canonical authority: declare the grain first — "if the grain isn't clearly defined, the whole design rests on quicksand" — and then "the facts must be true to the grain." His worked counter-example is a fact like "amount billed year-to-date": untrue to the row's grain, it "produces nonsensical, useless results" when reporting tools combine rows, and so belongs in a different fact table (or computed in the application). That is exactly the wiki's "never widen with attributes that don't share the key," stated from the other side. Kimball's framing — "the grain declaration is like a contract!" — makes grain the modeling-layer sibling of the boundary-level Data Contract.
Model the negative case explicitly. The source's sharpest grain corollary: a "didn't-happen" metric needs both rows. If you want to measure no-shows, cancellations, SLA misses, or any planned-but-not-actual outcome, the fact must carry both the planned record and the actual record at the right grain. If only actuals are stored, the negative case is uncomputable downstream — you can't count what was never written. The grain has to span plan and reality, or the metric simply can't exist.
Why it matters / where it shows up
- Aggregates inherit the grain's integrity. A correctly-grained fact sums and filters cleanly; a widened one double-counts in ways that are painful to detect.
- Completeness is a modeling choice, not a query trick. No amount of clever SQL recovers a "didn't happen" row that was never persisted — this has to be designed into the fact's grain up front.
- It composes with the contract. A Data Contract publishes values at a grain; declaring that grain (and what counts as a row) is half of what makes the contract usable.
Open questions
- Where's the line between "a legitimately coarser dimension" and "an attribute that doesn't share the grain"? Snowflaking vs. widening is a recurring judgment call.
Connections
- Data Contract — Kimball calls the grain declaration "like a contract"; grain is the modeling-layer contract, the data contract the boundary one — they compose.
- Medallion Architecture — gold-layer facts earn their tier by respecting grain.
- Pipeline Legibility — grain is part of what an honestly-named, readable table communicates to the next reader.
Sources
- Working Heuristics — Data, Matching, Product, Org, Tooling — "one fact per grain" and "a didn't-happen metric needs both rows."
- Kimball — Declaring the Grain — the foundational source: declare grain first; "facts must be true to the grain"; grain as a binding contract on the design.