青の統計学-DS Playground-

schema tests:列の契約を YAML で書く

Stage 2 — 第3章 | dbt入門カリキュラム 推定学習時間:35〜45分 | 難易度:★★★☆☆


この章で学ぶこと

dbt の schema tests は、YAML に宣言する 列単位の品質契約 です。 not_nullaccepted_values など、よく使うチェックは generic test として組み込まれており、ドキュメントと CI 検証を同じファイルで共有 できます。

データマネジメント入門 Stage 4 の品質次元や、データ品質チェック で手書きしていた SQL チェックが、宣言的 になった形と考えてください。

まず dbt の作法を整理し、続けて DS Playground の int_user_activity_events を中心に、grain を守るテストと enum を守るテストの読み方を学びます。

この章を終えると、こんなことができるようになります:

  • _intermediate__models.ymldata_tests ブロックを読める
  • not_nullgrain と必須時刻 を守るテストだと説明できる
  • accepted_valuesactivity_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 に載る前に、定義の前提が崩れていないかを機械的に確認します。

flowchart LR YAML["_staging__models.yml
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
flowchart TB subgraph stg["staging"] P[problem_attempts] E[exam_attempt_events] C[curriculum progress] end subgraph int["int_user_activity_events"] U["UNION ALL + activity_type"] end P --> U E --> U C --> U U --> T["accepted_values test"] T --> M["fct_*_daily 等"]

新しい行動を追加する場合の手順は次のとおりです。

  1. staging / union 分支に SQL を追加
  2. YAML の values リストを更新
  3. 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 は、数字を出す前に 「この指標は何を前提にしているか」 を固定する仕組みです。


関連教材


確認問題

問題 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 指標定義の単一正本