の macros/ ディレクトリに置いた Jinja マクロは、`macro_name()` で の中から呼び出せる 再利用可能な SQL 部品です。「同じパターンを 10 個書きそう」になったら macro 化のサイン。
Jinja の基本構文
| 構文 | 意味 | 例 |
|---|---|---|
| `...` | 値を SQL に展開 | `ref('orders')` |
| `{% ... %}` | ロジック (if/for/set 等) | `{% if is_incremental() %}` |
| `{# ... #}` | コメント (SQL に出ない) | `{# TODO: tier 追加 #}` |
| `var('x')` | プロジェクト変数 | `var('start_date')` |
| `env_var('X')` | 環境変数 | `env_var('DBT_PROFILE')` |
macro 1: マスキング
macros/mask_pii.sql
SQL
{% macro mask_email(column_name) %} CASE WHEN {{ var('mask_pii', false) }} THEN regexp_replace({{ column_name }}, '.+@', '***@') ELSE {{ column_name }} END{% endmacro %}
{% macro mask_phone(column_name) %} CASE WHEN {{ var('mask_pii', false) }} THEN concat(left({{ column_name }}, 3), '-****-****') ELSE {{ column_name }} END{% endmacro %}model から呼び出し
SQL
SELECT customer_id, {{ mask_email('email') }} AS email, {{ mask_phone('phone') }} AS phoneFROM {{ ref('stg_customers') }}実行時に var で切替
Bash
# 開発: マスクなしdbt run --select dim_customers
# QA: マスクありdbt run --select dim_customers --vars '{"mask_pii": true}'macro 2: 月次集計テンプレ
macros/monthly_aggregation.sql
SQL
{% macro monthly_agg(table, date_col, group_cols, measures) %} SELECT DATE_TRUNC({{ date_col }}, MONTH) AS month, {% for c in group_cols %}{{ c }},{% endfor %} {% for m in measures %} {{ m.func }}({{ m.col }}) AS {{ m.alias }}{% if not loop.last %},{% endif %} {% endfor %} FROM {{ table }} GROUP BY 1{% for c in group_cols %}, {{ loop.index + 1 }}{% endfor %}{% endmacro %}使う側
SQL
{{ monthly_agg( table=ref('fct_orders'), date_col='ordered_at', group_cols=['region', 'category'], measures=[ {'func': 'SUM', 'col': 'amount', 'alias': 'revenue'}, {'func': 'COUNT', 'col': '*', 'alias': 'order_count'}, {'func': 'COUNT', 'col': 'DISTINCT customer_id', 'alias': 'unique_customers'}, ]) }}macro 3: 動的 pivot
dbt_utils.pivot を使う
SQL
-- カテゴリのリストを動的取得して pivot{% set categories = dbt_utils.get_column_values( table=ref('fct_orders'), column='category') %}
SELECT customer_id, {{ dbt_utils.pivot( 'category', categories, agg='sum', then_value='amount', else_value=0, prefix='revenue_' ) }}FROM {{ ref('fct_orders') }}GROUP BY customer_idmacro 4: 環境別の挙動切替
macros/limit_dev.sql
SQL
{% macro limit_dev(rows=1000) %} {% if target.name == 'dev' %} LIMIT {{ rows }} {% endif %}{% endmacro %}model で使う
SQL
SELECT * FROM {{ source('events', 'raw') }}{{ limit_dev(rows=10000) }}-- dev では LIMIT 10000、prod ではフルmacro 5: pre/post-hook で監査ログ
macros/log_run.sql
SQL
{% macro log_run_start(model_name) %} INSERT INTO meta.dbt_run_log (model, started_at, target) VALUES ('{{ model_name }}', CURRENT_TIMESTAMP(), '{{ target.name }}'){% endmacro %}
{% macro log_run_end(model_name) %} UPDATE meta.dbt_run_log SET ended_at = CURRENT_TIMESTAMP() WHERE model = '{{ model_name }}' AND ended_at IS NULL{% endmacro %}dbt_project.yml で全モデルに適用
YAML
models: analytics: +pre-hook: "{{ log_run_start(this.name) }}" +post-hook: "{{ log_run_end(this.name) }}"macro デザインのコツ
- 複雑になりすぎたら model に戻す ── マクロの中に 50 行を超える は読めない
- 引数のデフォルト値を必ず設定 ── 呼び出し側を簡潔に
- `{% if execute %}` で `dbt parse` 時の挙動を抑制 (副作用あるクエリ)
- dbt_utils を真似ない ── パッケージ既存のものは再発明しない (EP.09)
- docstring を書く ── 引数と返り値を {% docs %} で
次の話
EP.09 では `dbt_utils` `dbt-expectations` などの定番パッケージを扱います。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。