dbt is the T in ELT — it turns raw warehouse data into modeled, tested, documented tables using just SQL and software engineering practices. Learn where it fits and set up your first project.
In the modern stack, a loader (Fivetran, Airbyte, or your own ingestion) dumps raw data into the warehouse, and dbt transforms it there with SQL. dbt does not extract or load and it is not an orchestrator — it compiles your SQL, runs it against the warehouse in dependency order, and manages the resulting tables and views. It brings software engineering — version control, testing, modularity, docs — to analytics SQL.
The modern ELT stack:
sources -> [ Loader ] -> raw tables -> [ dbt ] -> modeled tables -> BI
(Fivetran/ (transform (star schemas,
Airbyte/ in SQL) metrics)
custom ingest)
dbt does ONLY the "T": it compiles + runs your SQL in the warehouse,
in the right order. It is NOT ingestion and NOT an orchestrator
(Airflow/Dagster schedule dbt — see the Airflow course).
What it adds to SQL: git, tests, modularity, docs, lineage — analytics
engineering = software engineering applied to data transformation.A dbt project is a folder of SQL and YAML in version control. You install dbt with the adapter for your warehouse, configure a connection profile, and dbt handles the rest. The project structure is convention-driven: models live in models/, and dbt_project.yml declares defaults. From here everything is a file you can review in a pull request.
# install dbt + the adapter for your warehouse
pip install dbt-postgres # or dbt-snowflake, dbt-bigquery, dbt-redshift
dbt init analytics # scaffolds the project
cd analytics
dbt debug # verifies the warehouse connection
# project layout:
# dbt_project.yml project config + model defaults
# models/ your .sql models (+ .yml for tests/docs)
# seeds/ snapshots/ tests/ macros/
# profiles.yml connection (kept OUTSIDE the repo / in env vars)A dbt model is a single SELECT statement in a .sql file. dbt wraps it in the right DDL (create table/view) and names the object after the file. That is the core idea: you write the logic that shapes the data, dbt handles the boilerplate of building and rebuilding it. Running dbt materializes every model in the warehouse.
-- models/staging/stg_orders.sql
-- just a SELECT — dbt turns this into a view/table named stg_orders
select
id as order_id,
user_id as customer_id,
order_date,
status,
amount_cents / 100.0 as amount
from raw.jaffle_shop.orders
where status != 'deleted'
-- build it:
-- dbt run --select stg_orders
-- dbt compiles this to CREATE VIEW stg_orders AS (...) and runs it.Before dbt, warehouse transformations were scattered stored procedures and hand-run scripts with no tests, no lineage, and no review. dbt makes every transformation a versioned, tested, documented artifact built by a repeatable command. The result is trust: analysts and engineers can change data logic through pull requests and know a regression will be caught.
Before dbt With dbt
scattered stored procs / scripts models in git, reviewed in PRs
no tests -> silent breakage tests fail the build on bad data
"how is this table built?" auto lineage graph + docs
run by hand, hope it's current one command rebuilds everything
copy-paste SQL everywhere ref() + macros = DRY, reusable
The payoff is TRUST in the numbers, and the ability to change data
logic safely — the whole point of analytics engineering.