An open-source transformation tool that enables data analysts and engineers to transform data in their warehouse using SQL and software engineering best practices.
dbt (data build tool) changed analytics engineering the way React changed frontend — it didn't invent the primitives but it packaged them into a workflow that actually worked. Before dbt, SQL transformations lived in stored procedures, Airflow BashOperators running psql files, BI-tool "custom SQL" fields, or personal notebooks — fragmented, untested, undocumented. dbt's move was deceptively simple: treat SQL models as version-controlled code, compile them into a DAG based on {{ ref() }} dependencies, run them in order, and wrap tests + docs around the whole thing. That one conceptual shift turned analysts into analytics engineers and made dbt the de-facto transformation tool in the modern data stack.
Day-to-day, dbt is: a folder of .sql files (each a SELECT statement), a dbt_project.yml, a profile config pointing to your warehouse, and the dbt CLI. You run dbt build; it compiles each model, figures out dependencies from ref() calls, materializes models as tables/views/incrementals in the order the DAG demands, runs tests, and produces run results. That's it. No orchestration, no scheduling (use Airflow/Dagster/dbt Cloud), no ingestion (use Fivetran/Airbyte). The narrow scope is the strength — dbt does one thing well.
A 40-person analytics team at a retail client had 800 models in BigQuery .sql files scattered across a Confluence wiki, with transformations duplicated across dashboards, no tests, and a weekly cadence where the "real" numbers diverged from the "approximate" numbers shown on a Monday morning. We migrated to dbt over three months: consolidated duplicated logic into reusable staging + intermediate + mart layers, added ~2000 tests (unique, not_null, relationships, accepted_values), and wired CI to run dbt build --select state:modified+ on every PR. Ramp time for new analysts dropped from 6 weeks to 1 week (docs site was the biggest reason), three production data bugs were caught by tests in the first month, and dashboard-divergence incidents went to zero because everyone queried the same marts. dbt isn't magic; it's just software engineering applied to SQL. That's enough.
dbt (data build tool) is an open-source command-line tool that enables data analysts and engineers to transform data in their data warehouse more effectively. It brings software engineering best practices like version control, testing, and documentation to analytics workflows.
Traditional ETL tools require you to write transformations in proprietary languages or drag-and-drop interfaces. dbt takes a different approach:
dbt run # Execute all models
dbt test # Run all tests
dbt docs generate # Generate documentation
dbt build # Run + test in dependency orderdbt pitfalls I see at almost every client:
view materialization on frequently-queried models. Views re-execute the full SQL on every query; for a model sitting under a dashboard, that's dozens of identical recomputations per hour. Materialize as table or incremental for anything queried often. The inverse mistake — materializing everything as table — wastes compute rebuilding cold models.is_incremental() filter (WHERE updated_at > (SELECT MAX(updated_at) FROM {{ this }})) silently misses rows where updated_at is upstream-of-watermark. Use a lookback window (> MAX(updated_at) - interval '3 days') and a merge strategy that handles duplicates.{{ ref() }} vs {{ source() }} confusion. ref points to another dbt model; source points to raw ingested tables. Using the wrong one breaks the DAG or creates hardcoded table names. Always source() at the staging layer, always ref() afterwards.unique on an empty table passes. not_null on an all-null column returns zero failures if the column is added but nothing populates it. Combine tests with expect_table_row_count_to_be_between (via dbt-expectations package) to fail-fast.SELECT * in staging models. Feels convenient; means that when a source adds a new PII column, it silently flows downstream without review. Explicit column lists in staging layers force deliberate schema changes.dbt run without dbt test. dbt build does both in dependency order and stops on test failure, preventing downstream corruption. Using dbt run alone means a broken model's downstreams get rebuilt with bad inputs.{{ my_custom_macro(table, schema, custom_arg, another_arg) }} where a regular SELECT would be clearer. Use macros for genuine DRY wins (pivot helpers, surrogate keys, config-aware filters), not for abstraction's sake.on_schema_change on incremental models. Default is ignore; adding a column to an incremental model doesn't backfill it. Set on_schema_change='sync_all_columns' or handle full refreshes explicitly.dbt build --select state:modified+ is great until the baseline manifest in CI is stale and the "modified" selector picks up everything. Refresh state periodically; document the expected failure mode.owner meta on every model, nobody knows who to call. Add meta: {owner: team-name} as a convention from day one.dbt is used for transforming raw data in a cloud data warehouse into analytics-ready datasets — the T in ELT. You write modular SQL SELECT statements (models) that reference each other; dbt compiles them into a DAG, executes them in dependency order, runs data tests (unique, not_null, relationships, accepted_values, custom SQL), and auto-generates documentation. Common use cases: building a star-schema mart layer, consolidating logic across dashboards, enforcing data contracts, and making analytics changes reviewable via pull requests. dbt has become the de-facto transformation tool in the modern data stack precisely because it's narrow — it doesn't do ingestion or scheduling; it just does transformation well.
dbt is the T (Transform) component of an ELT pipeline. It assumes data has already been Extracted and Loaded into your warehouse by other tools (Fivetran, Airbyte, Stitch, Kafka Connect, custom scripts). dbt then performs the transformations inside the warehouse, leveraging the warehouse's compute (Snowflake, BigQuery, Redshift, Databricks). This ELT pattern is vastly more efficient than legacy ETL for cloud warehouses because transformation happens on the same infrastructure as storage — no data movement, no separate transform server. The ETL-vs-ELT war is essentially over; ELT won for cloud warehouses, and dbt won the transformation layer.
dbt Core is 100% free and open-source (Apache 2.0). You install the CLI, configure a `profiles.yml` with warehouse credentials, and run `dbt build` — no paid license. dbt Cloud is a commercial SaaS from dbt Labs offering a hosted IDE, job scheduling, CI integration, semantic layer, mesh, and enterprise features. Dbt Cloud has a free Developer tier (1 user), paid Team and Enterprise tiers. Many teams run dbt Core indefinitely, orchestrating with Airflow or GitHub Actions; others adopt dbt Cloud for the integrated IDE and managed scheduling. Both produce identical results because they're the same underlying engine.
First-party adapters: Snowflake, BigQuery, Redshift, Databricks, Postgres, and Apache Spark — maintained by dbt Labs. Community adapters add: DuckDB, MotherDuck, Trino/Presto, ClickHouse, SQL Server, Oracle, Teradata, Firebolt, Materialize, SingleStore, and more. Each adapter translates dbt's Jinja+SQL into warehouse-specific syntax. Feature support varies (Snowflake has the richest coverage; smaller warehouses may lack incremental materialization or specific macros). For any mainstream cloud warehouse, dbt works; for niche databases, check the adapter's GitHub for open issues before committing.
Models are SQL files (one per transformation) that compile to `CREATE TABLE AS SELECT` or `CREATE VIEW AS SELECT` — the fundamental unit of work. Sources are YAML declarations of raw ingested tables (`source('raw', 'orders')`) with metadata and tests; they're the upstream boundary of your DAG. Tests are assertions: built-in (`unique`, `not_null`, `relationships`, `accepted_values`) or custom SQL returning zero rows on success. Additional key concepts: Seeds (CSV files loaded as tables), Snapshots (SCD-Type-2 history tracking), Macros (Jinja-based reusable SQL), Packages (shared macro libraries like dbt_utils), and Exposures (downstream consumers like dashboards for lineage tracking).
dbt Core is the open-source CLI — you install it, configure `profiles.yml` with warehouse credentials, and run commands locally or in any orchestrator (Airflow, Dagster, GitHub Actions, cron). dbt Cloud is a managed SaaS wrapping dbt Core with: a browser-based IDE, Git integration, job scheduling, notifications, CI hooks, the Semantic Layer, dbt Mesh (cross-project refs), Explorer (enhanced docs), and enterprise SSO/RBAC. Both execute the same dbt Core underneath; dbt Cloud's value is the managed infrastructure and UX on top. Small teams often start with dbt Cloud's Developer tier or Airflow + dbt Core; enterprise teams typically use dbt Cloud for governance and semantic layer.
The widely-adopted convention (from dbt Labs' best-practices guide): `models/staging/` — 1:1 with source tables, light cleaning (renames, casts, no joins); `models/intermediate/` — joins and logic that doesn't yet produce a business entity; `models/marts/` — final fact and dimension tables consumed by BI, organized by business domain (`marts/finance/`, `marts/marketing/`). Tests live in `_schema.yml` files alongside models. Sources defined in `_sources.yml`. Macros in `macros/`. Seeds (reference CSVs) in `seeds/`. Snapshots (SCD2) in `snapshots/`. This layering keeps changes contained: source changes ripple through staging first; mart changes don't require staging edits.