skip to content

A snowflaked product dimension spans seven tables and every dashboard joins all of them — how would you restructure it?

level: seniorimportance: should knowfreq 45%

answer

  1. what grain must the flat dimension have?
  2. test one row per product key first
  3. inner join can silently drop members
  4. who still needs the normalized tables?
  5. reconcile totals before switching dashboards

basics

~20 s

Flatten it into one wide dimension at the product grain, keeping the normalized tables as the load and reference layer. Verify each level contributes at most one row per product, then publish the flat table and reconcile totals against the old joins before cutting dashboards over.

solid answer

~50 s

Treat it as a presentation-layer change, not a rewrite. First **declare the target grain**: one row per product key, exactly the grain the fact table already references, so no fact table changes. Then **prove flattening is safe** — for each of the six parent tables, check that a product maps to at most one row; any level that is genuinely multi-valued cannot be flattened and needs a different construct. Build the flat dimension as a view over the existing chain, materialize it once it stabilizes, and keep the normalized tables as the governed load layer so upstream maintenance is unaffected. Reconcile by running key aggregates against both shapes and comparing row counts and totals. Finally migrate consumers, and only drop nothing — the old tables stay as the source of the flat one. Also audit which of the seven tables' attributes anyone actually uses; deep snowflakes often carry levels no report has ever touched.

go deeper

for a junior

Know that the fix direction is one wide dimension table at the product grain, and that the fact table itself does not have to change.

for a middle

Be able to write the flattening join and the uniqueness test that proves each level contributes at most one row per product key.

for a senior

Demonstrate the safeguards: outer joins so members are not dropped, an attribute-usage audit, keeping the normalized layer as the source, and reconciling totals before migrating dashboards.

for a principal

Own the sequencing and the politics — preserving upstream ownership of governed levels, staging the consumer migration, and deciding what the platform's published dimension contract is going forward.

## Read the situation before you redesign A seven-table dimension chain means every dashboard query carries six joins to reach labels that logically describe one product. The symptoms are predictable: queries that are long and copy-pasted, analysts who join at the wrong level and produce numbers nobody can reconcile, and dashboards that are slow and hard to reason about. The fix is a flattening, but it has to be done in an order that cannot break the fact table or the load. ## Step 1 — declare the target grain of the dimension The flat dimension must be **one row per product key**, because that is the key the fact table already carries. Saying this out loud matters: it fixes the uniqueness contract you will test, and it makes clear that no fact table is being rewritten. The fact's grain, measures and foreign keys are untouched by anything that follows. ## Step 2 — prove that flattening is safe This is the step candidates skip and the one that actually causes incidents. A hierarchy chain flattens correctly only if every parent table contributes at most one row per child. If a product can belong to two categories, the join emits two rows per product, and any fact measure aggregated through the flat dimension is counted twice. Test it explicitly before building anything: ```sql -- must return zero rows for a safe flatten SELECT product_key, COUNT(*) AS n FROM dim_product_flat GROUP BY product_key HAVING COUNT(*) > 1; ``` If a level fails this test, it is a multi-valued relationship, not a hierarchy level, and it must stay out of the flat dimension and be modelled with the construct meant for many-to-many attribution. ## Step 3 — audit what is actually used Before copying 120 columns forward, look at which attributes real queries reference. Deep snowflakes accumulate levels that were modelled because the source had them, not because anyone asks questions at that level. Carrying dead attributes forward doubles the cost of every future change to the dimension. Keep what is used, keep what is plausibly about to be used, and leave the rest reachable in the normalized layer. ## Step 4 — build flat as a view first, then materialize Start with a view so the change is cheap to revise, and materialize once the column list has settled: ```sql CREATE VIEW dim_product_flat AS SELECT p.product_key, p.sku, p.product_name, b.brand_name, c.category_name, d.department_name, v.division_name FROM dim_product p LEFT JOIN dim_brand b ON b.brand_key = p.brand_key LEFT JOIN dim_category c ON c.category_key = b.category_key LEFT JOIN dim_department d ON d.department_key = c.department_key LEFT JOIN dim_division v ON v.division_key = d.department_key; ``` Use outer joins deliberately: with inner joins, a product whose brand row is missing disappears from the dimension entirely, and its facts silently drop out of every report. Outer joins keep the product and leave the label null, which is a visible defect rather than an invisible one — and it tells you to supply an "Unknown" default rather than a null. ## Step 5 — keep the normalized tables Do not delete the chain. It remains where the data is loaded, governed and tested, and it is the source of the flat table. The end state is the standard hybrid: normalized where you load, flat where you query. This also means upstream master-data ownership of any level is preserved unchanged, which removes the main political objection to the flattening. ## Step 6 — reconcile, then migrate consumers Run the same handful of business-critical aggregates against the old join chain and the new flat dimension and diff them: total rows in the dimension, distinct products, and two or three revenue-by-level totals. Any difference is either the fan-out you tested for, a null-versus-missing difference introduced by join type, or a genuine bug in the old queries — all three are worth knowing before anyone's dashboard changes. Then move consumers over, one dashboard at a time, rather than in a single cut. ## What the interviewer is testing That you separate the *shape* change from the *contract* change; that you test the one-row-per-key assumption instead of assuming it; that you know inner joins can silently drop dimension members; and that you preserve rather than demolish the layer other teams depend on. A candidate who answers only "denormalize it into one table" has the right instinct and none of the safeguards.

  • Why use outer joins rather than inner joins when flattening the chain?
    An inner join drops any product whose parent row is missing, so that product vanishes from the dimension and its facts fall out of every report silently. An outer join keeps the product with a null label, which surfaces the gap. Better still, replace the null with an explicit Unknown member so grouping stays clean.
  • One level in the chain maps a product to several categories. What do you do?
    Leave it out of the flat dimension. That relationship is multi-valued, not hierarchical, and joining it multiplies product rows and inflates every measure aggregated through it. Model the many-to-many attribution with the construct designed for it, and keep the flat dimension strictly at one row per product.
  • How do you confirm the flattening did not change any published number?
    Pick the aggregates the business actually watches, run them against the old join chain and the new flat dimension, and diff row counts and totals side by side for a full period. Investigate every difference — some will be fan-out, some join-type changes, and occasionally one is a pre-existing bug in the old queries.
  • Should the flat dimension be a view or a materialized table?
    Start as a view while the column list is still moving, since revisions are free and there is nothing to rebuild. Materialize once it stabilizes and consumers depend on it, so every dashboard is not re-executing six joins. The definition stays the same either way; only the refresh obligation changes.

saying these in an interview costs you the question

  • Flattens without testing one row per dimension key
  • Uses inner joins and silently drops dimension members
  • Deletes the normalized tables the load layer depends on
  • Copies every attribute forward without checking what is used
  • Claims flattening the dimension requires rebuilding the fact table

context