dbt

dbt Snapshots による履歴化:timestamp / check 戦略の使い分け

2026-04-19
NicheeLab編集部

dbt の Snapshot は、ソースの行が変化したときに履歴行を増やし、有効期間を管理することで SCD Type 2 を実現します。代表的な戦略は timestamp と check の2つで、どちらも unique_key が必須です。

本稿では、2つの戦略の選定基準、よくある落とし穴、倉庫別の実務チューニング、試験で狙われやすい論点を、公式ドキュメントの挙動に沿って解説します。

Snapshot の基本と生成カラム

dbt の Snapshot は、SELECT 文の結果を基準に、行ごとの変化を検知して履歴テーブルを更新します。更新検知が発生すると既存の現行行の有効終了を閉じ、新しい現行行を追加します。結果として、同じ unique_key の行に複数バージョンが蓄積されます。

Snapshot テーブルには、dbt が付与する制御カラムが含まれます。これらは戦略に関わらず共通で、履歴の整合性検証や期間クエリに利用できます。

  • 必須: unique_key、strategy(timestamp または check)
  • 共通の制御カラム: dbt_scd_id, dbt_valid_from, dbt_valid_to, dbt_updated_at
  • invalidate_hard_deletes: true でソースから消えた行も失効させられる(ハードデリートの無効化)
  • Snapshot はモデルではなく snapshot ブロックとして定義し、dbt snapshot コマンドで更新
カラム役割補足
dbt_scd_idSCD 行の一意識別子unique_key と戦略に基づき dbt が算出
dbt_valid_from行の有効開始時刻変化が検知された時刻(または updated_at)
dbt_valid_to行の有効終了時刻現行行は NULL、失効時にタイムスタンプが入る
dbt_updated_atdbt が処理時に付与する更新基準戦略により算出方法が異なる

最小構成の Snapshot(check 戦略)

{% snapshot customers_snapshot %}
{{
  config(
    target_schema='snapshots',
    unique_key='customer_id',
    strategy='check',
    check_cols=['email', 'address', 'status'],
    invalidate_hard_deletes=true
  )
}}
select customer_id, email, address, status
from {{ source('app', 'customers') }}
{% endsnapshot %}

timestamp vs check 戦略の比較

timestamp 戦略は、単一の更新時刻カラム updated_at を基準に行の変化を検知します。updated_at が単調に増加し、全ての変更時に正しく更新されるデータセットに最適です。

check 戦略は、指定したカラム集合の値変化で検知します。更新時刻が信用できない場合や、ビジネス上の重要属性だけで変更判定したい場合に有効です。

  • updated_at を正しく持つなら timestamp が単純で高速になりやすい
  • 更新時刻が不信・欠損・遅延するなら check で業務上重要な列のみ監視
  • check では非決定的・高変動な列(更新日時そのもの、乱数、浮動小数の微小誤差)は除外
観点timestamp 戦略check 戦略
検知ロジックupdated_at の増加で検知check_cols の値変化で検知
必須データ信頼できる updated_at安定かつ決定的な複数列(1列でも可)
誤検知リスク時刻が遅延・欠落すると取りこぼし不要列を含むと過剰検知(ノイズ)
パフォーマンス比較的軽い(単一列参照)列数・比較負荷に依存
ユースケースCDC 由来の updated_at がある DWH テーブルアプリ由来で updated_at 不整合、特定属性のみ追跡したい場合

Snapshot の履歴化イメージ(SCD2)

ソース(現在)customer_id=42 / email=a@x / address=TokyoSnapshot(履歴)v1: valid_from=t0, valid_to=t1 / v2: valid_from=t1, valid_to=NULL変更検知による SCD2 履歴化

両戦略のスケルトン

{% snapshot orders_ts %}
{{
  config(
    target_schema='snapshots',
    unique_key='order_id',
    strategy='timestamp',
    updated_at='updated_at'
  )
}}
select order_id, updated_at, amount, status
from {{ ref('stg_orders') }}
{% endsnapshot %}

{% snapshot users_ck %}
{{
  config(
    target_schema='snapshots',
    unique_key='user_id',
    strategy='check',
    check_cols=['email', 'is_active', 'plan']
  )
}}
select user_id, email, is_active, plan
from {{ ref('stg_users') }}
{% endsnapshot %}

timestamp 戦略の実務ポイント

updated_at はソース側で全更新時に必ず書き換わる必要があります。遅延到着やバックフィルで updated_at が過去を指すと、検知漏れの原因になります。信頼できる単調増加の時刻を確保してください。

タイムゾーンの取り扱いは倉庫依存です。ソースと DWH の時刻帯が不一致だとウィンドウ境界がずれます。ETL 側で UTC に正規化するか、Snapshot の SELECT 内で明示的に正規化しましょう。

  • バックフィル時は updated_at を本来の更新時ではなくロード時刻へ上書きしないこと
  • 日次ロールアップに合わせて時刻を丸めると変化が潰れる可能性あり
  • ハードデリートへの対応は invalidate_hard_deletes=true を検討
事象原因回避策
検知漏れupdated_at が遅延・逆行ETL で単調増加保証、遅延到着フラグで再投入
境界ずれタイムゾーン不一致UTC 正規化、SELECT で明示変換
無駄な履歴機械的な定時更新で updated_at 変化アプリ側で意味的変更時のみ更新

SELECT 内での時刻正規化例(概念)

{% snapshot payments_ts %}
{{
  config(
    target_schema='snapshots',
    unique_key='payment_id',
    strategy='timestamp',
    updated_at='updated_at_utc'
  )
}}
select
  payment_id,
  -- ここで UTC に正規化(倉庫に応じて適切な関数を使用)
  cast(updated_at as timestamp) as updated_at_utc,
  amount, status
from {{ ref('stg_payments_raw') }}
{% endsnapshot %}

check 戦略の実務ポイント

check_cols は業務的に意味のある変更を反映する列に限定します。ログ由来の更新時刻や並び順、非決定的関数の結果は除外してください。浮動小数は丸め誤差で不要な変更検知の原因になりやすいので、比較前に丸めるか文字列化するなどの前処理が有効です。

派生列をチェックしたい場合は、Snapshot の SELECT で安定的に計算してから check_cols に含めます。これによりソーススキーマの変更影響を最小化できます。

  • check_cols には NULL セーフな比較が必要になる場合があるため、SELECT で coalesce を活用
  • 大量カラムを比較するほどコスト増。必要最小限に絞る
  • 金額の丸めはビジネスルールに合わせて統一(例:2桁小数で固定)
事象原因回避策
過剰検知ノイズ列を含めたcheck_cols を絞り込み、前処理を行う
微小差で検知浮動小数の丸め誤差round や cast、文字列化で安定化
検知漏れ比較に含めるべき列の漏れ要件から差分定義を棚卸して列を見直す

check_cols を前処理して安定化する例

{% snapshot subscription_ck %}
{{
  config(
    target_schema='snapshots',
    unique_key='account_id',
    strategy='check',
    check_cols=['plan', 'is_active', 'mrr_str']
  )
}}
with src as (
  select account_id, plan, is_active, mrr
  from {{ ref('stg_subscription') }}
)
select
  account_id,
  plan,
  is_active,
  -- 丸めと型統一により比較を安定化
  cast(round(mrr, 2) as string) as mrr_str
from src
{% endsnapshot %}

運用とパフォーマンス:スケジュール、肥大化対策

Snapshot は実行のたびに差分を評価します。データ更新頻度に合わせたスケジュールが重要で、過剰な頻度は不要な比較コストを招きます。逆に低すぎると検知タイムラグが伸びます。

履歴テーブルは時間とともに増大します。クエリ対象が現行行のみなら、dbt_valid_to が NULL の行を参照するビューを用意し、パーティションやクラスタリングで読み取りを最適化します。

  • スケジュールはソース更新直後に寄せると無駄が減る
  • 大規模テーブルはパーティション・クラスタリングキーに dbt_valid_from など時系列列を活用
  • ハードデリート検知が必要なら invalidate_hard_deletes=true を有効化
  • スナップショット専用スキーマ target_schema を分離し権限・ライフサイクルを管理
倉庫・観点推奨設定例狙い
共通(スケジュール)更新直後に1回、必要なら時間帯を限定無駄な差分比較を削減
共通(ストレージ)dbt_valid_from/valid_to で分割・クラスタ時間順クエリの I/O 削減
共通(ビュー)現行行ビュー: where dbt_valid_to is null大半の参照を軽量化

現行行のみを返すビュー例

create or replace view analytics.v_customers_current as
select *
from snapshots.customers_snapshot
where dbt_valid_to is null;

試験対策の要点:出題されやすいひっかけ

Analytics Engineer 試験では、timestamp と check の適用条件、unique_key の必須性、ハードデリートの扱い、比較対象列の選定などが頻出です。演算子や倉庫固有関数の知識よりも、意味的に正しい差分定義と安定した比較の考え方が問われます。

Snapshot は増分モデルとは目的が異なります。増分は最新状態を効率更新する手段、Snapshot は履歴を保持する手段です。この違いを混同しないでください。

  • unique_key は必須。これが不安定だと履歴が壊れる
  • timestamp は updated_at 依存。check は業務列依存
  • invalidate_hard_deletes でソースから消えた行の失効が可能
  • 非決定的列や微小差を check に含めない
項目覚えるポイントひっかけ例
unique_key全スナップショットで必須自然キーが揺れる設計
timestamp信頼できる updated_at が前提遅延到着で検知漏れ
check業務的に重要な列だけ比較ログ列や更新時刻を含める

schema.yml でのテスト例(unique と not_null)

version: 2
snapshots:
  - name: customers_snapshot
    tests:
      - not_null:
          column_name: customer_id
      - unique:
          column_name: customer_id

問題で確認

Analytics Engineer

問題 1

オンライン注文テーブルを Snapshot 化したい。アプリは金額変更時に amount と status は更新するが、updated_at は一部のバックフィルで過去時刻に上書きされることがある。誤検知を抑え、取りこぼしも避けたい。どの構成が最も適切か。

  1. check 戦略で check_cols=['amount','status'] を指定し、amount は丸めて比較する
  2. timestamp 戦略で updated_at を使うが、丸めた updated_at を比較する
  3. check 戦略で check_cols に全カラムを指定して完全一致で比較する
  4. timestamp 戦略で updated_at と現在時刻の大きい方を比較する

正解: A

updated_at が信頼できないため timestamp は不適。check で業務的に意味のある列(amount, status)に限定し、金額は丸め誤差を避ける前処理を入れるのが妥当。全カラム比較はノイズ増、現在時刻を使うのは誤検知の温床。

よくある質問

Snapshot と増分モデル(incremental)の違いは何ですか?

増分モデルは最新状態のテーブルを効率的に更新する手法で、履歴は保持しません。Snapshot は変更のたびに旧行の有効終了を閉じ、新行を追加して履歴を保持します。用途が異なります。

ソースから行が物理削除された場合、Snapshot は自動で行を失効させますか?

デフォルトでは失効しません。invalidate_hard_deletes=true を有効にすると、スナップショット実行時にソースに存在しない行を検出し、dbt_valid_to を更新して失効させられます。

check_cols に計算列を含めても安全ですか?

決定的で安定した計算なら安全です。非決定的関数(現在時刻、乱数)や丸め誤差の出る浮動小数は避け、必要に応じて round や cast、coalesce で前処理してください。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
dbt

dbt Model の基礎: SQL で定義する変換の最小単位

Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...

dbt

dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点

dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...

dbt

dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点

dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...

dbt

dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト

Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....

dbt

dbt_project.yml の読み方:主要設定と命名を最短で掴む

dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...

dbtの記事一覧 (101件)
© 2026 NicheeLab All rights reserved.