Now, we master Idempotent Data Transformations with the industry standard: dbt (data build tool).
For decades, Data Engineers built "ETL" pipelines: Extract the data, Transform it row-by-row in a Python or Java server, and Load it into the database. But with the rise of massively powerful cloud warehouses (Snowflake, BigQuery) and Lakehouses (Delta Lake), we shifted to ELT: Extract, Load the raw data into the warehouse, and do the Transformations directly inside the database using SQL.
But writing a 2,000-line SQL script is a maintenance nightmare. Today, we bring software engineering best practices (version control, modularity, CI/CD) to SQL.
Scenario: You inherited a legacy pipeline. The core transformation is a single, 1,500-line SQL script with 12 nested subqueries.
The Problem: The business asks you to add a discount_code column to the final output. You stare at the query, terrified. If you change a GROUP BY on line 400, it might break a JOIN on line 1,200. Testing it is impossible.
Task: Redesign this transformation so a team of engineers can collaborate on it safely.
We ban nested subqueries completely. We break the massive script into smaller, logical files (models) and chain them together using Common Table Expressions (CTEs) and dbt's ref() function.
The Workflow:
- Staging Models (The Clean Up): We create a file
stg_orders.sql. It does one thing: reads raw JSON orders, casts data types (strings to dates), renames columns, and handlesNULLvalues.
WITH raw_orders AS (
SELECT * FROM {{ source('raw_data', 'orders') }}
)
SELECT id AS order_id, CAST(created_at AS DATE) AS order_date FROM raw_orders
- Intermediate Models (The Math): We create
int_daily_sales.sql. It references the staging model, not the raw table.
WITH stg_orders AS (
SELECT * FROM {{ ref('stg_orders') }}
)
SELECT order_date, COUNT(*) as total_orders FROM stg_orders GROUP BY 1
- The DAG Compilation: When you run
dbt run, dbt reads these files, figures out the dependencies (the DAG), and executes them in the correct order inside Snowflake.
Why this is best: You can test stg_orders in isolation. When you need to add discount_code, you add it to the staging model, and every downstream model inherits it cleanly. It makes SQL read like modular Python code.
Scenario: You have a massive table containing 50 billion log events.
The Problem: To build a daily aggregate table, you write an INSERT INTO daily_logs SELECT ... script that runs every midnight via Airflow. At 1:00 AM, the database restarts and the job fails halfway through. You manually restart the job at 2:00 AM.
The Disaster: The script blindly runs again. You now have double-counted the revenue for half the users. Your SQL was not idempotent.
Task: Design a transformation that can be run 100 times in a row without duplicating a single metric.
We stop writing manual INSERT and DELETE statements. We let dbt handle the boilerplate DDL/DML, and we only write the pure SELECT logic.
The Architecture:
We configure the dbt model to be incremental and define a unique_key.
{{ config(
materialized='incremental',
unique_key='log_id'
) }}
WITH new_events AS (
SELECT * FROM {{ ref('stg_logs') }}
{% if is_incremental() %}
-- Only grab data that arrived AFTER the last time this ran
WHERE event_time > (SELECT MAX(event_time) FROM {{ this }})
{% endif %}
)
SELECT * FROM new_events
The Workflow (Under the Hood):
When dbt executes this against a modern warehouse, it doesn't just do an INSERT. It automatically wraps your SELECT statement in a highly optimized MERGE statement.
- The Match: It looks at the
unique_key(log_id). - The Action: If
log_id = 123already exists in the target table, it runs anUPDATE(overwriting the half-finished data from the failed run). If it doesn't exist, it runs anINSERT.
Why this is best: You achieve perfect idempotency without writing 50 lines of complex procedural merge logic. The pipeline heals itself on the next run.
Scenario: A user signs up for your "Basic" software plan in January. In March, they upgrade to the "Pro" plan.
The Problem: The operational database (Postgres) simply runs an UPDATE users SET plan = 'Pro' WHERE id = 1. The "Basic" plan is erased from history.
The Disaster: When the Finance team runs a report in April to see "How much revenue did the Basic plan generate in February?", your SQL joins the February invoices to the current user table. Because the user is now "Pro", the revenue is falsely attributed to the Pro plan. You just ruined historical financial reporting.
Task: Design a system to track the complete history of dimensional changes over time, without complex scripting.
We must implement SCD Type 2. Instead of overwriting a row, we create a new version of the row and track its lifespan using valid_from and valid_to timestamps.
The Architecture:
Normally, building an SCD Type 2 pipeline requires complex state management and staging tables. With dbt, we use the snapshot feature.
The Configuration:
{% snapshot users_snapshot %}
{{
config(
target_schema='snapshots',
unique_key='id',
strategy='timestamp',
updated_at='last_updated_at',
)
}}
SELECT * FROM {{ source('raw_data', 'users') }}
{% endsnapshot %}
The Workflow:
- Run 1 (January): dbt queries the raw users table and saves the row. It adds metadata columns:
dbt_valid_from = '2026-01-01'anddbt_valid_to = NULL(meaning it's the current active record). - Run 2 (March): The user upgrades to Pro. Their
last_updated_attimestamp changes in the source DB. - The Snapshot Execution: You run
dbt snapshot. dbt detects the change.
- It updates the old row:
dbt_valid_to = '2026-03-01'. - It inserts a new row for the same user ID with
plan = 'Pro',dbt_valid_from = '2026-03-01', anddbt_valid_to = NULL.
Production Grade Architecture: Now, the Finance team's SQL query must be updated to respect time:
SELECT sum(i.amount), u.plan
FROM invoices i
JOIN users_snapshot u
ON i.user_id = u.id
AND i.invoice_date >= u.dbt_valid_from
AND (i.invoice_date < u.dbt_valid_to OR u.dbt_valid_to IS NULL)
GROUP BY u.plan
This guarantees mathematical perfection for point-in-time historical reporting, making your analytical warehouse an immutable source of truth.