A dbt model never names a table directly. It reads other dbt models with ref() and tables loaded by
other tools with source(). Both compile to the full table name, and both tell dbt what the model
depends on, which is how dbt knows the order to build things in.
Copy and paste
Declare the raw tables once:
# models/staging/_sources.yml
version: 2
sources:
- name: raw
database: RAW # optional: defaults to the target database
schema: shop
tables:
- name: orders
- name: customers
Read them in staging models with source():
-- models/staging/stg_orders.sql
select order_id, customer_id, order_date, status, amount
from {{ source('raw', 'orders') }}
Read models with ref():
-- models/marts/customer_orders.sql
select c.customer_id, c.name, count(o.order_id) as orders, sum(o.amount) as revenue
from {{ ref('stg_customers') }} c
left join {{ ref('stg_orders') }} o on o.customer_id = c.customer_id
group by c.customer_id, c.name
ref or source
ref('model') |
source('source', 'table') |
|
|---|---|---|
| Points to | a model, seed or snapshot built by dbt | a table loaded by another tool |
| Declared in | the model’s own file | a sources: block in YAML |
| Compiles to | the table in the current target, for example analytics.dbt_nicola.stg_orders |
the database and schema you declared, for example raw.shop.orders |
| Changes with the target | yes: dev and prod read their own copy | no: every target reads the same raw table |
Why not write the table name
- Order. dbt builds a model after everything it
refs. With a hard-coded name it does not know the dependency and may build the model first. - Environments.
ref('stg_orders')reads your development copy in development and the production table in production. A hard-coded name reads the same table everywhere. - Selection and lineage.
dbt build -s source:raw.orders+rebuilds everything that depends on that raw table, and the documentation shows the full lineage, from sources to marts.
Freshness checks
A source can declare how recent its data must be:
tables:
- name: orders
config:
loaded_at_field: updated_at
freshness:
warn_after: {count: 12, period: hour}
error_after: {count: 24, period: hour}
dbt source freshness
1 of 1 ERROR STALE freshness of raw.orders ...................... [ERROR STALE in 0.01s]
Running it before the models stops a pipeline from building marts on data that did not arrive.
A common pattern
Use source() only in staging models, one per raw table, and ref() everywhere else. When a raw table
moves or is renamed, you change one line of YAML.
Tested with dbt Core 1.12.