ref() and source() are what make dbt more than a pile of SQL files — they declare dependencies so dbt builds models in the correct order and draws the lineage graph automatically.
Rather than hardcoding raw.schema.table strings across your models, you declare raw tables once in a YAML file as sources. Models then reference them with source(), which centralizes the physical names, documents where data comes from, and unlocks source freshness checks. Change where raw data lands and you update one file.
# models/staging/_sources.yml
version: 2
sources:
- name: jaffle_shop # logical group
schema: raw # actual schema in the warehouse
tables:
- name: orders
- name: customers
# optional: freshness SLA -> 'dbt source freshness' warns/errors if stale
freshness:
warn_after: { count: 12, period: hour }
error_after: { count: 24, period: hour }The single most important dbt function is ref(). Instead of naming a table directly, a model refs another model, and dbt substitutes the correct database object at compile time. This does two things at once: it lets dbt know model B depends on model A, and it makes your SQL portable across dev/prod schemas without edits.
-- models/marts/fct_orders.sql
-- source() for RAW inputs, ref() for OTHER dbt models
with orders as (
select * from {{ ref('stg_orders') }} -- depends on stg_orders
),
customers as (
select * from {{ ref('stg_customers') }} -- depends on stg_customers
)
select
o.order_id, o.amount, c.customer_segment
from orders o
join customers c on c.customer_id = o.customer_id
-- never write "analytics.stg_orders" by hand — ref() resolves the real
-- name per environment (dev schema vs prod schema) automatically.Because every dependency is expressed through ref() and source(), dbt assembles a directed acyclic graph (DAG) of your whole project. It uses the DAG to build models in the correct topological order and to run independent models in parallel — you never manually sequence anything. The same graph powers lineage in the docs.
dbt reads all the ref()/source() calls and builds the DAG:
src.orders ---> stg_orders ---\
+--> fct_orders --> mart_revenue
src.customers -> stg_customers/
dbt run then:
- builds in dependency order (stg_* before fct_*), automatically
- runs independent branches in PARALLEL
- if stg_orders fails, skips everything downstream of it
You declare dependencies via ref(); dbt figures out the order. No manual
DAG wiring like a raw orchestrator.dbt’s node selection lets you build exactly the slice you need using graph operators, which is essential on a large project. You can build one model, everything downstream of a change, or only what a source feeds. This is what keeps runs fast during development and lets CI build just what a pull request touched.
dbt run # build everything
dbt run --select stg_orders # just this model
dbt run --select stg_orders+ # stg_orders AND everything downstream
dbt run --select +fct_orders # fct_orders AND everything upstream
dbt build --select state:modified+ # only changed models + downstream (CI!)
# dbt build = run models + run tests + snapshots + seeds, in DAG order.