Data Engineer · #046 · September 7, 2026 · 2 min read
What is dbt and how does it actually work?
Models as SELECTs, ref() and the DAG it builds, two-line data tests, staging vs marts, and the three materializations that cover real projects: dbt on one page.
Get the free PDF
One page, print-ready, free to share. No signup needed.
The T in ELT has a name, and it is the most-cited transform tool in data engineering job postings. dbt is just SQL files, run in the right order. Here is the whole idea on one page. The print-ready A4 PDF is at the bottom.
The idea
- A model = a SELECT: one
.sqlfile per table. ref('stg_orders'): never a raw table name.- dbt builds the DAG: the run order comes from the refs.
Commands
dbt run: build the models.dbt test: check the data.dbt build: both, in DAG order.dbt docs generate: lineage for free.
Tests
unique,not_null: two lines of yaml.accepted_values: enum columns.relationships: foreign keys, checked on every run.
Layers
staging/: rename, cast, clean.marts/: what the BI tool reads.sources.yml: the raw tables, declared once.
Materializations
view: the default, always fresh.table: heavy models read often.incremental: big fact tables, append new rows only.
A whole model, honestly
-- models/marts/daily_revenue.sql
SELECT
order_date,
SUM(amount) AS revenue,
COUNT(DISTINCT customer_id) AS buyers
FROM {{ ref('stg_orders') }}
WHERE status = 'complete'
GROUP BY order_date
This file IS the table. No CREATE TABLE, no DROP, no run order to remember: dbt reads the refs, orders the builds, and writes the DDL.
The trap: bypassing ref()
| You wrote | What happens | Write instead |
|---|---|---|
FROM analytics.stg_orders | invisible to the DAG | FROM {{ ref('stg_orders') }} |
SELECT * from staging | upstream drift hits marts | name the columns |
| no tests on the mart | bad join ships to the BI tool | unique + not_null minimum |
ref() is the entire product: lineage, run order, environments. Hardcode a name and you opt out of all three.
Frequently asked questions
What is a dbt model?
One .sql file containing one SELECT statement: that file IS the table or view. No CREATE TABLE, no DROP, no run order to remember. dbt reads the ref() calls inside your models, derives the dependency graph from them, and writes the DDL itself when you run dbt run or dbt build.
What does ref() do in dbt?
ref('stg_orders') replaces a hardcoded table name with a reference dbt can trace. Those references are the entire product: they give you lineage, the build order, and environment switching for free. Write FROM analytics.stg_orders instead and the model becomes invisible to the DAG, so it works today and breaks the day the raw table moves.
How do dbt tests work?
You declare them in yaml next to the model: unique and not_null on a key column take two lines, accepted_values covers enum columns, and relationships checks foreign keys on every run. dbt test runs them against real data, and dbt build runs models and tests together in DAG order, so a bad join fails before it reaches the BI tool.
What is the difference between staging and marts in dbt?
Staging models rename, cast and clean the raw tables declared in sources.yml, one staging model per source table. Marts are what the BI tool reads: the joined, aggregated, business-facing tables built on top of staging. Clean first, serve second, and never let a mart read a raw table directly.
Get the free PDF
One page, print-ready, free to share. No signup needed.