
【Redshift】dbtのgeneric testを利用してdbtでモデル化されていないテーブルとの完全一致テストを実施する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
データアナリティクス事業本部のueharaです。
今回は、Redshiftにおいて、dbtのgeneric testを利用してdbtでモデル化されていないテーブル(dbtの外で作成されたテーブル)との完全一致テストを実施するしてみたいと思います。
はじめに
dbtでモデル化したテーブルについて、dbtでモデル化されていないテーブルとの完全一致をテストしたいケースがあります。
例えば、既存のデータマート構築処理をdbtにリプレースする際に、既存のテーブルとdbtによって作成したテーブル(モデル)が完全一致しているか確認したい場合が挙げられます。
今回、完全一致とは 重複したレコードも許容しない ということになります。
すなわち、以下のテーブルAとテーブルBは「完全一致していない」ものとして扱いたいケースになります。

準備
dbtのpackageとして、dbt_utilsを使用します。
packages.ymlに以下を追記して下さい。
packages:
  - package: dbt-labs/dbt_utils
    version: 1.1.1
完全一致テストの実施
SQLファイルの作成
generic testはSQLファイルで定義されます。dbtでは、以下の2つの場所にgeneric testのファイルを配置することができます。
tests/generic/配下macros/配下
generic testについてより詳しくしりたい方はWriting custom generic data testsをご確認下さい。
今回は、tests/generic/配下に、以下の test_equality.sql を用意します。
{% test test_equality(model, schema_name, table_name) %}
{% set compare_table_relation = api.Relation.create(
    schema=schema_name,
    identifier=table_name
) %}
with model_with_cnt as (
  select
    *,
    count(*) as cnt
  from {{ model }}
  group by {{ dbt_utils.star(from=model) }}
),
compare_table_with_cnt as (
  select
    *,
    count(*) as cnt
  from {{ compare_table_relation }}
  group by {{ dbt_utils.star(from=compare_table_relation) }}
),
model_except_compare_table as (
  select * from model_with_cnt
  except
  select * from compare_table_with_cnt
),
compare_table_except_model as (
  select * from compare_table_with_cnt
  except
  select * from model_with_cnt
),
unioned as (
  select * from model_except_compare_table
  union all
  select * from compare_table_except_model
)
select * from unioned
{% endtest %}
引数の model はgeneric testにおけるデフォルトの引数で、こちらが完全一致の比較対象となるdbtのモデルとなります。
schema_name と table_name はdbtでモデル化されていないテーブルの所在を指すようにします。
dbtでモデル化されていないテーブルは別途 source として定義することもできますが、今回はテスト以外で利用しない想定のため api.Relation.create によりRelationを作成しています。
Relationオブジェクトについてより詳しく知りたい方はdbt Classesをご確認下さい。
8行目〜22行目の処理は、行が完全に一致するレコードをカウントして、新しい列に追加する処理となります。
すなわち、冒頭で図示したテーブルAとテーブルBを以下のように変換する形です。

GROUP BY句で利用している dbt_utils.star() 関数は、fromにRelationオブジェクトを渡すと、そのテーブルのカラムを列挙してくれる関数になります。
これにより、動的に「全レコードでGROUP BY」という処理を実現することができます。
24行目〜32行目は EXCEPT クエリによりそれぞれのテーブルに含まれない行を抽出し、36行目〜40行目でデータを連結しています。
これにより、最後の unioned テーブルが1件でもレコードが抽出されれば両テーブルは完全一致しておらず、0件であれば完全一致していると言えます。
ymlファイルでのテストの指定方法
モデルのスキーマのymlファイルについて、以下のようにテストを設定することができます。
version: 2
models:
  - name: table_a
    description: 'xxxxxに関するテーブル'
    columns:
      - name: col1
        description: 'カラムxxx'
      - name: col2
        description: 'カラムyyy'
      - name: col3
        description: 'カラムzzz'
    tests:
      - test_equality:
          schema_name: public
          table_name: table_b
上記はdbtのモデル table_a について、dbtの外で作成された public.table_b との完全一致をテストする形になります。
テストの実行
テストは dbt test コマンドで実行可能です。完全一致していればテストは PASS されます。
$ dbt test --select table_a ... 07:52:12 Completed successfully 07:52:12 07:52:12 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
逆に一致しなかった場合は FAIL となり、SELECTされたデータの件数が表示されます。
$ dbt test --select table_a ... 08:46:20 Got 2 result, configured to fail if != 0 ... 08:46:20 Done. PASS=0 WARN=0 ERROR=1 SKIP=0 TOTAL=1
generic testにおいてはどこが一致しなかったかまでは表示されませんので、テストが失敗した際は別途同様のクエリを叩いてデータの中身を確認する必要があります。
最後に
今回は、dbtのgeneric testを利用してdbtでモデル化されていないテーブル(dbtの外で作成されたテーブル)との完全一致テストを実施してみました。
参考になりましたら幸いです。







