👌

dbt macro tips advent calendar 2022 day 24 - 時間関係

2022/12/24に公開約4,200字

便利なデータ変換ツールである dbt の中のmacroに関するtipsを書いていく dbt macro tips Advent Calendar 2022 24日目です。

時刻

dbtでモデルを書いてる際、特にincrementalなモデルを書いていると、時刻関係を取り扱いたくなると思います。

例えば、データの遅延到着が発生したので、基準日から7日前の間を再処理したいとします。
var('current_datetime') のように var functionを使うことで処理の基準日を指定することを考えたとします。

https://docs.getdbt.com/reference/dbt-jinja-functions/var

このとき、いくつか懸念点があります。

  • 日付として正しいフォーマットの文字列が渡されるのか?
  • 7日前の算出はどうやって実現したら良いのか?
  • TimeZone関係は?
  • etc...

もちろんひとつの簡単な方法として、run_query を使ってDWHに問い合わせるという方法があります。
しかし、それでは時刻関係を取り扱いたいときに1クエリ実行することになり、とても非効率的です。

そんなときのために、dbtではmacro中でPythonのdatetime,pytz等の時刻関係のモジュールを取り扱えるようにするインタフェースが用意されています。
今回はそちらの使い方を見せつつ、便利なマクロをいくつか紹介します。

https://docs.getdbt.com/reference/dbt-jinja-functions/modules#datetime

さて、何をするにも、現在時刻の取得というのが大事だと思います。
試しに現在時刻を取得するmacroを書いてみましょう。

macros/datetime.sql
{%- macro var_current_datetime() %}
    {{ return(modules.datetime.datetime.now()) }}
{%- endmacro %}

modules.datetime 以下はPythonのdatetimeモジュールと同じ様になっています。

https://docs.python.org/ja/3/library/datetime.html

modules.datetime.datetime.now() というのは datetimeモジュールのdatetime オブジェクトのクラスメソッドである now() を使用しているわけですね。

now() は通常ローカル時刻のdatetimeオブジェクト(TZ情報なし)を返します異なるTimeZoneでの現在時刻がほしいというケースなどを考えて pytzを利用した改造をしてみましょう。

macros/datetime.sql
{%- macro var_current_datetime(tz=none) %}
    {%- set dt = modules.pytz.timezone('UTC').localize(modules.datetime.datetime.utcnow()) %}
    {%- if tz is none %}
        {{ return(dt) }}
    {%- endif %}
    {{ return(dt.astimezone(modules.pytz.timezone(tz))) }}
{%- endmacro %}

今度のmacroはUTCで返すのが基本となっています。
ためしに run-operation コマンドを使って動作を確認してみましょう。

{%- macro main() %}
    {% do log('UTC: ' ~ var_current_datetime(), info=True) %}
    {% do log('JST: ' ~ var_current_datetime(tz='Asia/Tokyo'), info=True) %}
{%- endmacro %}
$ dbt run-operation main                             
03:52:29  Running with dbt=1.3.1
03:52:30  UTC: 2022-12-22 03:52:30.059808+00:00
03:52:30  JST: 2022-12-22 12:52:30.060650+09:00

このようになります。  
さて、次は実行時に --var 'current_datetime: 2023-01-01T00:00:00+0900' のように実行時に値を渡せるようにしてみます。

{%- macro var_current_datetime(tz=none) %}
    {%- set dt_str = var('current_datetime', '') %}
    {%- if dt_str != '' %}
        {%- set dt = modules.datetime.datetime.strptime((dt_str | string), '%Y-%m-%dT%H:%M:%S%z').astimezone(modules.pytz.timezone('UTC')) %}
    {%- else %}
        {%- set dt = modules.pytz.timezone('UTC').localize(modules.datetime.datetime.utcnow()) %}
    {%- endif %}
    {%- if tz is none %}
        {{ return(dt) }}
    {%- endif %}
    {{ return(dt.astimezone(modules.pytz.timezone(tz))) }}
{%- endmacro %}
$ dbt run-operation main --var 'current_datetime: 2023-01-01T00:00:00+0900' 
05:29:10  Running with dbt=1.3.1
05:29:11  Unable to do partial parsing because config vars, config profile, or config target have changed
05:29:14  UTC: 2022-12-31 15:00:00+00:00
05:29:14  JST: 2023-01-01 00:00:00+09:00
 
$ dbt run-operation main --var 'current_datetime: invalid'                 
05:32:59  Running with dbt=1.3.1
05:33:00  Unable to do partial parsing because config vars, config profile, or config target have changed
05:33:03  Encountered an error while running operation: Runtime Error
  time data 'invalid' does not match format '%Y-%m-%dT%H:%M:%S%z'

コレで変えることができましたね。実行時に好きなように時間を指定できるようになりました。

さて、任意の時刻のdatetimeオブジェクトを取得できるようになりましたので、あとは3日前や指定のフォーマットでの出力になります。

mainのoperationを改造して、例を示してみます。

{%- macro main() %}
    {% do log('UTC: ' ~ var_current_datetime().strftime('%Y-%m-%d'), info=True) %}
    {% do log('JST: ' ~ var_current_datetime(tz='Asia/Tokyo').strftime('%Y-%m-%d'), info=True) %}
    {% do log('Before 7 day: ' ~ (var_current_datetime(tz='Asia/Tokyo')-modules.datetime.timedelta(days=7)).strftime('%Y-%m-%d'), info=True) %}
{%- endmacro %}
$ dbt run-operation main --var 'current_datetime: 2023-01-01T00:00:00+0900'
05:42:18  Running with dbt=1.3.1
05:42:19  UTC: 2022-12-31
05:42:19  JST: 2023-01-01
05:42:19  Before 7 day: 2022-12-25

strftimeやtimedeltaを使うことで日付の計算や文字列化ができます。

この辺の使い方を迷ったらPythonのドキュメントを見にいくようにしましょう。


25日目は何にしようかまだ悩んでいます。

Discussion

ログインするとコメントできます