Jinja și macrocomenzi în dbt: SQL dinamic, șabloane și macrocomenzi personalizate
Jinja transformă dbt din instrumentul SQL în cadru de transformare programabil: variabile, bucle reutilizabile, condiții și macrocomenzi pentru DRY SQL. Explorați dbt-utils (biblioteca standard) și cum să scrieți macrocomenzi personalizate cu parametrii tastați.
Jinja2 în dbt: Ce este și de ce există
dbt usa Jinja2, motorul de șabloane Python, pentru a adăuga programabilitate
la SQL. Tot ce se potrivește între paranteze duble {{ }}
este o expresie Jinja, totul între ele {% %} este
o instrucțiune de control (dacă, pentru, set).
Înainte de a rula fiecare model din depozit, dbt compila șablonul Jinja în SQL pur.
Puteți vedea SQL-ul compilat în folder target/compiled/ după fiecare dbt run.
Variabile: var() și env_var()
dbt oferă două funcții pentru accesarea variabilelor în codul SQL:
var(): variabile de proiect
-- 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(): Variabile de mediu
-- 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') }}"
condiții dacă/altfel în Șabloane
Instrucțiunile condiționale Jinja vă permit să scrieți modele care se comportă diferit pe baza contextului (mediu, variabile, tip de materializare):
-- 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(): model fundamental
Macro-ul încorporat is_incremental() este folosit în modelele incrementale pentru a adăuga
filtrul temporal numai atunci când modelul este rulat în modul incremental
(nu este reîmprospătat complet):
-- 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
Buclă pentru: Dynamic SQL Generation
Buclele Jinja sunt foarte puternice pentru a genera SQL repetitiv fără copiere și lipire:
-- 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-uri: Funcții SQL reutilizabile
Macro-urile sunt mecanismul de reutilizare a codului în funcțiile dbt: Jinja care preiau parametri
și returnează SQL. Ei intră în director macros/.
Macrocomandă simplă: curățarea valorii nule
-- 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') }}
Macro avansate: Dynamic UNION ALL Generation
-- 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 cu run_query(): Interogarea depozitului în macrocomenzi
-- 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: Biblioteca standard
dbt-utils este cel mai folosit pachet din ecosistemul dbt. Oferă macrocomenzi comune pe care orice proiect le-ar reinventa probabil de la zero:
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
# Installa con:
# dbt deps
Cele mai utilizate macrocomenzi dbt-utils
-- 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'
) }}
Cele mai bune practici pentru macrocomenzi
Ghid pentru macrocomenzi de calitate
-
STATELE UNITE ALE AMERICII
if executepentru macrocomenzi care execută interogări: vine DAG analizate de mai multe ori și nu toate fazele necesită execuție efectivă - Macrocomenzi pentru documente în același mod ca și șabloanele — va genera dbt documentație și pentru macrocomenzi cu documente Jinja
- Preferați pachetele consolidate (dbt-utils, dbt-expectations) la reinventarea roții — sunt testate de mii de proiecte
- Păstrați macrocomenzi simple: dacă o macrocomandă este greu de citit, probabil că este mai bine să-l împărțiți sau să exprimați logica ca SQL explicit în șablon
Anti-model: Macro-uri supra-proiectate
Cea mai frecventă greșeală este să folosiți macrocomenzi pentru orice. Macro-urile adaugă straturi de indirectă care face codul mai puțin lizibil. Folosiți-le pentru o logică cu adevărat reutilizabilă (3+ utilizări în proiect). Pentru SQL folosit o dată sau de două ori, codul explicit este mai întreținută.
Concluzii și pașii următori
Cu Jinja și macrocomenzi, dbt încetează să mai fie un simplu runner SQL și devine un cadru de transformare programabilă. Variabilele fac modelele adaptabile la medii, de exemplu buclele elimină repetiția, macrourile încapsulează logica reutilizabilă.
Următorul articol abordează un subiect crucial pentru performanța în producție: the materializări. Când să utilizați vizualizări, tabele, modele incrementale și instantanee — și cum să alegeți strategia potrivită pentru fiecare set de date.







