dbt Jinja 與 Macros:提升程式碼重用性
什麼是 Jinja?
你可能已經注意到,dbt 的 SQL 檔案中經常出現 {{ ... }} 這樣的符號。這其實是 Jinja,一個 Python 的模板語言。
dbt 使用 Jinja 來讓 SQL 變得動態化。SQL 本身是靜態的,但加上 Jinja 後,我們可以:
- 使用變數 (Variables)
- 使用控制結構 (if/else, for loops)
- 引用環境變數 (Environment Variables)
基本語法
{{ ... }}: 表達式 (Expressions),會將結果輸出到 SQL 中。例如{{ ref('users') }}。{% ... %}: 語句 (Statements),用於控制邏輯,不會直接輸出。例如{% if ... %}。{# ... #}: 註解 (Comments),不會出現在編譯後的 SQL 中。
範例:使用迴圈
假設我們要對這三個付款方式欄位做加總,不想手寫三次:
{% set payment_methods = ["bank_transfer", "credit_card", "gift_card"] %}
select
order_id,
{% for payment_method in payment_methods %}
sum(case when payment_method = '{{ payment_method }}' then amount else 0 end) as {{ payment_method }}_amount,
{% endfor %}
sum(amount) as total_amount
from {{ ref('raw_payments') }}
group by 1
編譯後的 SQL 會自動展開成三行 sum case when,非常方便!
進階 Jinja 技巧
除了基本的變數與迴圈,還有幾個在專案中非常常用的技巧:
使用變數 (Variables)
你可以定義全域變數,讓多個 model 共用同一個值 (例如:開始日期、稅率)。
設定變數 (dbt_project.yml):
vars:
start_date: '2023-01-01'
使用變數 (model.sql):
select *
from {{ ref('orders') }}
where created_at >= '{{ var("start_date") }}'
此外,你也可以在 CLI 執行時動態覆蓋變數,這在修復資料 (Backfill) 時非常有用:
dbt run --vars '{"start_date": "2022-01-01"}'
資料安全性:環境變數 (Environment Variables)
永遠不要將密碼或敏感 Token 寫死在程式碼中。dbt 提供了 env_var() 函數來讀取系統環境變數。
-- profiles.yml 範例
pass: "{{ env_var('DBT_PASSWORD') }}"
空白字元控制 (Whitespace Control)
Jinja 預設會保留程式碼中的換行與空白。如果你在意編譯出來的 SQL 是否美觀,可以使用減號 - 來去除空白。
{%- ... %}:去除左邊的空白/換行{% ... -%}:去除右邊的空白/換行
例如:
{%- for col in ['id', 'name'] -%}
{{ col }},
{%- endfor -%}
編譯結果會變成緊湊的 id,name,,而不會有一堆空行。
什麼是 Macros?
Macros 就像是程式語言中的 Function (函式)。它允許我們將常用的 SQL 片段封裝起來,在不同的 models 中重複使用。
定義 Macro
Macros 必須定義在 macros/ 目錄下的 .sql 檔案中。
範例:建立一個 macros/cents_to_dollars.sql,用來將美分轉成美元:
{% macro cents_to_dollars(column_name, scale=2) %}
({{ column_name }} / 100)::numeric(16, {{ scale }})
{% endmacro %}
使用 Macro
在任何 model 中,就可以像呼叫函數一樣使用它:
select
id,
{{ cents_to_dollars('amount_cents') }} as amount_usd,
{{ cents_to_dollars('tax_cents', scale=4) }} as tax_usd
from {{ ref('payments') }}
編譯後的 SQL:
select
id,
(amount_cents / 100)::numeric(16, 2) as amount_usd,
(tax_cents / 100)::numeric(16, 4) as tax_usd
from ...
常用的內建 Macros
dbt 已經內建了許多好用的 macros,不需要自己重寫:
{{ return(foo) }}: 回傳值。{{ log("message", info=True) }}: 在 CLI 輸出 log 訊息 (debug 用)。{{ run_query(sql) }}: 直接執行一段 SQL 並取得結果 (進階用法)。
總結
Jinja 與 Macros 是區分「會寫 SQL」與「精通 dbt」的分水嶺。善用它們可以大幅減少重複程式碼 (DRY),讓你的專案更整潔、更好維護。