Data / ML · Dbt

dbt Fundamentals — A Field Guide

dbt Fundamentals — A Field Guide is the work that turns raw data into decisions. The pipeline from "we have data" to "we have a model that runs in production" is the same in every.

John Kihiu12 min read

dbt (data build tool) does one job: it takes SQL SELECT statements you write and turns them into tables or views in your warehouse, in dependency order, with tests and documentation attached. It does not extract or load data — that is a job for Fivetran, Airbyte, or a custom pipeline — dbt owns the "T" in ELT.

A model is just a SELECT statement

Every dbt model is a .sql file containing one SELECT statement, no CREATE TABLE boilerplate. dbt wraps it in the DDL needed for whatever materialization you configure. The dependency graph is built entirely from ref() and source() calls inside the SQL — there is no separate config file listing dependencies, which is the detail that makes dbt projects easy to refactor.

SQL · MODELS/STG_ORDERS.SQL
-- models/staging/stg_orders.sql
{{ config(materialized='view') }}

select
    order_id,
    customer_id,
    cast(order_date as date) as order_date,
    amount_cents / 100.0 as amount
from {{ source('raw', 'orders') }}
where order_id is not null

source('raw', 'orders') points at a raw table declared in schema.yml, and ref('stg_orders') in any downstream model points back at this one. Run dbt run and dbt compiles the Jinja, resolves the dependency order via a topological sort of the ref()/source() graph, and executes each model against the warehouse.

Materializations — choosing how a model gets built

A view materialization creates a SQL view — cheap to build, always fresh, but re-executes the underlying query on every read. A table materialization runs the query once and stores the result — fast to query, stale until the next dbt run. incremental only processes new or changed rows on subsequent runs, essential once a table is too large to rebuild fully each time. ephemeral models are not built as objects in the warehouse at all — they get inlined as a CTE into whatever references them, useful for a small reusable snippet you do not want cluttering the schema.

Default to view, upgrade when it hurts

Start every staging model as a view. Only promote to table or incremental once query latency or warehouse compute cost actually justifies the added complexity of managing state. Premature materialization is the most common way dbt projects end up with stale data nobody trusts.

schema.yml — tests and documentation live next to the model

Alongside each model's SQL file, a schema.yml declares sources, documents columns, and attaches generic tests without writing SQL by hand.

YAML · MODELS/SCHEMA.YML
sources:
  - name: raw
    tables:
      - name: orders

models:
  - name: stg_orders
    description: "One row per order, cents converted to dollars."
    columns:
      - name: order_id
        tests: [unique, not_null]
      - name: customer_id
        tests:
          - relationships:
              to: ref('stg_customers')
              field: customer_id

The project layout that scales

Most dbt projects converge on three layers: staging models that clean and rename raw source columns one-to-one, intermediate models that join and reshape staging models, and marts models that produce the final, business-facing tables analysts and BI tools query. Staging models should never be queried directly by a dashboard — that direct dependency is what makes later refactors painful, because you can no longer change a staging model without checking every dashboard in the company.

ConceptWhat it does
ref()References another dbt model, builds the dependency graph
source()References a raw table outside dbt's control
{{ config(...) }}Sets materialization and other per-model settings
schema.ymlDeclares sources, tests, and documentation

Wrapping up

Everything else in dbt — macros, snapshots, exposures, incremental strategies — builds on this same core: SQL models linked by ref(), tested and documented in schema.yml, compiled and run in dependency order. Get the staging/intermediate/marts layering right early, and the rest of the tooling stays easy to reason about as the project grows.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.