Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

Building Your DAG in dbt

Every time you run dbt run, dbt doesnโ€™t just execute your models in whatever order they happen to appear. It builds a Directed Acyclic Graph โ€” a DAG โ€” from the dependencies you define with ref(), and executes that graph in the correct order. This is one of the most useful things dbt does, and understanding how it works gives you much better control over your pipelines.


What the DAG Actually Represents

A DAG in dbt is a map of every model and its relationships to every other model. Each node is a model (a .sql file), and each edge represents a dependency โ€” โ€œmodel B reads from model A, so A must run before B.โ€

The โ€œdirectedโ€ part means dependencies flow in one direction: from upstream models to downstream ones. The โ€œacyclicโ€ part means there are no circular dependencies โ€” model A cannot depend on model B if model B also depends on model A. dbt will throw an error if you create a cycle.

Example DAG
raw_orders raw_customers
| |
v v
stg_orders stg_customers
\ /
v v
orders_with_customers
|
v
customer_lifetime_value

In this graph, customer_lifetime_value is the most downstream model. It cannot be built until orders_with_customers is built, which in turn requires both staging models to complete first.


How dbt Detects Dependencies

dbt builds the graph by parsing your ref() calls. When you write:

-- models/marts/customer_lifetime_value.sql
SELECT
c.customer_id,
c.name,
SUM(o.amount) AS total_spent,
COUNT(o.order_id) AS order_count
FROM {{ ref('orders_with_customers') }} o
JOIN {{ ref('stg_customers') }} c ON o.customer_id = c.customer_id
GROUP BY 1, 2

dbt registers two edges: this model depends on orders_with_customers AND stg_customers. It stores this in the manifest so it knows the execution order.

You never need to write CREATE TABLE or manage schema names manually. dbt resolves the correct database reference for each ref() based on your target environment.


Running the Full DAG vs. a Subset

Running dbt run without arguments executes every model in the project in dependency order. But youโ€™ll often want to run a subset.

Run a single model:

Terminal window
dbt run --select customer_lifetime_value

Run a model and everything it depends on (upstream):

Terminal window
dbt run --select +customer_lifetime_value

Run a model and everything that depends on it (downstream):

Terminal window
dbt run --select customer_lifetime_value+

Run everything in a specific directory:

Terminal window
dbt run --select marts/

Run models with a specific tag:

Terminal window
dbt run --select tag:daily

These selectors are composable. You can build fairly precise execution plans without modifying your code.


The dbt Build Command

Since dbt 1.0, the preferred command for CI and production runs is dbt build, not dbt run. The difference is that dbt build runs models, tests, seeds, and snapshots in a single command, in the order the DAG requires.

Terminal window
dbt build --select +customer_lifetime_value

This will:

  1. Run any seed files the selected models depend on
  2. Run upstream models in dependency order
  3. Run tests on each model immediately after it builds
  4. Stop and surface an error if any test fails

In practice this gives you much faster feedback than running models and tests separately.


Visualizing the DAG

dbt Core generates a lineage graph you can view in dbt docs:

Terminal window
dbt docs generate
dbt docs serve

This opens a browser UI where you can explore the full dependency graph, click into any model to see its SQL, description, and tests, and trace lineage in both directions.

dbt Cloud includes a live lineage view built into the IDE. Third-party tools like Atlan, Datahub, and Secoda also integrate with the dbt manifest to render lineage alongside other catalog metadata.


YAML Configuration in the DAG

Models in the DAG can be configured either in their SQL file (via a {{ config() }} block) or in a schema.yml file. Both approaches set properties that dbt applies when building the model.

models/marts/schema.yml
models:
- name: customer_lifetime_value
description: "Total revenue and order count per customer, all time."
config:
materialized: table
tags: ["daily", "finance"]
columns:
- name: customer_id
description: "Primary key."
tests:
- unique
- not_null
- name: total_spent
description: "Sum of all order amounts for this customer."

Tags defined here flow through the DAG, so dbt build --select tag:finance will select this model and run it in proper dependency order.


Parallel Execution

dbt executes the DAG level by level. Models at the same level โ€” those with no dependency on each other โ€” run in parallel up to the thread limit you configure in profiles.yml.

Execution with 4 threads
Level 1 (parallel): raw_orders, raw_customers โ† 2 models, run together
Level 2 (parallel): stg_orders, stg_customers โ† 2 models, run together
Level 3: orders_with_customers โ† waits for level 2
Level 4: customer_lifetime_value โ† waits for level 3

Increasing threads speeds up pipelines with wide, independent layers. It has no effect on deep sequential chains.


dbt Packages That Extend DAG Capabilities

Two packages change how you work with the DAG in meaningful ways:

dbt-utils provides macros like union_relations, generate_surrogate_key, and star that reduce repetition in models without changing how the DAG resolves.

dbt-project-evaluator (dbt Labs) runs a set of rules against your DAG structure โ€” checking for root models without sources, models without tests, fan-out patterns that indicate poor layering. Itโ€™s useful for enforcing DAG hygiene on larger teams.


Common DAG Mistakes

Skipping staging models: Referencing raw source tables directly in mart models creates tight coupling. If the source schema changes, you have to update every downstream model. Staging models act as a translation layer that absorbs those changes in one place.

Using hardcoded schema names: Writing FROM analytics.stg_orders instead of FROM {{ ref('stg_orders') }} means dbt doesnโ€™t register the dependency, your model wonโ€™t appear in the DAG, and the execution order becomes unpredictable.

Overly wide fan-out: If one staging model has 40 downstream dependents, any change to that model causes a cascade rebuild. Intermediate models that group related transformations reduce this blast radius.


The DAG is what makes dbt more than a SQL runner. Itโ€™s the structure that enables reliable execution order, parallelism, targeted rebuilds, and the lineage visibility that makes data pipelines maintainable at scale. Everything else in dbt โ€” materializations, tests, documentation โ€” builds on top of it.