dbt の Macro は Jinja で書く小さなテンプレート関数です。モデル内の繰り返しをなくし、倉庫ごとの差異を吸収し、チーム全体で安全に再利用できます。
この記事では、公式ドキュメントの動作に基づき、使いどころ、書き方、adapter.dispatch による方言吸収、Generic Test までを短距離で一気に押さえます。Analytics Engineer 認定の試験観点も併記します。
Macro は Jinja の関数として SQL 断片を返し、モデルや他の Macro から呼び出されます。dbt は Jinja を解決してから最終的な SQL を生成し、ターゲットのデータウェアハウスへ送ります。
重複する CASE 式や正規化ルール、DDL の差分などを Macro に閉じ込めることで、保守性と方言耐性が上がります。試験では、このコンパイルと実行の段階差を言い当てられるかが頻出ポイントです。
dbt のコンパイル〜実行フロー(Macro/dispatch を含む)
最小の Macro 定義と呼び出し例
-- macros/cleaning.sql
{% macro trim_upper(col) -%}
upper(trim({{ col }}))
{%- endmacro %}
-- models/fct_orders.sql
select
{{ trim_upper('customer_name') }} as customer_name_norm
from {{ ref('stg_orders') }};似た用語が多いので、役割と実行タイミングで整理しておきます。Macro は他の構成要素の「中で使われる」再利用単位で、単体でスケジューリング対象になるわけではありません。
Analytics Engineer 試験では、どれがコンパイル時の仕組みで、どれが実行計画の単位かを選ばせる設問がよく出ます。
| 対象 | 使いどころ | 実装場所/呼び出し | 実行タイミング |
|---|---|---|---|
| Model | SELECT を定義してテーブル/ビューを構築 | models/*.sql(ref で依存管理) | dbt run 時に実行 |
| Macro | 再利用する SQL 断片やロジック | macros/*.sql({{ macro() }} で呼ぶ) | コンパイル時に展開(実行時 API は execute 時のみ) |
| Materialization | モデルの具体的な作り方(table/view/incremental 等) | macros/materializations/*.sql | dbt run 時(ターゲット倉庫ごとに実行) |
| Hook | 実行前後の副作用(GRANT, ANALYZE 等) | project.yml の hooks or Macro | on-run-start / on-run-end / on-model-... |
| Operation | 管理系/一回きりの処理を手動実行 | macros/*.sql(dbt run-operation) | CLI 実行時のみ |
run-operation で Macro を単発実行する例
-- macros/say_hello.sql
{% macro say_hello(name) -%}
{{ log('hello ' ~ name, info=True) }}
{%- endmacro %}
# 実行例(シェル)
# dbt run-operation say_hello --args '{"name": "NicheeLab"}'Macro は引数つきで定義し、SQL 文字列を返します。dbt では return 関数が使えます。Jinja の if/for で条件分岐・繰り返しが可能です。
実行時 API(run_query, load_result, log など)はコンパイル段階では未使用のため、if execute でガードします。試験では execute の意味と run_query の可用タイミングを問われがちです。
return と execute を使った安全な Macro
-- macros/label_case.sql
{% macro label_case(expr, label='unknown') -%}
{% set sql %}
case when {{ expr }} then '{{ label }}' else 'other' end
{% endset %}
{{ return(sql) }}
{%- endmacro %}
-- 実行時 API 例(ログ出力)
{% macro log_rowcount(model) -%}
{% if execute %}
{% set q %}select count(*) as c from {{ model }}{% endset %}
{% set t = run_query(q) %}
{% if t and t.rows and t.rows[0] %}
{{ log('rowcount=' ~ t.rows[0]['c'], info=True) }}
{% endif %}
{% endif %}
{{ return('') }}
{%- endmacro %}チームや OSS と共有する Macro はパッケージ化します。呼び出しは package_name.macro_name の名前空間で行い、衝突を避けます。
倉庫ごとの差異は adapter.dispatch を使って、snowflake__macro_name のような方言別実装と default__macro_name を用意します。dbt はターゲットのアダプタに応じて最適な実装に解決します。
dispatch の基本パターン
-- macros/some_macro.sql(公開ラッパー)
{% macro mypkg.some_macro(arg) -%}
{% set impl = adapter.dispatch('some_macro', 'mypkg') %}
{{ impl(arg) }}
{%- endmacro %}
-- macros/some_macro_default.sql(既定実装)
{% macro default__some_macro(arg) -%}
{{ return('/* default */ ' ~ arg) }}
{%- endmacro %}
-- macros/some_macro_snowflake.sql(Snowflake 実装)
{% macro snowflake__some_macro(arg) -%}
{{ return('/* snowflake */ ' ~ arg) }}
{%- endmacro %}
-- 呼び出し
select {{ mypkg.some_macro('select 1') }};dbt の Generic Test は Macro として実装し、失敗行を返す SELECT を生成します。0 行なら合格、1 行以上で不合格です。
モデル名は test 実行時に渡されるので、引数で受け取り硬コードは避けます。
条件付き not_null テストの例
-- tests/not_null_if_active.sql(Macro として)
{% macro test_not_null_if_active(model, column_name) -%}
select {{ column_name }}
from {{ model }}
where is_active = true
and {{ column_name }} is null
{%- endmacro %}
# schema.yml(抜粋)
# models:
# - name: dim_customer
# columns:
# - name: email
# tests:
# - not_null_if_active: {}Analytics Engineer 試験では、Macro の実行タイミング、adapter.dispatch の解決順、Generic Test の戻り形式などが狙われます。以下を押さえておけば取りこぼしにくいです。
実務では、ref や source といった dbt コンテキスト関数と Macro の役割を混同しないこと、倉庫依存の処理は if 文より dispatch で切り替えることが安定運用のコツです。
Analytics Engineer
問題 1
複数の倉庫で共通の API を保ちながら、Snowflake には専用 SQL、その他には汎用 SQL を使いたい。dbt で推奨される実装はどれか。
正解: A
公式の推奨は adapter.dispatch による方言別実装と default フォールバック。条件分岐は増えると保守が難しく、Materialization はモデルの作り方を司る別の機構。env_var は構成値であり、方言切り替えの一次手段ではない。
Macro はどこに置き、どう呼べばいいですか?
プロジェクトやパッケージの macros ディレクトリ(macros/*.sql)に置きます。同一プロジェクトなら {{ macro_name(...) }}、パッケージ経由なら {{ package_name.macro_name(...) }} で呼びます。名前衝突を避けるため、共有用途は必ずパッケージ名で呼び出すのが安全です。
Generic Test はどんな形で書けばよいですか?
Macro として実装し、失敗行を返す SELECT を生成します。0 行なら合格です。典型的には引数に model と column_name を取り、YAML の tests セクションから呼び出します。
実行時 API(run_query など)がエラーになるのはなぜ?
コンパイル段階では実行時 API は使えません。Macro 側で if execute でガードし、dbt run / test といった「実行フェーズ」でのみ呼ぶようにします。CLI の run-operation は実行フェーズなので使用可能です。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...