dbt

dbt incremental の merge 戦略で実現する堅牢な upsert パターン

2026-04-19
NicheeLab編集部

incremental + merge は、DWH 上での標準的な upsert(更新+挿入)パターンを実現するための dbt の中核機能です。

本稿では、unique_key 設計、重複排除、ウォーターマークの安全な取り方、パフォーマンスとスキーマ変更対応まで、試験で問われやすい論点と実務の勘所をまとめます。

merge 戦略の全体像と適用場面

dbt の incremental モデルで incremental_strategy=merge を使うと、ターゲット表に対し「キー一致時は更新、未一致時は挿入」という upsert が実行されます。これによりフルリロードを避け、到着データのみを効率的に反映できます。

merge を成立させる前提は一意なキー(unique_key)の適切な指定と、増分対象の正しい抽出です。試験観点では、unique_key が未設定または重複時のリスク(不定更新・MERGE エラー)を説明できることが重要です。

  • unique_key は論理的に一意であること。重複は前段で必ず排除する。
  • is_incremental() を用いて取り込み対象を絞るのが基本。ただし遅延到着に備えたルックバックが実務では有効。
  • full_refresh 時はテーブル再作成。インデックス/クラスタ設定はモデル側の config に明示。
戦略更新を反映削除対応unique_key 必須
merge可(キー一致で更新)原則不可(ソフトデリート推奨)必須
append不可(常に追記)不可不要
insert_overwrite (partition)パーティション単位で置換可(対象分は置換)不要
delete+insert可(対象を削除後に再挿入)対象範囲のみ可必須

UPSERT フロー(incremental + merge)

Sourceraw/feedIncremental SQLdedup & filterTarget (Dimension)MERGE on unique_keyUPSERT フロー(incremental + merge)

最小の merge インクリメンタル設定

{{ config(
    materialized='incremental',
    incremental_strategy='merge',
    unique_key='id'
) }}

with src as (
  select * from {{ source('app', 'orders') }}
  {% if is_incremental() %}
    where updated_at >= (select coalesce(max(updated_at), '1970-01-01') from {{ this }})
  {% endif %}
)

select * from src

unique_key 設計と重複排除の実務

merge 成功のカギは unique_key の健全性です。ビジネスキーが揺れる場合はサロゲートキーの導入を検討し、ソースに重複が届く前提で前段での重複排除を徹底します。

試験では「unique_key が重複したら?」に対して、事前のウィンドウ関数による行選択(最新優先など)で 1 レコードに確定させる回答が期待されます。

  • ビジネスキーが変化しうる場合は安定キー(サロゲート)を導入する。
  • 同一キーの複数候補は、更新時刻やバージョンで決定的に 1 行に絞る。
  • null キーは unique とみなせないため、not_null テストを必ず付与する。
キー種別長所注意点
ビジネスキー(自然キー)業務的に理解しやすい値の変更・再利用に弱い
サロゲートキー(代替キー)安定・衝突が少ない生成・同期の仕組みが必要
複合キー既存粒度を保てる列追加でクエリやテストが煩雑

重複排除の流れ

SourceDedup (ROW_NUMBER)Clean feed重複排除の流れ

ウィンドウ関数で重複を 1 行に確定

with staged as (
  select *,
         row_number() over (
           partition by id
           order by updated_at desc
         ) as rn
  from {{ source('app', 'orders') }}
), dedup as (
  select * from staged where rn = 1
)
select * from dedup

実践的 upsert: ルックバック付きウォーターマーク

遅延到着や時刻の揺れに備えるため、単純な max(updated_at) ではなく、少し巻き戻した閾値で取り込むルックバック方式が安全です。ターゲット側の最大更新時刻から一定期間戻し、その範囲のソースを再評価します。

この方式では一部の重複取込が発生しうるため、前段の重複排除と unique_key の merge が前提になります。

  • ルックバック期間はデータ到着 SLA に合わせて数時間〜数日で調整。
  • ターゲットの最大時刻が null の初回実行を必ず考慮。
  • 時間列は更新駆動(updated_at)を優先。作成時刻のみは避ける。
方式利点リスク/コスト
単純 MAX 方式最速・最小スキャン遅延到着を取り逃す可能性
ルックバック併用遅延に強い・堅牢多少の再処理・重複評価が増える
ソース側変更履歴テーブル完全再現性・差分明確履歴の提供/保管コストが必要

ルックバックの概念

processed取り込み済lookback再評価範囲new window新規取り込みtime →: 処理済み / ルックバック(再評価) / 新規ウィンドウ(境界は max(updated_at))

ルックバック付き incremental + merge の完全例

{{ config(
    materialized='incremental',
    incremental_strategy='merge',
    unique_key='id'
) }}

{% set lookback_hours = 24 %}

with base as (
  select * from {{ source('app', 'orders') }}
), window as (
  select coalesce(
           dateadd('hour', -{{ lookback_hours }}, max(updated_at)),
           cast('1970-01-01' as timestamp)
         ) as watermark
  from {{ this }}
), filtered as (
  select b.*
  from base b
  {% if is_incremental() %}
  where b.updated_at >= (select watermark from window)
  {% endif %}
), dedup as (
  select *, row_number() over (partition by id order by updated_at desc) as rn
  from filtered
)
select * from dedup where rn = 1

パーティション/クラスタと増分プルーニング

大規模テーブルでは、物理設計(パーティション/クラスタ)と増分時の絞り込みがクエリコストに直結します。dbt の config でアダプタに応じた設定を行い、スキャン範囲を最小化します。

incremental_predicates は、ターゲット/ソース双方に適用される追加述語の指定に対応したアダプタで有効です。サポート状況は利用アダプタのドキュメントを確認してください。

  • 時間列での partition_by(サポートする DWH のみ)を活用する。
  • クラスタ/ソートキーでマージの一致探索を高速化。
  • 増分述語は過度に複雑にせず、統計に効く列を選ぶ。
最適化ポイント効果留意点
partition_by(日付)スキャン削減・insert_overwrite と親和アダプタ依存の構文差異に注意
cluster_by/ソートキー結合・MERGE の一致探索を高速化過度な列数は書込コスト増
incremental_predicates対象範囲のさらなる絞り込みアダプタ対応状況の確認が必要

プルーニングのイメージ

scanskippartitionsrecent daysscanolder daysskipプルーニング: 最近の日付パーティションのみスキャン、古いパーティションはスキップ

最適化の例(アダプタ依存の一例)

{{ config(
    materialized='incremental',
    incremental_strategy='merge',
    unique_key='id',
    partition_by={'field': 'order_date'},          -- e.g. BigQuery/Spark family
    cluster_by=['id'],                              -- e.g. BigQuery/Snowflake clustering equivalents
    incremental_predicates=["order_date >= dateadd('day', -7, current_date)"]
) }}

select * from {{ ref('stg_orders_clean') }}
{% if is_incremental() %}
where order_date >= dateadd('day', -7, current_date)
{% endif %}

スキーマ変更と削除の扱い(on_schema_change とソフトデリート)

増分モデルではスキーマ変更時の挙動を on_schema_change で制御します。append_new_columns は新規列を追加、fail は不整合時に失敗、sync_all_columns は対応アダプタで列差分を同期します。

実データの削除対応は、merge の標準挙動だけでは網羅できない場合があります。実務では is_deleted フラグでのソフトデリートが安全で、履歴の整合性も保ちやすいです。ハードデリートを行う場合は、影響範囲の正確な同定とテストを必須にしてください。

  • on_schema_change は adapter のサポートに依存。試験では意味と使い所を説明できること。
  • 削除はソフトデリート優先。ハードデリートは監査要件と整合させる。
  • スキーマ変更後は full_refresh のオプションも検討。
on_schema_change挙動利用シナリオ
ignore無視(差分あっても実行継続)短期回避・後で整備
append_new_columns新規列を追加列追加のみが想定される運用
fail差分検知で失敗厳密なスキーマ管理が必要な環境
sync_all_columns列差分を同期(対応アダプタのみ)双方向で列変更を吸収したい場合

ソフトデリートの流れ

SourcedeletedTargetis_deleted=1ソフトデリートの流れ(MERGE on key / set is_deleted=1)

ソフトデリートを含む upsert ロジック(フラグ反映)

{{ config(
    materialized='incremental',
    incremental_strategy='merge',
    unique_key='id',
    on_schema_change='append_new_columns'
) }}

with src as (
  select id,
         updated_at,
         status,
         case when status = 'deleted' then true else false end as is_deleted
  from {{ ref('stg_orders_clean') }}
  {% if is_incremental() %}
    where updated_at >= (select coalesce(max(updated_at), '1970-01-01') from {{ this }})
  {% endif %}
)

select * from src

資格対策チェックリスト(Analytics Engineer 向け)

試験では、incremental の前提(unique_key、is_incremental の役割、full_refresh の意味)と、merge を選ぶべき状況の判断が頻出です。on_schema_change の選択肢、重複排除の書き方、ウォーターマークの安全策まで説明できるように準備しましょう。

テスト(not_null/unique・関数テスト)をモデルに付与し、データ品質を担保するのも評価対象になりやすいポイントです。

  • unique_key は必須。重複排除はモデル内で確定的に行う。
  • is_incremental() で取り込み範囲を制御、遅延到着にはルックバック。
  • on_schema_change の各モードと使い分けを言語化できること。
問われやすいテーマ誤答の典型正答の要点
unique_key の必要性append でも更新されるmerge はキー一致で更新、append は追記のみ
ウォーターマークmax(created_at) を使うupdated_at とルックバックで遅延を吸収
スキーマ変更対応常に ignore要件に応じて append_new_columns / fail / sync_all_columns

試験イメージ

ConfigModel SQLQuality testsBuild試験イメージ

品質テストの最小例(schema.yml 抜粋)

version: 2
models:
  - name: dim_orders
    columns:
      - name: id
        tests:
          - not_null
          - unique
      - name: updated_at
        tests:
          - not_null

問題で確認

Analytics Engineer

問題 1

dbt の incremental モデルで incremental_strategy=merge を用いる際、更新と新規挿入を正しく反映するための設計として最も適切なのはどれか。

  1. unique_key は不要。is_incremental() で新規行だけを選べば更新も反映される。
  2. append 戦略に切り替えれば、更新も自動的に上書きされる。
  3. unique_key を設定し、ソース側で重複を排除した上で、updated_at に基づくルックバックを併用する。
  4. on_schema_change=fail を指定すれば、キーの重複があっても MERGE が安全に行われる。

正解: C

merge では unique_key が必須で、同一キーの複数候補は前段で一意に確定させる必要があります。遅延到着に備えて updated_at とルックバックを併用すると取りこぼしを防げます。append は更新を反映せず、on_schema_change はスキーマ差分の挙動でありキー重複を解決しません。

よくある質問

merge 戦略で削除(ソースに存在しない行の削除)も自動反映されますか?

標準的な upsert では削除は自動反映されません。監査要件がある場合は is_deleted のようなフラグでソフトデリートを反映するのが一般的です。ハードデリートが必要な場合は、範囲を正確に絞った delete+insert やパーティション単位の insert_overwrite(対応アダプタのみ)を検討してください。

BigQuery/Spark/Snowflake/Databricks での設定差はどう扱えばよいですか?

partition_by や insert_overwrite などはアダプタ依存のため、利用基盤のアダプタドキュメントに従います。dbt モデルでは共通部分(unique_key、is_incremental、重複排除)を確実に実装し、物理設計は config で分岐または環境別に管理するのが安全です。

full_refresh のタイミングは?

大規模スキーマ変更、ウォーターマークの不整合修正、履歴の再構築が必要なときに実行します。build 実行時に --full-refresh を付与すると対象 incremental テーブルは再作成されます。

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

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の記事一覧 (100件)
© 2026 NicheeLab All rights reserved.