Fact Tables
The measurements, at one declared grain
Fact tables store measurements of business events at a declared grain. Additive measures sum safely; semi-additive and non-additive measures need special handling.
What you will learn
- Classify measures as additive, semi-additive and non-additive
- Choose grain for common fact patterns (transaction, periodic snapshot, accumulating)
- Design fact tables that aggregate without double-counting
What is this?
A fact table records measures of a business event — one row per order, per click, per daily account snapshot. The grain is its identity, and measures fall into three classes: additive (SUM across any dimension — sales), semi-additive (SUM across some dims but not time — balances), and non-additive (never SUM meaningfully — ratios, unique counts). Getting these classes right is what keeps dashboard math sane.
Why do you need it?
- Almost every wrong BI total traces to a measure summed at the wrong class/grain.
- Periodic snapshot facts (balances) are the only honest home for inventory/account levels.
- Fact design decides whether a dashboard can even express its question.
- Atomic grains make every later aggregation derivable — cheap optionality.
Mental model
A gardener's yield journal
A fact table
Each entry records one harvest event with date, crop and weight (one row per event). Adding August weights means something to a cook (additive), but a 'current water level in the rain barrel' is a snapshot — summing those would flood the metaphor (semi-additive).
How it works
- Transaction facts: one row per event (order, click) — all measures additive.
- Periodic snapshot facts: one row per entity per period (account, day) — balances are semi-additive.
- Accumulating facts: one row per process with lifecycle dates (order-to-cash) — timing measures non-additive.
- Factless facts: rows for events with no measure (attendance, coverage), counting rows instead.
- FK + measure + degenerate key columns; tests assert grain uniqueness.
Choosing the fact pattern by event type.
Examples
Adding balances vs flows
-- Transaction: safe to SUM across everythingSELECT d.month, SUM(f.sales_amount) FROM fact_sales f JOIN dim_date d ON f.date_key=d.date_key GROUP BY d.month; -- Periodic snapshot: SUM over time is wrongSELECT d.month, AVG(f.balance) AS avg_balance FROM fact_account_daily f JOIN dim_date d ON f.date_key=d.date_key GROUP BY d.month;-- (use AVG/LAST, not SUM, for semi-additive balances)Sales flow sums across months; daily balances should be averaged or last-in-pop taken, never summed.
In production
- An e-commerce transactional fact (one row per order line) powers sums of GMV over any dimension.
- A bank's daily account-balance snapshot supports 'average balance by branch this quarter' — computed as AVG, not SUM.
- An order-to-cash accumulating fact tracks each order's lifecycle dates so aging reports never double-count.
Best practices
- Classify every measure explicitly in the model documentation.
- Keep additive measures in transaction facts; push balances into periodic snapshots.
- Degenerate facts (order numbers) stay in the fact; attributes move to dimensions.
- Add grain tests to catch accidental row doubling.
Common mistakes
- SUM-ing an account balance across time — reporting 'oh, balances of 40M' from a million-week average.
- Putting customer 'current region' into the fact, corrupting history.
- Storing a ratio in the fact and summing it later.
- Duplicate keys from a source re-send silently multiplying measures.
Common issues
Inventory 'total' is astronomically highPeriodic snapshot summed across all days
Use AVG or LAST_VALUE over time; keep endpoint snapshots for 'current'.
Grain test fails after a source re-sendDuplicate event rows from retries
Add an idempotent source key (event_id + load_id) and dedupe in staging.
Debugging tips
- Wrong total? Classify the measure: if semi- or non-additive, sum is the probable bug.
- Fact row count vs source event count should match at atomic grain.
- If balances 'jump' at aggregation, check whether it's SUM vs AVG/LAST across time.
Interview checkpoint
Hands-on lab
- Build a transaction fact and a balance snapshot
- Compare SUM vs AVG for a semi-additive measure
- Fix a report that sums balances across time
Classify Measures and Fix a Broken Balance Report
35 min · intermediate
Quick quiz
An account balance from a daily snapshot is best classified as:
Unique active-user counts are safe to SUM across all rows of a fact table.