Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage「dbt_expectations」を試してみた #dbt

2022.08.08

さがらです。

Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage「dbt_expectations」を試してみたので、その内容をまとめてみます。

dbt_expectationsとは

dbt_expectationsに関する情報は、下記ページにまとまっております。

このページの説明を見ると、このように書いてあります。

dbt-expectations is an extension package for dbt, inspired by the Great Expectations package for Python. The intent is to allow dbt users to deploy GE-like tests in their data warehouse directly from dbt, vs having to add another integration with their data warehouse.

記事タイトルにもありますが、Great Expectationsというデータ品質のためにテスト・ドキュメント化・プロファイリングが行えるOSSがあり、その機能をdbtから直接実行できるというdbt packageになっております。

元のOSSであるGreat Expectationsに関しては、dbtやAirflowと組み合わせた事例がすでに存在しており、日本語でもいくつか検証記事が投稿されています。こちらもぜひ御覧ください。

標準のtestやdbt_utilsのtestと何が違うの?

dbtには標準でテスト機能が備わっており、別packageのdbt_utilsにもGeneric Testがいくつか含まれています。 そのため、このdbt_expectationsは何が違うのか、気になる方も多いと思います。

ざっくりではありますが、以下に2つ違いを記しておきます。

packageに含まれるテストの数が多い

まず、dbt_expectationsに含まれるテストの数が多いです。ver0.5.8時点で、なんと60個のテストが用意されています。

ver0.8.6のdbt_utilsに含まれるテストは15個のため、その数なんと4倍です。

dbtでは自分でGeneric Testを定義することも出来ますが、packageに該当するものがあればそれを使ってしまう方が楽だと思います。

そのため、dbt_utilsにも含まれないテストを探している場合には、dbt_expectationsから探してみましょう。

統計値を参照するテスト、など特有のテストがある

dbt_expectationsには60個のテストがあると上述しましたが、テストの種類としても多くの種類のテストがあります。

以下、dbt_expectationsのドキュメント上の分類ですが、このように分けられます。(各項目に記した”●個”は、その分類の中でいくつテストがあるかを意味しています。)

  • Table shape:14個
    • 保持するカラムやレコード数など、テーブルの形を確認するテスト
  • Missing values, unique values, and types:6個
    • 対象のカラムの値について、NULL、ユニーク、型、などを確認するテスト
  • Sets and ranges:5個
    • 対象のカラムの値が、指定した範囲やリストに含まれるかを確認するテスト
  • String matching:10個
    • 対象のカラムの値が、指定した長さ・パターン・正規表現に一致するか、を確認するテスト
  • Aggregate functions:16個
    • 対象のカラムに対して、重複削除した際の値や、最大値や中央値などの統計値を用いて指定した条件をクリアするか確認するテスト
  • Multi-column:6個
    • 複数のカラム間で値を比較して、指定した条件をクリアするか確認するテスト
  • Distributional functions:3個
    • 標準偏差を取り指定した範囲内に収まっているか、を確認するテスト
    • 指定した条件に合致する日時に関するレコードがあるか、を確認するテスト

dbt標準・dbt_utils・dbt_expectations、各テストをどう使い分けるか?

前述した通り、dbtには「dbt標準のテスト」「dbt_utilsを用いたテスト」「dbt_expectationsを用いたテスト」3種類あると述べました。

それぞれどう使い分ければよいのか悩むと思うのですが、私はこちらのFLYWHEEL社の記事の”基本方針”に記載の考え方に全面的に同意です。(FLYWHEEL社ではData Orchestrationツール上で、OSSのGreat Expectationsを実行しているという違いはありますが。)

dbt は SQL3を開発しながら繰り返し実行されるようなテストを記述し、Great Expectations には本番データを対象に Data Orchestration ツール (Dagster, Airflow 等) を介して実行されるテストを記述する。

dbtで行うことができるデータのテストとしては、大きく2種類あると思います。「dbtで開発した処理のテスト」「本番稼働中のデータ基盤で異常値がないかを確認するテスト」の2種類です。

それぞれ、こういった違いがあると思います。

  • dbtで開発した処理のテスト
    • 処理が上手く開発できていないと、本来uniqueであるべきカラムがuniqueでなくなったり、処理前と処理後のテーブルでレコード数が一致しなかったり、といったことが起こりえます。
    • この処理をテストするには、dbt標準のuniqueテストや、dbt_utilsequal_rowcountテストが向いていると思います。
  • 本番稼働中のデータ基盤で異常値がないかを確認するテスト
    • 本番稼働中のデータ基盤では、ソースデータがハンド入力のため異常値が入ってきたり、区切り文字の関係でロードすべきカラムの位置がずれたり、といったことが起こりえます。
    • こういったデータの異常値を確認するには、dbt_expectationsの統計値や集計値を用いたり、カラム間の値の関係性を用いたテストが向いていると思います。

また、dbtではtagを使って、dbt test実行時にチェックするテストを切り分ける事ができます。そのため、開発時に行うdbt testと、本番稼働中のデータ基盤で行うdbt testを分けることができますので、不要なテストの実行を避けることができます。

もちろん例外的なパターンはあるかと思いますが、1つの考え方として参考になると嬉しいです。

dbt_expectationsのインストール

ここからは、実際にdbt_expectationsを試していきます!まずはインストールからやってみます。

環境

  • dbt Cloud:v1.1.57.28
  • dbt version:1.0 ※Environmentsから指定
  • dbt_expectations:0.5.8

インストール

packages.ymlに、下記のように書いて保存した上で、dbt depsコマンドを実行するとインストール出来ます。

packages:
  - package: calogica/dbt_expectations
    version: 0.5.8

ちなみに、dbt_expectationsdbt_utilsdbt_dateを使用しているので、packages.ymlにこれらのパッケージ名を書いていなくてもインストールされます。

dbt_date用のタイムゾーン指定

先程、dbt_expectationsをインストールするとdbt_dateもインストールされると書きましたが、dbt_date向けにタイムゾーンを指定する必要があります。

デフォルトではAmerica/Los_Angelesが指定されているので、ほぼ全ての方は設定を変更するべき内容です。基本的には、使用するデータウェアハウス上のタイムゾーンに併せたタイムゾーンを設定することになると思います。

タイムゾーンの指定方法としては、dbt_project.ymlの中で、dbt_date:time_zoneという変数を定義する必要があります。

日本の方は、UTC、日本時間(UTC+09:00)、のどちらかを使うことになると思いますので、以下変数の定義方法を記しておきます。

  • UTCの場合
vars:
  'dbt_date:time_zone': 'UTC'
  • 日本時間(UTC+09:00)の場合
vars:
  'dbt_date:time_zone': 'Asia/Tokyo'

「expect_column_values_to_match_like_pattern_list」を試す

続いて、実際にdbt_expectationsで使用できるテストを2つ試してみます。

1つ目として、expect_column_values_to_match_like_pattern_listを試してみます。

このテストは、SQLのLIKE句で使用するパターンをいくつかlistとして定義することで、そのlistの定義いずれかに合致するかを確認することが出来るテストです。

定義の仕方

カラムのdescriptionなどを定義するyaml上で、以下の様にtestsを定義します。

tests:
  - dbt_expectations.expect_column_values_to_match_like_pattern_list:
      like_pattern_list: ["%@%", "%&%"]
      match_on: any # (Optional. Default is 'any', which applies an 'OR' for each pattern. If 'all', it applies an 'AND' for each regex.)
      row_condition: "id is not null" # (Optional)
  • like_pattern_list:SQLのLIKE句と同じ様に、一致確認させるパターンを記します。カンマ区切りで複数のパターンを併記することもできます
  • match_on:指定したオプションの方法で、テストを行います。
    • anyの場合:like_pattern_listに記載した1パターンに合致すればクリア
    • allの場合:like_pattern_listに記載したパターン全てに合致すればクリア
  • row_condition~~ is not nullのように記述することで、対象のカラムを用いたWHERE句をテストに追記してくれます。(下図参照)

テスト用のデータ

このテスト用に、OKサンプルとNGサンプルをdbtのseed機能で定義しておきます。テストでは、「supplier_prefectures列の値の末尾が”都道府県”いずれかで終わっているか」を確認します。

  • OKサンプル:like_pattern_test_ok_example.csv
toy_id,supplier_prefectures
001,北海道
002,青森県
003,東京都
004,京都府
  • NGサンプル:like_pattern_test_ng_example.csv
toy_id,supplier_prefectures
001,北海道
002,青森県青森市
003,東京都千代田区
004,京都府

テストの定義

下記のように、OKサンプル、NGサンプル、それぞれでtestsを定義しておきます。

tagsは自分が検証時に区分けしやすいように付与しただけです。

seeds:
  - name: like_pattern_test_ok_example
    columns:
      - name: supplier_prefectures
        tests:
          - dbt_expectations.expect_column_values_to_match_like_pattern_list:
              like_pattern_list: ["%都", "%道", "%府", "%県"]
              match_on: any
              row_condition: "supplier_prefectures is not null"
              tags: ['like_pattern_ok']
  - name: like_pattern_test_ng_example
    columns:
      - name: supplier_prefectures
        tests:
          - dbt_expectations.expect_column_values_to_match_like_pattern_list:
              like_pattern_list: ["%都", "%道", "%府", "%県"]
              match_on: any
              row_condition: "supplier_prefectures is not null"
              tags: ['like_pattern_ng']

テストの実行

  • OKの場合

dbt test --select tag:like_pattern_okというコマンドを実行します。

「対象のテストがPASSしたよ」と表示されていますね。

  • NGの場合

dbt test --select tag:like_pattern_ngというコマンドを実行します。

テストに失敗すると、下図のようにエラーとなります。少し小さいのですが、赤枠内のFAIL 2といった表記で、エラーを起こしている行数も返してくれます。

「expect_column_pair_values_A_to_be_greater_than_B」を試す

2つ目として、expect_column_pair_values_A_to_be_greater_than_Bを試してみます。

このテストは、「あるモデル上でA列の値の方がB列より常に大きい」ということを確認するテストです。

定義の仕方

モデルのdescriptionなどを定義するyaml上で、以下の様にtestsを定義します。

tests:
  - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B:
      column_A: col_numeric_a
      column_B: col_numeric_a
      or_equal: True
      row_condition: "id is not null" # (Optional)
  • column_Acolumn_Bとの比較対象のカラム名を入れます。※値が大きい方を選択
  • column_Bcolumn_Aとの比較対象のカラム名を入れます。※値が小さい方を選択
  • or_equalTrueの場合は「>=」、Falseの場合は「>」で比較します。
  • row_condition~~ is not nullのように記述することで、対象のカラムを用いたWHERE句をテストに追記してくれます。(下図参照)

テスト用のデータ

このテスト用に、OKサンプルとNGサンプルをdbtのseed機能で定義しておきます。テストでは、「sale_price列の値が、purchase_price列の値よりも常に大きいか」を確認します。

  • OKサンプル:a_greater_than_b_test_ok_example.csv
toy_id,sale_price,purchase_price
001,200,100
002,300,200
  • NGサンプル:a_greater_than_b_test_ng_example.csv
toy_id,sale_price,purchase_price
001,200,100
002,200,300

テストの定義

下記のように、OKサンプル、NGサンプル、それぞれでtestsを定義しておきます。

tagsは自分が検証時に区分けしやすいように付与しただけです。

seeds:
  - name: a_greater_than_b_test_ok_example
    tests:
      - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B:
          column_A: sale_price
          column_B: purchase_price
          or_equal: True
          tags: ['greater_than_ok']
  - name: a_greater_than_b_test_ng_example
    tests:
      - dbt_expectations.expect_column_pair_values_A_to_be_greater_than_B:
          column_A: sale_price
          column_B: purchase_price
          or_equal: True
          tags: ['greater_than_ng']

テストの実行

  • OKの場合

dbt test --select tag:greater_than_okというコマンドを実行します。

「対象のテストがPASSしたよ」と表示されていますね。

  • NGの場合

dbt test --select tag:greater_than_ngというコマンドを実行します。

下図のようにエラーメッセージが返ってきました。FAIL 1となっているので、「1行だけテストがFAILだったよ」ということを表しています。

生成されるドキュメントの確認

dbt_expectationsの元となっているGreat Expectationsはテストの定義に沿って、自動でその内容をドキュメント化する機能があります。(下図のようなイメージ、Great Expectations公式サイトからの抜粋です。)

一度dbt docs generateを実行して、ドキュメントがどうなっているかを確認してみます。

今回はSeedを対象としたので、対象のSeedをドキュメント上で確認してみます。

Referenced Byという項目に、Testsが記載されていますね。

しかし、このTestsからリンクした先でもテスト内容のドキュメント化はされていませんでした。このドキュメント化も対応してくれると本家のGreat Expectationsにより近づくので、今後のアップデートに期待しています!

最後に

Great Expectationsを用いたデータ品質テストがdbt上で行えるpackage、dbt_expectationsを試してみました。

記事中でも述べた通り、テストの種類が豊富で、かつ実際のデータの統計値や集計値などを用いて、予想される(expectされる)値を持つか、テストを行えることがdbt_expectationsのテストの強みだと思います。

データ基盤上に異常値があるかどうかをdbtでテストしたい方には非常にオススメのpackageですので、ぜひ一度使ってみてください!