How do you model a fixed-depth product hierarchy like department, category and brand in a warehouse dimension?
answer
- all levels live on one row
- drilling should not change the join
- two conditions on the tree's shape
- same depth everywhere, one parent each
- beware two branches sharing a label
basics
~20 sPut one column per level on every dimension row, so drilling down is just changing the GROUP BY column and no extra joins appear. This works only when the hierarchy has fixed depth and each member has exactly one parent.
solid answer
~50 sKeep the dimension at its lowest grain — one row per product — and carry every level of the hierarchy as an attribute column on that row: `department_name`, `category_name`, `brand_name`, `product_name`. Rolling up or drilling down is then only a change of which column is in the `SELECT` and `GROUP BY`; the join to the fact table never changes and no extra table appears. Two conditions have to hold: the hierarchy is **fixed depth** (every leaf sits the same number of levels below the top) and **strict** (each member has exactly one parent at each level). Watch for level labels that repeat across branches — two departments both containing a category called "Accessories" will silently merge if you group by the name, so group by a level key and display the name. The same dimension row can carry several alternate hierarchies side by side.
code
sql · 12 lines-- one dimension row carries every level of the drill path
CREATE TABLE dim_product (
product_key BIGINT PRIMARY KEY,
product_id VARCHAR(40) NOT NULL,
product_name VARCHAR(200),
brand_key INTEGER,
brand_name VARCHAR(100),
category_key INTEGER,
category_name VARCHAR(100),
department_key INTEGER,
department_name VARCHAR(100)
);go deeper
Be ready to sketch a dimension row that carries department, category, brand and product together, and to show that drilling down changes only the GROUP BY column, not the joins.
Explain the two preconditions — fixed depth and one parent per member — and demonstrate the repeated-label trap, where two branches sharing a category name silently merge unless you group on the level key.
Show judgment about alternate hierarchies on one dimension row, about who owns the rollup rules, and about diagnosing a report whose totals are right at one level and wrong at another.
Own the question of which hierarchies are enterprise-standard drill paths versus local reporting conveniences, and where those definitions live so that finance and merchandising cannot each invent their own department rollup.
## What a hierarchy is inside a dimension A hierarchy is an ordered set of levels in which each member rolls up to exactly one member of the level above: a product belongs to a brand, a brand to a category, a category to a department. Analysts use it as a **drill path** — start at department totals, drill to category, then to brand, then to individual product. In a dimensional model the dimension table is held at its lowest useful grain: one row per product. The higher levels are not separate tables in the default design; they are simply **descriptive attributes of that product row**. ## The flattened design ```sql CREATE TABLE dim_product ( product_key BIGINT PRIMARY KEY, product_id VARCHAR(40) NOT NULL, product_name VARCHAR(200), brand_key INTEGER, brand_name VARCHAR(100), category_key INTEGER, category_name VARCHAR(100), department_key INTEGER, department_name VARCHAR(100) ); ``` Every level of the drill path is a pair of columns — a stable key for grouping and a label for display. A department report and a brand report differ by one identifier: ```sql SELECT p.department_key, p.department_name, SUM(f.sales_amount) FROM fact_sales f JOIN dim_product p ON p.product_key = f.product_key GROUP BY p.department_key, p.department_name; ``` Swap `department_*` for `category_*` and you have drilled one level down. Nothing else in the query moves. ## Why this is the default Three things stay still. The **fact grain** is untouched — the hierarchy lives entirely on the dimension side. The **join count** stays at one per dimension however deep the hierarchy is. And the **query shape** is identical at every level, which is exactly what a BI tool needs in order to be told "these four columns, in this order, are a drill path". Filtering also works at any level without a join: `WHERE p.department_name = 'Footwear'` restricts a product-grain query to one branch. ## The two preconditions **Fixed depth**: every leaf is the same number of levels from the top. A three-level product tree where some products hang directly off a department is *ragged*, and flattening it leaves holes or forces padding. **Strict**: each member has exactly one parent. A product that legitimately belongs to two categories cannot be expressed by a single `category_name` column without choosing one and losing the other — that is a many-to-many, and it needs a bridge table instead. ## Pitfall: level labels are not unique across branches If two departments each contain a category named "Accessories", `GROUP BY category_name` collapses two unrelated branches into one row. The total is wrong and nothing errors. Fix it by grouping on the level's key column and carrying the name only for display, or by constructing a qualified label such as `Footwear > Accessories`. This is one of the most common silent reporting bugs in an otherwise sound model. ## Alternate hierarchies over the same dimension A dimension often supports more than one drill path. A date dimension typically carries both a calendar hierarchy (`day → month → quarter → year`) and a fiscal one (`day → fiscal_period → fiscal_quarter → fiscal_year`); a product may have a merchandising rollup and a different finance-reporting rollup. The flattened design handles this by simply adding a second set of level columns to the same row — one row per date, more columns. What you must **not** do is add a `hierarchy_type` column and duplicate rows: that breaks the one-row-per-member grain and every fact join fans out. ## When the hierarchy changes Re-parenting a category changes an attribute on the dimension row. Whether prior reports keep the old rollup or move to the new one is a slowly-changing-dimension decision and belongs to that topic; the relevant point here is that flattening keeps the hierarchy *inside* the dimension row, so it is versioned by whatever history policy that dimension already uses, with no separate hierarchy-history mechanism to maintain. ## When flattening stops working Non-strict membership (a member with several parents, or a fact that relates to several dimension members) calls for a bridge table with a group key. Variable depth or ragged branches call for a hierarchy bridge of ancestor–descendant pairs, or for padding the missing levels by repeating the lowest known value — with the phantom members that produces. Reach for those only when a precondition genuinely fails; the flattened columns are the design you should have to be argued out of. ## What interviewers listen for That you keep the dimension at leaf grain, that drilling is a `GROUP BY` change rather than a join change, that you can name the fixed-depth and strict preconditions unprompted, and that you spot the repeated-label trap.
- How would you let a single date dimension serve both a calendar and a fiscal drill path?Add a second set of level columns to the same row: `fiscal_period`, `fiscal_quarter_key`, `fiscal_year` alongside the calendar ones, one row per date. Each drill path is a documented ordered column list for the BI tool. Do not add a hierarchy-type column and duplicate rows — that breaks the one-row-per-date grain and every fact join fans out.
- A category is reassigned from one department to another. What decision does that force?Whether prior reports keep rolling up to the old department or move to the new one. Because the hierarchy is stored as attributes on the dimension row, that is settled by the dimension's history policy — overwrite and history is restated, or version the row and old facts keep the old rollup. Either way the choice must be made deliberately, not discovered in a report.
- When would you keep hierarchy levels as columns even though the source system stores them in separate tables?Almost always. The source's normalized shape is an operational storage decision; the mart's job is to make drill paths cheap and legible for analysts. Flattening costs some repeated text, which compresses well, and removes a join per level from every query and every BI model.
It is the difference between a filing cabinet where every folder is stamped with its drawer, section and shelf, and one where you must walk the building to work out where a folder sits. The stamps cost a little duplication and save every lookup.
saying these in an interview costs you the question
- Claims any hierarchy can be flattened into fixed level columns
- Groups by a level's name without qualifying the branch
- Thinks drilling down requires joining an extra level table
- Assumes a dimension can carry only one hierarchy
- Adds a hierarchy-type column and duplicates dimension rows