「半年前の時点で、この顧客はどの住所に住んでいたんだっけ」「価格改定前と後で、売上構成はどう変わった」。データを使い込むと、過去の状態を知りたい場面が必ず出てきます。けれども多くのシステムは「最新値」しか持っていないので、過去の問いに答えられません。

これを解くのが、Slowly Changing Dimensions(SCD)Type 2です。属性が変わるたびに新しい行を追加し、有効期間を持たせて履歴を残す手法です。dbtの`snapshot`機能を使えば、設定ファイル数十行で実装できます。実装と運用のポイントを順に押さえます。

SCDの3つのタイプ

SCDは履歴の持ち方で分類されます。代表的な3つは次の通りです。

タイプ持ち方過去の参照
Type 1上書き。最新値のみ不可
Type 2変更ごとに新しい行を追加、有効期間を持つ可能(時点指定)
Type 3「現在」と「直前」など、固定数の前値カラムを持つ限定的

実務でいちばん使われるのはType 2です。Type 1は最新値しか要らない場面(マスタの正規化)、Type 3は「前回の値」だけ覚えておきたい場面、というように使い分けます。本記事ではType 2を取り上げます。

dbt snapshotの基本

dbtのsnapshotは、SCD Type 2を標準で実装する機能です。`snapshots/`配下にSQLファイルを置き、`dbt snapshot`コマンドで実行します。

-- snapshots/snap_customers.sql
{% snapshot snap_customers %}

{{
    config(
      target_schema='snapshots',
      unique_key='customer_id',
      strategy='timestamp',
      updated_at='updated_at'
    )
}}

SELECT
  customer_id,
  customer_name,
  address,
  email,
  updated_at
FROM {{ source('raw', 'customers') }}

{% endsnapshot %}

これだけで、dbtは次のカラムを自動的に管理します。

カラム意味
dbt_scd_id各履歴行を一意に識別するID
dbt_updated_atこの履歴行が観測された時刻
dbt_valid_fromこの属性が有効になった時刻
dbt_valid_toこの属性が有効でなくなった時刻(最新行はNULL)

「2026年4月15日時点の顧客住所」を知りたければ、`dbt_valid_from <= ‘2026-04-15’ AND (dbt_valid_to IS NULL OR dbt_valid_to > ‘2026-04-15’)`で1行に絞れます。これが時点参照の基本形です。

timestamp戦略とcheck戦略

変更を検知する方法には2つの戦略があります。

strategy動き向く場面
timestamp更新日時カラム(updated_at等)が新しければ変更とみなすソースに信頼できる更新日時がある
check指定カラムの値が前回と違えば変更とみなす更新日時が無い、または信頼できない
-- check戦略の例
{{
    config(
      target_schema='snapshots',
      unique_key='customer_id',
      strategy='check',
      check_cols=['address', 'email']
    )
}}

timestampが使える場面では、こちらを推奨します。比較が軽く、計算も安定するためです。`check`は更新日時が無いマスタなどでの代替手段と位置づけてください。

invalidate_hard_deletes:削除された行をどう扱うか

ソースから物理削除された行があったとき、snapshotは既定では何もしません。「存在しなくなった顧客」を履歴上も無効化したい場合は、`invalidate_hard_deletes`を有効にします。

{{
    config(
      target_schema='snapshots',
      unique_key='customer_id',
      strategy='timestamp',
      updated_at='updated_at',
      invalidate_hard_deletes=True
    )
}}

これで、前回のsnapshotには存在したのに今回いない行に、`dbt_valid_to`が自動で入ります。論理削除されたとみなすイメージです。「物理削除を履歴に反映するか」はビジネス要件次第なので、要件を確認してから有効化してください。

下流モデルでの使い方

snapshotしたテーブルをファクトテーブルとJOINして「注文時点の顧客住所」を出すのが典型用途です。

-- models/gold/fct_orders_with_customer_state.sql
SELECT
  o.order_id,
  o.ordered_at,
  o.order_total,
  c.customer_id,
  c.customer_name,
  c.address AS address_at_order_time
FROM {{ ref('fct_orders') }} o
LEFT JOIN {{ ref('snap_customers') }} c
  ON o.customer_id = c.customer_id
  AND o.ordered_at >= c.dbt_valid_from
  AND (o.ordered_at < c.dbt_valid_to OR c.dbt_valid_to IS NULL)

このJOINで、注文時点で有効だった顧客情報が1対1で取り出せます。「住所変更後に発生した売上はどの地域に帰属するか」のような分析も正確に行えます。

snapshot運用の注意点

  • 実行頻度が履歴の粒度を決める:1日1回のsnapshotなら、その日のうちの複数回の変更は最後の状態しか残りません。粒度が必要なら頻度を上げます。
  • updated_atの信頼性:timestamp戦略はupdated_atに依存します。ソース側で更新日時が更新されない不具合があると、変更を見逃します。重要なカラムだけcheck戦略を併用するのも手です。
  • 大規模テーブルのsnapshotは重い:毎回全件を比較するため、テーブルが大きいと時間がかかります。パーティション境界で分けるか、Data VaultのSatelliteパターンに切り替える選択も検討します。
  • 変更履歴の保存期間:永久に履歴を残すと肥大化します。「3年で物理削除」のような保持期限ポリシーを決めておきます。

snapshot と Data Vault Satellite の違い

履歴管理という意味では、dbt snapshotとData VaultのSatelliteは似ています。違いは設計思想と運用範囲にあります。

dbt snapshot(SCD Type 2)Data Vault Satellite
主な用途ディメンションの履歴管理統合層全体の履歴管理
設計の粒度テーブル単位属性グループ単位(変更頻度・機密度で分割)
適した規模小〜中規模中〜大規模、複数ソース
並列ロード順次独立に並列

規模が小さいうちはdbt snapshot、ソースが増えて統合の複雑さが上がってきたらData Vaultへ、という流れが現実的です。Data Vault 2.0 入門で全体像を、スタースキーマとの判断軸で選び方を扱っています。

まとめ

  • SCD Type 2は属性の履歴を残す定石。dbtのsnapshotで簡単に実装できる。
  • 更新日時が信頼できればtimestamp戦略、無ければcheck戦略。
  • 物理削除を履歴に反映したいなら`invalidate_hard_deletes`を有効化。
  • ファクトとはdbt_valid_from/dbt_valid_toでJOINし、時点参照を可能にする。
  • 規模が大きくなったら、Data Vault Satelliteへの移行も視野に。

関連:dbt incremental modelsの設計dbt tests 実践dbt + Snowflakeのコスト最適化。履歴管理の設計や Data Vault への移行を一緒に詰めたいときは、DE-STKの初回相談(30分・無料)をご利用ください。

よくある質問(FAQ)

Q. snapshotは1日に何回実行すべきですか?

A. 業務での変更頻度に合わせます。住所のように1日1回で十分な属性なら日次で問題ありません。在庫や価格のように日中に頻繁に変わる属性は、1時間ごとや業務時間中の高頻度実行が向きます。「履歴の粒度として何を残したいか」が判断の起点です。粒度を上げると、テーブルサイズも比例して増えるので、保持期間とセットで決めます。

Q. snapshotを後から修正することはできますか?

A. 既存のsnapshotテーブルへの修正は基本的に避けるべきです。dbtのsnapshotは履歴を追記していく前提で、過去レコードの書き換えは想定外の動作になります。スキーマが大きく変わるなら、snapshotを止めて新しい名前で作り直し、古い履歴は保存版として残すのが安全です。

Q. check戦略のcheck_colsはどう選びますか?

A. 履歴として追跡したい属性だけを指定します。全カラムを入れると、業務上意味のない変更(システムが自動更新する管理項目など)まで履歴に残ってしまい、ノイズが増えます。「この属性が変わったら新しい行を残したい」と言える項目だけに絞ります。あとで増やすことはできますが、減らすと過去履歴とのズレが出るので、最初は控えめに始めるのが安全です。

Q. SCDの履歴は、どのくらいの期間保持すべきですか?

A. ビジネス・規制・分析要件で決めます。会計や監査の要件があれば、その保存義務期間(多くは7年)に合わせます。マーケティング分析中心なら、3年程度で見直す例が多いです。保持期間が決まったら、それを超えた行を物理削除するメンテナンスジョブを組み込みます。「無期限保存」を選ぶと、5年後にテーブルが肥大化して読み出しが重くなる、という事故が起きます。

▼ このトピックをポチポチ学ぶ

dbtを深く使いこなす

関連記事を順序立てて読みながら、ステップごとに4択クイズで理解を確認できる学習パスです。登録不要・進捗自動保存。

学習パスを始める →