dbt Models
SELECT once, materialize everywhere
A dbt model is a SQL SELECT that dbt turns into a table or view. Models read from sources or other models via ref(), and their dependencies are resolved and documented automatically.
What you will learn
- Write a model that refs a source and one that refs another model
- Choose between view, table, and incremental materializations
- Use the dependency graph to reason about lineage
What is this?
A model is the unit of transformation in dbt: a plain SQL SELECT stored in a file. dbt wraps it in materialization logic so it becomes a view, a table, or an incrementally updated table in the warehouse. Using ref('other_model') instead of a raw table name makes every dependency explicit, testable, and lineage-visible.
Why do you need it?
- ref() gives you a dependency graph and correct run ordering for free.
- One SQL file = one source of truth instead of nested transformations in the warehouse.
- Materialization is config, not logic — switching view to table is a keyword, not a rewrite.
Mental model
A chef calling for 'the stock'
A model using ref('stock')
The chef names the dish's dependency, not the brand of pot it came from, so the kitchen can change the recipe one place while guests still get the dish.
How it works
- Write a SELECT that reads from {{ source('raw', 'orders') }} or {{ ref('stg_orders') }}.
- dbt resolves refs into a dependency graph and orders model execution.
- Materialization config decides the output: view, table, or incremental.
- Downstream models can now ref() this one, forming the lineage chain.
Models ref each other into a graph dbt executes in dependency order.
In production
- An orders mart refs stg_orders and stg_customers, so the join lives in one place.
- A heavy weekly aggregation materializes as a table; a lightweight alias as a view.
- Renaming an upstream column happens in the staging model, and every consumer updates via ref().
Best practices
- Always use ref() for model-to-model dependencies; never hard-code table names.
- Materialize views for light logic, tables for heavy aggregations, incremental for append-heavy facts.
- Give models names that carry meaning and the layer prefix; avoid _final and _v2 suffixes.
Common mistakes
- Using source() for business-level queries instead of building a staging model first.
- Re-reading the same source table in ten models when one staging model would do.
- Over-use of incremental materialization that complicates pipeline logic for small data.
Debugging tips
- If a model returns too many rows, its source joins multiplied — check for fan-out at ref boundaries.
- If lineage looks wrong, search for direct table names in the SQL that should be ref().
Interview checkpoint
Hands-on lab
- Write a staging model for raw orders
- Write a mart that refs the staging model
- Run, inspect lineage, and change materialization
Build a Staging-to-Mart Model Chain
40 min · intermediate
Quick quiz
The right way to reference another model in dbt is
A model that re-reads a source table directly instead of the staging model