DataLane
← All cheat sheets

dbt Best Practices cheat sheet

Project structure, model design, testing, CI, and performance practices for dbt projects that stay maintainable.

Best PracticesIntermediate6 sections

Structure

models/staging, models/intermediate, models/marts
Three layers with clear roles. staging is one-to-one with sources, marts are consumer-facing, intermediate is private.
One staging model per source table, no joins
Rename, cast, and clean only. Joining in staging creates the dependency tangle that makes refactors impossible.
stg_, int_, fct_, dim_ prefixes
Lineage becomes readable in a list. Consumers can guess a model's shape from its name.
Folder per source in staging, per domain in marts
staging/stripe and marts/finance. Ownership is obvious when something breaks at 2 a.m.
Never reference a source outside staging
Enforce it with dbt-project-evaluator. One direct source ref in a mart defeats the whole layering.

Model design

Declare the grain in the model description
One sentence: one row per order line per day. The cheapest bug prevention in the project.
CTEs named for what they contain, in one direction
import CTEs first, then logic, then a final select. Readable top to bottom without scrolling back.
materialized: view for staging, table for marts
Views cost nothing to build and stay fresh; marts get materialized because they are queried repeatedly.
Incremental only when a full rebuild actually hurts
Incremental adds late-arriving-data complexity. Earn it with a slow build time, do not adopt it preemptively.
Always define a lookback window on incremental models
where ordered_at > (select max(ordered_at) from {{ this }}) - interval '3 days'. Without it, late rows never load.

Testing

unique and not_null on every primary key
The non-negotiable baseline. If a model has no testable key, its grain is probably undefined.
relationships on important foreign keys
Catches orphans that silently vanish from inner joins and quietly change reported totals.
Unit tests for CASE and window logic
Fast, need no warehouse data, and catch the logic bugs that data tests structurally cannot.
severity: warn for known-imperfect sources
Keeps the build meaningful. A permanently red build trains everyone to ignore failures.
store_failures on tests you actually triage
Failing rows in a table beat rerunning with guesswork during an incident.

CI/CD

dbt build -s state:modified+ --defer --state prod-artifacts/
Slim CI. Builds only what changed and defers unchanged refs to production, turning hours into minutes.
Build into a PR-specific schema
dbt_pr_1234 so concurrent PRs never collide, with automated cleanup after merge.
sqlfluff and dbt parse in a pre-commit hook
Formatting arguments and compile errors resolved before review, not during it.
Run dbt-project-evaluator on a schedule
Flags structural drift — direct source refs, models with no tests, rejoining upstream models.
Deploy with dbt build, never dbt run alone
build runs models, tests, snapshots, and seeds in DAG order, so a failing test stops downstream models.

Performance and cost

Check the warehouse bill per model
Snowflake QUERY_TAG or BigQuery labels attribute cost per model. Usually one model dominates the run.
dbt run --threads 8
More threads shorten wall clock at higher peak warehouse load. Tune it against your warehouse size, not to the maximum.
Do not materialize what nobody queries
Audit exposures and query history. Abandoned marts are pure recurring cost.
+tags: nightly for selective scheduling
Not everything needs to run hourly. Tag by freshness requirement and schedule accordingly.

Documentation and ownership

Describe every mart model and its columns
Staging descriptions can be terse; consumer-facing models cannot. The docs site is only useful if marts are covered.
meta: owner on every model
Renders in the docs and answers who to contact without asking in Slack.
exposures for dashboards and reverse-ETL syncs
Makes the blast radius of a change visible before you merge it.
contract: enforced on published models
Fails the build when a column or type changes, turning a silent breaking change into a CI failure.

From DataLane — tutorials at/blog, practice SQL live in theplayground.

↑↓ navigate openesc close