Van installatie tot productie: een complete handleiding voor het bouwen van betrouwbare, geteste en gedocumenteerde datamodellen met dbt (data build tool).
dbt (data build tool) is een open-source tool waarmee data engineers SQL-modellen schrijven die dbt omzet naar geteste, gedocumenteerde tabellen en views in je datawarehouse. dbt handelt de T in ELT — het transformeren van data — af binnen het warehouse zelf.
ref() en source()| Platform | Pakket | Versie |
|---|---|---|
| Snowflake | dbt-snowflake | ≥ 1.7 |
| Databricks | dbt-databricks | ≥ 1.7 |
| BigQuery | dbt-bigquery | ≥ 1.7 |
| Azure Synapse | dbt-synapse | ≥ 1.7 |
| Lokaal testen | dbt-duckdb | ≥ 1.7 |
# Virtuele omgeving aanmaken (aanbevolen) python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # Installeer dbt met de gewenste adapter pip install dbt-core dbt-snowflake # voor Snowflake pip install dbt-core dbt-databricks # voor Databricks pip install dbt-core dbt-duckdb # voor lokaal testen # Controleer installatie dbt --version # dbt Core: 1.8.x | Adapter: dbt-snowflake: 1.8.x # Nieuw project aanmaken dbt init mijn_project # → kies je adapter en vul de verbindingsgegevens in cd mijn_project dbt debug # test de verbinding met je datawarehouse
dbt-duckdb voor lokaal ontwikkelen zonder cloud kosten. Switch naar de productie-adapter zodra je klaar bent voor deployment.
Een dbt project heeft een vaste mappenstructuur. Begrijpen wat elke map doet is essentieel voor een schaalbaar project.
mijn_project/ ├── dbt_project.yml # Projectconfiguratie (naam, versie, paths) ├── profiles.yml # Verbindingsinstellingen (buiten repo!) ├── packages.yml # dbt packages (dbt_utils, dbt_expectations) │ ├── models/ # SQL-transformaties — de kern van dbt │ ├── staging/ # 1-op-1 mapping van bronnen │ │ └── stg_orders.sql │ ├── intermediate/ # Tussenliggende logica │ │ └── int_orders_enriched.sql │ └── marts/ # Eindproducten voor BI tools │ ├── core/ │ │ └── dim_customers.sql │ └── finance/ │ └── fct_revenue.sql │ ├── seeds/ # Kleine CSV bestanden → tabellen in DWH │ └── country_codes.csv │ ├── tests/ # Singular tests (custom SQL assertions) │ └── assert_orders_positive.sql │ ├── macros/ # Herbruikbare Jinja functies │ └── cents_to_euros.sql │ ├── snapshots/ # SCD Type 2 snapshots │ └── orders_snapshot.sql │ └── analyses/ # Ad-hoc analyses (niet materialiseerd)
name: 'mijn_project' version: '1.0.0' config-version: 2 profile: 'mijn_project' model-paths: ["models"] seed-paths: ["seeds"] test-paths: ["tests"] macro-paths: ["macros"] snapshot-paths: ["snapshots"] target-path: "target" clean-targets: ["target", "dbt_packages"] models: mijn_project: staging: +materialized: view # stagings zijn views +schema: staging intermediate: +materialized: ephemeral # geen tabel, inline CTE marts: +materialized: table # eindproducten zijn tabellen core: +schema: core finance: +schema: finance
Een dbt model is een .sql bestand met een SELECT statement. dbt wikkelt dit automatisch in een CREATE TABLE of CREATE VIEW op basis van de materialisatie.
-- Config block bovenaan het bestand (optioneel, overschrijft dbt_project.yml) {{ config( materialized = 'table', schema = 'core', tags = ['daily', 'finance'] ) }} with orders as ( select * from {{ ref('stg_orders') }} -- verwijzing naar een ander model ), customers as ( select * from {{ ref('dim_customers') }} ), final as ( select o.order_id, o.order_date, o.status, o.amount_cents / 100.0 as amount_euros, c.customer_name, c.country from orders o left join customers c on o.customer_id = c.customer_id ) select * from final
-- Jinja variabelen (worden gecompileerd voor uitvoering) {% set payment_methods = ['ideal', 'creditcard', 'banktransfer'] %} select order_id, {% for method in payment_methods %} sum(case when payment_method = '{{ method }}' then amount else 0 end) as {{ method }}_amount {% if not loop.last %},{% endif %} {% endfor %} from {{ ref('stg_payments') }} group by 1
ref() om naar andere dbt modellen te verwijzen. dbt bouwt hieruit de dependency graph en voert modellen in de juiste volgorde uit.
Sources zijn ruwe tabellen in je datawarehouse die door externe processen worden geladen (bijv. Fivetran, ADF, Airbyte). Je declareert ze in een schema.yml bestand.
version: 2 sources: - name: raw_salesforce database: raw_db # database naam (Snowflake) schema: salesforce loaded_at_field: _loaded_at # voor freshness checks freshness: warn_after: {count: 6, period: hour} error_after: {count: 24, period: hour} tables: - name: orders description: "Ruwe orders uit Salesforce CRM" columns: - name: id description: "Primaire sleutel" tests: - unique - not_null - name: status tests: - accepted_values: values: ['open', 'closed', 'pending']
{{
config(materialized='view')
}}
select
-- Identifiers
id as order_id,
customer_id,
-- Datums
cast(created_at as timestamp) as order_created_at,
cast(updated_at as timestamp) as order_updated_at,
-- Bedragen
amount_cents,
currency,
-- Status
lower(status) as status,
-- Metadata
_loaded_at
from {{ source('raw_salesforce', 'orders') }}
Incrementele modellen verwerken alleen nieuwe of gewijzigde records bij elke run. Dit spaart rekentijd bij grote tabellen.
{{
config(
materialized = 'incremental',
unique_key = 'order_id', # merge op basis van deze kolom
on_schema_change = 'sync_all_columns' # kolommen automatisch bijwerken
)
}}
select
order_id,
customer_id,
status,
amount_cents,
order_created_at,
order_updated_at
from {{ ref('stg_orders') }}
{% if is_incremental() %}
-- Dit blok wordt alleen uitgevoerd bij incrementele runs (niet bij eerste run)
where order_updated_at > (
select max(order_updated_at) from {{ this }}
)
{% endif %}
# Normaal: alleen nieuwe records dbt run --select fct_orders # Volledige rebuild (dropt en herbouwt de tabel) dbt run --select fct_orders --full-refresh
unique_key voert dbt een MERGE uit. Zonder unique_key wordt alleen INSERT gebruikt. Gebruik altijd een unieke sleutel voor correcte idempotentie.
Met dbt snapshots implementeer je Slowly Changing Dimension Type 2 — historische versies van records worden bewaard met dbt_valid_from en dbt_valid_to kolommen.
{% snapshot customers_snapshot %}
{{
config(
target_schema = 'snapshots',
unique_key = 'customer_id',
strategy = 'timestamp', # of 'check' voor kolom-vergelijking
updated_at = 'updated_at',
invalidate_hard_deletes = true
)
}}
select *
from {{ source('raw_salesforce', 'customers') }}
{% endsnapshot %}
# Alle snapshots uitvoeren dbt snapshot # Snapshot vervolgens in een mart gebruiken # In je model: select * from {{ ref('customers_snapshot') }} # Actieve records filteren: # where dbt_valid_to is null
| Kolom | Beschrijving |
|---|---|
| dbt_scd_id | Unieke hash per versie |
| dbt_updated_at | Tijdstip van de wijziging |
| dbt_valid_from | Begin van deze versie |
| dbt_valid_to | Einde van deze versie (NULL = actief) |
dbt heeft twee soorten tests: generic tests (gedeclareerd in YAML) en singular tests (custom SQL bestanden).
version: 2 models: - name: fct_orders description: "Feitentabel met alle orders" columns: - name: order_id description: "Primaire sleutel" tests: - unique - not_null - name: status tests: - not_null - accepted_values: values: ['open', 'closed', 'pending', 'refunded'] - name: customer_id tests: - not_null - relationships: to: ref('dim_customers') field: customer_id
-- Test faalt als deze query rijen teruggeeft select order_id, amount_cents from {{ ref('fct_orders') }} where amount_cents < 0
# Alle tests dbt test # Tests voor één model dbt test --select fct_orders # Alleen generic tests dbt test --select test_type:generic # Build: run + test in één stap dbt build --select fct_orders+
expect_column_values_to_be_between, regex checks en statistisch testen. Voeg dbt-labs/dbt_expectations toe aan packages.yml.
dbt genereert automatisch een documentatie website met een interactieve lineage graph. Beschrijvingen voeg je toe in schema.yml.
models: - name: fct_orders description: | Feitentabel met alle orders vanuit Salesforce. Bevat alle orders van status 'open' t/m 'refunded'. Wordt dagelijks bijgewerkt via incrementele refresh. meta: owner: "data-team@bedrijf.nl" contains_pii: false columns: - name: order_id description: "Unieke identifier per order (PK)" - name: amount_euros description: "Orderbedrag in euro's (geconverteerd vanuit cents)"
# Genereer documentatie dbt docs generate # Start lokale webserver dbt docs serve # → opent http://localhost:8080 in je browser # Lineage graph bekijken: # Klik op een model → klik 'Lineage' tabblad
Automatiseer je dbt runs met GitHub Actions zodat elk pull request getest wordt en productie alleen wordt bijgewerkt na goedgekeurd code review.
name: dbt CI on: pull_request: branches: [main] jobs: dbt-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Python setup uses: actions/setup-python@v5 with: python-version: '3.11' - name: Installeer dbt run: pip install dbt-snowflake - name: dbt dependencies run: dbt deps env: DBT_PROFILES_DIR: . - name: dbt build (slim CI) run: | dbt build \ --select state:modified+ \ --defer \ --state ./prod-artifacts env: SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
--select state:modified+ en --defer bouw je alleen gewijzigde modellen en hun afhankelijkheden. Dit bespaart significant op rekentijd en kosten.