Jinja en macro's in dbt: dynamische SQL, sjablonen en aangepaste macro's
Jinja transformeert dbt van SQL-tool naar programmeerbaar transformatieframework: variabelen, herbruikbare lussen, voorwaarden en macro's voor DRY SQL. Ontdek dbt-utils (de standaardbibliotheek) en hoe u aangepaste macro's schrijft met getypte parameters.
Jinja2 in dbt: wat het is en waarom het bestaat
dbt VS Jinja2, de Python-sjabloonengine, om programmeerbaarheid toe te voegen
naar de SQL. Alles wat tussen dubbele accolades past {{ }}
is een Jinja-uitdrukking, alles daartussenin {% %} het is
een controle-instructie (if, for, set).
Voordat elk model in het magazijn wordt uitgevoerd, moet dbt compileren de Jinja-sjabloon in pure SQL.
U kunt de gecompileerde SQL in de map zien target/compiled/ na elk dbt run.
Variabelen: var() en env_var()
dbt biedt twee functies voor toegang tot variabelen in SQL-code:
var(): Projectvariabelen
-- In dbt_project.yml puoi definire variabili globali:
# dbt_project.yml
vars:
start_date: '2024-01-01'
lookback_days: 30
payment_methods: ['credit_card', 'paypal', 'bank_transfer']
-- Usale nei modelli con var():
SELECT *
FROM {{ ref('stg_orders') }}
WHERE created_at >= '{{ var("start_date") }}'::date
-- Puoi sovrascrivere una variabile da CLI:
-- dbt run --vars '{"start_date": "2025-01-01", "lookback_days": 7}'
env_var(): Omgevingsvariabelen
-- Accedi alle variabili d'ambiente del sistema
SELECT *
FROM {{ source('raw', 'events') }}
WHERE environment = '{{ env_var("DBT_ENVIRONMENT", "development") }}'
-- Il secondo parametro è il valore di default (opzionale)
-- Nei profiles.yml per le credenziali (prattica consigliata):
# profiles.yml
my_profile:
outputs:
prod:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
if/else-voorwaarden in sjablonen
Met voorwaardelijke instructies van Jinja kunt u modellen schrijven die zich anders gedragen op basis van context (omgeving, variabelen, type materialisatie):
-- models/marts/finance/orders_with_taxes.sql
-- Logica di calcolo tasse diversa per paese
SELECT
order_id,
customer_id,
total_amount,
{% if var("target_market") == "US" %}
total_amount * 0.08 AS tax_amount, -- aliquota USA semplificata
{% elif var("target_market") == "IT" %}
total_amount * 0.22 AS tax_amount, -- IVA italiana
{% else %}
total_amount * 0.20 AS tax_amount, -- aliquota default EU
{% endif %}
total_amount + tax_amount AS total_with_tax
FROM {{ ref('stg_orders') }}
WHERE status = 'completed'
is_incremental(): Fundamenteel patroon
De ingebouwde macro is_incremental() het wordt gebruikt in incrementele modellen om toe te voegen
het tijdelijke filter alleen wanneer het model in incrementele modus wordt uitgevoerd
(niet volledig vernieuwd):
-- models/marts/events_daily.sql
{{ config(materialized='incremental', unique_key='event_date') }}
SELECT
DATE_TRUNC('day', event_timestamp) AS event_date,
event_type,
COUNT(*) AS event_count,
COUNT(DISTINCT user_id) AS unique_users
FROM {{ ref('stg_events') }}
-- Questo blocco viene incluso SOLO nelle esecuzioni incrementali
{% if is_incremental() %}
WHERE event_timestamp > (SELECT MAX(event_date) FROM {{ this }})
{% endif %}
GROUP BY 1, 2
Lus voor: dynamische SQL-generatie
Jinja-loops zijn zeer krachtig voor het genereren van repetitieve SQL zonder kopiëren en plakken:
-- Genera colonne per i giorni della settimana dinamicamente
SELECT
customer_id,
order_date,
{% for day_num in range(1, 8) %}
SUM(CASE WHEN DAYOFWEEK(order_date) = {{ day_num }}
THEN total_amount
ELSE 0 END) AS revenue_day_{{ day_num }}
{% if not loop.last %},{% endif %}
{% endfor %}
FROM {{ ref('stg_orders') }}
GROUP BY 1, 2
-- Pivot di metriche da una lista di variabile
{% set metrics = ['revenue', 'order_count', 'avg_order_value'] %}
SELECT
month,
region,
{% for metric in metrics %}
SUM(CASE WHEN metric_name = '{{ metric }}' THEN metric_value END) AS {{ metric }}
{%- if not loop.last %},{% endif %}
{% endfor %}
FROM {{ ref('metrics_unpivoted') }}
GROUP BY 1, 2
Macro's: herbruikbare SQL-functies
Macro's zijn het mechanisme voor hergebruik van code in dbt: Jinja-functies die parameters gebruiken
en retourneer SQL. Ze gaan naar de directory macros/.
Eenvoudige macro: opschoning van nulwaarden
-- macros/utils/safe_divide.sql
-- Divisione sicura che evita division by zero
{% macro safe_divide(numerator, denominator, default_value=0) %}
CASE
WHEN {{ denominator }} = 0 OR {{ denominator }} IS NULL
THEN {{ default_value }}
ELSE {{ numerator }} / {{ denominator }}
END
{% endmacro %}
-- Utilizzo nel modello:
SELECT
customer_id,
total_revenue,
order_count,
{{ safe_divide('total_revenue', 'order_count') }} AS avg_order_value
FROM {{ ref('customer_summary') }}
Geavanceerde macro: dynamische UNION ALL-generatie
-- macros/union_relations.sql
-- Crea UNION ALL da una lista di ref()
{% macro union_all_tables(relations) %}
{% for relation in relations %}
SELECT
'{{ relation }}' AS source_table,
*
FROM {{ ref(relation) }}
{% if not loop.last %}UNION ALL{% endif %}
{% endfor %}
{% endmacro %}
-- Utilizzo:
-- {{ union_all_tables(['events_jan', 'events_feb', 'events_mar']) }}
Macro met run_query(): Het magazijn bevragen in macro's
-- macros/get_column_values.sql
-- Recupera valori distinti da una colonna per uso in loop
{% macro get_column_values(table, column) %}
{% set query %}
SELECT DISTINCT {{ column }}
FROM {{ ref(table) }}
ORDER BY 1
{% endset %}
{% set results = run_query(query) %}
{% if execute %} -- execute è False durante la fase di parsing
{% set values = results.columns[0].values() %}
{% do return(values) %}
{% else %}
{% do return([]) %}
{% endif %}
{% endmacro %}
-- Utilizzo per un pivot dinamico:
{% set regions = get_column_values('stg_orders', 'region') %}
SELECT
order_date,
{% for region in regions %}
SUM(CASE WHEN region = '{{ region }}' THEN revenue END) AS revenue_{{ region | lower | replace(' ', '_') }}
{%- if not loop.last %},{% endif %}
{% endfor %}
FROM {{ ref('orders_daily') }}
GROUP BY 1
dbt-utils: de standaardbibliotheek
dbt-utils het is het meest gebruikte pakket in het dbt-ecosysteem. Het biedt algemene macro's die elk project waarschijnlijk helemaal opnieuw zou uitvinden:
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
# Installa con:
# dbt deps
De meest gebruikte dbt-utils-macro's
-- 1. generate_surrogate_key: chiave surrogata da più colonne (hash MD5)
SELECT
{{ dbt_utils.generate_surrogate_key(['order_id', 'customer_id']) }} AS sk,
order_id,
customer_id
FROM {{ ref('stg_orders') }}
-- 2. unpivot: trasforma colonne in righe (simile a UNPIVOT SQL)
{{ dbt_utils.unpivot(
relation=ref('orders_pivoted'),
cast_to='float',
exclude=['order_date', 'customer_id'],
field_name='metric_name',
value_name='metric_value'
) }}
-- 3. date_spine: genera una sequenza di date continua (per riempire i gap)
WITH date_spine AS (
{{ dbt_utils.date_spine(
datepart="day",
start_date="cast('2024-01-01' as date)",
end_date="current_date"
) }}
),
orders AS (
SELECT DATE_TRUNC('day', created_at) AS order_date, SUM(amount) AS revenue
FROM {{ ref('stg_orders') }}
GROUP BY 1
)
-- LEFT JOIN per avere 0 anche nei giorni senza ordini
SELECT
d.date_day,
COALESCE(o.revenue, 0) AS revenue
FROM date_spine d
LEFT JOIN orders o ON d.date_day = o.order_date
-- 4. pivot: trasforma righe in colonne
{{ dbt_utils.pivot(
column='status',
values=['completed', 'pending', 'cancelled'],
agg='count',
then_value='order_id'
) }}
Beste praktijken voor macro's
Richtlijnen voor kwaliteitsmacro's
-
VS
if executevoor macro's die query's uitvoeren: de DAG komt meerdere keren geparseerd en niet alle fasen vereisen daadwerkelijke uitvoering - Macro's documenteren op dezelfde manier als sjablonen: dbt zal genereren documentatie ook voor macro's met Jinja-docstrings
- Geef de voorkeur aan geconsolideerde pakketten (dbt-utils, dbt-verwachtingen) naar het wiel opnieuw uitvinden – worden getest door duizenden projecten
- Houd macro's eenvoudig: als een macro moeilijk te lezen is, het is waarschijnlijk beter om het te splitsen of de logica als expliciete SQL in de sjabloon uit te drukken
Anti-patroon: overontwikkelde macro's
De meest voorkomende fout is om voor alles macro's te gebruiken. Macro's voegen lagen toe indirectheid die de code minder leesbaar maakt. Gebruik ze voor echt herbruikbare logica (3+ toepassingen in het project). Voor SQL die één of twee keer wordt gebruikt, is de expliciete code dat wel beter onderhoudbaar.
Conclusies en volgende stappen
Met Jinja en macro's is dbt niet langer een eenvoudige SQL-runner, maar wordt het een raamwerk van programmeerbare transformatie. Variabelen maken modellen aanpasbaar aan omgevingen, d.w.z lussen elimineren herhaling, macro's kapselen herbruikbare logica in.
Het volgende artikel behandelt een cruciaal onderwerp voor prestatie in de productie: de materialisaties. Wanneer moet u weergaven, tabellen, incrementele modellen en momentopnamen gebruiken? en hoe u voor elke dataset de juiste strategie kiest.







