Dimension Tables
The vocabulary of every slicer and group-by
Dimensions describe the who, what, where and when. They are denormalized for reading, use surrogate keys, and carry the attributes that become every slicer and group-by in BI.
What you will learn
- Design dimension attributes that serve the known questions
- Use surrogate keys and role-playing dimensions correctly
- Handle degenerate and junk dimensions pragmatically
What is this?
A dimension table is a denormalized description of one entity — customer, product, date, store, employee. Each row is one member with its attributes (name, region, category, tier). Reports group and filter by these attributes, so the attributes you choose ARE the questions the organization can ask.
Why do you need it?
- Analysts think and filter by attributes — dimensions are the interface to the data.
- Denormalizing attributes makes star joins fast and readable, unlike normalized OLTP.
- Consistent, well-keyed dimensions are reusable across many fact tables.
- A complete date dimension alone solves calendars: holidays, fiscal quarters, ISO weeks.
Mental model
A contact card for each friend
A dimension row
The card holds name, city, phone, birthday — the attributes you sort your life by. Swap an attribute (city) and the whole 'who's nearby' question changes. Dimensions are those cards, shared by every address book (fact table) that references them.
How it works
- Each dimension row carries a surrogate key (integer) plus business keys and attributes.
- Attributes are denormalized — hierarchical categories flattened (category + subcategory + brand in one row).
- Role-playing dimensions reuse one physical dim for several roles (order_date, ship_date, due_date → one dim_date).
- Degenerate dimensions (order numbers) stay in the fact to avoid a pointless dim.
- Junk/outrigger dims group rare low-cardinality flags to declutter wide facts.
Anatomy of a dimension and its roles.
Examples
Slicing with dimension attributes
SELECT d.fiscal_quarter, c.tier, SUM(f.amount) AS revenueFROM fact_orders fJOIN dim_customer c ON f.cust_key = c.cust_keyJOIN dim_date d ON f.order_date_key = d.date_keyWHERE d.fiscal_year = 2026GROUP BY d.fiscal_quarter, c.tier;Every group-by here is a dimension attribute — fiscal quarter (date dim), tier (customer dim). Adding a new question usually means adding attributes, not new tables.
In production
- A date dimension powers fiscal quarters, holidays and ISO weeks uniformly in every report.
- A customer dim stores tier and region so churn reports slice by segment without locating raw tables.
- One dim_date serves order, ship and due dates in a logistics star via role-playing aliases.
Best practices
- Choose attributes by interviewing the questions, not by porting every source column.
- Use surrogate keys; keep business keys as attributes for joins with source.
- Keep dimensions slowly-changing-aware from day one (see SCD concept).
- Flatten hierarchies: two attributes beat a recursive self-join in a report.
Common mistakes
- Huge 'kitchen sink' dimensions with 500 attributes that slow scans for one useful field.
- Missing role-playing aliases, so analysts join the wrong date key.
- Updating a customer's region in place and silently rewriting history.
- No surrogate key — a 'code' that another team re-keys breaks the whole star.
Common issues
Report can't filter on 'fiscal quarter'Date dim stores calendar only
Add fiscal attributes to the date dim and backfill all keys.
Customer region changed for all historyIn-place update instead of a versioned dimension
Convert to SCD-2 (type 2) so history keeps its original region.
Debugging tips
- A slicer missing options? Check the dimension's row set and refresh, not the fact.
- If shipping_date filters are empty, check you aliased dim_date for the ship role.
- For surprising counts, verify one dimension row per member (uniqueness test).
Interview checkpoint
Quick quiz
The main job of a dimension table is to:
An order number belongs in a dedicated dimension table.