Jinja và macro
Bạn sẽ học được
- Phân biệt
{{ ... }}và{% ... %}trong Jinja. - Viết macro có tham số và dùng lại ở nhiều model.
- Truyền biến bằng
--varsthay vì sửa code. - Đọc biến môi trường và tên target trong model.
- Biết khi nào không nên viết macro.
Khái niệm cốt lõi
dbt biên dịch mỗi model qua Jinja trước khi gửi SQL xuống kho. Jinja có hai loại thẻ:
| Cú pháp | Vai trò | Ví dụ |
|---|---|---|
{{ ... }} | In ra một giá trị | {{ ref('stg_orders') }} |
{% ... %} | Điều khiển luồng, không in gì | {% if is_incremental() %} |
Chỉ hai loại này là đủ để làm hầu hết việc: ref, source, config, var đều là hàm có sẵn, và mọi logic điều kiện nằm trong {% %}.
Điểm quan trọng: Jinja chạy lúc biên dịch, không chạy trong kho. {{ 1 + 1 }} cho ra chữ 2 trong câu SQL; bản thân phép cộng không được gửi xuống database. Vì vậy không thể dùng Jinja để tính toán trên dữ liệu.
Jinja tạo ra SQL. SQL mới là thứ tính toán trên dữ liệu.
Ví dụ chạy được
Biến và điều kiện
-- models/marts/fct_orders.sql
{{ config(materialized='table', tags=['daily']) }}
select
order_date,
segment,
count(*) as orders,
sum(net_amount) as revenue
from {{ ref('int_orders_enriched') }}
where order_date >= '{{ var("start_date", "2026-01-01") }}'
and order_date < '{{ var("end_date", "2027-01-01") }}'
group by 1, 2Tham số thứ hai của var là giá trị mặc định. Chạy với khoảng thời gian khác mà không sửa file:
dbt run --select fct_orders --vars '{"start_date": "2026-03-01", "end_date": "2026-04-01"}'Macro
Macro là hàm Jinja đặt trong macros/, dùng lại ở bất kỳ model nào:
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name, scale=2) -%}
round({{ column_name }} / 100.0, {{ scale }})
{%- endmacro %}Dùng trong model:
select
order_id,
{{ cents_to_dollars('amount_cents') }} as amount,
{{ cents_to_dollars('discount_cents') }} as discount,
{{ cents_to_dollars('tax_cents', 4) }} as tax
from {{ ref('stg_orders') }}Sau biên dịch, mỗi lời gọi được thay bằng biểu thức SQL tương ứng. Sửa cách đổi đơn vị tiền chỉ cần sửa một file, mọi model dùng macro đều đổi theo.
Vòng lặp
Một trường hợp hay gặp: union nhiều bảng giống nhau theo tháng.
{% set months = ['2026-01', '2026-02', '2026-03'] %}
with raw as (
{% for m in months %}
select '{{ m }}' as thang, * from {{ source('raw', 'events_' ~ m | replace('-', '_')) }}
{% if not loop.last %}union all{% endif %}
{% endfor %}
)
select thang, count(*) as so_su_kien
from raw
group by 1Vòng lặp tạo ra ba khối select ... union all, điều kiện loop.last bỏ union all ở khối cuối.
Lặp để sinh SQL cho các bảng có cấu trúc giống nhau là hợp lý. Lặp để sinh ra 50 model gần như trùng nhau là dấu hiệu nên dùng
union allvới cột nguồn, không phải Jinja.
Biến môi trường và target
select *
from {{ ref('stg_orders') }}
{% if target.name != 'prod' %}
-- trên dev chỉ lấy 1000 dòng để chạy nhanh
limit 1000
{% endif %}target.name cho biết đang chạy target nào. Đây là cách phổ biến để dev chạy nhanh mà production vẫn đầy đủ.
env_var đọc biến môi trường — thường dùng trong profiles.yml, ít khi trong model:
{{ env_var('DBT_SCHEMA') }}Gói macro dựng sẵn
Không cần viết lại mọi thứ. Khai báo packages.yml:
packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]Chạy dbt deps, sau đó dùng được:
| Macro | Công dụng |
|---|---|
dbt_utils.generate_surrogate_key([...]) | Tạo khoá thay thế từ nhiều cột |
dbt_utils.star(ref('model'), except=[...]) | Chọn mọi cột trừ vài cột |
dbt_utils.date_spine(...) | Sinh dải ngày liên tục |
dbt_utils.union_relations(...) | Union các model cùng cấu trúc |
generate_surrogate_key đặc biệt hữu ích khi bảng không có khoá đơn:
select
{{ dbt_utils.generate_surrogate_key(['order_id', 'product_id']) }} as order_item_key,
order_id,
product_id,
quantity
from {{ ref('stg_order_items') }}Kết quả là một chuỗi băm ổn định — cùng cặp order_id, product_id luôn cho cùng khoá, dùng được cho unique_key của incremental.
Khi nào không nên viết macro
Macro che giấu SQL. Người đọc model chỉ thấy {{ tinh_doanh_thu(...) }} và phải mở file macro mới hiểu. Vì vậy:
- Viết macro khi cùng một biểu thức lặp ở ba model trở lên.
- Không viết macro cho logic chỉ dùng một lần.
- Không viết macro quá 20 dòng — tách nhỏ hoặc viết SQL trực tiếp.
- Đặt tên macro mô tả kết quả, không mô tả cách làm:
net_amounttốt hơnsubtract_discount_from_amount.
Thực hành
- Viết macro
cents_to_dollarsvà dùng ở hai model. - Thêm
var('start_date')cho một mart, chạy với hai giá trị khác nhau. - Dùng
target.nameđể giới hạn 1.000 dòng trên dev. - Cài
dbt_utilsvà dùnggenerate_surrogate_keycho một bảng chi tiết. - In SQL sau biên dịch bằng
dbt compile --select fct_orders, rồi mở file trongtarget/compiled/.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
Compilation Error: unexpected end of template | Thiếu {% endif %} | Đếm cặp if/endif, for/endfor |
var not found | Không có giá trị mặc định và không truyền --vars | Thêm tham số mặc định |
| Macro không được tìm thấy | Sai tên file hoặc sai thư mục | Đặt trong macros/, chạy dbt parse |
| SQL sau biên dịch rỗng | Dùng {# ... #} nhầm chỗ hoặc {% %} in giá trị | Dùng {{ }} khi cần in |
| Test chạy 20 phút trên dev | Dev chạy toàn bộ dữ liệu | Thêm limit theo target.name |
| Hai model cùng macro ra kết quả khác | Macro nhận tham số khác nhau | Kiểm tra target/compiled |
Tóm tắt
{{ }}in giá trị,{% %}điều khiển luồng; cả hai chạy lúc biên dịch.var()cho tham số,target.namecho môi trường,env_var()cho bí mật.- Macro dùng lại logic lặp ở từ ba model trở lên.
dbt_utilscó sẵngenerate_surrogate_key,star,date_spine.- Luôn đọc file trong
target/compiled/khi nghi ngờ Jinja sinh sai.
Use ← → to move between lessons