Dataform のアサーションを理解する
はじめに
こんにちは、クラウドエース株式会社 第一開発部所属の工藤です。
本記事では、Dataform のアサーションについてまとめました。
この内容を詳しく書いている記事はあまりない印象なので、これから Dataform を使ってみようと考えている方はぜひご覧ください。
Dataform とは
Dataform は、BigQuery Studio に含まれるプロダクトであり、BigQuery に格納しているデータの加工処理やアサーション等のワークフローを作成できます。また、ワークフローの作成だけでなく、アサーションによるテストやソースコードのバージョン管理などもできます。
Dataform でワークフローを作成するときは SQLX(SQL の拡張機能) か JavaScript を使用します。この記事では SQLX をメインに使うため JavaScript を使ったワークフローの作成方法は、以下のドキュメントをご覧ください。
ここで SQLX を使い作成するファイル(以下、SQLX ファイル)について、簡単に説明します。
例えば、test というカラムを持ち、「SELECT 1 AS test」という処理を内包するビューを作成する場合、以下のような SQLX ファイルを作成します。
-- config ブロック
config {
type: "view",
columns: {
test: "A description for the test column",
}
}
-- 処理内容
SELECT 1 as test
SQLX ファイルは、そのファイル内の処理で作成するテーブルの列名などのメタデータやアサーションと呼ばれるデータ品質テストの定義などを設定する config ブロックと、SQL で処理内容を記述する部分の 2 段構成になります(config ブロックは省略可能です)。
Dataform を初めて使う時は、以下のドキュメントを確認しながら SQLX ファイルを作成するとよさそうです。
なお、Dataform は無料で利用することができますが、BigQuery 等の関連するプロダクトの利用には料金が発生します。そのため、利用する時は、料金に関するドキュメントを確認することをおすすめします。
Dataform のアサーション
Dataform アサーションは、実装したワークフローをテストできるものになります。Dataform のアサーションには以下の 2 種類あり、SQLX ファイルでの定義方法がそれぞれ異なります。
- 組み込みアサーション
- 手動アサーション
以降、それぞれのアサーションについて説明します。
組み込みアサーション
組み込みアサーションは、テスト内容が Dataform 側で事前に準備されているものであり、NULL 値の有無や値の重複などを検査できます。組み込みアサーションで検査できる内容は以下の通りです。
- 対象のカラムに NULL 値が含まれていないか(nonNull アサーション)
- 対象のカラムに重複している値が含まれていないか(uniqueKey アサーション、uniqueKeys アサーション)
- 特定の条件に合致しているデータがないか(rowConditions アサーション)
組み込みアサーションは、SQLX ファイルの config ブロック内の assertions パラメータを使って定義します。以下は、user_id カラムと customer_id カラムに NULL 値が含まれていないか検査する時の config ブロックの入力例になります。
config {
type: "table",
assertions: {
nonNull: ["user_id", "customer_id"]
}
}
SELECT ...
FROM ...
uniqueKey アサーション等の例はドキュメントに記載されていますので、気になる方はご覧ください。
組み込みアサーションの検査タイミングは、SQLX ファイル内に記述された SQL が実行された後になります。そのため、アサーションが失敗した場合にファイル内の SQL を実行したくないときは、後述の手動アサーションを使用する必要があります。
また、検査できる対象は SQLX ファイルに記述された SQL の実行結果に対してのみになります。そのため、SQL の入力等で使う BigQuery テーブルやビューなどに対してテストを行いたいときも手動アサーションを利用する必要があります。
手動アサーション
手動アサーションは、テスト内容をユーザーが独自に定義できるものであり、組み込みアサーションで検査できない内容も対応できます。手動アサーションでは、テスト用の SQL を含むアサーション専用の SQLX ファイルを用意します。このときの SQL の実行結果が 0 件であればアサーションは成功、1 件以上あれば失敗とみなされます。
例えば、sometable.sqlx で定義されたオブジェクトに格納されたデータと、その処理で期待する結果を格納した verification_table テーブルのデータに差異がないかを検査する場合、使用する SQLX ファイルは以下のようになります。
config {
type: "assertion"
}
SELECT
*
FROM
${ref("sometable")}
EXCEPT DISTINCT
SELECT
*
FROM (
SELECT
1 AS a,
2 AS b ) AS verification_table
手動アサーションを含む処理において、アサーションの検査タイミングは後続のアサーションの依存関係に指定する SQLX ファイル内に記述された SQL が実行される前になります。また、検査できる対象は、SQLX ファイルに記述された SQL の実行結果だけでなく、入力等に使う BigQuery テーブルやビューなども対象にできます。
以降では、上記のようなアサーションをワークフローに含める方法を説明しますが、アサーション専用のファイルのみを単体で実行することもできます。その際の手順は、以下のドキュメントに記載されていますので、気になる方はご覧ください。
アサーションをワークフローに含める方法
アサーションをワークフローに含めるには、SQLX ファイルの config ブロックにそのアサーションを依存関係として設定する必要があります。この設定により、アサーションが失敗した(テストで NG の結果が見つかった)時は、後続の処理を実行せずにワークフロー自体を終了させることができます。
例えば、アサーションである「assertion1」と「assertion2」が失敗したときに後続の処理を実行せずにワークフローを終了させたいときは、以下のような SQLX ファイルを作成します。
config {
type: "table",
dependencies: [ "assertion1", "assertion2" ]
}
SELECT
*
FROM
`project_id.dataset_id.table_id`
依存関係の設定には、dependencies パラメータを使用します。上記の例では、依存関係としてアサーションの「assertion1」と「assertion2」を設定しており、これらのアサーションが失敗したときにはワークフローが終了します。
アサーションを含むワークフローを作る際に利用できるものとして dependOnDependencyAssertions パラメータがあります。
dependOnDependencyAssertions パラメータは、処理 B が依存する処理 A の結果に対するアサーションを、処理 B の実行前に実施させることができます。
つまり、dependencies パラメータなしで処理 A に対するアサーションを、処理 B の依存関係に自動的に設定できます。
例えば、集計処理「aggregate」と依存関係にあるデータ抽出処理「extract」にいくつかアサーションがある場合、dependencies パラメータを使ってこれらのアサーションを集計処理の依存関係として設定する場合は、以下のようになります。
※ このとき、データ抽出処理のアサーションを集計処理の依存関係として設定していない場合、そのアサーションが失敗しても集計処理は実行されてしまいます。
config {
type: "table",
dependencies: [ "extract_assertion1", "extract_assertion2" ]
}
SELECT
*
FROM
${ref("extract")}
上記の例では、対象のアサーションを個別に記述していますが、dependOnDependencyAssertions パラメータを使う場合は以下の内容だけで済みます。
config {
type: "table",
dependOnDependencyAssertions: true
}
SELECT
*
FROM
${ref("extract")}
依存関係として設定したいアサーションが多数ある場合は、dependOnDependencyAssertions パラメータを使うことで設定の手間が省けてよさそうです。
ここでは、dependencies パラメータと dependOnDependencyAssertions パラメータを使用する例を紹介しましたが、以下のドキュメントには dependOnDependencyAssertions パラメータと includeDependentAssertions というパラメータを組み合わせる例もありますので、興味のある方はご覧ください。
アサーションを含むワークフローを作る
それではアサーションを含むワークフローを作ってみます。
今回作成するワークフローは以下の構成になります。
ワークフローの構成
上記ワークフローの各要素は以下のような役割になります。
要素 | 説明 |
---|---|
usa_1910_2013 テーブル | 一般公開データセットの usa_names テーブルのデータを一部加工し、格納したテーブル |
処理 1 | usa_1910_2013 テーブルの state カラムの NULL 値を除外して extract テーブルを作成する処理。また、その処理結果を検証する組み込みアサーションを含む。 |
extract テーブル | 処理 1 で加工されたデータを格納するテーブル |
処理 2 | extract テーブルの name カラムと gender カラム を集計軸に number カラムの値を集計して aggregate テーブルを作成する処理。また、その処理結果を検証する手動アサーションを含む。 |
aggregate テーブル | 処理 2 で加工されたデータを格納するテーブル |
上記の処理 1,2 で作成した SQLX ファイルは、それぞれ以下のとおりです。
処理 1
この処理では、usa_1910_2013 テーブルのデータから state カラムが NULL であるデータを除外して extract テーブルを作成します。また、組み込みアサーションの nonNull アサーションを使い、extract テーブルの state カラムに NULL 値が含まれているか検査します。
config {
type: "table",
assertions: {
nonNull: ["state"]
}
}
SELECT
*
FROM
`project_id.dataset_id.usa_1910_2013`
WHERE
state IS NOT NULL
処理 2
この処理では、処理 1 で作成された extract テーブルにおいて name,gender カラムを集計軸に number カラムの値を集計し、aggregate テーブルを作成します。また、手動アサーションを使い、処理後の結果が合っているかを検証しています。config ブロック内で処理 1 と手動アサーションを依存関係として設定しており、これらの処理およびアサーションが失敗した場合は、処理 2 の集計処理が実行されないようにしています。
以下の「definitions/aggregate.sqlx」ファイルは処理 2 の集計処理を記述しているもので、「definitions/assertion.sqlx」ファイルは処理 2 の実行結果が、期待する処理結果を格納している aggregate テーブルのデータと差異がないかを検査するアサーション専用のファイルです。
config {
type: "table",
dependOnDependencyAssertions: true,
}
SELECT
name,
gender,
SUM(number) AS total
FROM
${ref("extract")}
GROUP BY
name,
gender
config {
type: "assertion"
}
SELECT
name,
gender,
SUM(number) AS total
FROM
${ref("extract")}
GROUP BY
name,
gender
EXCEPT DISTINCT
SELECT
*
FROM
`project_id.test.aggregate`
このワークフローを「COMPILED GRAPH」タブから確認すると、ワークフロー内の各処理とアサーションの関係を確認できます(以下画像)。
上記の画像から、処理 1(extract)のアサーション(article_extract_assertion...)は処理 1 の SQL が実行された後にアサーションが実行されそうなのに対し、処理 2(aggregate)のアサーション(assertion)は処理 2 の SQL が実行される前にアサーションが実行されそうです。
このワークフローを実行して、どんな結果になるか確認してみます。
「実行を開始」から「操作を実行」を押下すると「Execute」タブが開き、タブ内の「ALL ACTIONS」を選択した上で「実行を開始」を押下するとワークフローが実行されます。
ワークフローの実行結果は「EXECUTIONS」タブから確認することができ、今回の実行が正常終了したことがわかります。
作成された extract テーブル
作成された aggregate テーブル
「EXECUTIONS」タブから確認できる実行結果は、該当の開始時刻を押下することでの詳細な内容を確認できます。
この実行結果の詳細を確認すると、ワークフロー内でどの処理がどの順番で実行されたか確認することができます。
上記の実行結果より、先ほど「COMPILED GRAPH」タブから確認した通り、処理 1(extract)のアサーション(article_extract_assertion...)は SQL が実行された後にアサーションが実行されているのに対し、処理 2(aggregate)のアサーション(assertion)は SQL が実行される前にアサーションが実行されたことがわかります。
次にアサーションが失敗したときにワークフローの実行結果がどうなるか確認してみます。
ここでは、処理 1(extract)のアサーション(article_extract_assertion...)が失敗するようにコードを修正した上でワークフローを実行してみます。
修正したコードはこちら
config {
type: "table",
assertions: {
nonNull: ["state"]
}
}
SELECT
*
FROM
`project_id.dataset_id.usa_1910_2013`
-- WHERE
-- state IS NOT NULL
実行した結果は以下画像のとおりであり、実行したワークフローは異常終了しています。
詳細な実行結果を見ると、アサーションが失敗したところで処理は終わっており、後続の処理である aggregate が実行されていないことがわかります。
おわりに
本記事では、Dataform のアサーションについてまとめました。
BigQuery にデータがある状態でワークフローを作成する場合は、自動テストの導入やコードのバージョン管理などの観点から、Dataform を利用するのがよいと思いました。
今回はアサーションについてまとめましたが、Dataform を利用する上でのベストプラクティスもあり、理解すべきことが他にもありそうなので、時間があればその辺りもまとめたいと思います。
最後までご覧いただきありがとうございました。
Discussion