dbt 数据建模:ELT 中的转换层

FreeGuideOnline 最新 2026-06-17

dbt 数据建模:ELT 中的转换层完全指南

引言:为什么 dbt 成为现代数据仓库的转换标准

在当今的数据分析领域,数据建模早已不是传统 ETL 工具中“先提取再转换”的单一流程。随着云数据仓库(如 Snowflake、BigQuery、Redshift)的崛起,ELT(提取、加载、转换) 模式成为主流。在 ELT 中,原始数据直接加载到目标仓库,随后再由团队使用 SQL 进行转换。

dbt(data build tool) 正是这一转换层的核心工具。它让分析师和工程师能够用大家都会的 SQL,通过软件工程实践(版本控制、测试、文档)构建和维护数据模型。本教程将带你从零开始,理解 dbt 数据建模的核心概念、项目结构、建模方式与最佳实践。


dbt 的数据建模哲学:从原始数据到业务逻辑

dbt 在 ELT 流程中的角色

在 ELT 架构中,dbt 只负责 T(转换)。它连接到你已加载好数据的仓库,执行你编写的 SQL 模型,将底层表逐步转化为面向分析的干净、聚合的数据集。

dbt 最强大的地方在于它带来了 数据即代码 的理念:

  • 你的数据模型存放在 Git 仓库中,可协作、可审查。
  • 每次运行都会生成文档(dbt docs)和测试结果(dbt test)。
  • 自动处理依赖关系,确保父模型先运行。

建模的核心:SELECT 语句的模块化

dbt 模型本质上就是 .sql 文件中的一条 SELECT 语句。但这些 SELECT 可以相互引用,形成有向无环图(DAG)。

示例模型 customer_orders.sql

WITH customers AS (
    SELECT * FROM {{ ref('stg_customers') }}
),
orders AS (
    SELECT * FROM {{ ref('stg_orders') }}
)
SELECT
    c.customer_id,
    c.name,
    COUNT(o.order_id) AS order_count,
    SUM(o.amount) AS total_revenue
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY 1, 2

其中 {{ ref('...') }} 是 dbt 内置的依赖引用,dbt 会自动解析成正确的表名,并构建执行顺序。


搭建 dbt 项目的基础结构

创建一个 dbt 项目

安装 dbt(以 dbt-core + adaptor 为例,如 dbt-snowflake)后,使用命令初始化:

dbt init my_project

这会生成如下核心目录结构:

my_project/
├── models/        # 存放SQL模型
├── tests/         # 自定义测试(可选)
├── macros/        # 可复用的Jinja宏
├── analysis/      # 探索性查询(不构建到仓库)
├── seeds/         # CSV种子数据(按需)
├── dbt_project.yml
└── profiles.yml   # 连接配置(通常在 ~/.dbt/ 下)

配置 dbt_project.yml

该文件定义项目名称、版本、模型物化策略等:

name: my_project
version: 1.0.0
profile: default

models:
  my_project:
    staging:
      materialized: view
    marts:
      materialized: table

这里可以按文件夹设置不同的物化方式,例如临时区使用视图,最终分析层使用表以提高查询性能。

理解物化策略

dbt 提供多种物化方式:

  • view:每次查询时动态生成,节省存储但查询可能较慢。
  • table:物理创建表,查询快但占用存储,适合最终暴露给 BI 的表。
  • incremental:仅更新新数据,极大提升大表转换性能。
  • ephemeral:不建表,相当于公用表表达式(CTE),用于中间逻辑复用。

选择合适的物化策略是数据建模性能优化的关键一步。


核心建模技术:分层架构与依赖管理

推荐的分层建模方法

优秀的 dbt 项目通常采用 源层 → 准备层 → 业务层 的清晰结构:

  1. Sources(源表):在 models/sources.yml 声明仓库中已存在的原始表。
  2. Staging(准备层):对源表进行最小化清洗,如重命名列、转换类型,保持一对一关系,命名为 stg_
  3. Intermediate(中间层,可选):复杂逻辑的分解体,通常以 int_ 开头。
  4. Marts(业务层):面向业务领域的最终宽表或聚合表,如 fct_salesdim_customers

声明数据源(Sources)

models/staging/sources.yml

version: 2
sources:
  - name: raw
    database: raw_db
    schema: public
    tables:
      - name: orders
      - name: customers

之后在模型中用 {{ source('raw', 'orders') }} 引用,带来两个好处:

  • 自动生成源表血缘文档。
  • 可通过 dbt source freshness 检测数据新鲜度。

编写 Staging 模型

例如 models/staging/stg_orders.sql

SELECT
    id AS order_id,
    user_id AS customer_id,
    CAST(order_date AS date) AS order_date,
    COALESCE(amount, 0) AS amount
FROM {{ source('raw', 'orders') }}

这个模型做了干净的重命名和类型转换,成为下游模型唯一依赖的基础表,实现代码复用与一致性。

构建 Marts 模型(星型或宽表模式)

以 Kimball 星型模型为例,可以创建维度和事实表:

  • dim_customers.sql:客户维度,包含所有客户属性。
  • fct_orders.sql:订单事实表,包含外键和度量值。

举例 dim_customers.sql

SELECT
    customer_id,
    name,
    email,
    first_order_date
FROM {{ ref('stg_customers') }}

fct_orders.sql

SELECT
    order_id,
    order_date,
    customer_id,
    product_id,
    amount
FROM {{ ref('stg_orders') }}

通过 ref() 形成依赖链,dbt 运行时会按正确顺序执行,并支持并行。


数据测试与文档:保障模型质量

内置测试

dbt 提供简单但强大的测试类型,可直接在 schema.yml 中配置:

  • unique:确保列值唯一。
  • not_null:确保列无空值。
  • accepted_values:限定列值在枚举列表中。
  • relationships:确保外键引用完整性。

示例 models/marts/schema.yml

version: 2
models:
  - name: dim_customers
    columns:
      - name: customer_id
        tests:
          - unique
          - not_null
      - name: email
        tests:
          - not_null
  - name: fct_orders
    columns:
      - name: customer_id
        tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id

运行 dbt test 即可对所有模型执行这些断言,不合格的会自动报错。

自定义测试

你可以编写 SQL 文件放在 tests/ 目录下,返回任何不满足条件的行。例如测试订单金额不能为负数:

-- tests/assert_positive_amount.sql
SELECT order_id, amount
FROM {{ ref('fct_orders') }}
WHERE amount < 0

如果该查询返回任何行,测试失败。

文档与血统图

在模型或列的 .yml 中添加 description 字段,然后执行:

dbt docs generate
dbt docs serve

即可在本地浏览器查看交互式的项目文档,包含表说明、列描述、完整 DAG 血缘线图。这是数据团队沟通和新人上手的利器。


进阶建模技巧:宏、增量与快照

使用 Jinja 宏(Macros)

宏是可复用的 SQL 片段,类似于函数。dbt 自带许多宏,如生成日期数列。你可以在 macros/ 目录下自定义宏。

例如创建一个 clean_phone 宏:

{% macro clean_phone(phone_column) %}
    regexp_replace({{ phone_column }}, '[^0-9]', '', 'g')
{% endmacro %}

在模型中使用:

SELECT
    customer_id,
    {{ clean_phone('phone') }} AS phone_clean
FROM {{ ref('stg_customers') }}

增量模型(Incremental Models)

对于超大表,每次全量重建代价高昂。可以在模型顶部加入条件判断:

{{
    config(
        materialized='incremental',
        unique_key='order_id'
    )
}}
SELECT ...
FROM {{ ref('stg_orders') }}
{% if is_incremental() %}
  WHERE order_date > (SELECT MAX(order_date) FROM {{ this }})
{% endif %}

is_incremental() 在非首次运行时为 True,实现对增量数据的处理。务必指定 unique_key 以支持合并更新。

数据快照(Snapshots)

快照用于追踪缓慢变化维度(SCD),即数据的历史状态。例如客户地址的变化:

{% snapshot customer_snapshot %}
{{
    config(
        target_schema='snapshots',
        unique_key='customer_id',
        strategy='check',
        check_cols=['address']
    )
}}
SELECT * FROM {{ ref('stg_customers') }}
{% endsnapshot %}

每次运行,dbt 会对比 check_cols 指定的列,如有变化则插入新行记录历史,并加上 dbt_valid_fromdbt_valid_to 字段。


部署与生产环境最佳实践

使用 dbt Cloud 或 CI/CD 流水线

本地开发只需 dbt run,但在生产环境中,通常集成 dbt Cloud 或使用 GitHub Actions、Airflow 等编排调度。关键步骤:

  1. 代码合并到主分支后,触发 CI 流程。
  2. 运行 dbt test 确保无数据问题。
  3. 运行 dbt run 刷新生产模型。
  4. 生成并发布文档。

环境区分

使用 target 变量区分开发与生产环境,在 profiles.yml 中配置不同的连接。生产目标设为 prod,避免误操作。

模型监控与报警

可以结合 dbt 元数据(如 dbt build 的结果)将任务状态发送到 Slack 或 Datadog,实时掌握数据流水线健康度。


总结:dbt 建模的未来趋势

dbt 不仅是一个 SQL 执行器,更是数据分析工程化的催化剂。通过模块化建模、分层设计、自动化测试和清晰的文档,团队能够以更低的成本维护高质量的数据仓库。

随着 dbt Mesh语义层 等概念的引入,dbt 正从单纯的转换层向全链路的数据治理平台演进。掌握本教程介绍的建模基础,将为你深入数据工程与分析的职业道路打下坚实根基。

立即动手,用 dbt init 开始你的第一个模型吧!