skip to content

When do you deliberately keep tables in an analytics layer normalized instead of flattening them?

level: seniorimportance: nice to knowfreq 38%

answer

  1. not every layer is the mart
  2. staging deliberately mirrors the source
  3. flattening a one-to-many costs something
  4. columns you would number are a smell
  5. many-to-one flattens; many-to-many does not

basics

~20 s

Keep it normalized where flattening would change the grain, invent numbered columns, or hide a genuine one-to-many: staging layers that mirror the source, multi-valued relationships, and reference data that must be maintained in exactly one place.

solid answer

~50 s

"Denormalized" describes the layer users query, not the whole platform. Three cases keep their source-like shape on purpose. **The layers behind the mart.** Staging deliberately mirrors extracted source tables so ingestion stays mechanical, diffable and re-extractable; intermediate models hold reusable logic. Flattening early hides where a value came from and makes debugging a load much harder. **Genuine one-to-many relationships.** A customer with several industries cannot be flattened without either numbering columns (`industry_1..industry_5`, which breaks the moment there is a sixth and makes filtering awkward) or repeating the row, which multiplies any measure joined to it. That relationship stays in its own table. **Reference data with one point of maintenance.** Regulated code lists, mappings owned by a governance process, or attributes that change far more often than they are read are cheaper to hold once and resolve at query or load time. The default for the consumption layer is still wide; these are the reasoned exceptions.

code

text · 10 lines
text
customer 4471 belongs to: Retail, Logistics, Manufacturing

(a) numbered columns  -> one row, arbitrary limit, OR-filters
    dim_customer(customer_key, name, industry_1, industry_2, industry_3, ...)

(b) repeat the row    -> three rows per customer
    joining a fact to this triples every SUM  <-- silent wrong answer

(c) separate table    -> one row per pair, joined only when asked
    customer_industry(customer_key, industry_name)

go deeper

for a junior

Know that the wide, flattened shape describes the tables analysts query, and that the layers that load them often look much more like the source system's tables.

for a middle

Explain why a one-to-many relationship cannot be flattened safely: numbered columns encode an arbitrary limit, and repeating rows multiplies any measure joined to the table.

for a senior

Give the decision rule — flatten only when the relationship is many-to-one from the row you are flattening onto — and back it with the failure you have seen, usually an inflated total nobody noticed until reconciliation.

for a principal

Set the platform convention: which layers mirror the source, which layer is published, and what review gate stops a many-valued attribute from being flattened into the consumption model where it will silently inflate numbers.

## "The warehouse is denormalized" is a statement about one layer The contrast drilled in every interview — normalized source, flattened analytics model — describes the **presentation layer**, the tables a BI tool and an analyst actually query. A real platform has several layers behind that one, and most of them look much more like the source than like the mart. Knowing where the flattening starts is what separates someone who has built a pipeline from someone who has read about star schemas. ## Case 1: the layers behind the mart A staging layer that mirrors extracted source tables is not laziness; it is a design choice with three payoffs. First, extraction stays mechanical — the transform between source and staging is renaming and type casting, so an ingestion failure is easy to localize. Second, it is diffable: when a number changes, you can compare staged rows to the source without re-running business logic. Third, it makes re-extraction and backfills cheap, because the raw shape is already there and only the transformations downstream need to re-run. Intermediate models sit between staging and the mart, holding logic that several published tables share. They are shaped for reuse, not for readability by analysts, and they frequently stay narrow and keyed rather than wide. If you flatten at ingestion, you lose all three properties: every debugging session has to run the whole transformation to find out what the source actually said. ## Case 2: relationships that are genuinely one-to-many This is the case that most often gets modelled badly. Suppose a customer can belong to several industries. There are only three ways to represent that: 1. **Numbered columns** — `industry_1` through `industry_5`. The schema now encodes an arbitrary limit, and the sixth industry forces a schema change. Filtering means `WHERE industry_1 = 'Retail' OR industry_2 = 'Retail' OR …`, which is error-prone and unindexable in any useful sense. Counting distinct industries requires unpivoting them again. 2. **Repeat the customer row, once per industry.** Now the "dimension" has several rows per customer, so joining it to a fact multiplies fact rows and every `SUM` over that join **double-counts**. This is the single most common cause of an inflated revenue number in a mart. 3. **A separate table holding the customer-to-industry pairs**, joined only when the question needs it, with an explicit decision about how measures are allocated when they are. Only the third is honest. The rule generalizes: **flattening is safe when the relationship is many-to-one from the row you are flattening onto.** A customer has one home country, so `country_name` flattens cleanly. A customer has many industries, so it does not. Interviewers probe this because the failure is invisible — the query runs and the total is simply too big. ## Case 3: attributes with a single point of maintenance Some reference data is owned by a process rather than by a source table: a regulatory code list, a finance-approved account mapping, a currency conversion table. Holding it once and resolving it at load or query time is attractive when the value changes on a governance cadence, is corrected retroactively, or must be auditable as a single row that someone signed off on. Copying it across millions of rows is possible — the load can re-derive it — but it costs a full rewrite each time it changes and makes the audit story "which rows carry which version?" instead of "here is the row". A related case: an attribute that changes far more often than it is queried. If a status flips several times a day and only a small fraction of reports use it, carrying it flattened means constant rewriting for little read benefit. ## The cases that are *not* good reasons Be as clear about the anti-reasons, because an interviewer will push: - "Normalization is best practice" — it is best practice for a workload this one is not. - "It saves storage" — storage is rarely the binding constraint in a mart, and analyst time and correctness usually are. - "The source is normalized so the mart should be" — the source's shape is evidence about the business, not a template for the model. - "We might need it later" — flattening is a load-time decision that can be revisited by re-running a transformation. ## How to decide, in one pass Ask three questions of every attribute you are thinking of flattening onto a table: is the relationship many-to-one from this row? Is the value stable enough that re-deriving it on each load is acceptable? Is the table the one consumers actually query, or a layer behind it? Flatten when all three say yes; keep the separate table when any says no. ## Answering this in an interview Lead with "denormalized describes the consumption layer", then give the three exceptions with the double-counting risk as the sharp one, and close with the anti-reasons. Naming the numbered-columns smell and the row-multiplication failure by name is what makes the answer sound like experience rather than theory.

  • What is the concrete symptom when someone flattens a one-to-many relationship by repeating rows?
    Measures inflate. The table now has several rows per key, so joining it to a fact multiplies the fact rows and every SUM counts the same amount once per match. The report runs cleanly and the total is simply too large, which is why it often survives review and is caught by a finance reconciliation weeks later.
  • Why not flatten at ingestion and skip the staging layer entirely?
    Because you lose the ability to see what the source actually said without re-running the whole transformation. A staging mirror makes ingestion failures easy to localize, lets you diff against the source when a number moves, and makes backfills cheap since only downstream logic has to re-run.
  • Is 'it saves storage' ever a good reason to keep a mart normalized?
    Rarely. Storage is seldom the binding constraint on a consumption layer, while analyst time and the chance of a wrong join are. Storage becomes a real argument only for genuinely enormous flattened attributes or when the cost model makes the copy expensive relative to the query benefit — and then it is a measured decision, not a default.

Flattening is like printing a person's address on their business card: fine for the one address they have, absurd for the several languages they speak.

saying these in an interview costs you the question

  • Says everything in a warehouse must be one wide table
  • Flattens a one-to-many into numbered columns
  • Repeats dimension rows per child value, inflating measures
  • Cannot explain why staging mirrors the source shape
  • Argues any normalization in a mart is always wrong

context