[[dbt]] の `insert_overwrite` は、[[BigQuery]] のインクリメンタルモデルで、新しいデータが触れたパーティションを丸ごと差し替える戦略である。 この戦略は、行単位で更新する `merge` と違い、パーティション全体を消して入れ直すため `unique_key` を必要とせず、`partition_by` の設定を前提とする。 内部では [[BigQuery MERGE ステートメント|MERGE ステートメント]] の `ON FALSE` 洗い替えとして生成される。照合をやめ、パーティション述語で対象範囲を消してからソースを挿入する一文に変換される。 ## 生成される MERGE dbt は insert_overwrite を、次の形の MERGE に展開する。`ON FALSE` で照合せず、`NOT MATCHED BY SOURCE` の削除句にパーティション述語を足して、対象パーティションだけを消してから挿入する。 ```sql merge dataset.target as DBT_INTERNAL_DEST using (/* モデルの SELECT */) as DBT_INTERNAL_SOURCE on false -- 対象パーティションの既存行を消す when not matched by source and {{ パーティション述語 }} then delete -- ソースの全行を挿入する when not matched then insert (...) values (...); ``` この `ON FALSE` 洗い替えの仕組み自体は別ノートに譲る。insert_overwrite 固有の論点は、`パーティション述語` をどう組み立てるかにある。 ## 動的パーティションと静的パーティション 述語の作り方が 2 通りあり、コストが変わる。 **動的 (既定)**: 実行時に対象パーティションを見つける。 1. モデルの `SELECT` を一時テーブルに書き出す 2. ターゲットの最大パーティションを `_dbt_max_partition` 変数に取得する (モデル側でソースを絞るのに使える) 3. 一時テーブルから対象パーティションを集めて変数に入れる ```sql set (dbt_partitions_for_replacement) = ( select as struct array_agg(distinct partition_col ignore nulls) from dataset.target__dbt_tmp ); -- 削除句の述語 -- partition_col in unnest(dbt_partitions_for_replacement) ``` **静的 (`partitions` 設定)**: 上書きするパーティションをコンパイル時にリストで宣言する。述語はリテラルの `IN` になり、一時テーブルも introspection も要らない。 ```sql -- partitions=['2020-01-03', '2020-01-04'] のとき -- 削除句の述語 -- partition_col in ('2020-01-03', '2020-01-04') ``` | 観点 | 動的 (既定) | 静的 (`partitions`) | | ------------------ | ------------------------------------------- | ---------------------- | | 削除句の述語 | `in unnest(dbt_partitions_for_replacement)` | `in (リテラルリスト)` | | 一時テーブル | 作る | 作らない | | 追加クエリ | 最大パーティション取得 + 対象パーティション集計 | なし | | 対象パーティション | 実行時のデータから決まる | 書き手が事前に指定する | ## コストの観点 削除句のパーティション述語は、対象パーティションだけを読んで消すようプルーニングされる。動的・静的のどちらでも、述語が効けばターゲットの全走査は避けられる。 差が出るのは、対象パーティションを「見つける」コストである。動的は一時テーブルの書き出しと、最大パーティション・対象パーティションを求める追加クエリを払う。静的は述語がリテラルなのでそれらを省き、完全に静的なプルーニングになる。上書き範囲が事前に分かっているなら静的が安い。 > [!tip] > `partition_by` に `copy_partitions: true` を足すと、MERGE をやめて BigQuery のテーブルコピー API でパーティションを差し替える。挿入のスキャン課金が発生しないため大きいデータで安くなるが、動的モードでのみ使える。 ## 注意点 > [!warning] > insert_overwrite はパーティションを**丸ごと**差し替える。ソースは対象パーティションの全行を含む必要があり、パーティション内の差分だけを渡すと、ソースに無い既存行が消える。 > > また静的モードでは、`partitions` の値が `partition_by` の `data_type` (`date` / `timestamp` / `datetime` / `int64`) と一致していないと、削除句の `IN` がエラーになる。 ## 関連ノート - **[前提]** [[BigQuery MERGE の ON 句を FALSE にするとソースとターゲットを照合しない]]: insert_overwrite が内部で使う、照合せずパーティション単位で洗い替える仕組み - **[前提]** [[BigQuery MERGE のコストはソースとターゲット双方のスキャン量で決まる]]: 削除句のプルーニングと、動的・静的のコスト差を理解する土台