dbt-utils の generate_surrogate_key マクロについて調べてみました
こんにちは。株式会社 Belong にて Data Platform チームに所属する Shuhei です。
弊社は dbt-core を用いて BigQuery にてデータ分析基盤を構築しています。
dbt そのものだけでも十分強力なツールですが、 dbt-utils という便利なパッケージも有用です。dbt-utils を使えばちょっと実装が面倒な処理もサクッと書くことができます!
先日 dbt-utils の generate_surrogate_key
マクロについて調べる機会があったため、その機能や実装についてご紹介します。
generate_surrogate_key とは?
generate_surrogate_key
マクロは、その名の通りサロゲートキーを作成してくれるマクロです。
サロゲートキーとは、値にビジネス上の意味を持たない識別子であり、テーブルの行を一意に識別するためのキー (主キー) として利用されます。
具体的な使い方
generate_surrogate_key
は、行を一意に特定する複合ユニーク列のリストを引数に渡してあげることで、手軽にサロゲートキーを作成することができます。
{{ dbt_utils.surrogate_key(['field_a', 'field_b'[,...]]) }}
利用シーンとしては、例えば dbt の snapshot
や incremental model
を構築するときなどに、サロゲートキーを活用することがありますね。
個人的に気になるところ
個人的に、generate_surrogate_key
を使用するうえで、1 点気になることがありました。それは、引数に NULLABLE
な列を含む場合でも、うまいこと動作してくれるのか? ということです。
例えば、上記に示した引数のうち field_a
が NULL
であったとき、generate_surrogate_key
はどのような挙動を取るのかが気になりました。NULL
を含む列はしばしばエラーの元であるため、マクロ側でうまいことケアしてくれるとうれしいのですが...
generate_surrogate_key の実装を見てみよう
それでは、generate_surrogate_key
の実装を見てみましょう。実装は dbt-utils の Github リポジトリ から確認することができます。
少しずつ区切りながら見ていきましょう。
adapter.dispatch
{%- macro generate_surrogate_key(field_list) -%}
{{ return(adapter.dispatch('generate_surrogate_key', 'dbt_utils')(field_list)) }}
{% endmacro %}
{%- macro default__generate_surrogate_key(field_list) -%}
...
adapter.dispatch
により、使用している adapter 専用の関数があるかどうか探し、ある場合はその関数を用い、そうでなければ default の関数を用います。(adapter は dbt が接続するデータベース、データウェアハウスのことと読み替えるとわかりやすいでしょう)
※ dispatch には「同種の複数の対象から 1 つを選び取る」といった意味があるようです。
例えば今回のケースでは下記のように探索されると思われます。
adapter | macro |
---|---|
Postgres | postgres__generate_surrogate_key |
Redshift | redshift__generate_surrogate_key |
Snowflake | snowflake__generate_surrogate_key |
BigQuery | bigquery__generate_surrogate_key |
OtherAdapter | otheradapter__generate_surrogate_key |
default | default__generate_surrogate_key |
Belong のデータ分析基盤は BigQuery にて展開されているため、bigquery__generate_surrogate_key
を探しますが、今回は存在しませんので、default__generate_surrogate_key
が実行されます。
default_null_value
{%- macro default__generate_surrogate_key(field_list) -%}
{%- if var('surrogate_key_treat_nulls_as_empty_strings', False) -%}
{%- set default_null_value = "" -%}
{%- else -%}
{%- set default_null_value = '_dbt_utils_surrogate_key_null_' -%}
{%- endif -%}
default__generate_surrogate_key
ではまず、NULL
を置換するための文字列が決定され、デフォルトでは _dbt_utils_surrogate_key_null_
となります。
dbt project の変数 surrogate_key_treat_nulls_as_empty_strings
を False
に設定することで NULL
置換文字を空文字とすることもできますが、これは generate_surrogate_key
マクロの前身である surrogate_key
マクロが NULL
を空文字で扱っていたことに由来するそうです。
NULL
と空文字は区別したいケースが多いかと思いますので、特段理由がなければ surrogate_key_treat_nulls_as_empty_strings
は設定しなくてよいでしょう。
surrogate_key_treat_nulls_as_empty_strings | default_null_value |
---|---|
False | ""(空文字) |
False ではない | dbt_utils_surrogate_key_null(文字列) |
coalesce
{%- for field in field_list -%}
{%- do fields.append(
"coalesce(cast(" ~ field ~ " as " ~ dbt.type_string() ~ "), '" ~ default_null_value ~"')"
) -%}
{%- if not loop.last %}
{%- do fields.append("'-'") -%}
{%- endif -%}
{%- endfor -%}
引数で渡される field_list
の要素 1 つ 1 つを文字列型に CAST してその値を fields
に append しています。もしその値が NULL
であれば coalesce
関数により、前述の default_null_value
変数の値となり、append されます。
dbt.type_string()
は使用するデータベースの文字列型を返す macro です。例えば、BigQuery であれば STRING
を、PostgreSQL であれば TEXT
を返します。
hash
{{ dbt.hash(dbt.concat(fields)) }}
最後に fields
の値を連結させ、その値をハッシュ化して処理が完了します。
まとめ
今回は dbt-utils の generate_surrogate_key
マクロについて、その機能と実装について紹介しました。
NULLABLE
な列を含む列であっても安全にサロゲートキーを作成してくれるので、利用する側としてとても便利なマクロだと思いました。NULL
のケアをマクロ側で行ってくれるので、こちら側が coalesce
しなくて済むのはスマートですね!
NULL
を気にしてこのような書き方はする必要はなく、
▲ dbt_utils.surrogate_key([coalesce(XXX, YYY), ZZZ])
このように直接フィールドを渡せばよいので、記述もスッキリしますね!
◎ dbt_utils.surrogate_key([XXX, ZZZ])
Belong ではこのように dbt を用いた DWH の構築を積極的に実施しています!dbt がお好きな方、少しでも気になるよというははぜひ Belong Engineering Careers をご覧ください!