[[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 のコストはソースとターゲット双方のスキャン量で決まる]]: 削除句のプルーニングと、動的・静的のコスト差を理解する土台