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

Periodic Snapshots for Inventory, Account Balances, Daily State, and Semi-Additive Metrics

Model AtlasMart inventory as a daily periodic snapshot, prove completeness and semi-additivity, and show why summing balances across dates answers the wrong question.

Intermediate → Advanced110–130 minutesPeriodic snapshot labPython stdlib sqlite3 · local/syntheticLast reviewed: September 2026

Learning outcomes

AtlasMart’s inventory team asks “how many units were on hand at the end of September 20?” and “how did end-of-day stock change from September 19 to 20?” Those are state-at-period questions. A periodic snapshot intentionally records the state for each expected product/date combination so the warehouse does not confuse event counts with balances.

01

Declare a periodic snapshot grain as one product at one standard snapshot date.

02

Explain why balance measures are additive across products but usually semi-additive across time.

03

Validate snapshot completeness independently from the correctness of the balance values.

04

Compare “sum within a day” with the wrong “sum across days” result.

05

Define rerun/correction behavior for a missed or restated snapshot.

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

A periodic snapshot is not a change log. Repeating the same balance on consecutive days is legitimate because the row represents a new period measurement, not a new business event.

1. Period grain, not transaction grain

AtlasMart declares the inventory snapshot grain as one product at the end of one calendar day. The snapshot is expected to contain all four governed products on both dates in the lab, even if a product had no movement during that day.

Snapshot date Product rows On-hand units Aggregation rule
2026-09-19 4 146 sum products within this date
2026-09-20 4 137 sum products within this date
Both dates 8 283 if blindly summed not a valid current-balance metric

Within one snapshot date, on-hand units can be summed across products. Across dates, the same physical inventory can appear repeatedly, so 146 + 137 = 283 is not “inventory on hand.” It is merely the arithmetic sum of two balance observations.

2. Build and query the dense daily fixture

periodic_snapshot.sql
CREATE TABLE fact_inventory_daily (  snapshot_date TEXT NOT NULL,  product_id TEXT NOT NULL,  on_hand_units INTEGER NOT NULL CHECK(on_hand_units >= 0),  snapshot_batch_id TEXT NOT NULL,  PRIMARY KEY(snapshot_date,product_id));INSERT INTO fact_inventory_daily VALUES('2026-09-19','P100',42,'INV-20260919'),('2026-09-19','P200',63,'INV-20260919'),('2026-09-19','P300',13,'INV-20260919'),('2026-09-19','P400',28,'INV-20260919'),('2026-09-20','P100',40,'INV-20260920'),('2026-09-20','P200',60,'INV-20260920'),('2026-09-20','P300',12,'INV-20260920'),('2026-09-20','P400',25,'INV-20260920');SELECT snapshot_date,SUM(on_hand_units) AS on_handFROM fact_inventory_dailyGROUP BY snapshot_dateORDER BY snapshot_date;

Expected result: 146 on September 19 and 137 on September 20. The two rows answer two separate as-of questions.

3. Deliberately wrong approach: sum balances through time

wrong_time_sum.sql
SELECT SUM(on_hand_units) AS wrong_inventoryFROM fact_inventory_daily;-- Returns 283.-- That is not an end-of-day balance, average balance, or units moved.

The repair depends on the metric. For current/end-of-period inventory, choose the appropriate snapshot date. For average daily inventory, average the daily totals or product-level daily balances according to the intended grain; do not call a raw sum across days “inventory.”

safe_time_semantics.sql
-- End-of-period balanceSELECT SUM(on_hand_units) AS on_hand_20260920FROM fact_inventory_dailyWHERE snapshot_date='2026-09-20';-- 137-- Two-day average of daily total inventoryWITH daily AS (  SELECT snapshot_date,SUM(on_hand_units) AS daily_on_hand  FROM fact_inventory_daily  GROUP BY snapshot_date)SELECT AVG(daily_on_hand) AS avg_daily_on_hand FROM daily;-- 141.5

4. Snapshot completeness is a separate quality dimension

A daily total can look plausible even when one product row is missing. The lab therefore tests expected member coverage: 4 rows per date and the exact governed product set. Missing coverage should fail the snapshot before publication, not silently shrink the total.

snapshot_completeness.sql
SELECT snapshot_date,COUNT(*) AS product_rowsFROM fact_inventory_dailyGROUP BY snapshot_dateHAVING COUNT(*) <> 4;-- Expected: no rows.-- Negative test: delete P400 on 2026-09-20.-- Completeness becomes 3 rows and the total falls from 137 to 112.

Completeness proves all expected rows are present. It does not prove that each source balance is accurate; source-to-warehouse reconciliation is still required.

5. Reruns and corrections

Because (snapshot_date,product_id) is the declared key, a rerun must not create a second row for the same daily measurement. If the source corrects P300 from 12 to 11, AtlasMart must record an authorized snapshot restatement or correction lineage and then republish a reconciled total of 136. A “delete all history and rebuild” shortcut is unsafe unless the blast radius, source replayability, downstream dependencies, and rollback are explicitly controlled.

6. Production judgment

Choose a periodic snapshot when regular state measurements are themselves the analytical object: daily inventory, month-end balances, daily active subscriptions, or service-capacity state. The period must be part of the grain, snapshot completeness must be testable, and every semi-additive balance must carry a documented time aggregation rule.

Knowledge check

Check your understanding

  1. What is the AtlasMart inventory snapshot grain?
  2. Why is 283 not a valid inventory balance?
  3. What does a 4-row completeness test prove?
  4. How should a corrected snapshot be handled?
  5. Why may a row exist even when nothing changed?
Review the answers

1. One product at the end of one calendar day.

2. It sums the same stateful measure across two dates, double-representing inventory through time.

3. It proves all four expected product rows exist for that date, not that their values are accurate.

4. Through an authorized restatement/correction policy with lineage, reconciliation, and controlled republishing.

5. The row represents the state for a new standard period, not a change event.

Summary and next step

Periodic snapshots make regular state visible and queryable, but their balances demand explicit time semantics. Lesson 3 moves to a different mutable fact shape: one row that accumulates predictable lifecycle milestones.

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.