Skip to content

1. What dbt is, and why it exists

Three guides on this site already point here. Spark’s chapter 15 says “if your ETL is a chain of SQL transformations, dbt over a warehouse is a better tool than Spark.” Airflow’s chapter 15 says dbt is a complement rather than a competitor. Flink’s chapter 1 lists batch ETL as something that moved away to “Spark, a warehouse, dbt.”

This is the guide about what they are all pointing at.

dbt is what happens when you decide that a SQL transformation is a piece of software, and should be treated like one.

That is the whole thing. Everything else in this guide follows from it.

Here is the world before:

THE STORED PROCEDURE ERA
a .sql file on someone's laptop
a scheduled job in the warehouse UI
a stored procedure nobody will touch
a chain of CREATE TABLE AS statements
run in an order somebody remembers
→ no version control
→ no tests
→ no idea what depends on what
→ no way to run it on a copy
→ the person who wrote it has left

And the claim dbt makes:

THE ANALYTICS ENGINEERING ERA
a model is a SELECT statement in a .sql file
in git, reviewed in a pull request
with tests that run in CI
with dependencies dbt works out itself
built into dev, staging or prod by config
→ the same discipline software has had
for thirty years, applied to SQL

Nothing in the right-hand column is a new idea. Version control, testing, code review, dependency resolution, environments — software engineering had all of it. dbt’s contribution was noticing that the people writing SQL transformations had none of it, and building the smallest possible tool that gave it to them.

This is the part people get wrong on first contact, so it is worth being blunt.

A dbt model is a SELECT statement. That is all it is. You do not write CREATE TABLE. You do not write INSERT. You do not write a schema. You write the query that produces the rows you want, and dbt wraps it in the DDL needed to persist it.

-- models/marts/customer_orders.sql
select
c.customer_id,
c.customer_name,
count(o.order_id) as order_count,
sum(o.amount) as lifetime_value
from {{ ref('stg_customers') }} as c
left join {{ ref('stg_orders') }} as o
on c.customer_id = o.customer_id
group by 1, 2

That file becomes a table or a view in your warehouse called customer_orders. dbt writes the create table customer_orders as (...) around it.

The one piece of non-SQL is {{ ref('stg_customers') }}. It is a function call that returns the fully-qualified name of another model — analytics.dbt_sarah.stg_customers in your development schema, analytics.prod.stg_customers in production. That single function is where most of dbt’s value comes from, and chapter 3 is about why.

dbt does one letter of ETL. Knowing which one saves a lot of confusion.

E EXTRACT pull data out of the source
→ Fivetran, Airbyte, Debezium,
a Python script
L LOAD land it raw in the warehouse
→ the same tools
T TRANSFORM turn raw tables into tables
people can actually query
→ dbt

dbt does not move data. It has no connectors to Salesforce or Postgres-as-a-source. It cannot read a CSV off S3 and load it. It assumes the data is already in your warehouse, and it turns it into better-shaped data in the same warehouse.

This is the ELT pattern rather than ETL: load first, transform afterwards, in the warehouse, in SQL. That ordering only became sensible when warehouses got cheap and fast enough to do the transformation work — which is to say, when Snowflake, BigQuery and Redshift arrived. dbt is a product of that shift and could not have existed before it.

Worth stating on its own, because it surprises people.

dbt run
│
│ 1. read your .sql files
│ 2. render the Jinja
│ 3. work out the dependency order
│ 4. wrap each model in DDL
│ 5. send SQL over a connection
▼
┌───────────────────────────────┐
│ YOUR WAREHOUSE │
│ Snowflake / BigQuery / │
│ Databricks / Postgres / ... │
│ │
│ ← all the actual work │
│ happens here │
└───────────────────────────────┘

dbt is a code generator and a scheduler of statements. The data never passes through it. Your laptop running dbt run is sending text over a database connection and waiting.

Three consequences that matter:

  • Your warehouse bill, not your dbt bill, is the cost of a dbt project. Chapter 13 is largely about this.
  • dbt is as fast as your warehouse. Optimising a slow model means optimising SQL, not tuning dbt.
  • dbt runs anywhere — a laptop, a container, a CI runner. It needs nothing but network access and credentials.
  • 2016 — Fishtown Analytics, a consultancy, builds an internal tool to stop rewriting the same transformation scaffolding for every client. Tristan Handy is the name usually attached.
  • 2018 — open-sourced as dbt. Apache 2.0. It spreads by word of mouth through analytics teams who all have the same problem.
  • 2019–2021 — dbt Cloud arrives; Fishtown Analytics becomes dbt Labs. The term “analytics engineer” is coined for the role the tool creates — someone between the data engineer and the analyst, who owns the transformation layer.
  • December 2021 — dbt Core 1.0.
  • 2023 — the Transform acquisition brings MetricFlow and the Semantic Layer.
  • 2024–2025 — dbt Core 1.8 splits adapters out and adds unit tests; 1.9 adds the microbatch incremental strategy and YAML snapshots.
  • 2025 — dbt Labs acquires SDF Labs and announces the Fusion engine, a Rust rewrite that actually parses and understands SQL rather than treating it as text. A merger with Fivetran was also announced during 2025.

The last point needs a caveat. Fusion and the Fivetran merger were both moving quickly as this was written, and the details — which features land in Core, what stays in Cloud, how licensing settles — are the kind of thing that changes. Check dbt’s own docs for the current state before making a decision that depends on it. Everything else in this guide is about the model that has been stable since 1.0.

Strip away the features and it is these:

1. A dependency graph you did not have to write. You write ref(); dbt builds the DAG and runs models in the right order, in parallel where it can. Nobody maintains a list of which table comes first.

2. Environments for free. The same code builds into your personal dev schema, a CI schema, or production, because model names are resolved at compile time rather than hard-coded.

3. Tests that run on data, not just code. unique, not_null, relationships and accepted_values are one line of YAML each, and they run as SQL against real rows.

4. Documentation and lineage that are generated, not written. Because dbt knows the graph, it can draw it. Because it reads the warehouse catalogue, it knows the columns.

An honest list, because half the disappointment with dbt comes from expecting one of these:

✗ NOT an orchestrator
it has no scheduler, no retries across
runs, no calendar, no alerting
→ Airflow, Dagster, Prefect, dbt Cloud jobs
✗ NOT an ingestion tool
it cannot pull data from anywhere
→ Fivetran, Airbyte, Debezium, custom code
✗ NOT a compute engine
the warehouse does everything
→ Snowflake, BigQuery, Databricks
✗ NOT a BI tool
it builds tables; it draws nothing
→ Looker, Tableau, Metabase, Superset
✗ NOT for streaming
dbt is batch. every run is a full
re-evaluation of a graph
→ Flink, Materialize
✗ NOT a general programming environment
Jinja is templating, not a language you
want business logic in

The orchestration one is the most common confusion, so to be explicit: dbt run is a command. Something has to decide to run it. In practice that something is Airflow, Dagster, a dbt Cloud job, or a GitHub Action. Airflow’s chapter 15 describes the standard shape — Airflow around the outside, dbt in the middle.

dbt fits when most of your transformation logic is expressible as SQL over tables that are already in one warehouse.

That is a real constraint and it excludes plenty. If your logic needs a machine-learning model, arbitrary Python over unstructured text, or data that lives in six systems that never meet, dbt is the wrong shape. Spark’s chapter 15 draws that line from the other side.

But the category dbt fits is enormous — it is most of what analytics teams do — and inside it dbt is close to unopposed. The realistic alternatives in 2026 are SQLMesh, your warehouse’s native tooling, or writing the scaffolding yourself. Chapter 15 takes each of them seriously.

  • dbt’s single idea is that a SQL transformation is software and deserves version control, tests, review and dependency management. Every feature follows from that.
  • A model is a SELECT. dbt writes the DDL around it. You never write CREATE TABLE.
  • ref() is the load-bearing feature — it builds the dependency graph and makes environments work, from one function call.
  • dbt is the T in ELT. It does not extract, does not load, and assumes the data is already in your warehouse.
  • dbt has no compute engine. It generates SQL and sends it over a connection; the warehouse does all the work, and the warehouse bill is the cost of the project.
  • It came out of a consultancy’s internal tooling in 2016, went open source in 2018, hit 1.0 in December 2021, and created the “analytics engineer” role along the way.
  • The Fusion engine and the Fivetran merger were both in motion during 2025 — check current docs rather than trusting any write-up, including this one, on those specifics.
  • It is not an orchestrator, not ingestion, not compute, not BI, and not streaming. Something else must decide to run it.

Next: the project — dbt_project.yml, profiles.yml, the directory layout, and how dbt finds a warehouse to talk to.

Written by Sepuri Sai Krishna

© 2026CC BY 4.0Source