dbt Model 與 SQL 編譯
在 dbt 的世界裡,Model 是最核心的基本單位。簡單來說,一個 Model 就是一個寫著 SQL SELECT 語句的 .sql 檔案。
這裡有幾個重要的規則:
- Model 檔案必須放在
models/目錄下。 - 一個檔案對應一個 Model (即一張 Table 或 View)。
- 檔案中只需要寫
SELECT語句,不需要寫CREATE TABLE或INSERT INTO(這些 dbt 會幫你處理)。
建立你的第一個 Model
假設我們要建立一個名為 customers 的 model。請在 models/ 資料夾中建立一個檔案 customers.sql,內容如下:
-- models/customers.sql
with source_data as (
select 1 as id, 'Alice' as name
union all
select 2 as id, 'Bob' as name
)
select *
from source_data
這就是一個最基本的 dbt model。
執行 dbt run
建立好 model 後,我們要告訴 dbt 去執行它。在終端機輸入:
dbt run
當你執行 dbt run 時,dbt 會做以下幾件事:
- 讀取
models/下的所有.sql檔案。 - 將你的
SELECT語句編譯,包裹上CREATE VIEW as ...或CREATE TABLE as ...的 DDL (Data Definition Language)。 - 依照設定的
profile,連線到資料庫並按照順序執行這些 SQL。
預設情況下,dbt 會將 model 建立為 View (視圖)。執行成功後,你可以到資料庫中檢查,應該會看到一個名為 customers 的 view。
模組化與 ref() 函數
dbt 最強大的功能之一就是 ref() 函數。它讓我們可以在 model 之間建立參照關係,而不需要寫死資料表的名稱 (如 my_schema.customers)。
為什麼要用 ref()?
- 自動建立相依性 (Lineage):dbt 會解析
ref(),知道哪個 model 應該先被執行。 - 環境隔離:在開發環境 (Dev) 和生產環境 (Prod),schema 名稱通常不同。
ref()會根據當前環境自動編譯出正確的資料表全名 (如dbt_alice.customers或analytics.customers)。
範例:建立相依的 Model
現在我們建立第二個 model,名為 stg_customers.sql,這次讓它參照上面的 customers model:
-- models/stg_customers.sql
select
id,
name,
'Active' as status
from {{ ref('customers') }}
注意我們使用了 {{ ref('customers') }} 來代替直接寫表名。這裡的語法 {{ ... }} 是 Jinja 模板語言。
再次執行 dbt run:
dbt run
dbt 會自動偵測到 stg_customers 依賴於 customers,因此會確保 customers 先執行成功,才接著執行 stg_customers。這就構成了一個 DAG (有向無環圖)。
定義 Model 的資源屬性 (properties file)
寫好了 SQL 邏輯 (.sql),我們還需要一個地方來定義 Model 的「屬性」,例如:這張表在做什麼?欄位有哪些?主鍵是什麼?
這就是 Properties YAML 檔案 (通常命名為 schema.yml) 的用途。
在 dbt 中,我們習慣在同一個資料夾下建立一個 schema.yml 檔案,用來描述該資料夾內的 models:
version: 2
models:
- name: customers # 必須對應到 customers.sql 的檔名
description: '客戶資料表,包含基本資料與狀態'
columns:
- name: id
description: '客戶唯一識別碼 (PK)'
tests: # 定義資料品質測試
- unique
- not_null
- name: name
description: '客戶姓名'
這個檔案結合了三大功能:
- 文件化 (Documentation):寫在這裡的
description會自動生成精美的 dbt 文件網站。 - 資料品質測試 (Testing):透過
tests設定,讓 dbt 自動檢查資料 (如:確保id不重複且非空值)。 - 屬性設定:除了文件與測試,也能在此設定其他的 model 配置。
SQL 負責「邏輯」,YAML 負責「描述與規範」。兩者結合,才是一個完整、高品質的 data model。
查看編譯後的 SQL
如果你好奇 dbt 到底傳了什麼 SQL 給資料庫執行,可以查看 target/compiled/ 和 target/run/ 目錄。
target/compile/: 存放 dbt 解析 Jinja 後的純 SQLSELECT語句。target/run/: 存放實際執行到資料庫的 DDL (包含create view ...等)。
打開 target/run/my_dbt_project/models/stg_customers.sql,你可能會看到類似這樣的內容:
create view "my_database"."dbt_alice"."stg_customers" as (
select
id,
name,
'Active' as status
from "my_database"."dbt_alice"."customers"
);
這就是 dbt 在背後默默幫你完成的工作!
Model 命名慣例 (Naming Convention)
隨著專案越來越大,擁有良好的命名規範至關重要。這能幫助團隊快速理解 model 的用途與層級。
在實務上,你可能在 staging (原始資料清洗) 和 marts (業務邏輯層) 處理的是同一份業務實體 (例如 Orders),如果都叫 orders.sql,不僅容易搞混,在 IDE 中切換檔案也看不清楚。
為了避免衝突並保持清晰,dbt 社群最推薦的解法是使用 前綴 (Prefix) 區分層級:
Staging 層 (原始層)
- 前綴:
stg_ - 範例:
stg_orders.sql,stg_stripe_payments.sql - 用途:一對一對應 source table,僅做輕量清洗 (改名、轉型別),不做複雜 join。
Intermediate 層 (中間層)
- 前綴:
int_ - 範例:
int_orders_joined.sql,int_user_payment_history.sql - 用途:處理複雜的 join 或聚合邏輯,但還不是最終報表。
Marts 層 (實體層 / 商業邏輯層)
- 前綴:
fct_(Fact) 或dim_(Dimension) - 範例:
fct_orders.sql:事實表,記錄交易事件。dim_customers.sql:維度表,記錄人或物的屬性。
- 用途:最終呈現給 BI 工具或是分析師使用的乾淨資料表。
透過這種命名方式,你在查看 DAG 或檔案列表時,一眼就能知道這個 model 處於資料流的哪個階段。