Chapter 11 · Surrogate Keys, Late-Arriving Facts/Dimensions, Deletes, Restatements, and History Corrections

Surrogate Key Assignment, Natural Key Reuse, Source-System Key Collisions, and Durable Identity

Separate source natural keys, durable enterprise identity, and version surrogate keys so source collisions, key reuse, and SCD history cannot silently merge different customers.

Intermediate → Advanced115–135 minutesIdentity/key-map labPython stdlib sqlite3 · local/syntheticLast reviewed: September 2026

Learning outcomes

AtlasMart receives customer identifiers from CRM, a marketplace, and a legacy system. The string C001 appears in more than one source, and legacy key 77 is eventually reused. If the warehouse treats those strings as globally unique customer keys, unrelated people can be merged before any SCD logic even begins.

01

Distinguish a source natural/business key, a source-qualified identity, a durable warehouse identity, and a Type 2 version surrogate key.

02

Explain why surrogate keys solve dimension-version identity while durable keys solve cross-version entity continuity.

03

Detect source-system key collisions and natural-key reuse with validity windows rather than relying on key text alone.

04

Use an explicit key map to make identity resolution auditable and replayable.

05

Reject natural-key-only fact foreign keys and other shortcuts that make history ambiguous.

Chapter 11 continuity contract

Chapter 11 preserves all accepted AtlasMart contracts from Chapters 01–10. Before this chapter, current sales contain seven paid order-line facts, four paid orders, nine units, 625 paid GMV, 380 cost-at-sale, and 245 gross profit. Customer history uses Chapter 07's half-open business-effective intervals; Chapter 08 special dimensions, Chapter 09 bridges, and Chapter 10 fact-shape contracts remain valid. Chapter 11 adds versioned fact revisions and correction/audit surfaces without changing the business grain. A backdated C001 segment correction affects historical attribution, one late order line increases the current sales population, and one authorized O1002 amount correction changes a published metric under an explicit restatement record.

Execution, legal, and interpretation note

The mandatory lab is synthetic, local, and disposable. It uses Python's standard-library sqlite3 module and SHA-256 hashes only as reproducibility evidence. The lab proves local key-resolution, revision, temporal, and audit mechanics; it does not prove distributed exactly-once delivery, legal compliance, production deletion completeness, or cloud-warehouse behavior. Privacy/erasure requirements depend on applicable law, controller policy, purpose, retention obligations, backups/exports, and other state surfaces.

Important boundary

A surrogate key and a durable key answer different questions. One identifies a specific dimension row/version; the other persists across versions for the same real-world entity under the warehouse identity policy.

1. Four identity layers, four jobs

A natural/business key is assigned by a source system. A source-qualified key adds the source namespace so CRM:C001 and MARKETPLACE:C001 are not accidentally treated as the same record. A durable identity is the warehouse's persistent identity for the entity across source-key changes and Type 2 versions. A dimension surrogate key identifies one specific dimension version row.

Source identity Validity Durable identity What it proves
CRM / C001 2026-01-01 → open D-CUST-001 normal source-to-enterprise identity
MARKETPLACE / C001 2026-01-01 → open D-CUST-MP-001 same natural key text can mean a different entity in another source
LEGACY / 77 2025-01-01 → 2026-07-01 D-CUST-LEGACY-A first identity epoch
LEGACY / 77 2026-07-01 → open D-CUST-LEGACY-B same source key reused for a different entity

The tiny identity-map fixture intentionally includes identities with no sales facts. The point is to prove the identity rule, not to imply every mapped source identity is already represented in every downstream mart.

2. DDL that makes the distinctions observable

chapter11_identity.sql
PRAGMA foreign_keys = ON;DROP TABLE IF EXISTS deletion_manifest;DROP TABLE IF EXISTS source_tombstone;DROP TABLE IF EXISTS correction_ledger;DROP TABLE IF EXISTS dimension_change_ledger;DROP TABLE IF EXISTS fact_sales_revision;DROP TABLE IF EXISTS fact_event_registry;DROP TABLE IF EXISTS dim_customer_history;DROP TABLE IF EXISTS identity_map;CREATE TABLE identity_map (  source_system TEXT NOT NULL,  source_customer_id TEXT NOT NULL,  valid_from TEXT NOT NULL,  valid_to TEXT NOT NULL,  durable_customer_id TEXT NOT NULL,  identity_note TEXT NOT NULL,  CHECK(valid_from < valid_to),  PRIMARY KEY(source_system,source_customer_id,valid_from));CREATE TABLE dim_customer_history (  customer_sk INTEGER PRIMARY KEY AUTOINCREMENT,  durable_customer_id TEXT NOT NULL,  source_system TEXT NOT NULL,  source_customer_id TEXT NOT NULL,  customer_name TEXT NOT NULL,  segment TEXT NOT NULL,  geography_code TEXT NOT NULL,  lifecycle_status TEXT NOT NULL,  privacy_state TEXT NOT NULL DEFAULT 'normal',  effective_from TEXT NOT NULL,  effective_to TEXT NOT NULL,  is_current INTEGER NOT NULL CHECK(is_current IN (0,1)),  CHECK(effective_from < effective_to),  UNIQUE(durable_customer_id,effective_from));CREATE UNIQUE INDEX ux_customer_one_current  ON dim_customer_history(durable_customer_id)  WHERE is_current=1;CREATE TABLE fact_event_registry (  source_event_id TEXT PRIMARY KEY,  order_id TEXT NOT NULL,  line_no INTEGER NOT NULL,  first_seen_at TEXT NOT NULL);CREATE TABLE fact_sales_revision (  fact_revision_sk INTEGER PRIMARY KEY AUTOINCREMENT,  order_id TEXT NOT NULL,  line_no INTEGER NOT NULL,  source_event_id TEXT NOT NULL,  event_ts TEXT NOT NULL,  durable_customer_id TEXT NOT NULL,  customer_sk INTEGER NOT NULL REFERENCES dim_customer_history(customer_sk),  product_id TEXT NOT NULL,  quantity INTEGER NOT NULL CHECK(quantity>0),  extended_amount NUMERIC NOT NULL CHECK(extended_amount>=0),  extended_cost NUMERIC NOT NULL CHECK(extended_cost>=0),  revision_no INTEGER NOT NULL,  is_current INTEGER NOT NULL CHECK(is_current IN (0,1)),  correction_id TEXT,  recorded_at TEXT NOT NULL,  UNIQUE(order_id,line_no,revision_no));CREATE UNIQUE INDEX ux_sales_one_current_revision  ON fact_sales_revision(order_id,line_no)  WHERE is_current=1;CREATE TABLE dimension_change_ledger (  event_id TEXT PRIMARY KEY,  durable_customer_id TEXT NOT NULL,  attribute_name TEXT NOT NULL,  business_effective_from TEXT NOT NULL,  old_value TEXT NOT NULL,  new_value TEXT NOT NULL,  applied_at TEXT NOT NULL);CREATE TABLE correction_ledger (  correction_id TEXT NOT NULL,  item_key TEXT NOT NULL,  correction_type TEXT NOT NULL,  reason TEXT NOT NULL,  authorized_by TEXT NOT NULL,  requested_at TEXT NOT NULL,  applied_at TEXT NOT NULL,  before_hash TEXT NOT NULL,  after_hash TEXT NOT NULL,  PRIMARY KEY(correction_id,item_key));CREATE TABLE source_tombstone (  tombstone_id TEXT PRIMARY KEY,  source_system TEXT NOT NULL,  source_customer_id TEXT NOT NULL,  durable_customer_id TEXT NOT NULL,  observed_at TEXT NOT NULL,  business_effective_at TEXT NOT NULL,  reason TEXT NOT NULL,  payload_hash TEXT NOT NULL);CREATE TABLE deletion_manifest (  request_id TEXT PRIMARY KEY,  durable_customer_id TEXT NOT NULL,  action TEXT NOT NULL,  scope TEXT NOT NULL,  policy_context TEXT NOT NULL,  requested_at TEXT NOT NULL,  applied_at TEXT NOT NULL,  before_hash TEXT NOT NULL,  after_hash TEXT NOT NULL,  status TEXT NOT NULL);SELECT source_system,source_customer_id,valid_from,valid_to,durable_customer_idFROM identity_mapORDER BY source_system,source_customer_id,valid_from;

The warehouse does not infer durable identity from matching text. It resolves a source-qualified key at the relevant validity interval, then resolves a version surrogate key from business event time.

3. Deliberately wrong approach: natural key as dimension primary key

A fact table that stores only customer_id='C001' cannot tell whether the row came from CRM or Marketplace. A legacy fact with key 77 cannot tell which identity epoch is intended after key reuse. Appending an effective date to the natural key also fails to establish enterprise identity; it merely manufactures another source-shaped identifier.

wrong_natural_key_join.sql
-- Deliberately ambiguous:SELECT f.order_id,d.customer_nameFROM fact_sales fJOIN dim_customer d ON d.source_customer_id=f.customer_id;-- Safer pipeline idea:-- 1) resolve (source_system, source_customer_id, event_ts) -> durable_customer_id-- 2) resolve (durable_customer_id, event_ts) -> customer_sk-- 3) persist the version surrogate key in the fact

The second step is temporal because Type 2 history can produce multiple dimension rows for the same durable identity.

4. Baseline controls remain unchanged

Stage Current lines Orders Units GMV Cost Gross profit
Accepted Chapter 10 baseline 7 4 9 625 380 245
After backdated dimension split + key restatement 7 4 9 625 380 245
After late O1000 line arrives 8 5 10 700 425 275
After authorized O1002 amount restatement 8 5 10 690 425 265
After source-delete + privacy workflow 8 5 10 690 425 265

Changing key architecture must not silently change revenue, order, unit, cost, or profit controls. A key migration is accepted only after referential-integrity and reconciliation checks pass.

5. Unknown members and unresolved identities

AtlasMart retains a governed D-UNKNOWN dimension member for cases that truly cannot yet resolve. But unresolved source identifiers should be retained in an error/placeholder workflow so they can be repaired later. Collapsing every unresolved fact onto one anonymous row without preserving the source identity makes later correction difficult.

6. Production judgment

Identity policy needs ownership. Decide which source is authoritative, when two source identities represent the same entity, how mergers/splits are handled, whether source-key reuse is allowed, and how changes propagate to conformed dimensions. A simple sequential integer surrogate key is an implementation detail; the hard part is the durable identity contract behind it.

Knowledge check

Check your understanding

  1. Why is CRM:C001 different from MARKETPLACE:C001 in the fixture?
  2. What remains stable when a Type 2 customer gets a new version row?
  3. Why are validity windows present on the identity map?
  4. Can a surrogate key by itself prove two source records are the same person?
  5. What must a key migration reconcile?
Review the answers

1. The source namespace is part of the source identity; matching key text alone does not prove the same entity.

2. The durable customer identity remains stable while the dimension surrogate key changes.

3. They make source-key reuse over time explicit, such as LEGACY:77 representing different identities in different periods.

4. No. The surrogate key is warehouse-assigned after identity resolution; it does not replace the identity-matching policy.

5. Fact counts, business totals, referential integrity, unknown/unresolved rates, and the source-to-durable-to-version mapping evidence.

Summary and next step

Chapter 11 begins by separating identity continuity from dimension-version identity. Lesson 2 uses that distinction to load a genuinely late fact without binding it to the customer version that happens to be current at load time.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.