Jinja2 dbt'de: Nedir ve Neden Vardır

ABD Jinja2Programlanabilirlik eklemek için Python şablon oluşturma motoru SQL'e. Çift süslü parantezlerin arasına uyan her şey {{ }} bir Jinja ifadesidir, aradaki her şey {% %} öyle bir kontrol ifadesi (if, for, set).

Her modeli depoda çalıştırmadan önce dbt derlemek saf SQL'deki Jinja şablonu. Derlenmiş SQL'i klasörde görebilirsiniz target/compiled/ her birinin ardından dbt run.

Değişkenler: var() ve env_var()

dbt, SQL kodundaki değişkenlere erişmek için iki işlev sağlar:

var(): Proje değişkenleri

-- 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(): Ortam Değişkenleri

-- 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') }}"

Şablonlardaki if/else koşulları

Jinja koşullu ifadeleri farklı davranan modeller yazmanıza olanak tanır bağlama dayalı olarak (ortam, değişkenler, gerçekleştirilme türü):

-- 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(): Temel Desen

Yerleşik makro is_incremental() artımlı modellerde eklemek için kullanılır geçici filtre yalnızca model artımlı modda çalıştırıldığında (tam yenilemede değil):

-- 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

Döngü: Dinamik SQL Oluşturma

Jinja döngüleri, kopyalayıp yapıştırmadan tekrarlayan SQL oluşturmak için çok güçlüdür:

-- 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

Makrolar: Yeniden Kullanılabilir SQL İşlevleri

Makrolar dbt'deki kodun yeniden kullanım mekanizmasıdır: Parametre alan Jinja işlevleri ve SQL'i döndürün. Dizine giriyorlar macros/.

Basit Makro: Boş Değer Temizleme

-- 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') }}

Gelişmiş Makro: Dinamik UNION ALL Nesli

-- 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']) }}

run_query() ile Makro: Depoyu Makrolarda Sorgulama

-- 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: Standart Kütüphane

dbt-utils dbt ekosisteminde en çok kullanılan pakettir. Herhangi bir projenin muhtemelen sıfırdan yeniden keşfedeceği ortak makrolar sağlar:

# packages.yml
packages:
  - package: dbt-labs/dbt_utils
    version: 1.3.0

# Installa con:
# dbt deps

En Çok Kullanılan dbt-utils Makroları

-- 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'
) }}

Makrolar için En İyi Uygulamalar

Kalite Makrolarına Yönelik Yönergeler

  • Amerika if execute sorguları yürüten makrolar için: DAG gelir birkaç kez ayrıştırılır ve tüm aşamalar fiili yürütme gerektirmez
  • Belge makroları şablonlarla aynı şekilde — dbt oluşturacaktır Jinja docstring'lerine sahip makrolar için de belgeler
  • Birleştirilmiş paketleri tercih edin (dbt-utils, dbt-beklentiler) Tekerleğin yeniden icadı — binlerce proje tarafından test edilmiştir
  • Makroları basit tutun: Bir makronun okunması zorsa, muhtemelen onu bölmek veya mantığı şablonda açık SQL olarak ifade etmek daha iyidir

Desen önleme: Aşırı mühendislik gerektiren makrolar

En sık yapılan hata her şey için makro kullanmaktır. Makrolar katmanlar ekler Kodu daha az okunabilir hale getiren dolaylılık. Bunları gerçekten yeniden kullanılabilir mantık için kullanın (Projede 3+ kullanım). Bir veya iki kez kullanılan SQL için açık kod şöyledir: daha sürdürülebilir.

Sonuçlar ve Sonraki Adımlar

Jinja ve makrolarla dbt, basit bir SQL çalıştırıcısı olmaktan çıkıp bir çerçeve haline gelir programlanabilir dönüşüm Değişkenler modelleri ortamlara uyarlanabilir hale getirir. döngüler tekrarı ortadan kaldırır, makrolar yeniden kullanılabilir mantığı kapsar.

Bir sonraki makale üretimde performansa ilişkin çok önemli bir konuyu ele alıyor: materyalizasyonlar. Görünümler, tablolar, artımlı modeller ve anlık görüntüler ne zaman kullanılmalı? ve her veri kümesi için doğru stratejinin nasıl seçileceği.