What is the grain of a fact table, and why is it the first thing you declare rather than something that emerges from the design?
Why they ask this
It is the foundational question of dimensional modeling, and the answer separates people who can recite "the level of detail" from people who know it is an enforceable contract.
Say this
The grain is the business meaning of one row, stated as a sentence: "one row per line on an order." Declaring it first turns every later decision into a yes/no question — a column either exists at that grain or it does not — and it gives you a uniqueness rule you can actually test.
The reasoning
A grain is a sentence, not a column list. "One row per line on an order", "one row per account per day", "one row per shipment status change". Once it is written down, every subsequent question has a mechanical answer: can this dimension join at this grain, is this measure additive at this grain, does this column belong here at all.
The reason it comes first is that it is the only decision you cannot change cheaply. Adding a dimension later is a column; changing the grain later means rewriting the load, restating every measure, and invalidating every report built on the old meaning. Kimball puts grain declaration second in the four-step process for exactly this reason — after choosing the business process and before anything else.
It also gives you a test. A declared grain implies a unique key — order plus line number, here — so "is my grain intact?" becomes a query anyone can run: count the rows and count the distinct key. When those two numbers diverge, the table is no longer what it claims to be, and every SUM over it is inflated by the difference. The snippet shows exactly that: three rows loaded, two distinct at the declared grain, and revenue reporting 250 instead of 150.
That test belongs in the pipeline, not in someone's head. A uniqueness assertion on the grain key is the single highest-value data test you can write, because a broken grain does not fail — it silently multiplies money.
See it verified against SQLite
The grain is "one row per order line". One line was loaded twice, and revenue is 67% too high.
Given these rows
| order_id | line_no | product | amount |
|---|---|---|---|
| SO-1 | 1 | widget | 100 |
| SO-1 | 2 | gadget | 50 |
| SO-1 | 1 | widget | 100 |
The query
SELECT
COUNT(*) AS rows_loaded,
COUNT(DISTINCT order_id || '|' || line_no) AS distinct_at_declared_grain,
SUM(amount) AS revenue_as_loaded,
(SELECT SUM(amount) FROM
(SELECT DISTINCT order_id, line_no, amount FROM fct_order_line))
AS revenue_deduped
FROM fct_order_line;Returns
| rows_loaded | distinct_at_declared_grain | revenue_as_loaded | revenue_deduped |
|---|---|---|---|
| 3 | 2 | 250 | 150 |
The answer most people give
"The grain is the level of detail — the lowest level you have data at." Lowest available detail is a common *choice* of grain, not the definition. The definition is what one row means, and plenty of correct fact tables are deliberately coarser than the source.
They’ll ask next
Your fact table is at order-line grain and someone asks for shipping cost, which exists per order. Where does it go?
