dbt Fundamentalsの「Tests」のセクションについて理解した内容を言葉にしてみた。
Models,Sourcesの各セクションと比較して1行が含む意味が増えている印象がある。
基礎的な内容をインプットするには結構なスローペースだとは思うが抑えていこうと思う。
この記事は前回の記事(以下)の続き。
これだけで「データ品質」をどうこうするには足りないと思う。
データ品質に関わる何かについてはどこかで入門して記事化しようと思います。
最後の2節は「それってあなたの感想ですよね」と言って読み飛ばしてください。
【目次】
- ∨Generic Tests
- ∨Singular Tests
- ∨Source Tests
- ∨testを実行するコマンド
- ∨dbt buildとDAGの仕組み
- ∨(感想1)ソフトウエア開発と品質保証
- ∨(感想2)品質保証の品質は誰が保証するのか
- ∨まとめ
Generic Tests
Generic Testsは、Assertionの定義をyaml形式で記述するタイプのテスト。
dbtには4つのGeneric Testsがビルトインで定義されていて、特別なインストール不要で利用できる。
Generic Testsに関係する公式のリファレンスページは以下。
About tests property
Assertの定義を書いたyaml形式のファイルをmode-pathに配置し、dbt testコマンドで実行する。
not_null
指定したテーブル・カラムの全ての値がNULLでないことをAssertする。
1 2 3 4 5 6 7 8 |
version: 2 models: - name: orders columns: - name: order_id tests: - not_null |
unique
指定したテーブル・カラムがユニークであることをAssertする。
OPTIONALな属性であるconfigにより、Assertの範囲を追加することができる。
1 2 3 4 5 6 7 8 9 10 |
ersion: 2 models: - name: orders columns: - name: order_id tests: - unique: config: where: "order_id > 21" |
accepted_values
指定したテーブル・カラムにvaluesで指定した値のみが存在することをAssertする。
OPTIONALな属性であるquoteにより、values内に整数値、ブール値などを配置できる。
まぁ”false”とfalseは違うし、”100″と100は違うので、そういう配慮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version: 2 models: - name: orders columns: - name: status tests: - accepted_values: values: ['placed', 'shipped', 'completed', 'returned'] - name: status_id tests: - accepted_values: values: [1, 2, 3, 4] quote: false |
relationships
参照整合性制約、つまり外部キーが参照するレコードが相手先テーブルに存在することをAssertする。
公式には、childテーブルとparentテーブルという表現が出てくる。
This test validates that all of the records in a child table have a corresponding record in a parent table. This property is referred to as “referential integrity”.
これは、yaml上の上位階層と下位階層の関係を言っていて、以下であればordersのcustomer_idと
customersのidの間で参照整合性が成立するかをAssertする。
toの先として、ref関数、またはsource関数を指定する。
1 2 3 4 5 6 7 8 9 10 |
version: 2 models: - name: orders columns: - name: customer_id tests: - relationships: to: ref('customers') field: id |
dbt test
dbt testコマンドによりテストを開始する。
Test an expression
上記までは特定のカラムに対するAssertionを定義するものだった。
もし複数カラムを使用したAssertionを定義しようとすると、columns:以下に書けない。
この場合、models:直下にcolumns:ではなくtests:を配置することができる。
以下は(ちょっと不思議)、ordersテーブルにある「country_code」と「order_id」をハイフンで繋いだ
文字列をカラム名として持つカラムがユニークであることをAssertする。
例はアレだけど、式の評価(expression)結果をAssert定義に使用できる、といったところ。
1 2 3 4 5 6 7 |
version: 2 models: - name: orders tests: - unique: column_name: "country_code || '-' || order_id" |
Use custom generic test
自力で実装したテストをビルトインテストの代わりに使用することができる。
この記事では詳細は省略。
1 2 3 4 5 6 7 8 |
version: 2 models: - name: orders columns: - name: order_id tests: - primary_key # name of my custom generic test |
Singular Tests
Assertをyml形式で記述するスタイルの他に、単体のSELECT文形式で定義するスタイルもある。
SELECT文を1個書いたファイルをtest-pathに配置する。
SELECT文が1件以上のレコードを返す場合、そのテストは失敗、という扱いになる。
dbt Fundamentalsの動画では1例だけサラッと説明されている。
1 2 3 4 5 6 7 8 |
-- Refunds have a negative amount, so the total amount should always be >= 0. -- Therefore return records where this isn't true to make the test fail. select order_id, sum(amount) as total_amount from {{ ref('stg_payments') }} group by 1 having not(total_amount >= 0) |
Source Tests
ModelだけでなくSourceに対してもテストを書ける。(テストを書ける対象は他にもある)
dbt Fundamentalsの動画に出てくる例では、前節で説明されていたSourceにテストを追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
version: 2 sources: - name: jaffle_shop database: raw schema: jaffle_shop tables: - name: customers columns: - name: id tests: - unique - not_null - name: orders columns: - name: id tests: - unique - not_null loaded_at_field: _etl_loaded_at freshness: warn_after: {count: 12, period: hour} error_after: {count: 24, period: hour} |
testを実行するコマンド
Generic tests、Singular testsを実行するコマンドは以下の通り。
dbt Fundamentalsには一部しか載っていない。(★を付けた)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# ★ run all generic and singular tests in your project. $ dbt test # ★ run only tests defined singularly $ dbt test --select test_type:singular # ★ run only tests defined generically $ dbt test --select test_type:generic # ★ run source test $ dbt test --select source:test_name # run tests for one_specific_model $ dbt test --select one_specific_model # run tests for all models in package $ dbt test --select some_package.* # run singular tests limited to one_specific_model $ dbt test --select one_specific_model,test_type:singular # run generic tests limited to one_specific_model $ dbt test --select one_specific_model,test_type:generic |
dbt buildとDAGの仕組み
先に作ったモデルを使って後のモデルを作る、という仕組みを考えようとすると、
先の実行が上手くいかなかった場合に、後の処理を止めたいという欲求は必ず発生する。
dbt buildコマンドにより、上流側でrunとtestを行い、その結果に基づいて
下流側のrunとtestを実行する、という仕組みを作ることができる。
1 2 |
# hogeモデルとその上流のDAGを構築して実行 $ dbt build +hoge |
これ自力で作ったら大変だし、本当にその通り動くのか確認するのも大変。
たぶんこれがdbtを使う大きなメリットなんだろうな。
dbt FnudamentalsのTestsセクション内のThe dbt Build commandというパートで、
実例を使ってDAGが最後まで通るパターン、途中でテストがNGになって止まるパターンの両方が
すごくライトな感じで説明されていてイメージ掴むにはとても良かった。
公式により詳細な記述があるので追ってみる。
About dbt build command
(感想1)ソフトウエア開発と品質保証
昔、とあるメーカーで医療機器・システムの設計・実装・保守を長いことやっていたことがある。
医療機器が医療機器であるためには、様々な法令に基づいた品質保証を行う必要がある。
おそらく設計・開発よりも品質保証と保守のウェイトが圧倒的に高かっただろう記憶がある。
患者、医療従事者ほか、品質が保証されていないとヤバい人たちのために品質を保証する。
それはほぼ自分達のためであり、こんな具合にエコシステム全体で品質保証を言ってた。
言いたいのは、綺麗事でテストしていたのではなく明日の飯のためにテストしていた。
dbt Fundamentalsでは「データは信頼に値する品質である必要がある」みたいに書かれている。
データ分析はデータに基づいてアナリストが分析した結果で意思決定する人がいる。
嘘八百並んだ虚構のパイプラインを構築してデータ分析とか、まぁないよねと思う。
というか綺麗事抜きに持続的にお仕事もらえないよね、と思う。
(感想2)品質保証の品質は誰が保証するのか
dbt FundamentalsにはAnalytics Engineering版の自動テストをやりましょう、
という世界観が書かれている。
個人的には、品質保証がコアパートであるプロダクトとOSSの相性はあまり良くないと感じている。
それは、品質保証の品質を保証するコストが高すぎるから、なのだろうなと勝手に思っている。
dbtでは、Analytics EngineerはData EngineerとAnalystの間に位置付けられ、
もしこれがビジネスに寄ったエンジニアなのであれば、どこまで寄ったとしても埋まらない溝はある。
分析要件を熟知したアナリストがdbtを使うのであればワンチャン行ける気がするが大変だろう。
ymlで書いたTestが保証する範囲をエンジニアとアナリストが連携できるのか。
Analytics Engineerって、本当に実在できるのだろうか。むずくね?、と思う。
結果が致命的になるドメインとか、あまりにほ品質保証のコストが高いドメインとか、
おそらく、プロの品質保証家がいるはずだろうから、彼らの指示に従うべきで、
そうではない「普通の」品質保証レベルを効率的に達成するためのCI/CDだと思う。
まとめ
dbt Fundamentalsの「Tests」セクションを聴き、公式の情報で補足しながら内容をまとめた。
4つのビルトインテストの仕様を理解した。dbtにおけるDAGの構築の仕組みについて公式を読み、
理解した内容をまとめてみた。
大きなトピックである「データ品質」について、どこかで入門したいと思う。