dbt platformでジョブをエラー終了させずに祝日のみ更新する方法を実装してみた

dbt platformでジョブをエラー終了させずに祝日のみ更新する方法を実装してみた

2026.03.16

かわばたです。

前回の記事では、dbtで祝日マスターテーブルを作成し、マクロを使って祝日のみジョブを実行する方法を紹介しました。
祝日の日だけ実行し、それ以外の日付はジョブをエラーとして終了させるものでした。
課題感として、エラー検知を運用でカバーする必要があります。

今回は別の手法でエラー終了を前提とせずに、マクロを使ったモデル内での条件分岐を試していきます。

【前回の記事】

https://dev.classmethod.jp/articles/dbt_platform_job_conditional/

【公式ドキュメント】
Jinja and macros
https://docs.getdbt.com/docs/build/jinja-macros

incremental models
https://docs.getdbt.com/docs/build/incremental-models?version=1.12

対象読者

  • dbtモデル内で祝日・祝日でない日による条件分岐を実装したい方
  • Jinjaの if 文を使った動的SQLを学びたい方

検証環境

  • dbt platform Enterprise版
    • Latestバージョン
  • Snowflakeトライアルアカウント Enterprise版

今回のゴール

前回は「祝日でなければジョブを中断する」という方法でした。今回は以下を実現します。

  • モデル内での条件分岐: 祝日ならseedから更新、祝日でない日なら既存データを維持
【処理フロー】
祝日の場合:
  dbt run → is_today_holiday() = true → seedから最新データを取得 → テーブル更新

祝日ではない場合:
  dbt run → is_today_holiday() = false → 既存テーブル相当の内容を返す → 実質的に変更なし(dbtの実行自体は行う)

is_today_holiday マクロの実装

マクロの概要

is_today_holiday()true/falseを返すマクロです。Jinjaの if 文と組み合わせて、モデル内で条件分岐を実現できます。

マクロのコード(holiday_job_control.sql)

{#
  ============================================================================
  is_today_holiday マクロ
  ============================================================================
  概要:
    今日が祝日かどうかを判定し、true/false を返す
    Jinja の if 文で分岐処理に使用可能

  使用方法:
    {% if is_today_holiday() %}
      -- 祝日の処理
    {% else %}
      -- 祝日でない日の処理(または何もしない)
    {% endif %}

  戻り値:
    祝日の場合: true
    祝日でない日の場合: false
#}
{% macro is_today_holiday() %}
    {% set is_holiday_query %}
        select count(*) as cnt
        from {{ target.database }}.DBT_TKAWABATA.JAPANESE_HOLIDAYS
        where holiday_date = DATE(CONVERT_TIMEZONE('UTC', 'Asia/Tokyo', CURRENT_TIMESTAMP()))
    {% endset %}

    {% if execute %}
        {% set result = run_query(is_holiday_query) %}
        {% set holiday_count = result.columns[0].values()[0] %}
        {{ return(holiday_count > 0) }}
    {% else %}
        {{ return(false) }}
    {% endif %}
{% endmacro %}

ポイント解説

要素 説明
run_query() dbt実行時にSnowflakeへクエリを発行し、結果を取得
{% if execute %} compile時とrun時を区別。compile時はクエリを実行しない
{{ return(true/false) }} マクロから値を返す

incrementalモデルでの条件分岐(incremental_dim_holidays)

is_today_holiday() マクロと incremental マテリアライゼーションを組み合わせて、祝日のみ更新するモデルを実装します。

実装コード

{#
  ============================================================================
incremental_dim_holidays - 日本の祝日マスターテーブル
  ============================================================================
  概要:
    日本の祝日データを管理するディメンションテーブル

  動作:
    - 初回実行/フルリフレッシュ: seedから全データをロード
    - 増分実行 + 祝日: seed全件をソースとしてMERGEし、既存行を更新・新規行を追加
    - 増分実行 + 祝日でない日: where 1=0 により何も追加せず既存データを維持
    - この例では差分抽出は行わず、incremental materialization を“条件付き更新制御”のために使っています
#}

{{ config(
    materialized='incremental',
    unique_key='holiday_date'
) }}

with source_data as (
    select
        holiday_date,
        holiday_name,
        holiday_type,
        extract(year from holiday_date) as holiday_year,
        extract(month from holiday_date) as holiday_month,
        dayname(holiday_date) as day_of_week
    from {{ ref('japanese_holidays') }}
)

select * from source_data
{% if is_incremental() and not is_today_holiday() %}
    where 1 = 0  -- 祝日でない日の増分実行時は何も追加しない
{% endif %}

ポイント解説

要素 説明
materialized='incremental' 増分更新モード。既存テーブルに対してMERGE/INSERTを実行
unique_key='holiday_date' マージ時のキー。同じ日付があれば更新、なければ追加
is_incremental() dbt組み込み関数。増分実行時のみ true を返す
where 1 = 0 常にFALSEとなる条件。結果として0行が返される

動作パターン

状況 動作
初回実行(祝日でない日/祝日問わず) 全データをロード
フルリフレッシュ 全データをロード
増分実行 + 祝日 seed全件をソースとしてMERGEし、既存行を更新・新規行を追加
増分実行 + 祝日でない日 where 1=0 で何も追加しない

Jinjaのif文で条件分岐(dim_holidays.sql)

このパターンは、平日分岐で既存テーブルを参照するため、対象テーブルがすでに存在していることが前提です。初回実行や削除後の再作成時には別途考慮が必要です。

実装コード

{#
  ============================================================================
  dim_holidays - 日本の祝日マスターテーブル
  ============================================================================
  概要:
    日本の祝日データを管理するディメンションテーブル

  動作:
    - 祝日の場合: seedデータから最新の祝日マスターを再構築
    - 祝日でない日の場合: 既存テーブルと同等の内容で再作成し、結果として実質的なデータ変更を発生させない
#}

-- depends_on: {{ ref('japanese_holidays') }}

{{ config(
    materialized='table'
) }}

{% if is_today_holiday() %}
    {# === 祝日の場合: seedから最新データを取得して更新 === #}
    {{ log("Today is a holiday! Updating dim_holidays table.", info=True) }}

    select
        holiday_date,
        holiday_name,
        holiday_type,
        extract(year from holiday_date) as holiday_year,
        extract(month from holiday_date) as holiday_month,
        dayname(holiday_date) as day_of_week
    from {{ ref('japanese_holidays') }}

{% else %}
    {# === 祝日でない日の場合: 既存テーブルと同等の内容を返し、実質的な変更を発生させない === #}
    {{ log("Today is not a holiday. Keeping existing dim_holidays data.", info=True) }}

    select
        holiday_date,
        holiday_name,
        holiday_type,
        holiday_year,
        holiday_month,
        day_of_week
    from {{ target.database }}.DBT_TKAWABATA.dim_holidays

{% endif %}

-- depends_on の必要性

動作確認

incrementalモデルでの条件分岐

事前準備

祝日マスタの元となるcsvにテストデータを入れます。

2026-03-16_12h23_02

dbt seedでseedデータをSnowflakeにロードします。

2026-03-16_12h25_53

初回実行を行います。

dbt run --select incremental_dim_holidays --full-refresh

2026-03-16_12h28_14

祝日ではない日の場合(例: 2026-03-16)
下記のように増分対象となるデータを追加し、dbt seedを実行します。
2026-03-16_12h31_35

データが追加されていることを確認できました。
2026-03-16_12h33_18

dbt run --select incremental_dim_holidays

上記を実行し、増分実行時のロジックが適用されていることを確認します。

2026-03-16_12h36_17

想定通りSnowflakeの対象テーブルには反映されていませんでした。

祝日の場合
祝日マスタに2026-03-16を加えて実行していきます。

2026-03-16_12h39_03

データが追加されていることを確認できました。

2026-03-16_12h40_15

dbt run --select incremental_dim_holidays

上記を実行し、2回目以降のロジックが適用されていることを確認します。(祝日なのでレコードが更新される)

2026-03-16_13h09_49

Jinjaのif文での条件分岐

※検証日が2026-03-15のため、incrementalモデルでの条件分岐の検証日とズレています。

祝日ではない日の場合(例: 2026-03-15)
dim_holidaysテーブルに格納されているデータ
2026-03-15_21h19_29

祝日マスタの元となるcsvにテストデータを入れます。

2026-03-15_21h19_00

dbt seedでseedデータをSnowflakeにロードします。

2026-03-15_21h30_24

dbt runを実行します。Today is not a holiday. Keeping existing dim_holidays dataと想定通りのログが出力されました。

2026-03-15_21h38_26

Snowflake の対象テーブルには反映されていませんでした。

2026-03-15_21h39_21

祝日の場合
祝日マスタに2026-03-15を加えて実行していきます。

2026-03-15_21h42_07
dbt seedでseedデータをSnowflakeにロードします。

2026-03-15_21h44_16

dbt runを実行します。Today is a holiday! Updating dim_holidays tableと想定通りのログが出力されました。

2026-03-15_21h51_47

Snowflake の更新先に想定通り反映されていました。
2026-03-15_21h52_58

運用時のポイント

Snowflake挙動

Snowflake の CREATE OR REPLACE TABLE 自体は atomic に実行されます。並行クエリは旧版または新版のいずれかを参照します。

ただし、dbt の materialization 実装や依存関係、初回実行時の存在有無には注意が必要です。

https://docs.snowflake.com/en/sql-reference/sql/create-table

初回実行時

初回実行時に対象テーブルが未作成の場合、祝日でない日の分岐では既存テーブルを参照できず失敗する可能性があります。初回は祝日分岐で作成する、または存在確認を入れる実装を検討してください。

ログの確認

モデルの実行ログで、どちらの分岐が実行されたか確認できます。

# 祝日の場合
Today is a holiday! Updating dim_holidays table.

# 祝日ではない日の場合
Today is not a holiday. Keeping existing dim_holidays data.

コスト面の確認

今回はジョブエラーを前提とせず、祝日の場合のみ更新を行い、それ以外の日付は既存のデータのままという構成です。
そのため、実行自体は行われており毎日ジョブが回っていることになり、その分の実行コストが発生する点に注意が必要です。

最後に

今回はマクロを使ったモデル内での条件分岐を検証しました。

ポイントまとめ:

  • is_today_holiday() でtrue/falseを返し、Jinjの if 文で分岐
  • incremental + where 1=0 パターンで安全に条件分岐を実現
  • -- depends_on: で条件分岐内の ref() の依存関係を明示

前回の「ジョブを中断する」方法と、今回の「モデル内で分岐する」方法ではそれぞれの課題がトレードオフの関係にあるため、ご自身の組織で運用しやすい方を選択ください。
個人的にはincrementalモデルでの管理もやりやすいなと感じました。

この記事が何かの参考になれば幸いです!

この記事をシェアする

FacebookHatena blogX

関連記事