dbt 是什麼?用寫程式的方式做資料轉換

· tech

#dbt#data-engineering#analytics-engineering

dbt 是什麼

一句話:dbt(data build tool)是一個讓你「用一堆 SQL SELECT 把資料倉儲裡的原始資料,轉換成乾淨、可信、可重用的模型表」的框架 —— 而且把寫程式的紀律(版控、模組化、測試、文件、CI)帶進這層轉換。

關鍵認知:dbt 自己不搬資料、也不運算。它把你的 SQL 編譯好、丟進資料倉儲(BigQuery / Snowflake / Redshift / Databricks / Postgres)去執行。它負責的是 ELT 裡的 T(Transform)

它在資料流程的哪個位置

Data Warehouse 來源 DB / API 原始層 raw tables 建模層 staging / marts BI / ML 報表 / 模型 EL 載入 dbt:SQL
先把原始資料載進倉儲(EL),dbt 再在倉儲「內部」用 SQL 把它轉成建模層

這正是 ELT 裡的那個 T 在工具上的落地,也是 Medallion 那套 Bronze → Silver → Gold 分層實際的建法 —— 一層 model 疊一層 model,把原始資料逐步煉成可用的模型。

核心概念

概念是什麼
Model一個 .sql 檔 = 一句 SELECT = 在倉儲裡被建成一張 table 或 view
ref()在 model 裡引用另一個 model;dbt 靠它建出相依圖、決定 build 順序、自動換成各環境的真實表名
source()宣告外部原始表,可做新鮮度(freshness)檢查
Materializationmodel 怎麼落地:view / table / incremental / ephemeral
JinjaSQL 裡的模板語言(變數、迴圈、macro),編譯成最終 SQL
Test資料測試:uniquenot_nullaccepted_valuesrelationships,寫在 YAML
Seed / SnapshotCSV 灌成表 / 記錄維度的歷史變化(SCD type-2)
Docs + Lineage自動產生 model 之間的血緣關係圖與文件

一個 model 怎麼接另一個:ref() 建出血緣

dbt 最核心的機制是 ref():你在一個 model 裡 ref('另一個 model'),dbt 就知道它們的相依,自動排出建置順序、畫出 lineage。

source: raw.orders source: raw.customers stg_orders stg_customers fct_orders
ref() / source() 自動建出這張血緣圖,dbt 依它決定先建 staging、再建 fct_orders

範例(dbt 的語言是 SQL + YAML,不是 Python)

dbt 的 model 就是 SQL SELECT;測試、來源宣告寫在 YAML;模板用 Jinja。(它也支援 Python models 跑在 Snowflake/Databricks 上,但核心與日常九成是 SQL。)

Model:一句 SELECT,用 ref() 串起相依

-- models/marts/fct_orders.sql
{{ config(materialized='table') }}

with orders as (
    select * from {{ ref('stg_orders') }}
),
customers as (
    select * from {{ ref('stg_customers') }}
)
select
    o.order_id,
    o.order_date,
    c.customer_name,
    o.amount
from orders o
join customers c on o.customer_id = c.customer_id
where o.status = 'paid'

{{ ref('stg_orders') }} 在編譯時會換成該 model 在當前環境的真實表名(dev / prod 自動不同),同時告訴 dbt「fct_orders 相依於 stg_orders」。{{ config(...) }} 則指定這個 model 要落地成一張實體 table。

測試與來源:寫在 YAML

# models/marts/schema.yml
version: 2

sources:
  - name: raw
    tables:
      - name: orders
      - name: customers

models:
  - name: fct_orders
    description: "每筆已付款訂單一列"
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: customer_name
        tests:
          - not_null

這幾行就讓 dbt 在每次跑測試時,自動檢查 order_id 真的唯一且不為空 —— 測的是資料,不是程式碼。這是 dbt 最被低估的價值。

一鍵照血緣順序執行

dbt run     # 依血緣順序建/更新所有 model
dbt test    # 跑所有資料測試
dbt build   # run + test + snapshot + seed,一條龍照 DAG 跑

反思

dbt 的價值不是 SQL,是「把分析層當成正式工程」

在 dbt 之前,資料轉換邏輯常常散在 stored procedure、零散腳本、或 BI 工具的設定裡:沒有版控、沒有測試、沒有血緣,改一個欄位不知道會炸到哪。dbt 真正帶來的是把這層變成一個能 review、能測試、能追血緣的程式碼庫。對我來說這才是重點 —— 它賣的不是「用 SQL 轉資料」(那本來就會),而是工程紀律

它不是運算引擎,也不是排程器 —— 別搞混

最常見的誤解是期待 dbt 去抽資料、或自己排程。它兩者都不做:它只編譯 SQL、推給倉儲執行。所以實務上它要跟另外兩塊搭:用 EL 工具(Fivetran / Airbyte)把資料載進來、用 Airflow 來定時觸發 dbt build。而當轉換大到 SQL 倉儲吃不下、需要分散式引擎時,那是 Spark 的場;dbt 則專心做「SQL 能表達、倉儲跑得動」的那一段。三者是互補,不是競爭。

這次我的採用門檻比較低

AirflowSpark 時我都說「別太早上、先確認痛點」,因為它們的複雜度與維運成本高。dbt 剛好相反:如果你的轉換本來就是 SQL、又跑在資料倉儲上,dbt 的導入成本其實很低(裝起來就能用、學習曲線平),換來的是可測試、有版控、自動產文件與血緣的轉換層。投報率高、下檔風險小,所以這是少數我會說「幾乎可以直接上」的工具。

最該先用起來的是 test 與 ref()

如果只能挑兩個功能先落地,我會選 ref() 和 test。ref() 給你血緣 —— 改一個 model 前能看清下游會受影響的有誰;test 則攔住「資料悄悄壞掉」這種最難抓的 bug(程式沒錯、但數字錯)。這兩個合起來,就把分析從「能跑就好」推向「可信」。