dbt(資料建構工具)
dbt 使用 SQL 管理倉儲中的資料轉換。v2.0 引入了 Fusion Engine(Rust)以提升效能。
使用時機
- 分析工程與資料轉換:在現代雲端倉儲(Snowflake、BigQuery、Databricks、Redshift)內部轉換原始資料。
- 使用 Jinja 與版本控制的模組化 SQL:透過 DAG 依賴與巨集建構可重複使用的 SQL 模型。
- 資料品質與結構測試:強制執行
unique、not_null、參考完整性以及自訂的單數測試。 - 自動化資料文件與血緣:直接從程式碼庫結構產生互動式依賴血緣圖。
快速開始
-- models/marts/fct_orders.sql
{{ config(materialized='table') }}
with orders as (
select * from {{ ref('stg_orders') }}
),
payments as (
select * from {{ ref('stg_payments') }}
)
select
orders.order_id,
orders.customer_id,
orders.order_date,
coalesce(payments.amount, 0) as total_amount
from orders
left join payments using (order_id)
核心概念
#使用 ref() 與 source() 進行 Declarative SQL 建模
建立具有依賴解析的轉換模型:
-- models/marts/core/fct_customer_orders.sql
{{
config(
materialized = 'incremental',
unique_key = 'order_id',
on_schema_change = 'fail'
)
}}
with orders as (
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
where order_date >= (select coalesce(max(order_date), '1970-01-01') from {{ this }})
{% endif %}
),
customers as (
select * from {{ ref('stg_customers') }}
)
select
o.order_id,
o.customer_id,
c.full_name as customer_name,
o.order_date,
o.total_amount_usd
from orders o
inner join customers c on o.customer_id = c.customer_id
#在 YAML 中進行結構測試與文件
強制執行欄位限制與文件:
# models/marts/core/schema.yml
version: 2
models:
- name: fct_customer_orders
description: "將暫存訂單與客戶維度結合的每日客戶訂單事實表。"
columns:
- name: order_id
description: "訂單的主鍵。"
tests:
- unique
- not_null
- name: customer_id
description: "參照暫存客戶的外鍵。"
tests:
- not_null
- relationships:
to: ref('stg_customers')
field: customer_id
- name: total_amount_usd
description: "以美元計價的交易總額。"
tests:
- not_null
#用於 DRY 可重複使用邏輯的 Jinja 巨集
建立自訂的可重複使用 SQL 工具:
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name, decimal_places=2) -%}
round(cast(({{ column_name }} / 100.0) as numeric), {{ decimal_places }})
{%- endmacro %}
-- 在模型中使用:
select
id,
{{ cents_to_dollars('amount_in_cents') }} as amount_usd
from {{ ref('stg_payments') }}
常見模式
使用時間戳過濾的增量模型
問題:每次執行 dbt 時從頭重建數十億行資料表既慢又昂貴。
解決方案:
使用 incremental 實體化搭配 is_incremental():
{{ config(
materialized='incremental',
unique_key='event_id'
) }}
select * from {{ source('raw', 'events') }}
{% if is_incremental() %}
-- 僅查詢在目標表中最新事件之後建立的行
where event_timestamp > (select max(event_timestamp) from {{ this }})
{% endif %}
最佳實踐(2026)
- 務必將專案組織成標準層級:
staging(與原始來源 1 對 1)、intermediate和marts。 - 務必始終使用
{{ ref('model_name') }}和{{ source('source_name', 'table_name') }}來維護 DAG 血緣。 - 務必為數百萬行的事實表實作增量模型(
materialized='incremental')。 - 務必在每次拉取請求的 CI 管線中執行
dbt test,以捕捉資料結構回歸。 - 切勿編寫原始的資料庫表參照(例如
analytics.raw.users);務必使用source()或ref()。 - 切勿在 staging 模型中執行業務邏輯;staging 應僅負責清理、型別轉換和重新命名欄位。
- 切勿硬編碼環境(開發環境 vs 正式環境);在 profiles.yml 中使用
target.name條件式。
故障排除
| 錯誤 | 原因 | 解決方案 |
|---|---|---|
Compilation Error: Model '...' depends on a node named '...' which was not found | {{ ref('model_name') }} 拼寫錯誤或模型檔案遺失。 | 檢查模型檔案名稱與參照的字串完全相符。 |
dbt test failed: unique constraint violated | 非唯一聯結條件產生了重複鍵。 | 檢查 target/compiled/ 中已編譯 SQL 的重複鍵。 |
Schema change error during incremental run | 上游模型新增/刪除欄位,破壞了現有的增量表結構。 | 執行一次 --full-refresh 重建結構:dbt run --full-refresh。 |