delete+insert は、対象テーブルから該当キーの行を一旦削除し、新しい行を挿入して整合性を保つ増分戦略です。MERGE が最適でない、またはサポートが限定的な環境で堅実に動作します。
本記事では、公式ドキュメントの前提に沿い(dbt docs: Incremental models と各アダプタの戦略)、バージョン差異に依存しにくいベーシックな設計・運用パターンに絞って、実務と試験(Analytics Engineer)双方で役立つ視点を提供します。
delete+insert は、ユニークキーで対象行を特定し、古い行を削除して新しい行を挿入することで、結果整合性を保つ増分アップサートを実現します。MERGE のような単一文でのアップサートが使えない、あるいは運用上の理由で避けたい場合に有効です。
再構築アプローチとしては、対象キー範囲を明示しつつ部分的に作り直す、あるいは問題発生時に該当キーの削除と再投入で巻き戻しやすい、という利点があります。トランザクション分離やロックの特性はアダプタ依存のため、ジョブ並列度とテーブル設計(クラスタリング、パーティション)を合わせて検討します。
| 戦略 | 要件/依存 | 強み・向き不向き |
|---|---|---|
| delete+insert | unique_key 必須。DELETE/INSERT サポート。 | 再実行が簡単。I/O は多め。キーの選定が命。 |
| merge | MERGE DML/Delta MERGE などのアダプタ機能。 | 高速かつ一貫したアップサート。機能未対応環境では不可。 |
| insert_overwrite | パーティション・クラスタの明示的制御。 | パーティション単位の置換が速い。設計要件が厳格。 |
delete+insert の処理フロー(再構築志向)
最小構成の dbt 増分モデル(delete+insert)
with source as (
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
where updated_at >= (select coalesce(max(updated_at), '1900-01-01') from {{ this }})
{% endif %}
),
new_rows as (
select
order_id as id,
customer_id,
status,
updated_at
from source
)
select * from new_rows
-- model config (at top of file, or via config block)
-- {{ config(
-- materialized='incremental',
-- incremental_strategy='delete+insert',
-- unique_key='id'
-- ) }}
delete+insert は unique_key がないと成立しません。キーの重複は削除対象の不確定性を生み、誤削除や重複挿入の温床になります。キーは論理一意(複合可)で、ターゲットでもインデックスやクラスタリングの観点から実体化しておくのが実務的です。
差分抽出は is_incremental() で制御します。updated_at やロード境界列で範囲を絞り、delete 範囲と一致するキー集合を生成します。タイムスタンプの遅延着地(late arriving)を考慮し、やや広めのウィンドウを取るか、再実行ポリシーを明確化します。
| 項目 | 推奨 | 理由 |
|---|---|---|
| unique_key | 論理一意・NULL なし | DELETE 範囲を正しく決定するため |
| 差分列 | updated_at + 余裕窓 | 遅延到着の吸収・再実行で安定 |
| 制約/物理設計 | クラスタ/パーティション/索引 | DELETE 対象探索と INSERT 性能の両立 |
差分抽出とキー集合の対応
複合キーとウィンドウ付与の例
{{ config(
materialized='incremental',
incremental_strategy='delete+insert',
unique_key=['id','effective_date']
) }}
with src as (
select * from {{ ref('stg_line_items') }}
{% if is_incremental() %}
where updated_at >= dateadd(day, -1, (select coalesce(max(updated_at),'1900-01-01') from {{ this }}))
{% endif %}
)
select id, effective_date, qty, price, updated_at from src
コンフィグはモデルファイル先頭の config ブロック、または dbt_project.yml の models セクションで管理します。環境差(本番・検証)で戦略を切り替える場合は、vars や target.name を条件に分けると管理が楽です。
一部アダプタでは MERGE がデフォルト化される場合がありますが、delete+insert を明示すれば当該戦略が用いられます。サポート可否や既定挙動はアダプタの公式ドキュメントで確認してください。
| 構成箇所 | 使い分け | 注意点 |
|---|---|---|
| model内 config | モデル固有の要件を即記述 | 設定過多だと横断管理が難しい |
| dbt_project.yml | パターンの一括適用 | 例外モデルを override で上書き |
| 環境変数/vars | 環境ごとに戦略切替 | 分岐が複雑化しすぎないように |
環境別の戦略切替イメージ
dbt_project.yml での既定戦略と上書き
models:
project_name:
+materialized: incremental
+incremental_strategy: delete+insert
+unique_key: id
marts:
orders:
+incremental_strategy: delete+insert # explicit
+unique_key: [id, effective_date]
delete+insert は I/O が増えがちです。削除対象の探索を速くするため、ターゲットテーブルのクラスタリング(Snowflake)、クラスタ/ソートキー(Redshift)、クラスタリング/パーティショニング(BigQuery)、Z-ORDER やクラスター化(Databricks Delta)など、プラットフォーム固有の最適化を組み合わせます。
バルク DELETE のロック影響やタイムトラベル/保持期間のストレージ増分にも注意します。スケジュールをずらして同一キー範囲の併走を避ける、ジョブ単位でキー範囲をシャーディングするなど、運用での工夫が効きます。
| 最適化項目 | 期待効果 | 注意点 |
|---|---|---|
| クラスタ/分割設計 | DELETE と INSERT のスキャン削減 | 過剰分割は小ファイル化・オーバーヘッド |
| バッチサイズ調整 | コミット回数削減・スループット向上 | 長大トランザクションの失敗リスク |
| キー範囲のシャーディング | 同時実行性向上 | 抜け漏れ防止のキー範囲管理が必要 |
キーに沿ったクラスタリングでスキャン最小化
クラスタリング/分割の設計例(概念)
-- Snowflake 例(概念)
-- create or replace table T cluster by (id);
-- BigQuery 例(概念)
-- partition by date(updated_at) cluster by id
-- Databricks Delta 例(概念)
-- optimize T zorder by (id)
delete+insert は、失敗時に該当キーの再削除→再挿入で回復しやすいのが強みです。モデル間の依存関係(ref)で下流が参照するタイミングを管理し、テスト(unique、not_null、関数的重複排除)をパイプライン終端で走らせて不整合を早期検出します。
フルリフレッシュ(--full-refresh)は全件再構築の最後の手段です。まずは影響キーの特定と部分再実行(--select model_name)で切り戻しを試み、必要に応じて再実行ウィンドウを一時的に広げて整合性を取り戻します。
| 失敗シナリオ | 挙動 | 推奨対応 |
|---|---|---|
| INSERT 中断 | 一部のキーが未挿入 | 同一増分で再実行、重複は事前 DELETE で吸収 |
| DELETE 中断 | 古い行が残留 | 次回実行で再度 DELETE→INSERT で解消 |
| 差分抽出漏れ | 更新落ち・欠損 | ウィンドウ拡大 or 影響キー特定で再実行 |
失敗時の可逆フロー
部分再実行とフルリフレッシュの使い分け
# 影響モデルのみ再実行
# dbt run --select my_incremental_model
# フルリフレッシュ(最終手段)
# dbt run --full-refresh --select my_incremental_model
試験では、増分戦略ごとの適用条件の理解、unique_key が delete+insert で必須であること、is_incremental() の使い方、--full-refresh の意味、アダプタ差に対する保守的な判断が問われます。
また、MERGE と delete+insert の違い、insert_overwrite の前提(パーティション置換)を比較で整理できているかが重要です。
| テーマ | キーワード | ひっかけポイント |
|---|---|---|
| 増分戦略の選択 | unique_key / MERGE / overwrite | unique_key なしで delete+insert は不可 |
| 差分制御 | is_incremental() / 境界列 | 等号の向き・遅延到着の扱い |
| 再実行 | --full-refresh / 部分選択 | フルリフレッシュ濫用によるコスト |
戦略間の学習マップ
試験で頻出の設定スニペット
{{ config(
materialized='incremental',
incremental_strategy='delete+insert',
unique_key='id'
) }}
{% if is_incremental() %}
-- 差分条件を忘れない
{% endif %}
Analytics Engineer
問題 1
dbt の増分モデルで delete+insert 戦略を選ぶ主な理由として、最も適切なのはどれか。
正解: A
delete+insert は unique_key を前提に、該当行を削除してから新行を挿入する戦略。MERGE が使えない/避けたい場合でもアップサートを安定提供でき、失敗時の再実行も容易。完全再計算(B)は full-refresh の話、パーティション置換(C)は insert_overwrite、unique_key なし(D)は不適。
delete+insert で unique_key が NULL になる可能性がある列でも使えるか?
推奨されません。NULL が混在すると削除対象が確定できず、重複や残存が発生します。モデル上で coalesce による正規化、または複合キー化で NULL を排除し、dbt の unique / not_null テストで担保してください。
MERGE が使える環境でも delete+insert を選ぶ利点は?
再実行の単純さ(該当キーの再削除→再挿入)や、ロジックの可観測性を重視する場合に向きます。とはいえ、多くの環境では MERGE の方が効率的なことが多く、I/O とロック影響を評価したうえで選択してください。
insert_overwrite と delete+insert はどう使い分ける?
パーティション設計が明確で、置換範囲をパーティション単位で定義できるなら insert_overwrite が高速です。パーティション設計が難しい、またはキー単位の精緻な再構築が必要なら delete+insert を検討します。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...