dbt

dbtのconstraints機能で押さえるべきNOT NULL / PRIMARY KEY実務と試験対策

2026-04-19
NicheeLab編集部

dbtはモデル作成時にデータウェアハウスの制約(NOT NULL、PRIMARY KEY、UNIQUE、CHECKなど)を付与できるアダプタがあります。これにより、実行時に不正データの書き込みをブロックできます。

一方で、dbtのテスト(generic tests)やモデルコントラクト(contracts)との役割分担を誤ると、意図せず本番ジョブが落ちたり、逆に品質保証が弱くなったりします。本稿は、制約の宣言方法、アダプタ差、移行運用、試験対策の観点で実務に落ちる線を解説します。

制約とテストの役割分担をまず整理する

dbtのconstraints機能は、アダプタ(SnowflakeやDatabricksなど)が対応している場合に、CREATE/ALTER TABLE時にデータベース制約を作成します。これにより、ロード時(INSERT/MERGE)に不正データを書き込めなくなります。

dbtのテストは、ロード後に集計・走査して品質を検証します。制約は“予防”、テストは“検知”の役割です。モデルコントラクト(contracts.enforced)はスキーマの整合(列名・型)を強制する仕組みで、値制約とは別物です。

  • 制約: ウェアハウスが実行時に拒否(NOT NULL、CHECK、アダプタによりPRIMARY/UNIQUEの扱いは差異)
  • テスト: dbtが後段で検知(not_null、unique、relationshipsなど)
  • モデルコントラクト: 列名・型の逸脱をビルド時に拒否(値の妥当性は別途)
観点dbtテスト制約(constraints)モデルコントラクト(contracts)
定義の場所schema.yml(generic tests)モデルconfigやschema.yml(アダプタ対応時)モデルconfig(contracts.enforced: true)
実行タイミングビルド後に検査DML時にウェアハウスが検査ビルド時にDDLとクエリ整合を検査
失敗時の挙動テストがfail(ジョブは設定次第)書き込みがエラーで中断ビルドがエラー(列名・型不一致)
主目的品質の継続的検知実行時の不正データの遮断スキーマ契約の強制

dbtにおける制約・テスト・コントラクトの流れ

DDLDMLcheckSELECTdbt buildCREATE/ALTERwith constraintsWarehouse TableConstraint Enforcementdbt tests (generic)dbtにおける制約・テスト・コントラクトの流れ

NOT NULL / PRIMARY KEYなどの宣言方法(dbt側)

アダプタが対応している場合、モデルのconfigやschema.ymlで制約を宣言できます。宣言はDDL生成時に反映されます。既存テーブルに対する追加はアダプタの挙動に依存し、ALTER TABLEで付与される場合と、再作成(--full-refresh)が必要な場合があります。

PRIMARY KEYやUNIQUEの実効性はウェアハウスごとに異なります。たとえばSnowflakeではNOT NULLは強制されますが、PRIMARY KEY/UNIQUEは情報的(enforcedされない)であることがあります。Databricks(Delta Lake)はNOT NULLやCHECKを強制できますが、PRIMARY KEYはネイティブサポートが限定的です。

  • 列レベルのNOT NULLは、実行時のバリデーションとして強力
  • PRIMARY KEYは、実体としては“unique + not null”の意図表明。Snowflakeでは多くのケースで情報的
  • UNIQUEはアダプタの実装に従う(Snowflakeでは情報的、DeltaはCHECKで代替することも)

宣言例(モデルconfigとschema.yml)

-- models/customers.sql
{{ config(
  materialized='table',
  constraints=[
    { 'type': 'primary_key', 'columns': ['customer_id'] },
    { 'type': 'not_null',    'columns': ['customer_id', 'email'] },
    { 'type': 'unique',      'columns': ['email'] }
  ],
  contracts={'enforced': true}
) }}
select
  customer_id::number as customer_id,
  email::string       as email,
  created_at::timestamp as created_at
from {{ ref('stg_customers') }};

# schema.yml(補助的にテストも併用)
models:
  - name: customers
    columns:
      - name: customer_id
        tests:
          - not_null
          - unique
        constraints:
          - type: not_null
          - type: primary_key
      - name: email
        tests:
          - not_null
        constraints:
          - type: not_null

アダプタ別サポート差分(Snowflake / Databricks)

Snowflake: NOT NULLはエンジンで強制されます。PRIMARY KEYやUNIQUEは情報的で、クエリ最適化や実行時の重複拒否を必ずしも保証しません。そのため、実運用ではNOT NULL制約とdbtのuniqueテスト、もしくはモデル側の重複排除ロジックを併用します。

Databricks(Delta Lake): NOT NULLとCHECK制約を強制できます。PRIMARY KEYは限定的で、ユニーク性の保証はモデルでの去重やマージキーの一意性担保で実現するのが一般的です。

  • Snowflake: NOT NULLは強制。PK/UNIQUEは情報的であるケースに留意
  • Databricks Delta: NOT NULL/CHECKが有効。PKは限定的
  • いずれも、dbtのgeneric testsは併用する(早期検知と可観測性)

Delta LakeでのCHECK代替例(疑似ユニーク制約)

-- Databricks Deltaの例:メールの形式や範囲チェック
{{ config(
  materialized='table',
  constraints=[
    { 'type': 'not_null', 'columns': ['customer_id', 'email'] },
    { 'type': 'check',    'expression': "email LIKE '%@%._%'" }
  ]
) }}
select * from {{ ref('stg_customers') }};

インクリメンタルモデルと制約の相性

インクリメンタルモデルではunique_keyを用いたMERGEを行うケースが多く、NOT NULL制約はそのカラムに対する安全装置になります。制約の追加・変更は、アダプタによってはテーブル再作成が必要となるため、--full-refreshの計画的な実行が求められます。

制約はテーブル作成時に付与されるのが基本です。運用中のテーブルにALTERで追加できるか、dbtが自動でALTERを発行するかはアダプタ実装依存です。変更管理では、ステージング環境で先に制約を試し、本番は低リスクのNOT NULLから段階導入するのが無難です。

  • unique_keyは重複を“更新ロジックで”さばく。PK/UNIQUE制約の実効性はアダプタ差
  • --full-refreshで制約定義を確実に反映(ダウンタイム計画も)
  • on_schema_changeの設定と合わせて、列追加・同期の方針を明確化

インクリメンタルモデルでの設定例

{{ config(
  materialized='incremental',
  unique_key='customer_id',
  on_schema_change='sync_all_columns',
  constraints=[ { 'type': 'not_null', 'columns': ['customer_id'] } ]
) }}

with src as (
  select * from {{ ref('stg_customers') }}
)
select * from src
{% if is_incremental() %}
  -- 追加ロジック(例:更新日時で絞り込み)
  where updated_at > (select coalesce(max(updated_at), '1970-01-01') from {{ this }})
{% endif %}

移行・運用のベストプラクティス

既存テーブルにNOT NULLを追加する場合は、事前にNULL埋めや補正を完了し、dbtのnot_nullテストでグリーンを確認した上で制約を付与します。本番ではまずステージングで適用→本番は低トラフィック時間帯に--full-refresh、が安全です。

PRIMARY KEYやUNIQUEの“意図表明”は有用ですが、Snowflakeのように実効性が限定的な環境では、モデル側での去重(window関数・QUALIFYなど)とテストの併用で一意性を保証します。

  • 低リスク順(テスト→NOT NULL→CHECK→PK/UNIQUEの宣言)で段階導入
  • dbt Cloud/CIでテストを必須化し、制約追加前後の差分を可視化
  • 失敗時リカバリ手順(制約のDROP/再ビルド)を事前に用意

去重の例(一意行のみ残す)

with base as (
  select *,
         row_number() over (partition by customer_id order by updated_at desc) as rn
  from {{ ref('stg_customers') }}
)
select * from base where rn = 1

Analytics Engineer試験で問われやすいポイント

制約・テスト・コントラクトの役割分担を言語化できることが重要です。特にSnowflakeのPK/UNIQUEが情報的である点、NOT NULLは強制される点、DeltaはNOT NULL/CHECKが強い点など、アダプタ差分は頻出です。

“ユニーク性を保証したい=PK制約で完璧”とは限らない、という落とし穴に注意。現実装ではモデルの去重ロジック+テストの併用が最も再現性が高いケースが多いです。

  • 制約は予防、テストは検知、コントラクトはスキーマ整合の強制
  • Snowflake: NOT NULLは強制、PK/UNIQUEは情報的
  • Databricks Delta: NOT NULL/CHECKが強力、PKは限定的。ユニーク性はモデルとテストで担保

問題で確認

Analytics Engineer

問題 1

dbtでSnowflakeをターゲットにしており、customersモデルのcustomer_idにNULLを許さず、実行時に不正行の書き込みをブロックしたい。さらに品質可観測性も維持したい。最も適切な対応はどれか。

  1. モデルでcustomer_idにNOT NULL制約を宣言し、schema.ymlのnot_nullテストも残す
  2. schema.ymlのnot_nullテストだけを設定し、制約は使わない
  3. PRIMARY KEY制約のみを宣言すれば実行時に重複とNULLがブロックされる
  4. モデルコントラクト(contracts.enforced: true)だけを有効化すればNULLもブロックされる

正解: A

SnowflakeではNOT NULLはエンジンで強制され、実行時にNULLの書き込みをブロックできます。可観測性のためにdbtのnot_nullテストも維持するのが実務的に最適です。PKのみでNULL/重複が実行時にブロックされるとは限らず、contractsは列名・型の整合を強制する仕組みで値のNULLは別管理です.

よくある質問

dbtの制約とテストはどちらを優先すべきですか?

役割が異なります。制約は実行時のブロック(予防)、テストはビルド後の検知(可観測性)です。可能なら両方を使い、特にNOT NULLは制約+テストの二段構えを推奨します。

SnowflakeでPRIMARY KEYやUNIQUEを宣言すれば重複は拒否されますか?

SnowflakeではPRIMARY KEY/UNIQUEは情報的であるケースが多く、実行時に必ずしも重複を拒否しません。ユニーク性はモデル側の去重処理とdbtのuniqueテストで担保する運用が一般的です。

制約を後から追加したい場合、テーブルを落とす必要はありますか?

アダプタ実装によります。ALTERで付与できる場合もありますが、変更内容によっては--full-refreshが必要です。ステージングでの事前検証と本番のメンテナンス計画を用意してください。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
dbt

dbt Model の基礎: SQL で定義する変換の最小単位

Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...

dbt

dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点

dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...

dbt

dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点

dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...

dbt

dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト

Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....

dbt

dbt_project.yml の読み方:主要設定と命名を最短で掴む

dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...

dbtの記事一覧 (100件)
© 2026 NicheeLab All rights reserved.