Skip to content
PHP2Shell: The PHP Bug
Akshay Thakare

Three Kinds of Fact Tables: Transaction, Snapshot and Accumulating

Most facts fit one of three patterns. Picking the right one decides whether questions like 'what was the balance on the 1st?' are easy or impossible.

Data ModelingIntermediate3 min readUpdated 11 Oct 2026

Why the type matters

A fact table records measurements at a declared grain. Kimball-style modelling recognises three common shapes. Each answers different questions cheaply, and choosing the wrong one makes some questions expensive or impossible.

TypeOne row perTypical measuresRows change?
Transactionevent (order line, payment, click)amount, quantityNo — insert only
Periodic snapshotentity per period (account per day)balance, stock on handNo — new row each period
Accumulating snapshotprocess instance (one order's lifecycle)dates and durations per milestoneYes — updated as milestones happen

Transaction facts

The default. One row per business event at the finest grain you can get.

SQL.sql
-- fact_order_line: one row per order line
order_line_id, order_date_key, customer_key, product_key, quantity, line_amount

Fully additive: you can SUM(line_amount) across any dimension. But "what was each customer's open balance on 1 March?" requires replaying every event up to that date.

Periodic snapshot facts

One row per entity per period, capturing state as of the end of the period.

SQL.sql
-- fact_account_daily: one row per account per day
snapshot_date_key, account_key, closing_balance, transactions_count

Snapshots trade storage for speed: point-in-time questions become a simple filter on snapshot_date_key.

Accumulating snapshot facts

One row per instance of a process with a clear start and end, updated as it moves through milestones.

SQL.sql
-- fact_order_fulfilment: one row per order, updated in place
order_key, ordered_date_key, packed_date_key, shipped_date_key, delivered_date_key,
hours_to_ship, hours_to_deliver
Accumulating snapshot milestonesSwipe to explore →

Rendering accumulating snapshot milestones

An order row is inserted when ordered and updated when packed, shipped and delivered.

Great for pipeline and lag analysis ("average hours from order to ship"), but it requires updates — use MERGE and keep it idempotent.

How to choose

  • Need every event or flexible slicing? Transaction fact.
  • Need state at regular points in time? Periodic snapshot.
  • Need durations between known milestones? Accumulating snapshot.

Many warehouses use all three for the same domain: transaction facts as the source of truth, with snapshots derived from them for fast reporting.

Finished this note?

Share