schema tests:列の契約を YAML で書く
Stage 2 — 第3章 | dbt入門カリキュラム 推定学習時間:35〜45分 | 難易度:★★★☆☆
この章で学ぶこと
dbt の schema tests は、YAML に宣言する 列単位の品質契約 です。
not_null や accepted_values など、よく使うチェックは generic test として組み込まれており、ドキュメントと CI 検証を同じファイルで共有 できます。
データマネジメント入門 Stage 4 の品質次元や、データ品質チェック で手書きしていた SQL チェックが、宣言的 になった形と考えてください。
まず dbt の作法を整理し、続けて DS Playground の int_user_activity_events を中心に、grain を守るテストと enum を守るテストの読み方を学びます。
この章を終えると、こんなことができるようになります:
_intermediate__models.ymlのdata_testsブロックを読めるnot_nullが grain と必須時刻 を守るテストだと説明できるaccepted_valuesが activity_type 語彙 を固定する理由を理解できる- schema test と singular test(次章)の 使い分け ができる
schema test とは何か
dbt プロジェクトでは、モデル定義 YAML(models/**/_*.yml)の各列に data_tests を付けます。
build 時に dbt が SQL を生成・実行し、契約違反の行があれば CI を止めます。
| 種類 | 宣言場所 | 典型例 |
|---|---|---|
| schema test(generic) | columns[].data_tests |
not_null, unique, accepted_values |
| singular test | tests/*.sql |
複数列・複数表にまたがる断言(次章) |
schema test の本質は「テストコード」であると同時に、指標 SSOT の仕様書 です。 Metabase に載る前に、定義の前提が崩れていないかを機械的に確認します。
data_tests"] BUILD["dbt build"] CI["CI 失敗 / 成功"] BI["Metabase / mart"] YAML --> BUILD --> CI BUILD --> BI
分析前チェックリスト の「キーに NULL はないか」「enum は想定内か」を、パイプラインに組み込む イメージです。
レイヤーごとにテストの焦点が変わる
| レイヤー | テストの焦点 | 例 |
|---|---|---|
| staging | ソース列の 生 enum、必須キー | consent_type の accepted_values |
| intermediate | 統合後 の enum、SSOT grain | activity_type の accepted_values |
| marts | grain の一意性、measure の not_null | unique 組み合わせ |
staging でソースの enum を固定し、intermediate で 統合後の語彙 を固定する——レイヤーをまたいだ 二段構え もよくあります。
int_user_activity_events
この intermediate モデルは、アクティブユーザー指標の SSOT(Single Source of Truth) です。 YAML の description にも、次のように書かれています。
Unified user action events for product-active-user metrics. This is the SSOT for Active User: a user is active in a period when the user has at least one row in this model during that period.
models:
- name: int_user_activity_events
description: >
Unified user action events for product-active-user metrics.
This is the SSOT for Active User: a user is active in a period when
the user has at least one row in this model during that period.
columns:
- name: user_id
data_tests:
- not_null
- name: occurred_at
data_tests:
- not_null
- name: date_day
data_tests:
- not_null
- name: activity_type
data_tests:
- accepted_values:
arguments:
values:
- practice_problem_saved
- mock_exam_saved
- curriculum_page_completed
- skill_check_completed
- learning_goal_updated
- member_survey_submitted
YAML を読むだけで、「アクティブユーザー定義の 最小契約」が見えます。 これが schema test の強みです。
not_null — grain と時系列の前提
| 列 | not_null が守るもの |
|---|---|
user_id |
「誰の」イベントか不明な行が SSOT に混入しない |
occurred_at |
順序・時系列分析の前提 |
date_day |
日次 DAU 等の GROUP BY キー |
3 列そろって初めて イベント 1 件 として日次集計に載せられます。
前章 で staging に作った attempted_date などが、ここでは date_day として統合されています。
モデル SQL の末尾にも、同型の防御が入ることがあります。
select
user_id,
occurred_at,
date_day,
activity_type
from activity_events
where user_id is not null
and occurred_at is not null
and date_day is not null
| SQL WHERE | schema test |
|---|---|
| build 時に行を落とす | build 後に 残った行 が契約を満たすか検証 |
| ロジックの一部 | 契約の公開(YAML に載り、レビュー対象になる) |
WHERE と schema test は 補完関係 です。 WHERE だけだと「なぜ NULL を落とすのか」が YAML から読み取れません。 schema test だけだと、NULL 行が build を通過してから失敗するため、意図的に落とす 場合は SQL 側にも書きます。
accepted_values — activity_type 語彙
activity_type は 明示的学習行動 の enum です。
複数 staging を UNION した結果として、下流 mart が 共通語彙 で内訳を切れるように固定します。
| 値 | 由来 staging(概念) |
|---|---|
practice_problem_saved |
problem_attempts |
mock_exam_saved |
exam_attempt_events |
curriculum_page_completed |
user_curriculum_page_progress |
skill_check_completed |
skill_check_results |
learning_goal_updated |
user_learning_preferences |
member_survey_submitted |
member_survey_responses |
新しい行動を追加する場合の手順は次のとおりです。
- staging / union 分支に SQL を追加
- YAML の values リストを更新
- docs / semantic_ontology を更新
リスト更新を忘れると CI が失敗し、サイレントに新 enum が BI へ流れる のを防ぎます。 enum の変更は「SQL 1 行追加」では済まず、語彙契約の更新 として扱う——これが dbt プロジェクトの運用感覚です。
staging 側の schema test 例
int_* だけでなく staging でも同様です。
# stg_supabase__consents(抜粋)
- name: consent_type
data_tests:
- not_null
- accepted_values:
arguments:
values: ["terms", "privacy", "marketing", "job_opt_in"]
第1章 で読んだ consents の staging が、ソース enum をそのまま契約化 している例です。
アプリ側で新しい consent_type が追加されたら、staging YAML の更新が 最初の検知ポイント になります。
schema test で表現しにくいもの → singular へ
| 要件 | なぜ schema 不足か | 次章の例 |
|---|---|---|
| 0 ≤ total_score ≤ 100 | 連続値の範囲 | assert_skill_check_total_score_valid |
| JSONB 軸スコアの版依存 | 列構造が version で変わる | assert_skill_check_axis_scores_valid |
| seed との被覆 | 別表参照 | assert_problem_attempts_catalog_covered |
単純な NOT NULL / enum は schema、ビジネスルール SQL は singular——この切り分けを次章で詳述します。
CI での実行イメージ
DS Playground では PR 時に dbt build(target: analytics_ci)が走り、モデル実行・schema tests・singular tests が一括で検証されます。
git push → GitHub Actions → dbt build (target: analytics_ci)
├── run models
├── run schema tests(各 yml の data_tests)
└── run singular tests(tests/*.sql)
schema test は 「マージ前の品質ゲート」 です。 テストを無効化して黙らせるのは、品質インシデント で学んだアンチパターンと同型です。
体系コラム:カリキュラム上の位置づけ
| 項目 | 内容 |
|---|---|
| Stage / 章 | Stage 2 — 第3章 |
| 今回の論点 | 列契約を YAML で 公開 し CI で検証 |
| 前章との接続 | 派生列と命名 |
| 次章への伏線 | singular tests |
まとめ
| テスト | 役割 |
|---|---|
not_null |
必須キー・時刻・日付 |
accepted_values |
enum / 語彙固定 |
unique |
grain(他モデルで使用) |
| YAML 宣言 | docs と CI とレビューの共通言語 |
int_user_activity_events の YAML を読めば、アクティブユーザー定義の最小契約が見えます。
schema tests は、数字を出す前に 「この指標は何を前提にしているか」 を固定する仕組みです。
関連教材
- 前章: 派生列と命名
- 次章: singular tests
- 関連: 品質次元
確認問題
問題 1
列に accepted_values テストを付ける 主目的 はどれですか。
A. SQL の実行時間を短縮する
B. 列値が許容リスト外に崩れていないか検証する
C. 主キーを一意にする
D. seed CSV を自動更新する
正解: B
問題 2
イベント 1 行の grain を user_id, occurred_at, date_day と定義したとき、3 列すべてに not_null を付ける 最も本質的な理由 はどれですか。
A. データベースが NOT NULL 制約を要求するから
B. grain のどの要素も欠ける行を許容しないから
C. BI ツールが NULL を表示できないから
D. staging が view だから
正解: B
問題 3
0〜100 のスコア範囲チェックを schema test だけで書かず singular test に回す 典型理由 はどれですか。
A. not_null より速いから
B. 範囲や版依存 JSON など、列単位の宣言では表しにくい複合条件だから
C. YAML が禁止されているから
D. intermediate には schema test を書けないから
正解: B
用語メモ(この章)
| 用語 | 意味(この章での使い方) |
|---|---|
| schema test | _*.yml に宣言する generic テスト |
| data_tests | 列に付与するテストリスト |
| accepted_values | 列値が許容リストに含まれるか |
| grain | 1 行の意味単位 |
| SSOT | 指標定義の単一正本 |