dbt compile is the no-execution side of dbt. It evaluates Jinja and macros, resolves dependencies, and expands templates into executable SQL — but it does not create tables or views. Understanding that boundary dramatically improves review efficiency and reduces failures.
This article unpacks what happens inside compile based on the official documentation, the exam points the Analytics Engineer certification likes to target, and the verification workflows that pay off in real projects.
dbt compile loads the project's nodes — models, snapshots, tests — evaluates Jinja and macros, resolves dependencies such as ref, source, and config, and writes the resulting executable SQL under target/compiled. Because it issues no DML or DDL against the database, you can safely inspect the final SQL.
It is ideal for pre-run checks, diff reviews, and detecting syntax breakage in CI. Being able to visually confirm how ephemeral models get inlined and how adapter-specific macros expand is a major win.
The dbt pipeline stages and where compile fits
Minimal model before/after (conceptual)
-- models/orders.sql (template)
with src as (
select * from {{ source('raw', 'orders') }}
)
select * from src where order_date >= '{{ var('from_date', '2024-01-01') }}'
-- target/compiled/<project>/models/orders.sql (example, after expansion)
with src as (
select * from RAW.SOURCE_ORDERS -- resolved to an FQN by the adapter/naming rules
)
select * from src where order_date >= '2024-01-01'
compile identifies the selected nodes and renders each resource's SQL file inside a Jinja environment. Macro calls and adapter-specific implementations chosen via adapter.dispatch are expanded, and ref/source are replaced with the relation names that exist (or are about to be created). Because ephemeral models are not materialized upstream, they are embedded as CTEs inside the referring query.
Tests are translated into executable SQL during compile — for example, a SELECT that returns failing rows. The crucial point is that compile itself does not execute that SQL.
| Command | Primary purpose | Artifacts (target/) | Executes (DDL/DML) |
|---|---|---|---|
| dbt parse | Parse syntax and build the DAG | manifest.json and similar | No |
| dbt compile | Evaluate Jinja/macros and expand SQL | target/compiled, manifest.json | No |
| dbt run | Execute model SQL | target/run, run_results.json | Yes |
| dbt build | Run + test (+ snapshot) in one shot | target/run and others | Yes |
| dbt test | Execute test SQL | target/run, run_results.json | Yes |
How ephemeral inlining looks
-- models/_dim_ephemeral.sql
-- {{ config(materialized='ephemeral') }}
select id, lower(email) as email_norm from {{ ref('stg_users') }}
-- models/dim_users.sql
select u.*, e.email_norm
from {{ ref('stg_users') }} u
left join {{ ref('_dim_ephemeral') }} e on u.id = e.id
-- target/compiled/.../dim_users.sql (conceptual)
select u.*, e.email_norm
from PROD.ANALYTICS.STG_USERS u
left join (
select id, lower(email) as email_norm from PROD.ANALYTICS.STG_USERS
) e on u.id = e.id
After compile runs, rendered SQL is written under target/compiled/<project>/.... That is the static snapshot of "the SQL that would be executed." manifest.json carries the DAG and metadata (relation names, dependencies, configs) and is useful for tooling integrations and diff analysis.
Unlike run or test, compile normally does not produce target/run. Do not expect execution logs or run_results.json from it.
A typical output tree
$ dbt compile --select +models/orders.sql
target/
compiled/
my_project/
models/
staging/
stg_users.sql
marts/
orders.sql # final SQL after expansion
manifest.json # DAG and metadata
logs/
dbt.log
Because compile actually evaluates Jinja and macros, var(), env_var(), config()-driven branches, and adapter.dispatch dialect switches are all reflected in the output. SQL itself is not executed, however, so the final effect of conditions that depend on real data cannot be fully verified.
is_incremental() drives branches inside incremental models, but it depends on the compile-time context, connectivity, and whether the existing relation can be detected. compile does not issue DDL or DML, but it may still consult adapter information during macro evaluation. Final behavior must be confirmed with run or build.
An incremental branch example (expanded at compile time)
-- models/fct_events.sql
{{ config(materialized='incremental', unique_key='id') }}
select * from {{ ref('stg_events') }}
{% if is_incremental() %}
where _ingest_ts > (select max(_ingest_ts) from {{ this }})
{% endif %}
-- Note: compile expands the above, but assumptions like whether the table exists are not finalized until execution.
Selection in compile mirrors run: --select, --exclude, the + (parent/child) operator, tags, and resource types (model:, test:, and so on) are all available. That lets you narrow the review surface and inspect only the diffed expansion.
Since ephemeral upstreams get inlined into their downstream referrers, it is safer to compile the surrounding nodes too using the + operator or path-based selection to avoid missing changes.
Selection examples
$ dbt compile --select path:models/marts/
$ dbt compile --select model:orders+
$ dbt compile --select state:modified --state ./target
Undefined vars, unresolved ref/source, and macro dispatch mismatches tend to surface during compile. A solid workflow is to knock out syntax errors early with dbt parse, then use compile to inspect the rendered output.
The Analytics Engineer exam frequently asks you to recognize the characteristics of compile: it does not execute, it inlines ephemeral models, it resolves ref/source to FQNs, and it produces target/compiled and manifest.json. In real projects, attaching the compiled SQL to PRs is the standard way to make reviews faster.
Debugging basics
$ dbt clean && dbt deps
$ dbt parse # syntax and DAG
$ dbt compile -s +model:orders --vars '{from_date: 2024-01-01}'
$ tail -n 100 logs/dbt.log # look for macro and unresolved-reference traces
Analytics Engineer
問題 1
Which statement about dbt compile is correct? Choose the best option.
正解: A
dbt compile evaluates Jinja and macros, resolves dependencies, and writes the rendered SQL to target/compiled, but it does not execute DDL or DML. B describes parse and is wrong; C describes test; D is wrong because ephemeral models are inlined into the referrer at compile time.
Does dbt compile require a database connection?
It does not execute user SQL, but a connection can still be initialized while evaluating macros or looking up adapter information. Do not assume zero connectivity is required; keep a valid profile configured to be safe.
What is the difference between target/compiled and target/run?
target/compiled holds rendered SQL — the static pre-execution artifact. target/run stores the SQL and other artifacts produced when run, test, or build executes. dbt compile typically does not create target/run.
Can I verify incremental branching from the compile output alone?
You can confirm how the branch expands, but conditions that depend on live data — whether the table exists, the current max timestamp — cannot be fully verified by compile alone. Confirm the final behavior with run or build.
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...