dbt macro tips advent calendar 2022 day 25 - その他のTips
便利なデータ変換ツールである dbt の中のmacroに関するtipsを書いていく dbt macro tips Advent Calendar 2022 25日目です。 最終日です。
最終日はすごくニッチ過ぎて、めったに使わないdbtで使えるJinjaの機能についてです。
raw block
これは ヒアドキュメントのようなもので、rawで囲った部分はJinja templateとして解釈されません。
例えば以下のようなモデルを作ってみます。
{{
config(
materialized='table',
)
}}
{% raw %}
select '{{ "hogehoge" }}' as raw_text
{% endraw %}
$ dbt build --select raw
09:03:45 Running with dbt=1.3.1
09:03:45 Unable to do partial parsing because config vars, config profile, or config target have changed
09:03:45 Unable to do partial parsing because profile has changed
09:03:46 Found 3 models, 4 tests, 0 snapshots, 0 analyses, 313 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
09:03:46
09:03:47 Concurrency: 4 threads (target='dev')
09:03:47
09:03:47 1 of 1 START sql table model dev_dev.raw ....................................... [RUN]
09:03:47 1 of 1 OK created sql table model dev_dev.raw .................................. [SELECT 1 in 0.17s]
09:03:47
09:03:47 Finished running 1 table model in 0 hours 0 minutes and 0.60 seconds (0.60s).
09:03:47
09:03:47 Completed successfully
09:03:47
09:03:47 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
$ psql -h 127.0.0.1 -U postgres -p 5432 -d postgres
Password for user postgres:
psql (14.2, server 14.5 (Debian 14.5-2.pgdg110+2))
Type "help" for help.
postgres=# select * from dev_dev.raw;
raw_text
------------------
{{ "hogehoge" }}
(1 row)
postgres=#
テンプレートとして解釈されずにそのまま実行されてますね。
場合によっては、一部だけテンプレートの処理を無効化したいときに有効です。
まぁ、めったにそんな場面ありませんけれども…
filter
Jinjaにはfilterと呼ばるものがあります。
filterというタグもあり、このタグで囲った中身は指定のフィルタが適用されます。
{{
config(
materialized='table',
)
}}
{%- set value %}
{%- filter upper | trim | reverse -%}
This text becomes uppercase {{ "hoge" }}
{%- endfilter %}
{%- endset %}
select '{{ value }}' as filter_text
やってみれば、わかりやすいですね。 大文字化して、Trimしたあとに鏡文字にしてます。
$ dbt build --select filter
09:22:51 Running with dbt=1.3.1
09:22:51 Found 3 models, 4 tests, 0 snapshots, 0 analyses, 313 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
09:22:51
09:22:52 Concurrency: 4 threads (target='dev')
09:22:52
09:22:52 1 of 1 START sql table model dev_dev.filter .................................... [RUN]
09:22:52 1 of 1 OK created sql table model dev_dev.filter ............................... [SELECT 1 in 0.18s]
09:22:52
09:22:52 Finished running 1 table model in 0 hours 0 minutes and 0.44 seconds (0.44s).
09:22:52
09:22:52 Completed successfully
09:22:52
09:22:52 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
[~/go/.../macro-tips-advent-calender/macro_tips_advcal] *[main]
$ psql -h 127.0.0.1 -U postgres -p 5432 -d postgres
Password for user postgres:
psql (14.2, server 14.5 (Debian 14.5-2.pgdg110+2))
Type "help" for help.
postgres=# select * from dev_dev.filter;
filter_text
----------------------------------
EGOH ESACREPPU SEMOCEB TXET SIHT
(1 row)
postgres=#
caller
普通のmacroを呼ぶときは、そのまま {{ macro_name() }}
と呼ぶと思います。
じつは、callタグを使った呼び方もあり、その場合はcallerという無名macroが手に入ります.
百聞は一見にしかずということで。
{%- macro add_hader(header) %}
-- this is header
-- {{ header }}
{{ caller() }}
{%- endmacro %}
{{
config(
materialized='table',
)
}}
{%- call add_hader(header='hogehoge') %}
select '1'
{%- endcall %}
これを compileすると
-- this is header
-- hogehoge
select '1'
このようになります。これだけでも面白いのですが、callの呼び方を工夫すると一風変わったmacroが作れます。
{%- macro select_value_unions(values=[]) %}
{%- if values | length == 0 %}
{{ exceptions.raise_compiler_error('values length is 0') }}
{%- endif %}
{%- for value in values %}
{{ caller(value) }}
{%- if not loop.last %} union all {%- endif %}
{%- endfor %}
{%- endmacro %}
{{
config(
materialized='table',
)
}}
{%- set values_json %}
[
{"hoge": 1, "fuga": "aaaa"},
{"hoge": 2, "fuga": "bbbb"}
]
{%-endset %}
{%- call(value) select_value_unions(values=fromjson(values_json)) %}
select {{ value.hoge }} as hoge, '{{ value.fuga }}' as fuga
{%- endcall %}
このように、callに引数をつけると、その引数を受取るcallerが定義されます。
$ dbt build --select caller
09:48:55 Running with dbt=1.3.1
09:48:55 Found 3 models, 4 tests, 0 snapshots, 0 analyses, 314 macros, 0 operations, 0 seed files, 3 sources, 0 exposures, 0 metrics
09:48:55
09:48:56 Concurrency: 4 threads (target='dev')
09:48:56
09:48:56 1 of 1 START sql table model dev_dev.caller .................................... [RUN]
09:48:56 1 of 1 OK created sql table model dev_dev.caller ............................... [SELECT 2 in 0.20s]
09:48:56
09:48:56 Finished running 1 table model in 0 hours 0 minutes and 0.44 seconds (0.44s).
09:48:56
09:48:56 Completed successfully
09:48:56
09:48:56 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1
$ psql -h 127.0.0.1 -U postgres -p 5432 -d postgres
Password for user postgres:
psql (14.2, server 14.5 (Debian 14.5-2.pgdg110+2))
Type "help" for help.
postgres=# select * from dev_dev.caller;
hoge | fuga
------+------
1 | aaaa
2 | bbbb
(2 rows)
postgres=#
callerの例
前に @takimoさん がSSOTの文脈でクラスのmethodみたいなの作れないかな?呟いてたのをPoC的に書いたdbt packageがあります。
実は、このpackageはcallerとconfig組み合わせた例です。
{{
config(
materialized='view',
)
}}
{%- call dbt_methods.def("prefix_added", arguments=["prefix"]) %}
select '{{ dbt_methods.argument("prefix") }}' || name as name
from {{ this }}
{%- endcall %}
{%- call dbt_methods.def("suffix_added", arguments=["suffix"]) %}
select name || '{{ dbt_methods.argument("suffix") }}'as name
from {{ this }}
{%- endcall %}
select 'hoge' as name
union all
select 'fuga'
union all
select 'piyo'
{{
config(
materialized='view',
)
}}
with base as (
{{ dbt_methods.call('sample', 'prefix_added', prefix='hoge') }}
)
select * from base
こうすると compileした結果が
with base as (
select 'hoge' || name as name
from "postgres".public.sample
)
select * from base
こんな感じで書けますよ。 というものです。
このdef macroの中身を覗くと次のようになってます。
ここで渡されたcallerの中身をparse phaseでconfigに設定してます。
そして、呼び出し時にそのconfigの中身を取り出して、 render()
で描画して返しています。
dbt macro tips Advent Calendar 2022 いかがでしたでしょうか?
dbtのmacroを書くための細々としたTips的なものを書き続けた25日でしたが楽しんでいただけたでしょうか。
dbtはデータ変換のお供になっているので、今回のシリーズを機会にいろいろなmacroを書くきっかけになったら幸いです。
Discussion