dbt Model 與 SQL 編譯

在 dbt 的世界裡,Model 是最核心的基本單位。簡單來說,一個 Model 就是一個寫著 SQL SELECT 語句的 .sql 檔案

這裡有幾個重要的規則:

  1. Model 檔案必須放在 models/ 目錄下。
  2. 一個檔案對應一個 Model (即一張 Table 或 View)。
  3. 檔案中只需要SELECT 語句,不需要寫 CREATE TABLEINSERT 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 會做以下幾件事:

  1. 讀取 models/ 下的所有 .sql 檔案。
  2. 將你的 SELECT 語句編譯,包裹上 CREATE VIEW as ...CREATE TABLE as ... 的 DDL (Data Definition Language)。
  3. 依照設定的 profile,連線到資料庫並按照順序執行這些 SQL。

預設情況下,dbt 會將 model 建立為 View (視圖)。執行成功後,你可以到資料庫中檢查,應該會看到一個名為 customers 的 view。

模組化與 ref() 函數

dbt 最強大的功能之一就是 ref() 函數。它讓我們可以在 model 之間建立參照關係,而不需要寫死資料表的名稱 (如 my_schema.customers)。

為什麼要用 ref()?

  1. 自動建立相依性 (Lineage):dbt 會解析 ref(),知道哪個 model 應該先被執行。
  2. 環境隔離:在開發環境 (Dev) 和生產環境 (Prod),schema 名稱通常不同。ref() 會根據當前環境自動編譯出正確的資料表全名 (如 dbt_alice.customersanalytics.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: '客戶姓名'

這個檔案結合了三大功能:

  1. 文件化 (Documentation):寫在這裡的 description 會自動生成精美的 dbt 文件網站。
  2. 資料品質測試 (Testing):透過 tests 設定,讓 dbt 自動檢查資料 (如:確保 id 不重複且非空值)。
  3. 屬性設定:除了文件與測試,也能在此設定其他的 model 配置。

SQL 負責「邏輯」,YAML 負責「描述與規範」。兩者結合,才是一個完整、高品質的 data model。

dbt 並不在乎檔名,只要它在 dbt_project.yml 指定的 model-paths 路徑下,且格式正確,dbt 都能讀取。在大型專案中,我們通常會按目錄拆分,例如 stg_models.yml、marts_models.yml。

查看編譯後的 SQL

如果你好奇 dbt 到底傳了什麼 SQL 給資料庫執行,可以查看 target/compiled/target/run/ 目錄。

  • target/compile/: 存放 dbt 解析 Jinja 後的純 SQL SELECT 語句。
  • 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 處於資料流的哪個階段。