Claude Media
Claude Codeでdbtのデータテストを足す — dbt testで検証して失敗行を読む

Claude Codeでdbtのデータテストを足す — dbt testで検証して失敗行を読む

dbtのモデルにunique・not_null・relationshipsのテストをClaude Codeで足し、dbt testで検証して失敗行をstore_failuresで読む手順を、CLAUDE.mdの書き方とまとめます。

dbtのモデルにデータテストを足す作業は、Claude Codeに任せやすい部類です。ymlにunique、not_null、relationshipsを書き、dbt testで結果を読み、落ちた行を直す。この反復は、検証コマンドと完了条件を先に決めておけば回ります。この記事では、テストの足し方、CLAUDE.mdに書く指示、失敗行をstore_failuresで確かめる流れを順に示します。

データテストが検証するもの

dbtのデータテストは、前提を破る行を返すselect文です。uniqueなら重複した値、not_nullならnullの行を探します。返る行が0件なら合格で、1件でもあれば失敗です。

Claudeに任せるときは、この仕組みが効いてきます。失敗はいつも「具体的な行」として現れるので、メッセージを読むだけでなく、行そのものを見て原因を切り分けられます。

テストの書き方は2系統あります。

  • 汎用テスト(generic): ymlに名前を書くだけで使える。組み込みはunique、not_null、accepted_values、relationshipsの4つ
  • 単発テスト(singular): 失敗行を返すSQLをtests/の下の.sqlに置く

この記事は、最もよく使う汎用テストの3つ(unique、not_null、relationships)を中心に扱います。dbt buildで影響範囲だけを回す方法はdbt build --selectの記事、SQLの整形はsqlfluffをhookに入れる記事にあります。

unique・not_null・relationshipsをymlに足す

モデルのプロパティファイルに、列ごとのdata_testsを書きます。主キーにはuniqueとnot_null、外部キーにはrelationshipsが基本の組み合わせです。

models:
  - name: orders
    columns:
      - name: order_id
        data_tests:
          - unique
          - not_null
      - name: customer_id
        data_tests:
          - relationships:
              arguments:
                to: ref('customers')
                field: id

relationshipsは、ordersの各customer_idがcustomersのidに存在することを確かめます。参照整合性のテストです。

押さえておく点が3つあります。

  • 引数を取るテストは、argumentsの下に書く。この書き方はv1.10.5以降で使え、dbt v2では必須になり、旧式の書き方は検証エラーになる
  • relationshipsはnullを検証から除く。外部キーがnullで困るなら、同じ列にnot_nullも足す
  • キーの名前はdata_tests。かつてのtestsも動くが、同じリソースにtestsとdata_testsを併記することはできない

3つ目は、Claudeが古い記事の記憶でtests:を書いたときに効きます。既存のymlがtests:なら、それに合わせるか、全体をdata_tests:に揃えるか、どちらかにします。

CLAUDE.mdにテストの方針を書く

Claude Codeは、CLAUDE.mdを「強制される設定」でなく文脈として読みます。守らせたいことは、確かめられる形で具体的に書きます。メモリのドキュメントも、「テストを確認する」でなく実行するコマンドを書くよう勧めています。

dbtのテスト方針は、モデルのymlを触るときだけ効けば足ります。そこで.claude/rules/に、paths付きのルールとして置く方法があります。パスに合致するファイルをClaudeが読んだり編集したりしたときに読み込まれるので、常時の文脈を増やしません。

---
paths:
  - "models/**/*.yml"
---
 
# dbtデータテストの方針
 
- 新しいモデルには、主キー列に `unique` と `not_null` を足す
- 外部キー列には `relationships` を足し、`arguments` の下に `to` と `field` を書く
- キー名は `data_tests` を使う(既存が `tests` の場合はそれに合わせる)
- ymlを編集したら `dbt test --select <モデル名> --store-failures` を実行し、結果を読む
- テストが落ちたら、`severity: warn` や `where` で合格させず、失敗行を確認して原因を報告する
- 全件の `dbt test` は実行しない

最後の2行が肝心です。テストが落ちたときにClaudeがとりうる最短経路は、テストを緩めて通すことです。禁止する書き方を、あらかじめ明文化しておきます。

指示が効いているかを疑うときは、/contextで読み込まれたファイルを確かめます。パス付きルールは、該当ファイルを読むまで載らない点に注意します。

dbt testで検証し、失敗行をstore_failuresで読む

ymlを書いたら、対象モデルだけにテストを走らせます。

dbt test --select "orders" --store-failures

--store-failuresを付けると、dbtはテストのクエリ結果をまずデータベースのテーブルへ保存し、そのテーブルから失敗数を数えます。落ちた行を後から検索できるので、開発中のデバッグが速くなります。

保存の挙動は次のとおりです。

  • 保存先は既定で、<profile.schema>_dbt_test__auditという名前のスキーマ
  • 失敗行は、テスト名のテーブルに入る
  • 同じテストの結果は毎回置き換わる。失敗が0件でも置き換わる
  • limitを設定していれば、保存される件数はその上限まで(設定例は次の節)

Claudeには、落ちたテストの名前を出力から拾わせ、そのテーブルを読ませます。

select * from <スキーマ>_dbt_test__audit.<テスト名> limit 20;

重複なら重複した値、参照切れなら孤立した外部キーの値が見えます。原因が「データの欠陥」なのか「テストの前提の誤り」なのかは、行を見ないと決められません。

テストごとに保存を切り替える

全テストで保存したくなければ、ymlのconfigで指定できます。store_failuresの設定はコマンドラインのフラグより優先されます。

      - name: order_id
        data_tests:
          - unique:
              config:
                store_failures: true
          - not_null:
              config:
                store_failures: false

プロジェクト全体の既定にするなら、dbt_project.ymlにdata_tests:の下で+store_failures: trueを置きます。

保存先をtable・view・ephemeralから選ぶ

store_failuresは保存するかどうかの真偽値です。保存の形まで選びたいときはstore_failures_asを使います。値は3つあります。

  • table: 失敗行をテーブルとして保存する
  • view: 失敗行をビューとして保存する
  • ephemeral: データベースに何も保存しない(既定)
      - name: order_id
        data_tests:
          - not_null:
              config:
                store_failures_as: view
          - unique:
              config:
                store_failures_as: ephemeral

store_failuresとstore_failures_asを両方設定したときは、store_failures_asが優先されます。store_failures: falseと書いても、store_failures_asが設定されていれば上書きされる点に注意します。ビューで保存した場合も、同じテストの結果は毎回置き換わります。設定できる場所はstore_failuresと同じで、単発テストのconfig()やdbt_project.ymlにも書けます。階層で値が重なるときは、より具体的な側の値が丸ごと採用されます。

保存する件数に上限をつける

大きなテーブルでは、失敗行を全件保存するとデータベースの負担が増えます。limitを設定すると、保存する失敗行が先頭からその件数までになります。

      - name: status
        data_tests:
          - accepted_values:
              arguments:
                values: ["placed", "shipped", "completed", "returned"]
              config:
                limit: 1000

プロジェクト全体の既定にするなら、dbt_project.ymlのdata_tests:の下に+limit: 1000を置きます。公式ドキュメントは、大きなデータセットで失敗行を保存するときにこの設定を使うよう勧めています。

スキーマ作成の権限で落ちたとき

permission denied for schema ..._dbt_test__auditのようなエラーは、dbt公式の説明では、開発スキーマの所有者でも新しいスキーマを作る権限がないことが原因とされます。権限のあるロールでスキーマを作る必要があるので、Claudeに権限を広げさせず、人が対処します。

落ちたテストを、緩めずに直す

既存のデータでテストが落ちるのは珍しくありません。重複や孤立した外部キーが実際にあり、テストがそれを見つけただけの場合です。

dbtには、失敗を許容する設定もあります。

  • severity: warn: 失敗してもエラーにせず警告にする
  • error_if / warn_if: 失敗件数のしきい値でエラーか警告かを決める
  • where: テストの対象行を絞る
          - unique:
              config:
                severity: error
                error_if: ">1000"
                warn_if: ">10"

これらは、上流の欠陥を認めたうえで、段階的に直す運用で使うものです。Claudeが成功させるためだけに使うと、検証の意味が消えます。CLAUDE.mdで「緩める前に失敗行を報告する」と決めておけば、人が判断する場面が残ります。

依頼文と、完了の条件

ルールを置いたら、依頼は短くて足ります。ただし終わりの条件は、依頼の側に書いておくと確実です。

models/marts/orders.yml に、主キーと外部キーのデータテストを足して。
dbt test --select "orders" --store-failures を実行し、
全テストが PASS になるか、失敗行の内容を報告するまで続けて。

「PASSになるまで」だけだと、緩める方向に傾きます。「失敗行の内容を報告する」という出口を並べておくのは、そのためです。dbtの出力は、テストごとの結果の行と、末尾のDone. PASS=… WARN=… ERROR=… SKIP=… TOTAL=…というサマリで終わります。テストごとの行にFAILが無く、サマリがERROR=0であることを、Claudeに最後に読み上げさせます。警告はWARNに別に数えられるので、WARNが0でない場合は、その中身も報告させます。

必ず走らせたいなら、hookにする

CLAUDE.mdは文脈であり、守られる保証はありません。メモリのドキュメントも、「コミット前に必ず」のように決まった時点で実行したい処理は、hookに書くよう案内しています。hookはライフサイクルの決まった時点で、Claudeの判断と関係なくシェルコマンドを実行します。ymlの編集後にdbt testを回す仕組みを確実にしたければ、ルールでなくhookの出番です。lintをhookに載せる具体例は、sqlfluffの記事にあります。

4つ目のaccepted_valuesと、失敗行に文脈を足す

組み込みの4つ目はaccepted_valuesです。列の値が、渡したリストのどれかであることを検証します。

      - name: status
        data_tests:
          - accepted_values:
              arguments:
                values: ['placed', 'shipped', 'completed', 'returned']

ステータス列のように、取りうる値が閉じている列に向きます。

組み込みテストは、失敗した値だけを返すことが多く、どの行が原因かが分かりにくい場合があります。そのときは、必要な列を選ぶ単発テストをtests/に書きます。

select
    order_id,
    status,
    customer_id
from {{ ref('orders') }}
where status not in ('placed', 'shipped', 'completed', 'returned')

store_failuresを有効にすれば、選んだ列がそのまま保存されます。Claudeが調査するとき、order_idが手元にあれば、元のテーブルと突き合わせて原因を探せます。

本数が増えたときの実行時間

テストが増えると、クエリの本数が効いてきます。dbt v2には、同じモデルに付いたuniqueとnot_nullを、テスト種別ごとに1つのクエリへまとめるバッチ実行があります。各テストの合否は、個別に報告されます。ウェアハウスの課金が気になる規模で、候補になる設定です。

バッチの対象は、次の条件をすべて満たすテストです。

  • uniqueかnot_nullである(accepted_valuesとrelationshipsは対象外)
  • 同じモデルに付いている
  • 既定の設定のまま使っている
  • 同じ種別のテストが、そのグループに2つ以上ある

severity、error_if、warn_if、fail_calc、limit、where、store_failures、store_failures_asのどれかを設定したテストは対象から外れ、単独で実行されます。前の節で紹介した設定を足すほど、バッチの恩恵は減ります。

有効にする方法は3つあり、上から順に優先されます。

  1. コマンドラインの--batch-testsフラグ
  2. 環境変数DBT_ENGINE_BATCH_TESTS
  3. ルートプロジェクトのdbt_project.ymlのflagsブロック
dbt build --batch-tests
flags:
  batch_tests: true

バッチはdbt buildとdbt testの両方に効きます。v2より前のバージョンでは使えません。

つまずきやすい点

  • 単体テスト(unit test)とデータテストは別物。データテストだけを走らせるにはdbt test --select "test_type:data"を使う
  • uniqueとnot_nullは引数がない。accepted_valuesとrelationshipsだけがargumentsを必要とする
  • store_failuresで保存した結果には、テストのselectが返した列しか入らない。組み込みテストは失敗した値だけを返すことが多く、文脈が足りなければ、追加の列を選ぶ単発テストを書く
  • 警告は、--warn-errorで全体をエラーに格上げできる

最後の点は、CIでClaudeの変更を検証するときに役立ちます。

まとめ

データテストの追加は、書く、走らせる、失敗行を読む、直す、の4拍で閉じます。Claudeに任せるなら、paths付きのルールで方針を渡し、「緩めて通さない」を明文化するのが要です。モデルを実行して結果を会話から確かめたいなら、dbt MCPサーバーの記事も参考になります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.getdbt.com/docs/build/data-tests
  2. docs.getdbt.com/reference/resource-configs/store_failures
  3. docs.getdbt.com/reference/resource-configs/severity
  4. code.claude.com/docs/en/memory
この記事を共有:XはてブLinkedIn