dbt は Jinja2 を使って SQL をテンプレート化し、コンパイル時に最終的な SQL を生成します。つまり Jinja は「実行前に展開されるルール」であり、正しく理解すればモデル定義が簡潔かつ再利用可能になります。
本稿では、Analytics Engineer 試験と日常運用の両方で必須となる Jinja の最小限文法と dbt 固有コンテキスト(ref, source, var, env_var, config, マクロ)を、誤りやすいポイントとともに整理します。
dbt はモデルを実行する前に Jinja を評価し、最終的な SQL を生成します。依存関係の解決(ref/source)やマクロ展開はこの段階で行われます。Jinja 自体はデータベースで動くのではなく、dbt(ローカルないし実行環境)で処理されます。
評価タイミングは大きく「パース/コンパイル中」と「実行時のマクロ呼び出し」に分かれます。データベース接続が必要な run_query は実行時にのみ安全に呼び出せます。一方で、ref/source/var/env_var/config はモデルやマクロのテンプレート内で一般的に利用されます。
| フェーズ | できること | 代表 API/構文 | 注意点 |
|---|---|---|---|
| パース/コンパイル | テンプレート展開・依存解決 | ref, source, var, env_var, config | DB への問い合わせは不可 |
| 実行(マクロ内) | 補助的なクエリ実行 | run_query | 使い所を限定。モデル本体の SQL 生成に混ぜない |
| DB 実行 | 最終 SQL の実行 | 純粋な SQL | Jinja はここでは既に展開済み |
dbt における Jinja の評価と実行の流れ
最小例: ref と Jinja 展開のイメージ
-- models/orders_enriched.sql
select
o.id,
o.customer_id,
c.segment
from {{ ref('stg_orders') }} as o
left join {{ ref('dim_customers') }} as c
on o.customer_id = c.customer_id;Jinja の区切りは3種類だけ押さえれば十分です。{{ }} は値(式/変数)の出力、{% %} は制御構文(if, for, set など)、{# #} はコメントです。dbt ではこれらが SQL 内に混在します。
フィルタはパイプ記法で値を加工します。default, lower, upper, join, replace あたりが頻出です。数値・文字列の扱いでは引用符の有無に注意し、特に識別子(カラム名など)を生成するときは安全性と可読性を意識します。
| 区切り | 用途 | 例 |
|---|---|---|
| {{ ... }} | 値を出力 | {{ var('days', 7) }} |
| {% ... %} | 制御構文 | {% if target.name == 'prod' %} ... {% endif %} |
| {# ... #} | コメント | {# build only for daily partition #} |
区切りの役割ミニ図
フィルタと set の例
{% set cols = ['id', 'customer_id', 'order_ts'] %}
select {{ cols | map('lower') | join(', ') }}
from {{ ref('stg_orders') }};環境やターゲットに応じて列や条件を切り替えるときに if は有効です。配列やマップを回す for は選択列や CASE 文の量産に向いています。テンプレート化は可読性を落とさない範囲で行い、長いロジックはマクロに切り出します。
ループ内のカンマ区切りや末尾カンマに注意しましょう。join フィルタで安全に区切るか、loop.last を用いて制御します。
| 構文 | 用途 | 試験での着眼点 |
|---|---|---|
| if/elif/else | 環境・フラグで分岐 | target/var による切替 |
| for | 列や条件の量産 | join or loop.last で末尾カンマ回避 |
| set | 中間値の保持 | 複雑化前にマクロ化を検討 |
for で列リストを組み立てるイメージ
for と loop.last の例
{% set dims = ['country', 'city', 'zip'] %}
select
customer_id,
{% for d in dims %}
{{ d }}{% if not loop.last %},{% endif %}
{% endfor %}
from {{ ref('dim_customers') }};ref はモデル間依存を張りつつ、適切なスキーマ・識別子を解決します。source は外部テーブル参照のために使い、catalog.yml の宣言と一致させます。var は dbt_project.yml やコマンドラインで渡した変数を取得し、env_var は OS 環境変数を参照します。config はマテリアライゼーションやタグなどモデル設定に使います。
Analytics Engineer 試験では、ref と source の区別、var/env_var の優先度、config の適用範囲(モデル内 vs プロジェクト設定)が頻出です。
| 関数/構文 | 主用途 | 使える場所 | 試験の注意 |
|---|---|---|---|
| ref('model') | モデル参照・依存解決 | モデルSQL/マクロ | モデル間依存を自動で張る |
| source('src','table') | 外部テーブル参照 | モデルSQL/マクロ | sources.yml の定義と一致 |
| var('key', default) | 変数取得 | モデルSQL/マクロ | 未定義時の default で安全化 |
| env_var('NAME') | 環境変数取得 | モデルSQL/マクロ | 実行環境に依存 |
| config(...) | モデル設定 | モデルSQL(冒頭)/マクロ | モデル内 config はそのモデルにのみ適用 |
ref と source の関係イメージ
ref, source, var, env_var, config の実例
-- models/fct_orders.sql
{{ config(materialized='table', tags=['finance']) }}
with src as (
select * from {{ source('raw', 'orders') }}
), stg as (
select * from {{ ref('stg_orders') }} where order_date >= {{ var('from_date', "'1970-01-01'") }}
)
select * from stg
where {{ 'true' if env_var('ALLOW_ALL', 'false') == 'true' else 'total_amount > 0' }};繰り返し現れる SQL 断片は macros ディレクトリにマクロとして切り出し、呼び出し側で引数を渡します。マクロは Jinja のテンプレートであり、呼び出し時に展開されます。複数プロジェクトで共有するならパッケージ化し、packages.yml から導入します。
run_query はマクロ内で補助的な問い合わせを行いたい場合に限定して使います。モデル本体の SQL 生成を run_query の結果に依存させると、コンパイル・キャッシュ・並列実行で予期せぬ挙動になる可能性があるため、設計としては避けます。
| 対象 | 配置/定義 | 呼び出し/適用 |
|---|---|---|
| マクロ | macros/*.sql | {{ my_macro(arg='x') }} |
| パッケージ | packages.yml で参照 | {{ package_name.macro_name(...) }} |
| モデル設定 | config(materialized='...') | {{ config(...) }} or dbt_project.yml |
プロジェクト構造の最小例
マクロ定義と呼び出し
-- macros/utils.sql
{% macro coalesce_zero(expr) %}
coalesce({{ expr }}, 0)
{% endmacro %}
-- models/fct_orders.sql(一部)
select {{ coalesce_zero('total_amount') }} as total_amount
from {{ ref('stg_orders') }};ref/source を使わずにハードコードしたスキーマ・テーブル名は、環境差分やリネームで壊れます。必ず ref/source を通し、識別子はアダプタが適切にクオートします。識別子やリテラルの動的生成では、文字列クオートの整合性に注意します。
Jinja のロジックをテストで担保するには、dbt test と unit tests(利用可能な場合)を組み合わせます。テンプレート展開が意図通りかは、dbt compile で生成 SQL を確認するのが最短です。
| 落とし穴 | 症状 | 対策 |
|---|---|---|
| ハードコード参照 | 環境切替で失敗 | ref/source を使用 |
| 末尾カンマ | SQL 構文エラー | join or loop.last で制御 |
| 危険な run_query 依存 | 非決定的・遅い | メタデータは vars/ソース管理へ |
安全な参照と危険な参照
良い: {{ ref('stg_orders') }} -> コンパイル時に解決
悪い: raw_schema.stg_orders -> 環境で破綻run_query はマクロ内の補助用途に限定
-- macros/get_max_order_date.sql
{% macro get_max_order_date() %}
{% set res = run_query("select max(order_date) as d from " ~ ref('stg_orders')) %}
{% if res is not none and res.columns and res.rows %}
{{ return(res.rows[0][0]) }}
{% else %}
{{ return("'1970-01-01'") }}
{% endif %}
{% endmacro %}
-- 悪手: モデル本体の where に直接 run_query 結果を依存させないAnalytics Engineer
問題 1
Analytics Engineer として、環境ごとに適切なテーブル名を参照しつつ、モデル間の依存関係を正しく張る必要があります。次のうち最も適切な記述はどれか?
正解: A
ref は依存グラフの構築とアダプタごとの識別子解決を同時に行い、環境差分にも強い。スキーマやテーブルをハードコード、あるいは手作りの変数で連結する方法は移植性や依存解決の点で劣る。
var と env_var はどう使い分けますか?
var は dbt プロジェクトやコマンドラインで管理するテンプレート変数、env_var は実行環境(OS)の値を参照します。再現性の観点から、アプリ挙動に影響する値は原則 var で管理し、資格情報や機密は環境変数(env_var)で渡します。
モデル内と dbt_project.yml の config、どちらが優先されますか?
モデル内の config がそのモデルに対して最終的に適用されます。プロジェクト設定はデフォルト、モデル内は上書きという関係です。
ループで列リストを組み立てるときの安全な書き方は?
join フィルタを使うか、loop.last を用いて末尾カンマを制御します。例: select {{ cols | join(', ') }}。これにより構文エラーを避けられます。
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)、設定優先度...