dbt-utils の generate_surrogate_key マクロについて調べてみました

2024-12-06

こんにちは。株式会社 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 の snapshotincremental model を構築するときなどに、サロゲートキーを活用することがありますね。

個人的に気になるところ

個人的に、generate_surrogate_key を使用するうえで、1 点気になることがありました。それは、引数に NULLABLE な列を含む場合でも、うまいこと動作してくれるのか? ということです。

例えば、上記に示した引数のうち field_aNULL であったとき、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 つを選び取る」といった意味があるようです。

例えば今回のケースでは下記のように探索されると思われます。

adaptermacro
Postgrespostgres__generate_surrogate_key
Redshiftredshift__generate_surrogate_key
Snowflakesnowflake__generate_surrogate_key
BigQuerybigquery__generate_surrogate_key
OtherAdapterotheradapter__generate_surrogate_key
defaultdefault__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_stringsFalse に設定することで NULL 置換文字を空文字とすることもできますが、これは generate_surrogate_key マクロの前身である surrogate_key マクロが NULL を空文字で扱っていたことに由来するそうです。

NULL と空文字は区別したいケースが多いかと思いますので、特段理由がなければ surrogate_key_treat_nulls_as_empty_strings は設定しなくてよいでしょう。

surrogate_key_treat_nulls_as_empty_stringsdefault_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 をご覧ください!

参考

generate_surrogate_key

About dispatch config

type_string