ファクトテーブルの基本的な種類とその使い分け
こんにちは。株式会社 Belong にて Data Platform チームに所属する Shuhei です。
DWH のデータモデリングについて検討する中で、ファクトテーブルについて改めて整理する機会がありました。ファクトテーブルはいくつかに分類が可能であり、その分類ごとに適したユースケースがあるということがわかりました。
今回の記事では、ファクトテーブルの基本的な種類とその使い分けについて整理していきます。
ファクトテーブルとは
ファクトテーブルは、DWH においてビジネスイベントや測定値を記録するテーブルです。売上金額、注文数、在庫数など、分析の対象となる数値データを保持します。
ファクトテーブルは、ディメンションテーブルと組み合わせることで、多角的な分析を可能にします。例えば、売上データを日付、商品、顧客などの軸(ディメンション)で集計することができます。
ファクトテーブルには主に以下の 3 つの種類があることで知られています。
- Transaction Fact Table (トランザクションファクトテーブル)
- Periodic Snapshot Fact Table (定期スナップショットファクトテーブル)
- Accumulating Snapshot Fact Table (累積スナップショットファクトテーブル)
以降でそれぞれの特徴と使い分けについて、詳しく見ていきましょう。
Transaction Fact Table (トランザクションファクトテーブル)
特徴
Transaction Fact Table は、最小粒度のイベントをすべて記録するファクトテーブルです。特徴を整理すると下記のようになります。
データの特性
- 粒度: ビジネスイベント単位
- 更新頻度・タイミング: イベント発生時点の情報を記録
- データ量: 時間とともに増加し、3 つの種類の中で最もデータ量が多くなる傾向
テーブル操作の特性
- Insert Only なテーブル
- 更新や削除は基本的に行わない
- 冪等性の確保が重要
- 同じイベントの重複登録を防ぐ仕組みが必要
集計・分析の特性
- 最小粒度のイベント単位のデータのため、あらゆる粒度での集計が可能
- 集計クエリのコストが高くなりがち
- 大量のレコードをスキャンする必要がある
- 時系列分析、トレンド分析、詳細なドリルダウン分析に適している
Transaction Fact Table は、ビジネスイベントごとに 1 行のレコードとなる Atomic な粒度であるため、集計の柔軟性が最も高く、最も基本的なファクトテーブルといえます。
具体例
例えば、販売注文イベントを記録するファクトテーブルは下記のようになります。
erDiagram
fct_orders {
STRING order_id PK
STRING user_id FK
STRING device_id FK
TIMESTAMP order_timestamp
NUMERIC price
INTEGER quantity
}
実際のレコード例は下記のようになります。
| order_id | user_id | device_id | order_timestamp | price | quantity |
|---|---|---|---|---|---|
| ORD001 | U001 | DEV123 | 2025-12-01 10:30:00 | 45000 | 1 |
| ORD002 | U002 | DEV456 | 2025-12-01 14:20:00 | 38000 | 1 |
| ORD003 | U001 | DEV789 | 2025-12-02 09:15:00 | 52000 | 1 |
このテーブルには、顧客が注文するというイベントのたびに、1 行ずつレコードが追加されていきます。
Transaction Fact Table の活用
Transaction Fact Table は、最小粒度のデータを保持しているため、集計の自由度が高いという特徴があります。
例えば、下記のように様々な軸で集計することができます。
-- 日次の売上集計
SELECT
DATE(order_timestamp) AS order_date,
SUM(price * quantity) AS daily_revenue
FROM fct_orders
GROUP BY order_date
-- デバイスごとの売上集計
SELECT
device_id,
COUNT(*) AS order_count,
SUM(price * quantity) AS total_revenue
FROM fct_orders
GROUP BY device_id
一方で、Transaction Fact Table には運用上の課題もあります。ビジネスイベント単位でレコードが追加される上に Insert Only な特性を持つため、3 つのファクトテーブルの中でも最もレコード数が増加しやすいです。その結果、以下のような問題が発生する可能性があります。
- 集計のためのクエリのスキャン範囲が拡大する
- スキャンによるコストが増大する
- クエリ実行時間が長くなる
- ストレージ容量が増大する
これらの課題に対処するため、以下のような工夫が必要になります。
- Partitioning を活用したスキャン範囲の限定
- データのライフサイクル管理
- 古いデータのアーカイブ等
- 頻繁にアクセスされる集計結果は後述の他のファクトテーブルやデータマート化を検討
Transaction Fact Table は最も基本的なファクトテーブルですが、データ量の増加を見越した設計が重要と感じました。
Periodic Snapshot Fact Table (定期スナップショットファクトテーブル)
特徴
Periodic Snapshot Fact Table は、ある時点の状態を定期的に記録するファクトテーブルです。特徴を整理すると下記のようになります。
データの特性
- 粒度: 特定の時点 (日次、週次、月次など) × 集計対象
- 日次の在庫状況: 日次 × 商品
- 更新頻度・タイミング: 特定の時点の状態を定期的に記録
- データ量: 事前集計済みのためトランザクションファクトより少ない
テーブル操作の特性
- Insert Only なテーブル
- 過去のスナップショットは不変
- 定期的な Batch 処理で生成される
- 冪等性の確保が重要
- 同じ期間のスナップショットを再計算可能にする。つまり Transaction Fact Table を適切に構築していることが前提となる。
集計・分析の特性
- 期間比較 (前日比、前月比など) が容易
- 特定時点の状態を高速に取得可能
Periodic Snapshot Fact Table は、Transaction Fact Table に対して毎回集計クエリを発行するコストを削減し、簡易的なフィルタで素早く取得したい値にリーチできるというメリットがあります。
具体例
例えば、日次の在庫状況を記録するファクトテーブルは下記のようになります。
erDiagram
fct_daily_inventory_snapshot {
DATE snapshot_date PK
STRING device_id PK
INTEGER stock_quantity
NUMERIC total_value
}
実際のレコード例は下記のようになります。
| snapshot_date | device_id | stock_quantity | total_value |
|---|---|---|---|
| 2025-12-01 | DEV123 | 15 | 675000 |
| 2025-12-01 | DEV456 | 8 | 304000 |
| 2025-12-02 | DEV123 | 12 | 540000 |
| 2025-12-02 | DEV456 | 10 | 380000 |
このテーブルには、日次 × デバイス (商品) のレベルで在庫状況が記録されていきます。
Periodic Snapshot Fact Table の活用
Periodic Snapshot Fact Table は、特定の時点での状態を簡単に取得できるという利点があります。
たしかに、出入庫を表現した Transaction Fact Table があれば、これに対して集計するクエリを発行することで日次の在庫数は得られます。しかし、Transaction Fact Table はイベントごとに行が存在していることから、集計にはしばしばコストがかかります。
したがって、Periodic Snapshot Fact Table を採用すれば、集計のコストを抑えながら素早く取得したい値を取得できるというメリットがあります。
例えば、特定の日付の在庫状況を取得する場合、下記のようなシンプルなクエリで取得できます。
-- 2025-12-01 の在庫状況を取得
SELECT
device_id,
stock_quantity,
total_value
FROM fct_daily_inventory_snapshot
WHERE snapshot_date = '2025-12-01'
また、ソースシステム側ですでに事前集計済みの指標があることもあります。このような場合は粒度がすでに大きくまとめ上げられているため、無理に Transaction Fact Table として扱おうとするのではなく、Periodic Snapshot Fact Table を検討してもよいのかなと思いました。
Accumulating Snapshot Fact Table (累積スナップショットファクトテーブル)
特徴
Accumulating Snapshot Fact Table は、ある業務プロセスの進行を 1 行で表現するファクトテーブルです。特徴を整理すると下記のようになります。
データの特性
- 粒度: 1 つの業務プロセス単位
- 1 つの注文
- 更新頻度・タイミング: プロセスの進行に応じて随時更新
- データ量: 発生した業務プロセスの数によるが、Transaction Fact Table より少ない
テーブル操作の特性
- Insert or Update されるテーブル
- 業務プロセスの開始時に Insert、進行に応じて Update される
- 明確な開始と終了を持つプロセスに適している
集計・分析の特性
- プロセスの現在の状態を 1 行で表現するため、現在の状態に対する問いには簡潔なクエリで対応可能
- イベントおよびマイルストーン間のリードタイムを容易に計算可能
- ボトルネック分析、プロセス改善に有効
Accumulating Snapshot Fact Table は、1 つのプロセスに対する様々なイベントの進行を横持ち (列持ち) で記録し、業務プロセスの現在のステータスを効率的に取得できるという特徴があります。
具体例
例えば、注文処理プロセスを記録するファクトテーブルは下記のようになります。
erDiagram
fct_order_fulfillment {
STRING order_id PK
STRING user_id FK
STRING device_id FK
DATE order_date
DATE payment_date
DATE shipment_date
DATE delivery_date
STRING order_status
NUMERIC order_amount
}
実際のレコード例は下記のようになります。プロセスの進行に応じて、各日付カラムが順次埋まっていく様子を示しています。
注文受付直後の状態
| order_id | user_id | device_id | order_date | payment_date | shipment_date | delivery_date | order_status | order_amount |
|---|---|---|---|---|---|---|---|---|
| ORD001 | U001 | DEV123 | 2025-12-01 | NULL | NULL | NULL | ordered | 45000 |
決済完了後の状態 (同じレコードが Update される)
| order_id | user_id | device_id | order_date | payment_date | shipment_date | delivery_date | order_status | order_amount |
|---|---|---|---|---|---|---|---|---|
| ORD001 | U001 | DEV123 | 2025-12-01 | 2025-12-01 | NULL | NULL | paid | 45000 |
出荷後の状態 (同じレコードが Update される)
| order_id | user_id | device_id | order_date | payment_date | shipment_date | delivery_date | order_status | order_amount |
|---|---|---|---|---|---|---|---|---|
| ORD001 | U001 | DEV123 | 2025-12-01 | 2025-12-01 | 2025-12-02 | NULL | shipped | 45000 |
配送完了後の状態 (同じレコードが Update される)
| order_id | user_id | device_id | order_date | payment_date | shipment_date | delivery_date | order_status | order_amount |
|---|---|---|---|---|---|---|---|---|
| ORD001 | U001 | DEV123 | 2025-12-01 | 2025-12-01 | 2025-12-02 | 2025-12-04 | delivered | 45000 |
このように、注文が発生すると 1 行が挿入され、その後、決済、出荷、配送完了といったイベントが発生するたびに、対応する日付カラムが更新されていきます。
Accumulating Snapshot Fact Table の活用
Accumulating Snapshot Fact Table は、プロセスの現在のステータスを効率的に取得できるという利点があります。
例えば、下記のようなクエリで、現在出荷中の注文を簡単に取得できます。
-- 現在出荷中の注文を取得
SELECT
order_id,
user_id,
device_id,
order_date,
order_amount
FROM fct_order_fulfillment
WHERE order_status = 'shipped'
また、各イベントのタイムスタンプを横持ちで記録しているため、各イベント間のリードタイムも簡単に計算できます。
-- 注文から決済完了までのリードタイムを計算
SELECT
order_id,
DATE_DIFF(payment_date, order_date, DAY) AS order_to_payment_days
FROM fct_order_fulfillment
WHERE payment_date IS NOT NULL
たしかに、Transaction Fact Table に対するクエリにより集計してもよいのですが、Periodic Snapshot Fact Table と同様、そのクエリはコストが高くなることが多いでしょう。例えば、qualify 句などで最新の状態を取得しなければならず、高負荷な処理となるケースが多いのではないでしょうか。
Accumulating Snapshot Fact Table を採用することで、このような集計コストを削減できます。
また、Update は多くの DWH にとって不得意なオペレーションであるため、DWH への負荷を考慮した実装も意識する必要があると感じました。例えば、dbt incremental model を使用した merge 文による Bulk での Upsert の採用や、incremental_predicate の指定による適切な Partition Pruning など検討するとよいでしょう。
まとめ
今回は、DWH におけるファクトテーブルの基本的な種類とその使い分けについて整理しました。
3 つのファクトテーブルをまとめると、下記のようになります。
| ファクトテーブルの種類 | 粒度 | テーブル操作 | 適したユースケース |
|---|---|---|---|
| Transaction Fact Table | ビジネスイベント単位 | Insert only | イベントの詳細な分析、柔軟な集計 |
| Periodic Snapshot Fact Table | 特定の時点 × 対象 | Insert only | 特定時点の状態の取得、定期的な集計、KPI ダッシュボード |
| Accumulating Snapshot Fact Table | 1 つの業務プロセス単位 | Insert and Update | プロセスの現在状態の取得、リードタイム分析 |
どのようなユースケースでどのファクトテーブルを選択すべきかを整理しました。
重要なポイントとして、これらのファクトテーブルは排他的ではないということです。たしかに Transaction Fact Table は最も基本的で、多くの場合優先して作成すべきファクトテーブルと思います。しかし、それゆえに Periodic Snapshot Fact Table や Accumulating Snapshot Fact Table が採用されないわけではありません。
Transaction Fact Table と Periodic Snapshot Fact Table、Accumulating Snapshot Fact Table は共存すること、必要に応じてアジャイルに追加され得るということがわかりました。
それぞれのファクトテーブルの特性を理解し、ユースケースに応じて適切に使い分けることで、より効率的で分析しやすい DWH を構築するヒントになりそうです。
Belong ではこのように DWH の構築を積極的に実施しています! Data Platform チームに興味を持っていただけた方は是非 Entrancebook をご覧ください!