Data Modeling for Engineers
Get the grain right and the meters follow
Data modeling is how you decide what a table is and how tables relate. Facts and dimensions, normalization versus denormalization, and grain discipline turn raw data into analyzable structure.
What you will learn
- Distinguish facts, dimensions and grain
- Explain normalization (OLTP) vs denormalized analytical modeling
- Avoid classic modeling traps: wrong grain, many-to-many joins, slowly changing confusion
What is this?
Data modeling defines the structure of your analytical tables: facts (measured events: sales, sessions), dimensions (descriptive context: customer, store, date) and the grain (the level of detail of one row). Analytical modeling often denormalizes — intentionally duplicating descriptive columns to make queries fast and simple — unlike the normalized relational modeling of transactional systems.
Why do you need it?
- Grain is the single most error-prone decision: get it wrong and every aggregate is misleading.
- Well-modeled tables are consumable by anyone — analysts write correct SQL without reverse-engineering joins.
- Facts+dimensions give dashboards stable, queryable structure instead of ad-hoc columns.
- Modeling is where data engineering's quality promises actually get encoded.
Mental model
A spreadsheet of receipts + a database of clients
Facts + dimensions
Receipt facts are measured transactions ('what happened'), client/dimension tables are reference data ('who/where/when they are'). You join a receipt to a client to answer 'how much did Paris customers spend?'
How it works
- A fact table has low-cardinality foreign keys to dimensions plus measures (amount, quantity).
- A dimension table describes one entity (store, customer, product) with descriptive attributes.
- Grain is the statement 'one row = one X' (one order line, one daily store total).
- Analytical models mix normalized (wide dims) and denormalized (some repeated attributes) pragmatically.
- Slowly changing dimensions (SCDs) track how dimension attributes change over time without rewriting history.
Star schema: central fact surrounded by dimension tables.
In production
- A daily sales fact at order-line grain joins dim_store, dim_date and dim_customer; a BI table groups by region/month in milliseconds.
- SCD Type 2 on customer segment preserves history: 'where a promoter segment customers bought' stays answerable even after they churn.
- A normalized OLTP core feeds a denormalized analytics mart — engineered duplication absorbs analytics load.
Best practices
- State the grain of every fact in its documentation; test it with a uniqueness check.
- Use surrogate keys in dimensions so natural-key changes don't break referential history.
- Keep measures numeric and unambiguous (amount in currency X, excluding tax).
- Add conformed dimensions across marts so 'store' means the same thing every report.
Common mistakes
- One row per customer in a fact table → revenue numbers are double-or-triple counted on joins.
- Many-to-many dimension joins without a bridging table producing inflated counts.
- Over-normalizing analytical tables so every dashboard session needs 6 joins.
- Changing a dimension attribute in place, silently rewriting history for every past report.
Common issues
Customer counts inflate when joining to ordersJoining customers (one side of a one-to-many) in a fact that's per-order, fanning out rows
Aggregate to customer level first, or count DISTINCT at the right grain.
Changing 'unlimited' region names breaks old reportsSCD Type 1 overwrite erased history
Adopt SCD Type 2 with effective dates to preserve attribute history.
Fact grain drifted from order-line to store-day-rec — tables silent changed meaningNo documented grain and no uniqueness gate
Document grain in the catalog, add a uniqueness test, and split tables when grains genuinely differ.
Debugging tips
- A doubled metric? Test uniqueness at the fact grain and your join's fan-out first.
- Wrong historical numbers after an attribute update? Check if an SCD strategy exists at all.
- Slow star queries? Confirm the grain is consistent with the BI grouping, then check indexes on keys.
- Same measure, two answers? Check whether both reports read the same fact or two marts.
Interview checkpoint
Quick quiz
Which of these belongs in a dimension table?
Over-normalizing an analytical mart is fine because joins are cheap.