dbt (data build tool) is the dominant tool for warehouse-resident transformations. Combined with the rise of cloud data warehouses, dbt enabled a shift in how data work is done — and a new role, "analytics engineering," which sits between data engineering and data analysis.
This page covers how dbt works and what analytics engineering actually means.
dbt is a tool for transforming data inside a warehouse using SQL.
The core idea: write SQL SELECT statements that define transformations; dbt executes them in dependency order; the result is materialized tables/views in the warehouse.
-- models/staging/stg_orders.sql
SELECT
id AS order_id,
customer_id,
amount,
status,
created_at
FROM {{ source('raw', 'orders') }}
The {{ source(...) }} is dbt's Jinja templating. dbt resolves it to the actual table name. Models reference each other via ref():
-- models/marts/customer_lifetime_value.sql
SELECT
customer_id,
SUM(amount) AS lifetime_value
FROM {{ ref('stg_orders') }}
WHERE status = 'completed'
GROUP BY customer_id
dbt builds a DAG of dependencies. Run dbt run and it executes models in order.
The role that emerged with dbt:
Between raw data and analyst-ready data, transformations are needed. Traditionally, data engineers built these in code (Python, Spark) or analysts did ad-hoc SQL. Neither was great.
A new role that:
dbt is the tool; analytics engineering is the practice.
ref() for dependenciesModels reference each other. dbt builds the DAG; runs in correct order.
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique
- not_null
Run dbt test — assertions on the data. Catches regressions, broken assumptions, schema drift.
Every model can have description; columns can have descriptions and tests. dbt docs generate produces a website.
Reusable SQL snippets. For common patterns (date casting, type coercion, etc.).
For large tables, only process new rows:
{{ config(materialized='incremental', unique_key='id') }}
SELECT * FROM {{ ref('source') }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}
Speeds up runs dramatically for append-mostly tables.
sources:
- name: raw
schema: raw_data
tables:
- name: orders
loaded_at_field: _ingested_at
freshness:
warn_after: { count: 12, period: hour }
Source freshness checks; lineage from external systems.
sources → staging → intermediate → marts
stg_*): one model per source table; cleaning, namingint_*): business logic, joinsfct_*, dim_*): final shape for analystsThe standardized structure makes large dbt projects manageable.
Run dbt test in CI. Tests must pass before merge. Catches data issues before they reach production.
dbt projects live in git like any code. Branch, PR, review. The same engineering rigor as application code.
Documentation lives next to models. Stays current.
dbt is for "transform inside the warehouse." Other transformations need other tools.