What Compilation Actually Does
Your model file is a template, not SQL. Compilation renders it to real SQL; the materialization then wraps that SQL in DDL. Three files, three stages, and only the third one ever reaches the warehouse.
Your first model in Chapter 2 read a hard-coded table name, which worked. This chapter removes that cheat, and the reason it matters requires understanding what happens between the file you write and the statement your warehouse receives.
Three files exist for every model: the one you wrote, the compiled SQL with all templating resolved, and the run SQL with DDL wrapped around it. Step through them below — the second one is the file you will open most often for the rest of your dbt career.
{{ config(materialized='table') }}
select
l.listen_id,
l.user_id,
s.show_title,
l.ms_played
from {{ ref('int_listens_enriched') }} l
join {{ ref('dim_show') }} s using (show_id)Your model file is a template, not SQL. Compilation renders it to real SQL; the materialization then wraps that SQL in DDL. Three files, three stages, and only the third one ever reaches the warehouse.
Almost every “why is my model doing that?” has an immediate answer in `target/compiled/`, because that file is not an approximation — it is exactly what was sent. Reading it turns debugging from inference into observation, and it is the fastest habit you can form in your first month.
- Parse
- dbt reads every file in the project, discovers the nodes, and records the graph edges from ref() and source() calls. No SQL is generated and nothing is sent anywhere.
- Compile
- All Jinja is rendered and every ref()/source() becomes a fully-qualified table name. The result is plain SQL, written to target/compiled/.
- Run
- The compiled SQL is wrapped in whatever DDL the materialization requires and issued to the warehouse. Written to target/run/ so you can read exactly what executed.
- Relation name
- The database.schema.name a ref() resolves to. All three parts come from configuration and the active target, which is why the same file produces different names in dev, CI and production.
Trying to run a model file directly in a warehouse console to debug it. It is not valid SQL — the Jinja will be rejected. Run `dbt compile` and paste the file from target/compiled/ instead, which is guaranteed to be the same SQL dbt would issue.
Assuming compiled SQL differs between environments in some deep way. The only difference is usually the resolved schema. If a model works in dev and fails in prod, diff the two compiled files — the answer is nearly always visible in the first two lines.
Run `dbt compile` whenever you are unsure. It costs nothing, touches no warehouse, and answers the question exactly rather than approximately.
When reviewing someone’s pull request on a complex templated model, ask for the compiled SQL. Reviewing Jinja is guessing; reviewing compiled SQL is reading.
Learn to tell compiled/ from run/ on sight: compiled/ is your SELECT, run/ is that SELECT inside a create statement.
`dbt compile` opens no warehouse connection for the model SQL itself, so it costs nothing and is nearly instant. There is no reason to guess what a model will produce when you can look.
A model file is a template. compiled/ is what it became; run/ is what was issued. When something surprises you, read the second one.
