モデルの出力スキーマが揺れると、下流ダッシュボードやMLパイプラインが壊れます。dbtのModel Contractsは、モデルのカラム構造とデータ型を“契約”として明示し、実装とスキーマの不一致を早期に検知・防止します。
本記事は、dbt公式ドキュメントの安定機能に基づき、書き方・挙動・テストやDB制約との違い・CI実装の勘所を、資格(Analytics Engineer)で問われやすい観点と実務運用の観点から整理します。詳細仕様や最新のアダプタ対応はdbt Docsを参照してください。
Model Contractsは、モデルの出力スキーマ(カラム名・順序・データ型)をYAMLで宣言し、contract.enforcedを有効化すると、dbtがビルド時にその契約を満たすようSQLを補正(投影・キャスト)し、不一致があれば失敗させます。これにより、上流の変更が下流にサイレントに伝播することを防げます。
主な効能は次の3つです。1) スキーマの固定(ブレイクの早期検知)、2) 型の明示(暗黙キャストの抑止)、3) 変更レビュー容易化(差分がYAMLに現れる)。ただし、データ内容の品質保証はテストやDB制約の領域です。
契約の流れ(開発→CI→実行基盤)
契約はモデルのYAMLファイルでカラム定義と型を宣言し、contract.enforcedを有効にして適用します。データ型は“使用するウェアハウスの型名”で記述します。型エイリアスは多くのアダプタで受理されますが、曖昧な表現は避け、実環境でcompile/build確認を行うのが安全です。
contractはモデル単位、パッケージ単位(dbt_project.ymlのモデル選択器)で有効化できます。組織標準として、上流に消費されるすべての公開モデル(例: marts)で必須にする運用が現実的です。
例: schema.yml と dbt_project.yml で契約を有効化
version: 2
models:
- name: dim_customers
description: 顧客ディメンション(公開対象)
config:
contract:
enforced: true
columns:
- name: customer_id
description: 事業内一意の顧客ID
data_type: BIGINT # Snowflake: NUMBER(38,0) などでも可
tests:
- not_null
- unique
- name: customer_name
data_type: VARCHAR
- name: is_active
data_type: BOOLEAN
# dbt_project.yml(marts配下を既定で契約有効化)
models:
my_project:
marts:
+contract:
enforced: truecontractが有効な場合、dbtはモデルのSELECTに対し、宣言された順序でカラムを投影し、必要に応じてアダプタが許す範囲で型キャストを挿入します。SELECTに余分なカラムがあっても、契約外は出力から落ちます。欠落カラムや明示的にキャスト不能な型不一致はビルド失敗になります。
ビューでも列の投影とキャストでスキーマは固定されます。テーブル作成時は宣言型でCREATE TABLEが行われ、後段にINSERT/CREATE OR REPLACEが続くのが一般的です(正確なDDLはアダプタに依存)。
| 機能 | 主な目的 | 実行タイミング | 失敗時の挙動 |
|---|---|---|---|
| Model Contracts | スキーマ(名前・順序・型)の固定 | dbt build/run のモデル生成時 | 不一致でジョブ失敗。余分列は除外、欠落/非対応キャストはエラー |
| dbt Tests | 値品質と関係の検証 | dbt test 実行時(しばしばCI) | テスト失敗をレポート。モデル生成は成功済みのことが多い |
| DB制約(NOT NULL, PK等) | DBレベルの一貫性担保 | DML/DDL適用時(DBが評価) | DBが挿入/更新を拒否しトランザクション失敗 |
例: SQLモデル内でconfigを直接指定(adapterに応じてキャストが挿入される)
{{ config(materialized='view', contract={'enforced': True}) }}
with src as (
select * from {{ ref('stg_customers') }}
)
select
cast(customer_id as bigint) as customer_id,
cast(customer_name as varchar) as customer_name,
cast(active as boolean) as is_active
from srcテーブル/ビューは一般に契約適用の第一候補です。ビューでも投影とキャストでスキーマは固定されますが、下層テーブルの型変更が伝播してキャスト不能になればビルド時に失敗します。
インクリメンタルモデルでは、既存テーブルの列追加や型変更はDB側のDDL互換性に依存します。契約で列を追加した場合、必要に応じてfull-refreshで再作成する運用が安全です。
運用ヒント: 変更種別に応じたリリース手順メモ
- 互換追加(列の後方追加): 事前にYAMLへ追加 → ビルド → 下流へ告知
- 互換削除(未使用列の除去): 下流の参照削除 → YAMLから除外 → ビルド
- 非互換(型変更/列名変更): ブランチで下流も同時修正 → CIで一括検証 → 本番はfull-refresh契約は“出力の形”を固定します。値の品質はテストの役割、実データ操作の強制力はDB制約の役割です。3者を重ねると、設計の意図がYAML/DDL/テストに明示化され、変更も可視化されます。
実務では、公開モデルには契約必須、主キー/外部キー相当はテストで担保、可能な範囲でDB制約(NOT NULLや一意制約)を活用、という使い分けが安定します。
例: 契約とテストの併用(relationshipsで参照整合性を担保)
version: 2
models:
- name: fct_orders
config:
contract:
enforced: true
columns:
- name: order_id
data_type: BIGINT
tests: [unique, not_null]
- name: customer_id
data_type: BIGINT
tests:
- not_null
- relationships:
to: ref('dim_customers')
field: customer_id
- name: order_total
data_type: NUMERICPRでdbt buildを走らせ、契約不一致をブロックするフローを必須化します。モデルの契約変更(列追加・型変更・削除)は必ずPRでYAML差分が見えるため、レビューしやすくなります。カタログ/ドキュメント生成と合わせると、APIライクな公開契約を実現できます。
ブレイク検知を強くするには、公開階層(marts)をターゲットに選択実行、seed/sourceのスキーマ変更検知、full-refreshの定期実施(インクリメンタルの負債解消)を組み合わせます。
例: GitHub ActionsでのラフなCIステップ
steps:
- run: dbt deps
- run: dbt build -s state:modified+ --profiles-dir .
- run: dbt test -s tag:public
# 契約不一致やテスト失敗でPRをブロックAnalytics Engineer
問題 1
公開モデルdim_customersに新しい列customer_tier(文字列型)を追加したい。下流の互換性を維持しつつ、dbt Model Contractsを使って安全にリリースする最も適切な手順はどれか?
正解: A
契約はYAMLで宣言されたカラムが真実。先にschema.ymlへ適切なdata_typeで列を追加し、CIで契約整合と型キャストの成立を検証してからリリースするのが安全。SQLだけの変更や契約の後回しは、下流互換性の見落としにつながる。
data_typeはdbtの共通型名でも書けますか?
いいえ。基本は使用するウェアハウスのネイティブ型名で指定します。多くのアダプタはエイリアスを受理しますが、曖昧さを避けるため実環境でdbt buildを行い、生成DDLとキャストを確認してください(参照: dbt Docs)。
契約があるのに余分なカラムがSELECTに入っていても通るのはなぜ?
契約有効時は、宣言されたカラムのみを順序通りに投影するため、契約外のカラムは出力から落とされます。これは下流の互換性維持のための仕様です。一方で、欠落カラムや不可能な型キャストはビルド失敗となります。
インクリメンタルモデルで型変更はどう扱うべきですか?
既存テーブルの型変更はDBのALTER互換性に依存し、運用リスクが高いです。契約を更新した上で、原則としてfull-refreshで再作成する計画を取り、必要に応じて移行期間を設けましょう。
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)、設定優先度...