Vivek's Field Notes

Data build tool

• 11 min read

dbt (data build tool) is an open-source analytics engineering framework that enables data teams to transform data inside the cloud data warehouse using SQL + software engineering best practices.

Key Points

How it Works in Practice

  1. Extract & Load: Raw data is loaded into the warehouse (by Fivetran, Airbyte, Stitch, custom scripts, etc.).
  2. Transform (dbt): dbt transforms it (staging → intermediate → marts).
  3. Consume: Clean data is consumed by BI tools, data science, applications, or AI models.

Models in dbt

A model is a single .sql file that contains a SELECT statement (plus optional configuration). When you run dbt run, dbt compiles that SQL and executes it in your data warehouse to create a table or view.

Key Characteristics of a Model


Referencing Models: The ref() Function

ref() is a Jinja function that tells dbt: “I depend on another model. Please give me its final table/view name and build me after it.”

Basic Syntax

1select * from {{ ref('stg_orders') }}

Why ref() is Revolutionary


ref() vs. source()

FunctionUsed ForExampleWhen to Use
ref()Referencing other models{{ ref('stg_orders') }}Almost always for transformations
source()Referencing raw data{{ source('raw', 'orders') }}Only for the very first layer (staging)

Advanced ref() Examples

1-- Referencing a model in a specific package
2{{ ref('package_name', 'model_name') }}
3
4-- Using a variable
5{{ ref(var('staging_prefix') ~ 'orders') }}

How the DAG (Directed Acyclic Graph) Works


Materializations

A materialization tells dbt how to persist the output of a model in the data warehouse. You define it using a config block at the top of your model:

1{{ config(materialized='table') }}

Full Breakdown of Materializations

MaterializationWhat it CreatesWhen to UseProsConsPerformance
viewDatabase ViewLightweight transformations, frequently changing logicFast to create, always up-to-dateSlower query performanceFastest to build
tablePhysical TableMost final marts, heavy transformationsFastest queriesSlower to build, more storageSlow to build
incrementalPhysical Table (smart)Large tables where only new/changed data mattersBest of both worldsMore complex to writeExcellent for large data
ephemeralNothing (CTE only)Intermediate logic you don’t want to persistNo storage costCannot be referenced with ref() from other models easilyN/A
materialized_viewMaterialized ViewReal-time-ish data with automatic refreshAuto-refreshWarehouse-specific supportVaries

Deep Dive into Materialization Types

1. View

When using the view materialization, your model is rebuilt as a view on each run, via a CREATE VIEW AS statement.

2. Table

When using the table materialization, your model is rebuilt as a physical table on each run, via a CREATE TABLE AS statement.

3. Ephemeral

Ephemeral models do not create any physical representation in the database.

4. Incremental

Incremental models allow dbt to insert or update records into a table since the last time that model was run.

[!NOTE] Detailed settings and strategies for incremental models are covered in the Incremental Models in Depth section below.

Example: Incremental Model Syntax

Here is a basic example of an incremental model configuration:

 1{{ config(
 2    materialized='incremental',
 3    unique_key='order_id'
 4) }}
 5
 6select
 7    order_id,
 8    customer_id,
 9    order_date,
10    amount
11from {{ ref('stg_orders') }}
12
13{% if is_incremental() %}
14  where order_date > (select max(order_date) from {{ this }})
15{% endif %}

How Models, ref(), and Materializations Work Together

This is the core mental model of dbt:

  1. Write a model: Create a .sql file.
  2. Declare dependencies: Use ref() to reference other models.
  3. Choose a materialization: Control how the table/view is built in the warehouse.
  4. Execute: dbt compiles the code into optimized SQL and executes it in topological order.

Real-World Pipeline Example

[stg_orders (view)] ──> [int_orders_enriched (table)] ──> [fct_sales (incremental)]

When you execute dbt run, dbt will:

  1. Build stg_orders first.
  2. Build int_orders_enriched next (since it depends on stg_orders).
  3. Build fct_sales last (applying incremental logic).

Model Naming Conventions & Project Structure

Organizing models into distinct layers makes your codebase modular, easier to maintain, and logical to navigate.

1. Staging (stg_)

2. Intermediate (int_)

3. Fact Tables (fct_)

4. Dimension Tables (dim_)


Best Practices for Models

Example of an Optimized Config Block

1{{ config(
2    materialized='incremental',
3    unique_key='order_id',
4    incremental_strategy='merge',
5    on_schema_change='sync_all_columns',
6    tags=['finance', 'daily']
7) }}

Incremental Models in Depth

Incremental models process only new or updated records rather than rebuilding the entire table from scratch. To configure them successfully, you must tell dbt:

  1. How to filter the rows on an incremental run.
  2. The unique key of the model (optional, but highly recommended for handling updates).

The is_incremental() Macro

The is_incremental() macro powers incremental materializations. It returns true only if all of the following conditions are met:

Filtering for New Rows

To ensure we only process new data and avoid duplicate rows, we apply a conditional filter using is_incremental().

 1{{
 2    config(
 3        materialized='incremental'
 4    )
 5}}
 6
 7select
 8    *,
 9    my_slow_function(my_column)
10from {{ ref('app_data_events') }}
11
12{% if is_incremental() %}
13  -- This filter will only be applied on an incremental run.
14  -- Uses >= to include records whose timestamp occurred since the last run of this model.
15  -- If event_time is NULL or the table is truncated, the condition will always be true and load all records.
16  where event_time >= (select coalesce(max(event_time), '1900-01-01') from {{ this }})
17{% endif %}

Managing Updates with unique_key

Defining the optional unique_key parameter enables updating existing rows instead of just appending new rows:

Rebuilding Incremental Models

If your transformation logic changes, the old rows in the database will not match the new logic. In this case, you must rebuild the table from scratch:

1dbt run --full-refresh --select my_incremental_model+

[!WARNING] Using the --full-refresh flag will cause dbt to drop the existing target table in the database before rebuilding it for all time.

Handling Schema Changes (on_schema_change)

You can configure the on_schema_change parameter to control behavior when source and target column structures diverge. This helps avoid unnecessary --full-refresh operations.

Supported values for on_schema_change:


Executing Models with dbt run

The dbt run command compiles and executes SQL model files against the target database.

[!NOTE] The dbt run command only applies to models. It does not execute tests, snapshots, seeds, or other resource types.

How dbt run Works

  1. Establishes Connections: dbt connects to the target database and runs the compiled SQL required to materialize the data.
  2. Topological Execution: Models are executed in the exact order defined by the Directed Acyclic Graph (DAG).
  3. Multi-Threading: Intelligent multi-threading is used to run independent models in parallel, minimizing execution time.
  4. Zero-Downtime Deployment: Deploying updated models often requires destroying prior versions. dbt minimizes downtime by first building models under a temporary name, then dropping and renaming them within a single transaction (for adapters that support transactions).

Incremental Strategies

Incremental strategies control how dbt applies data changes to target tables during incremental runs.

Strategy-to-Macro Mapping

Incremental StrategyCorresponding Macro
appendget_incremental_append_sql
delete+insertget_incremental_delete_insert_sql
mergeget_incremental_merge_sql
insert_overwriteget_incremental_insert_overwrite_sql
microbatchget_incremental_microbatch_sql

Detailed Strategy Breakdown

1. append

2. delete+insert

3. merge

4. insert_overwrite

5. microbatch


Custom Incremental Strategies

From dbt v1.2 onwards, users can define custom incremental strategies without creating a completely new materialization:

  1. Define a macro: Create a macro named get_incremental_STRATEGY_sql (replace STRATEGY with the name of your custom strategy).
  2. Configure the model: Set incremental_strategy='STRATEGY' within your incremental model configuration.

[!IMPORTANT] dbt does not validate custom user-defined strategies; it will search for the corresponding macro and raise an error if it cannot be found.

#Blog #Database #Distributed_systems #LLM #Notes #Sharding #Tips #Training