Chapter 10 · Transaction, Periodic Snapshot, and Accumulating Snapshot Fact Tables

Choosing Multiple Fact Tables for One Business Process Instead of Forcing One Universal Grain

Compare transaction, periodic snapshot, and accumulating snapshot grains side by side, then reject a universal mixed-grain fact through concrete query failures.

Intermediate → Advanced110–130 minutesFact-shape comparison labPython stdlib sqlite3 · local/syntheticLast reviewed: September 2026

Learning outcomes

AtlasMart now has three legitimate analytical needs: atomic sales events, end-of-day inventory state, and order-fulfillment lifecycle progress. A common anti-pattern is to force them into one “fact_activity” table because they share dates, products, or orders. Chapter 10 instead keeps each measurement process at its own grain and aligns answers only after each fact has been aggregated safely.

01

Compare transaction, periodic snapshot, and accumulating snapshot grains without ranking one as universally superior.

02

Identify null-heavy and double-counting symptoms of a mixed-grain universal fact.

03

Map each business question to the fact table whose row meaning matches it.

04

Drill across separate facts only after grain-appropriate aggregation.

05

Preserve conformed dimensions without forcing identical foreign-key sets on unrelated grains.

Chapter 10 continuity contract

Chapter 10 starts from the accepted Chapter 09 state. Canonical paid sales remain seven order-line facts, four paid orders, nine units, 625 paid GMV, 380 cost-at-sale, and 245 gross profit. Customer SCD history keeps Chapter 07's half-open business-effective intervals; Chapter 08's special-dimension patterns and Chapter 09's bridge/allocation contracts remain valid and are not rewritten here. The September 20 inventory control remains 137 units. Chapter 10 adds a second explicit inventory snapshot for September 19 (146 units) and an order-level fulfillment lifecycle fixture so learners can compare transaction, periodic snapshot, and accumulating snapshot grains without forcing them into one table.

Execution and interpretation note

The mandatory lab is synthetic, local, and disposable. It uses Python's standard-library sqlite3 module, so learners should record python --version and sqlite3.sqlite_version when executing. The fixture proves grain, aggregation, idempotency, and lifecycle-update semantics in one local process; it does not claim warehouse-scale performance, distributed exactly-once delivery, cloud concurrency behavior, or a universal SQL MERGE contract.

Important boundary

Shared dimensions do not imply shared fact grain. Conformance enables compatible slicing; it does not justify storing every measurement process in the same table.

1. Three facts, three row meanings

Fact shape Declared grain Row behavior Best questions Unsafe shortcut
Transaction one paid order line event insert for each event; corrections follow an explicit policy what sold, how much, which product/customer/date derive point-in-time inventory from current sales only
Periodic snapshot one product at one snapshot date new row for every expected period/member what was on hand at a date; trend of end-of-day state sum balances across dates as though they were independent units
Accumulating snapshot one order fulfillment lifecycle same row revisited as predictable milestones occur where is the order now; milestone lags; pipeline state treat each update as a new transaction or expect full event history from the current row

The same enterprise can legitimately maintain all three. The design question is whether each row has one clear measurement meaning, not whether the warehouse has “too many tables.”

2. Deliberately wrong universal table

wrong_universal_fact.sql
CREATE TABLE fact_everything (  row_type TEXT NOT NULL,  order_id TEXT,  line_no INTEGER,  snapshot_date TEXT,  product_id TEXT,  quantity INTEGER,  revenue NUMERIC,  on_hand_units INTEGER,  shipped_ts TEXT,  delivered_ts TEXT);-- Sales rows need order/line/revenue but not snapshot balance.-- Inventory rows need snapshot_date/on_hand but not order/line.-- Lifecycle rows need one row per order and milestone updates.-- NULLs are not the main problem: the row meaning is inconsistent.

Now a query such as COUNT(*) has no stable business meaning, date columns mix event and snapshot roles, and consumers can accidentally sum revenue beside repeated inventory balances. A discriminator does not restore one declared grain.

3. Safe alignment happens after aggregation

Suppose a dashboard wants September 20 paid GMV, end-of-day inventory, and count of orders not yet delivered. Compute each measure from its own fact first, then place the scalar or date-level results side by side. Do not atomic-join order lines to product snapshots: that would repeat the daily balance once per matching sales line.

safe_drill_across.sql
WITH sales AS (  SELECT substr(event_ts,1,10) AS d, SUM(extended_amount) AS paid_gmv  FROM fact_sales_transaction  GROUP BY substr(event_ts,1,10)), inventory AS (  SELECT snapshot_date AS d, SUM(on_hand_units) AS on_hand  FROM fact_inventory_daily  GROUP BY snapshot_date), open_fulfillment AS (  SELECT '2026-09-20' AS d, COUNT(*) AS not_delivered  FROM fact_fulfillment_accum  WHERE delivered_ts IS NULL     OR delivered_ts > '2026-09-20T23:59:59')SELECT s.d,s.paid_gmv,i.on_hand,o.not_deliveredFROM sales sLEFT JOIN inventory i USING(d)LEFT JOIN open_fulfillment o USING(d)WHERE s.d='2026-09-20';

Each CTE reduces its source fact to the requested report grain first. This is the same safety principle used for cross-process drill-across in Chapter 06.

4. Conformed dimensions still matter

Product/date/customer conformance makes independent facts comparable, but every fact includes only dimensions meaningful at its grain. Inventory has no customer dimension. An order-level lifecycle may not carry product when one order contains multiple products; if product-level fulfillment is required, choose a line-level lifecycle grain or a separate bridge/child process rather than stuffing multiple product keys into one order row.

5. Storage and performance tradeoffs

Transaction facts grow with event volume. Dense periodic snapshots grow with periods × covered members. Accumulating snapshots grow with process instances but incur repeated updates. None of those statements implies a universal performance winner. Physical design depends on the target engine, workload, data size, update behavior, partitioning, clustering, compression, concurrency, and retention policy. This chapter evaluates semantic fit first; later chapters address physical/columnar design and performance with measured workloads.

6. Migration and rollback

If AtlasMart previously published a mixed-grain table, migration should run old and new outputs in parallel, map each legacy metric to its new authoritative fact, reconcile historical periods, update semantic/report dependencies, and define rollback criteria. Deleting the old table before consumers prove semantic equivalence converts a modeling improvement into an operational incident.

Knowledge check

Check your understanding

  1. Why is a discriminator column insufficient to fix a mixed-grain fact?
  2. When should separate facts be aligned?
  3. Does a conformed product dimension require inventory and sales to share the same fact table?
  4. Why might an order-level accumulating fact omit product?
  5. Which fact type is universally fastest?
Review the answers

1. The rows still represent different measurement processes, so shared aggregates and dimensions have inconsistent meanings.

2. After each fact has been aggregated safely to a compatible reporting grain.

3. No. Conformance enables compatible slicing while preserving independent fact grains.

4. One order can contain multiple products; adding one product key would misrepresent the order-level grain.

5. None. Physical performance is engine- and workload-dependent and must be measured after semantic correctness is established.

Summary and next step

Multiple fact tables are a semantic strength when they preserve distinct measurement processes. Lesson 5 assembles the three shapes into one reproducible acceptance suite and a question-to-fact decision matrix.

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.