dbt の materialization は、モデルを「どのようなデータベースオブジェクトとして、どのタイミングで更新するか」を決める仕組みです。選び方を誤ると、コスト・レイテンシ・可用性に直結します。
この記事では view / table / incremental / ephemeral の違いと適切な使い分けを、実務の現場感と Analytics Engineer 試験の観点の両方から整理します。
dbt はモデルごとに materialized の種類を切り替えられます。最終的に作られる(または作られない)DBオブジェクトの種類、更新の粒度、権限・コスト特性が変わります。まずは4種類の性質を俯瞰し、要件(再計算コスト、レイテンシ、下流依存、権限)に合わせて選びます。
大雑把な指針として、軽い探索や中間段階は view / ephemeral、安定した提供テーブルは table、継続的に差分更新するファクトや巨大テーブルは incremental が適します。ただし、パーティションやクラスタ、サポートされる戦略(merge / append / insert_overwrite)はアダプタ依存なので、使用するデータプラットフォームのサポート範囲を確認してください(dbt docs と各アダプタのドキュメントが一次情報)。
| Materialization | DBオブジェクト | 更新方式 | コスト/性能の傾向 |
|---|---|---|---|
| view | VIEW | 都度クエリ時に再計算 | ストレージはゼロ。都度の計算コストは発生 |
| table | TABLE | dbt 実行時に全再作成(既定) | ストレージ使用。読み取りは高速で安定 |
| incremental | TABLE | 差分のみ反映(append / merge / insert_overwrite 等) | 全再計算回避で大幅に効率化 |
| ephemeral | なし(CTE にインライン展開) | コンパイル時に上流クエリへ埋め込み | DBオブジェクト増加なし。最適化次第で高速 |
4種類の materialization と依存関係(概念図)
プロジェクト既定とモデル単位の切り替え例
# dbt_project.yml(抜粋)
models:
my_project:
+materialized: view # プロジェクト既定を view に
marts:
+materialized: table # サブパスは table で上書き
# models/stg_orders.sql(個別上書き)
{{ config(materialized='ephemeral') }}
select ...
# models/fct_orders.sql
{{ config(materialized='incremental', unique_key='order_id', incremental_strategy='merge') }}
select ... -- is_incremental() で差分を絞り込むのが定石view は CREATE VIEW により論理的なビューを作成します。ストレージを消費せず、参照時に元テーブルへクエリが流れます。開発初期や小規模な中間集計に向きます。
注意点として、重い集計を多段で view に重ねると、下流クエリのたびに上流が再計算され遅くなります。配布用に安定した性能を求める場合は table 化を検討します。アダプタによっては late binding view(依存関係解決を遅延)などのオプションがあり、スキーマ進化時のエラー耐性に差が出ます。必要に応じてアダプタのドキュメントでサポートを確認しましょう。
view モデルの最小構成
{{ config(materialized='view') }}
select
o.id,
o.created_at,
c.country
from {{ ref('raw_orders') }} as o
left join {{ ref('dim_customers') }} as c on o.customer_id = c.idtable は dbt 実行時に CREATE TABLE AS SELECT で作成し、モデルの再実行で再作成(既定)されます。読み取り性能と下流の安定性を優先する用途に向きます。
全再作成のコストが無視できない場合は incremental への移行を検討します。パーティション/クラスタの指定はアダプタ依存です(BigQuery の partition_by/cluster_by、Snowflake のクラスタリングキーなど)。指定できる場合は読み取り・再作成コストのバランスを最適化できます。
table モデル(クラスタ指定はアダプタ依存)
{{ config(materialized='table') }}
select * from {{ ref('int_orders_enriched') }}incremental は初回はテーブルを作成し、以降は差分だけを反映します。差分適用の戦略(incremental_strategy)はアダプタにより異なり、典型的に append(追記)、merge(キーで更新/挿入)、insert_overwrite(パーティション単位の置換)などが使えます。merge では unique_key の指定が重要です。
差分対象の絞り込みは is_incremental() マクロで条件分岐し、更新日時(updated_at)やロード境界(watermark)で制御します。全再計算したい場合は dbt run --full-refresh を用います。スキーマ進化(列追加等)が発生する場合、on_schema_change 設定値(ignore, fail, append_new_columns, sync_all_columns 等。アダプタサポート差あり)を必ず確認してください。
merge 戦略の典型パターン(キー更新あり)
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge'
) }}
with src as (
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
where updated_at >= (
select coalesce(max(updated_at), '1900-01-01') from {{ this }}
)
{% endif %}
)
select * from srcephemeral はデータベースにオブジェクトを作らず、依存先のモデル SQL に共通表式(CTE)としてインライン展開されます。中間ステップをファイルとして分離しつつ、DB 内のオブジェクトを増やしたくない場合に有効です。
注意点として、巨大な処理を多数の ephemeral に詰め込むと、単一クエリの長大化や最適化の難しさに繋がります。また、外部ツールから直接参照したり権限付与したりはできません。挙動の確認には dbt compile で生成 SQL を見るのが手早いです。
ephemeral モデルと利用側の例
-- models/_int_orders_ephemeral.sql
{{ config(materialized='ephemeral') }}
select *
from {{ ref('stg_orders_raw') }}
where is_valid = true
-- models/fct_orders.sql(利用側にCTEとして展開される)
{{ config(materialized='table') }}
with cleaned as (
select * from {{ ref('_int_orders_ephemeral') }}
)
select * from cleaned試験では、どの要件にどの materialization が適するか、差分戦略と unique_key、--full-refresh の意味、ephemeral の特性が頻出です。実務では、開発初期は view/ephemeral、安定後に table/incremental へ昇格する流れが安全です。
環境別(開発/本番)で materialization を切り替える場合は、変数や target.name を条件にして config を分岐させます。依存の広がり・再計算コスト・権限モデルを意識して、計画的に昇格/降格を管理します。
環境で materialization を切り替える例
{{ config(
materialized= (target.name == 'prod') and 'incremental' or 'view',
unique_key= (target.name == 'prod') and 'id' or none
) }}
select * from {{ ref('stg_items') }}Analytics Engineer
問題 1
巨大な fact テーブルを毎日更新する。更新対象は updated_at で識別でき、既存行の上書きも必要。処理時間とコストを最小化しつつ重複を避ける設計として最も適切なのはどれか?
正解: A
差分更新と既存行の上書きが必要な要件には incremental(merge 戦略)+ unique_key が定石。is_incremental() で更新日時に基づく範囲を絞ることで、実行時間とコストを抑えつつ重複を防げる。table は毎回フル再作成で非効率、view は参照時コストが高く一貫性能を確保しづらい。ephemeral は配布用の最終オブジェクトにならない。
開発では view、本番では incremental に切り替えたい。どう書けばよい?
config で target.name(環境名)や vars を条件に分岐します。例: {{ config(materialized=(target.name=='prod') and 'incremental' or 'view', unique_key=(target.name=='prod') and 'id' or none) }} のように書くと、prod では incremental、他は view になります。
incremental モデルで列が追加されたらどうなる?
挙動は on_schema_change とアダプタのサポートに依存します。ignore(無視)、fail(失敗)、append_new_columns(新列の追加)、sync_all_columns(差分同期)などがあり、利用可否はアダプタで異なります。確実に反映したい場合は --full-refresh、またはサポートされていれば append_new_columns / sync_all_columns を設定してください。
ephemeral と view の使い分けは?
ephemeral は中間処理をCTEとしてインライン化しDBオブジェクトを増やしません。下流から直接参照・権限付与が不要で、処理が軽いときに向きます。view は外部から直接参照可能で、共通ロジックの共有やアクセス制御が必要なときに適します。重い処理や安定性能が必要なら table/incremental を検討します。
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)、設定優先度...