unique テストは、主キー候補やビジネスキーの重複を即座に検知するための最小単位の品質ゲートです。
本稿では、定義・実行・失敗時の対処・CI 連携・ウェアハウス差分・試験で問われやすい要点を、公式ドキュメントの挙動に沿って解説します。
dbt の generic テスト unique は、指定列の各値がテーブル内で一意であることを検証します。内部的には対象列で GROUP BY し、同じ値が2件以上あれば失敗と判定します。NULL も1つの値として集約されるため、NULL が2件以上あると unique は失敗します。したがって、主キー相当の厳密性を求める場合は unique と not_null を組み合わせます。
テストは通常、モデルの YAML プロパティファイルに宣言します。dbt test 実行時、dbt は該当モデルに対して検査用の SQL を生成し、行が返れば失敗、0件なら成功という単純明快なルールで評価します。
| テスト | 目的 | よくある失敗例 | 併用推奨 |
|---|---|---|---|
| unique | 重複検出(同値が2件以上) | JOIN の多重化で fan-out | not_null |
| not_null | NULL の排除 | 欠損許容列に誤適用 | unique |
| relationships | 参照整合性(外部キー的) | 参照先未投入・遅延到着 | unique + not_null(参照元キー側) |
ETL/ELT とテストの配置(概念図)
YAML による unique と not_null の併用例
version: 2
models:
- name: fct_orders
description: 受注のファクトテーブル(order_id は主キー相当)
columns:
- name: order_id
tests:
- unique
- not_null
- name: customer_id
tests:
- relationships:
to: ref('dim_customers')
field: customer_id
プロパティは models ディレクトリ配下の YML(例: models/schema.yml)に配置します。dbt test は選択子で対象を絞れます。変更の影響範囲のみ検査したい場合、state:modified といったステートフル選択を CI と組み合わせるのが定石です。
unique 失敗時は即座に修正できるよう、モデル名や列名をエラーに出す命名・コメント整備が有効です。
| コマンド | スコープ | 用途 |
|---|---|---|
| dbt test -s fct_orders | モデル単位 | 対象モデルのみ検査 |
| dbt test -s fct_orders.order_id | 列単位 | 問題列のスポット検査 |
| dbt test -s state:modified+ | 変更+依存先 | PR での差分検査 |
選択子によるテスト対象の絞り込み(概念)
CLI 実行例(ローカル / CI 共通)
# 単一モデル
$ dbt test -s fct_orders
# 列を直接指定
$ dbt test -s fct_orders.order_id
# 変更の下流まで
$ dbt test -s state:modified+
unique 失敗の多くは、JOIN による fan-out(多重化)、重複行の取り込み、遅延到着データの再投入が原因です。まずは失敗テストが生成した検査 SQL を dbt debug ではなく、コンパイル結果(target/compiled)やログから確認し、GROUP BY 対象の値ごとの件数を観察します。
重複除去は、ウィンドウ関数で最新レコードを1件だけ残すのが定番です。増分モデルでは unique_key 設定と合わせて、重複そのものを作らない設計(ソース段階の正規化やキー定義)も重要です。
| 症状 | 観測ポイント | 対処 |
|---|---|---|
| JOIN 後に件数が倍増 | 結合キーの一対多 | 事前集約 or DISTINCT or 代表行選択 |
| 取り込みで同一レコードが再出現 | 同一キー・異なる更新時刻 | row_number で最新のみ採用 |
| NULL の多発 | NULL が2件以上 | not_null の追加と上流修正 |
重複除去の流れ(例)
重複除去の SQL 例(最新1件を採用)
with src as (
select * from {{ ref('stg_orders') }}
), ranked as (
select
*,
row_number() over(
partition by order_id
order by updated_at desc
) as rn
from src
)
select
-- 必要な列
order_id, customer_id, amount, updated_at
from ranked
where rn = 1
unique は本番反映の可否を左右するゲートとして運用するのが一般的です。失敗時にパイプラインを中断したい列には severity: error、許容したい状況(移行期間など)には severity: warn を使い分けます。severity の既定は error です。
変更差分のみ検査することで、CI の所要時間を抑えつつ品質を担保できます。テストの命名・説明文を充実させると、失敗時の判断が早くなります。
| severity | パイプラインの挙動 | 主な用途 |
|---|---|---|
| error(既定) | 失敗で中断 | 主キー・参照キー・課金影響の大きい集計 |
| warn | 警告ログのみで続行 | 移行中の暫定品質・段階的是正 |
CI での実行順序(概念)
YAML で severity を列ごとに設定
version: 2
models:
- name: fct_orders
columns:
- name: order_id
tests:
- unique:
config:
severity: error
- not_null
- name: legacy_order_code
description: 移行期間中は重複の可能性あり
tests:
- unique:
config:
severity: warn
dbt の unique は生成 SQL の GROUP BY/HAVING に依存します。NULL は2件以上で失敗、1件だけなら成功という挙動は各主要 DWH で一貫しています。一方で、文字列比較の大文字小文字や照合順序(collation)により、重複と見なすかが変わる場合があります。
ケース非依存で一意性を保証したい場合は、正規化(lower/upper)した派生列に対して unique を当てる、あるいはカスタム generic テストを実装します。
| DWH | ケース感度の既定 | 対策例 |
|---|---|---|
| Snowflake | BINARY(一般に大小区別) | lower() 列を検査 |
| BigQuery | 大小区別(既定) | 正規化列や collation 指定(一部関数依存) |
| Databricks SQL | Spark SQL 準拠(大小区別が基本) | 正規化列で検査 |
正規化して検査する流れ
カスタム generic テスト(ケース非依存の一意性)
{% macro test_unique_lower(model, column_name) %}
with base as (
select lower({{ column_name }}) as val from {{ model }}
), agg as (
select val, count(*) as n from base group by 1 having count(*) > 1
)
select * from agg
{% endmacro %}
# 呼び出し(YAML)
# tests:
# - unique_lower:
# column_name: user_email
試験では、unique テストの意味、NULL との関係、not_null との組み合わせ、選択子によるテスト実行の知識が頻出です。また、増分モデルの unique_key とテストの役割の違い(実行時のマージ鍵 vs 品質検査)は区別して覚えておきましょう。
関係性テストや受け入れ値テストとの使い分け、警告とエラーの運用判断も問われやすい領域です。
| トピック | 重要度 | 覚えるフレーズ |
|---|---|---|
| unique と not_null の関係 | 高 | 主キー=重複なしかつ NULL なし |
| 選択子(state:modified+) | 高 | 差分と下流をテスト |
| severity の使い分け | 中 | error でゲート、warn で観測 |
知識整理のイメージ
チートシート(YAML 抜粋)
version: 2
models:
- name: some_model
columns:
- name: business_key
tests:
- unique
- not_null
- relationships:
to: ref('dim_ref')
field: business_key
Analytics Engineer
問題 1
dbt の generic テスト unique の挙動として正しいものはどれか。主キー相当の厳密性を担保したい場合の推奨も含めて選べ。
正解: A
unique は対象列で GROUP BY/HAVING を行い、同一値が2件以上(NULL も1つの値として集約されるため2件以上の NULL を含む)で失敗します。主キー相当の厳密性には unique と not_null の併用が必要です。
unique と not_null はどちらを先に入れるべきか?
順序は任意ですが、主キー相当の列には両方を必ず定義します。unique だけでは NULL が1件だけ存在しても失敗しないため、NULL を禁止する not_null を合わせて担保します。
複合ユニーク(複数列の組合せ)はどう検査する?
公式の unique は単一列向けです。複合ユニークはカスタム generic テストを実装するか、コミュニティパッケージ(例: dbt_utils の unique_combination_of_columns)を利用します。
incremental モデルの unique_key 設定と unique テストの違いは?
unique_key は増分実行時のマージ鍵(upsert キー)で、重複を発生させにくくする実行時の挙動制御です。unique テストは生成物の一意性を検査する品質チェックであり、両方を併用するのが実務では一般的です。
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)、設定優先度...