A dbt macro is a small template function written in Jinja. It removes duplication inside models, absorbs warehouse-specific differences, and lets your whole team safely reuse the same logic.
Based on the official documentation, this article quickly covers when to use macros, how to write them, dialect handling with adapter.dispatch, and Generic Tests. It also calls out what matters for the Analytics Engineer certification.
A macro is a Jinja function that returns a SQL fragment, called from models or other macros. dbt first resolves Jinja, then generates the final SQL and sends it to the target data warehouse.
Encapsulating duplicated CASE expressions, normalization rules, or DDL differences inside macros improves both maintainability and dialect tolerance. The exam frequently asks whether you can clearly distinguish the compile and execute stages.
dbt compile-to-execute flow (including macros and dispatch)
Minimal macro definition and call
-- 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') }};These terms overlap, so it helps to sort them by role and execution timing. A macro is a reusable building block used inside other constructs — it is not something you schedule on its own.
The Analytics Engineer exam often asks you to pick which items are compile-time mechanisms and which are units in the execution plan.
| Item | When to use | Location / how to call | Execution timing |
|---|---|---|---|
| Model | Define a SELECT to build a table or view | models/*.sql (dependencies managed via ref) | Runs during dbt run |
| Macro | Reusable SQL fragments or logic | macros/*.sql (called with {{ macro() }}) | Expanded at compile time (runtime APIs only during execute) |
| Materialization | How a model is actually built (table/view/incremental, etc.) | macros/materializations/*.sql | During dbt run (per target warehouse) |
| Hook | Side effects before/after a run (GRANT, ANALYZE, etc.) | hooks in project.yml, or a macro | on-run-start / on-run-end / on-model-... |
| Operation | Manual one-off or administrative tasks | macros/*.sql (dbt run-operation) | Only when invoked via the CLI |
Running a macro one-off with run-operation
-- macros/say_hello.sql
{% macro say_hello(name) -%}
{{ log('hello ' ~ name, info=True) }}
{%- endmacro %}
# 実行例(シェル)
# dbt run-operation say_hello --args '{"name": "NicheeLab"}'A macro is defined with arguments and returns a SQL string. dbt provides a return function, and you can branch and loop with Jinja's if/for.
Runtime APIs (run_query, load_result, log, etc.) are not active during compilation, so guard them with if execute. The exam frequently tests the meaning of execute and when run_query is actually usable.
A safe macro using return and execute
-- 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 %}Macros shared across teams or as OSS belong in a package. Call them through the package_name.macro_name namespace to avoid collisions.
Handle warehouse differences with adapter.dispatch: provide dialect-specific implementations like snowflake__macro_name plus a default__macro_name fallback. dbt resolves to the right implementation based on the target adapter.
The basic dispatch pattern
-- 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') }};A dbt Generic Test is implemented as a macro that generates a SELECT returning failing rows. Zero rows means pass; one or more rows means fail.
The model name is passed at test time, so receive it as an argument and avoid hard-coding.
Example of a conditional not_null test
-- 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: {}The Analytics Engineer exam targets macro execution timing, adapter.dispatch resolution order, and the return shape of Generic Tests. Lock in the points below and you should not lose easy marks.
In practice, do not conflate dbt context functions like ref or source with the role of macros, and switch warehouse-dependent logic with dispatch rather than if statements — that is the key to stable operations.
Analytics Engineer
問題 1
You want a common API across multiple warehouses, with Snowflake-specific SQL for Snowflake and generic SQL for everything else. Which implementation does dbt recommend?
正解: A
The official recommendation is dialect-specific implementations via adapter.dispatch plus a default fallback. Conditional branches become hard to maintain as they grow, materializations govern a different concern (how a model is built), and env_var carries configuration values, not the primary mechanism for dialect switching.
Where should I place macros and how do I call them?
Put them in the macros directory of your project or package (macros/*.sql). Call them as {{ macro_name(...) }} within the same project, or {{ package_name.macro_name(...) }} when going through a package. To avoid name collisions, always call shared macros with the package namespace.
What does a Generic Test look like?
Implement it as a macro that generates a SELECT returning failing rows. Zero rows means the test passes. Typically the macro takes model and column_name arguments and is invoked from the tests section of a YAML file.
Why do runtime APIs like run_query throw errors?
Runtime APIs are unavailable during compilation. Guard them with if execute inside the macro so they only run during the execution phase (dbt run / test). The run-operation CLI is itself an execution phase, so runtime APIs work there too.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
dbt Models: SQL-Defined Transformation Units (2026)
Model fundamentals — SELECT-based definitions, naming, refs,...
dbt Analytics Engineering Exam: Complete Guide (2026)
Pass the AE Certification — scope, weighting, sample questio...
dbt Cloud vs dbt Core: Feature & Cost Comparison (2026)
Honest comparison of dbt Cloud vs. dbt Core — IDE, scheduler...
dbt Project Structure: models/seeds/macros Layout (2026)
Recommended dbt project layout — models, seeds, macros, snap...
dbt_project.yml Explained: Every Config (2026)
Every dbt_project.yml setting that matters — paths, vars, ma...