skip to content

Evolving a Published Model

Changing a fact table's grain, adding a dimension attribute, or retiring a column once dashboards and downstream models depend on it — without breaking reports or silently rewriting history. The senior version of this topic.

on this pageshow

questions

6

Which changes to a published star schema are backward-compatible for existing reports, and which are not?

level: middleimportance: must knowfreq 66%

answer

  1. three buckets, not two
  2. the harmless one only appends columns
  3. errors are the easy failure mode
  4. some changes move numbers without moving schema
  5. grain change means a new table

basics

~20 s

Additive changes — a new dimension attribute, a new dimension table, a new fact measure — leave existing queries valid. Grain changes, drops, renames and type changes break consumers loudly; redefining what an existing measure means breaks them silently, which is worse.

solid answer

~50 s

Sort every proposed change into three buckets. **Additive**: a new attribute on a dimension, a new dimension table, a new measure column, a new fact table — existing SELECTs keep returning what they returned, so these ship freely. **Structurally breaking**: dropping or renaming a column, narrowing a type, reissuing surrogate keys, changing a foreign key's target — queries error or silently filter to nothing, and consumers must be migrated first. **Semantically breaking**: the column list is unchanged but the numbers move — a measure's business definition changes, a Type 1 dimension is converted to Type 2 so joins now fan out to several history rows, an "unknown" member starts appearing. That third bucket is the dangerous one because nothing fails; dashboards just start disagreeing with last week's. Treat any grain change as a new table rather than an edit.

code

text · 10 lines
text
-- before: one row per customer
customer_sk | customer_id | segment
    501     |   C-100     | SMB

-- after: history kept, two rows match C-100
customer_sk | customer_id | segment | is_current
    501     |   C-100     | SMB     | false
    902     |   C-100     | ENT     | true

-- a fact row joined on customer_id now appears twice

go deeper

for a junior

Be ready to say which changes leave an existing SELECT working: adding a column or a table is safe, dropping or renaming one is not. Knowing that dashboards read these tables and will break is the point.

for a middle

Explain the mechanics: why a new column is invisible to a named-column query, why a rename errors, and why converting a dimension to keep history changes join cardinality and inflates sums.

for a senior

Demonstrate that you find consumers from query logs and lineage before shipping, reconcile old versus new totals, and treat silent numeric shifts as the highest-risk category rather than the harmless one.

for a principal

Own the classification as a policy: what your organisation is allowed to change in place, what requires a deprecation window and announcement, and who signs off when a measure's definition moves under reports the business already acted on.

## What "published" means here A model is published once something you do not control reads it: a BI dashboard, a scheduled extract, a downstream transformation owned by another team, a spreadsheet someone refreshes monthly. From that moment the table's shape and the meaning of its columns are a contract. The useful skill in an interview is not reciting rules but classifying a proposed change by what it does to that contract. ## Bucket 1 — additive, ship it Additive changes only append surface area: - a new attribute column on an existing dimension (`dim_customer.industry_code`), - a new dimension table plus its foreign key on a fact table, - a new measure column on a fact table, - an entirely new fact table for a new business process. Existing queries name the columns they want, so a new column is invisible to them. The two traps are `SELECT *` consumers (a new column can widen an extract or shift positional references) and adding a column with a definition that only holds for recent rows — history is null or wrong until backfilled, and a report that averages over all time silently mixes populated and unpopulated periods. ## Bucket 2 — structurally breaking, loud Dropping a column, renaming a column or table, narrowing a type (`DECIMAL(18,4)` to `INTEGER`), changing nullability, or regenerating surrogate keys so that yesterday's `customer_sk = 501` is somebody else any of these makes downstream queries fail, or worse, makes saved filters and hard-coded key lists match nothing. These are unpleasant but self-announcing: someone sees an error. They still require finding consumers and giving them a migration window, because "it errored in production at 6am" is not a migration plan. Key regeneration deserves special mention. Warehouse surrogate keys are frequently persisted outside the warehouse — in a BI tool's cached extract, in a saved filter, in another team's model. A full rebuild that re-issues sequence values is technically "the same data" and practically a silent corruption. ## Bucket 3 — semantically breaking, silent The schema is byte-identical and the numbers change: - **The measure's definition moves.** `revenue` starts including shipping, or excluding cancelled orders, or is now net of returns. Every report keeps running and every historical comparison quietly shifts. - **A Type 1 dimension becomes Type 2.** Previously one row matched each customer; now several history rows do. Any report that joins on the natural business key rather than the surrogate key, or that forgets the current-row predicate, multiplies its fact rows and inflates sums. - **Membership changes.** A row that used to be excluded by an ETL filter is now included; test accounts start appearing; an "Unknown" member is introduced for orphan keys and shows up in every breakdown. - **A dimension attribute changes cardinality or spelling.** `segment = 'SMB'` becomes `'Small Business'` and every dashboard filter pinned to the old literal returns zero. Silent changes are the ones that destroy trust in the warehouse, because the failure mode is a number, not an error. ## Grain changes are a category of their own Changing a fact table's grain — one row per order becoming one row per order line — is not a modification of the table; it is a different table that happens to reuse the name. Every `COUNT(*)`, every `AVG`, every join cardinality changes. The safe pattern is to build the new grain alongside the old and let the old table become a derived aggregate over the new one until consumers move. ## A practical protocol 1. Classify the change into the three buckets above before writing any DDL. 2. For buckets 2 and 3, enumerate consumers from warehouse query logs and BI lineage — never from memory. 3. Reconcile: run old and new side by side over the same period and diff the totals. A change you intended to be additive that moves any total is not additive. 4. Announce semantic changes with a dated note in the catalog and, where the old definition still has users, keep both measures under distinct names rather than overwriting one. 5. Expose consumers a view rather than the base table, so that renames and reshapes can be absorbed behind the facade instead of being pushed onto every dashboard.

  • Why is converting a dimension from Type 1 to Type 2 treated as a breaking change even though no column is removed?
    Because it changes join cardinality. Before the change one row matched each business key; afterwards several history rows do. Any query that joins on the business key, or omits the current-row predicate, now fans out and inflates every sum and count. The schema looks compatible; the numbers are not.
  • Adding a nullable column to a dimension is additive — when can that still mislead a report?
    When history is not backfilled. Old rows carry NULL or a placeholder, so a breakdown by the new attribute shows a large "unknown" bucket that looks like a data quality problem, and any average or share computed across all time mixes populated with unpopulated periods. Either backfill, or document the date the column becomes meaningful.

saying these in an interview costs you the question

  • Says any change is safe if the pipeline still runs
  • Treats a grain change as an ordinary schema edit
  • Ignores silent semantic changes because nothing errors
  • Assumes surrogate keys can be freely regenerated on rebuild
  • Thinks adding a column is always safe with no backfill

context

open as a page

How do you roll out a fact table grain change from one row per order to one row per order line?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Treat it as a new table, not an edit. Build the line-grain fact alongside the order-grain one, reconcile their totals, then rebuild the old table as an aggregate over the new one so consumers keep working while they migrate, and retire it on a published date.

open as a page

You add a derived column to an incrementally loaded fact table — how do you backfill history?

level: middleimportance: should knowfreq 50%

basics

~20 s

Either rebuild the table in full, or run a bounded backfill over historical date ranges in idempotent, re-runnable batches while the nightly incremental load keeps running. Pick by cost, and first check whether the historical source values still exist at all.

open as a page

How do you retire a column from a published mart when dashboards you don't own still read it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Find the consumers from warehouse query logs and BI lineage, publish a replacement and a sunset date, then remove the column from the consumer-facing view first while the base table keeps it. Only drop the base column once nothing has read the view column for a full reporting cycle.

open as a page

When a metric definition changes, do you restate history in the fact table or apply it going forward?

level: seniorimportance: should knowfreq 46%

basics

~20 s

It depends on whether previously published numbers must remain reproducible. Restating makes the whole series internally comparable but rewrites numbers the business already acted on. Applying it going forward preserves the record but creates a break in the series. Publishing both measures side by side avoids choosing.

open as a page

When is publishing a v2 data mart alongside v1 better than migrating consumers in place?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Version in parallel when the change is structural or semantic, the consumer set is large or partly unknown, and each team must migrate on its own schedule. Only do it with a named owner, a sunset date, and v1 derived from v2 so the two cannot diverge.

open as a page