dbt 是什么:Data Engineering Zoomcamp 中的 SQL 转换工作流工具与 ELT 实践指南
【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
导读:本文基于 Data Engineering Zoomcamp 模块 4 的课程笔记 4_1_2_what_is_dbt.md,系统讲解 dbt 的核心定位、解决的问题、运行机制,以及 dbt Core 与 dbt Cloud 两条使用路径。结合本仓库中完整的 dbt 项目 taxi_rides_ny 与 本地 DuckDB + dbt Core 搭建指南,你将理解 dbt 如何充当 ELT 中的 "T",并掌握从零构建分层数据模型(staging → intermediate → marts)的完整思路。
dbt 是什么:数据仓库之上的转换层
dbt(data build tool)是一个转换工作流工具(transformation workflow tool)。它不负责数据的抽取(Extract)和加载(Load),而是"坐在"数据仓库之上,把原始数据转换为下游消费者(分析师、BI 工具、ML 管道)真正可用的干净、结构化数据。
在实际的公司环境中,数据来自四面八方:后端系统、前端应用、第三方 API(如天气数据)。这些数据被加载进数据仓库(BigQuery、Snowflake、Databricks 等),而 dbt 就是负责把这堆原始数据"加工"成业务能消费形态的那一层。
一个关键概念需要先厘清:本模块采用ELT(Extract → Load → Transform)范式——先把原始数据全部加载进仓库,再在仓库内部完成转换。dbt 恰好落在 ELT 的 "T" 上:它用 SQL 在数据仓库内部运行转换逻辑。这套范式之所以成为主流,正是云数据仓库让存储变得足够便宜,可以先"全部加载,之后再想怎么转换"。
你只需要用 SQL(或 Python)定义转换逻辑,剩下的交给 dbt:
- 编译 SQL(解析
ref()、source()、Jinja 宏等一切引用) - 把编译后的 SQL 发送给数据仓库执行
- 把结果物化为表(table)、视图(view)、增量表(incremental table)或临时 CTE(ephemeral)
你不需要自己写CREATE TABLE语句,只写SELECT,dbt 负责其余的一切。
dbt 解决的痛点:把软件工程最佳实践带入分析代码
转换这一步历来都存在,但传统分析工作流中缺失的是工程化。dbt 带来的核心价值,是让分析师和数据工程师像软件工程师写代码一样写 SQL:
- 版本控制(Version control)——转换逻辑像普通代码一样托管在 git 中,可追溯、可评审、可回滚;
- 模块化(Modularity)——把复杂逻辑拆成可复用的组件,而不是巨型面条式 SQL;
- 测试(Testing)——每次部署自动运行数据质量检查,而不是靠人工抽查;
- 文档(Documentation)——从代码自动生成,而不是一份迟早过期的独立 Wiki;
- 多环境(Environments)——开发与生产分离,每个开发者拥有独立沙箱,互不踩踏;
- CI/CD——带校验与回滚的自动化部署。
最终结果是更高质量的管道:更易维护、更少在生产环境"翻车"。
这套理念在仓库中能直接看到落地痕迹:taxi_rides_ny项目把模型按 staging / intermediate / marts 分层组织,每层都有独立的 schema 描述与测试声明(见下文"仓库中的 dbt 项目"一节)。
dbt 的工作机制:从dbt run到物化结果
当你执行dbt run,dbt 会依次做三件事:
- 编译 SQL——解析
ref()调用、source()调用、Jinja 宏等一切模板逻辑; - 把编译后的 SQL 发送到数据仓库执行;
- 物化结果——按你的配置生成表、视图、增量表或临时 CTE。
物化策略在仓库中的体现
物化方式由配置文件声明,而不是写在 SQL 里。看 dbt_project.yml 中的项目级默认配置:
models: taxi_rides_ny: staging: +materialized: view # 分层模型用视图,不占存储 intermediate: +materialized: table # 中间层物化为表 marts: +materialized: table # 面向消费的表分层默认值不同是刻意的:staging 层是源表的 1:1 轻清理拷贝,用视图即可;intermediate 与 marts 承载复杂逻辑与最终消费结果,需要物化为表保证查询性能。
单模型也可以覆盖默认配置。例如事实表 fct_trips.sql 在模型头部用config()声明了增量物化:
{{ config( materialized='incremental', unique_key='trip_id', incremental_strategy='merge', on_schema_change='append_new_columns' ) }}并在 SQL 末尾利用is_incremental()只处理新增数据:
{% if is_incremental() %} -- Only process new trips based on pickup datetime where trips.pickup_datetime > (select max(pickup_datetime) from {{ this }}) {% endif %}{{ this }}指向当前模型对应的目标表。这是 dbt 增量管道处理海量数据的典型写法——首次dbt run全量构建,后续运行只追加新数据,这正是课程笔记所述"dbt 处理依赖、持久化结果"机制的实战形态。
Jinja 模板与宏
dbt 的编译能力来自 Jinja 模板引擎。宏(macro)是可复用的 SQL 片段,如同 Python 函数。仓库中的 safe_cast.sql 就是一个简洁实例:
{% macro safe_cast(column, data_type) %} {% if target.type == 'bigquery' %} safe_cast({{ column }} as {{ data_type }}) {% else %} cast({{ column }} as {{ data_type }}) {% endif %} {% endmacro %}这个宏根据目标数据库类型(target.type)自动选择SAFE_CAST还是CAST,让同一套模型代码可以跨 BigQuery 与 DuckDB 运行——这也为下文"两条课程路径"提供了技术基础。宏在 stg_green_tripdata.sql 中被实际调用,例如{{ safe_cast('ratecodeid', 'integer') }} as rate_code_id。
dbt Core vs dbt Cloud:两种使用方式
dbt 有两种使用形态,理解其差异是选择路径的前提。
dbt Core:开源引擎,完全掌控
- 开源免费,本地安装,终端运行命令;
- 你需要自己负责:开发环境搭建、生产运行编排(Airflow、cron 等)、文档托管、日志与元数据管理。
它是"裸引擎":给你完全的控制权,但周边的配套基础设施要自己搭。
dbt Cloud:托管的 SaaS 产品
- 底层仍运行 dbt Core,但替你管理外围设施:
- 基于 Web 的 IDE(或本地开发的 Cloud CLI);
- 环境管理(dev/staging/prod 全托管);
- 内置编排(任务调度、触发器、依赖);
- 托管文档(自动生成并发布);
- 日志与可观测性;
- 管理与元数据访问的 API;
- 指标语义层(如需要)。
免费 Developer 计划适合小团队或个人学习;更大规模则是付费产品。
课程的两条实操路径
Zoomcamp 提供两条路径,视频会在两者间切换:
选项 A:BigQuery + dbt Cloud(推荐)
- 数据仓库:BigQuery(前几周已配置);
- dbt:dbt Cloud Developer 计划(免费账号 + Web IDE);
- 无需本地安装。
这是多数视频采用的路径,上手最快,也最接近团队在生产环境使用 dbt 的真实方式。
选项 B:DuckDB + dbt Core
- 数据仓库:DuckDB(本地);
- dbt:本地安装 dbt Core;
- 开发环境:自己的 IDE(VS Code 等);
- 编排:需要自行处理(Airflow、Prefect 等)。
这条路径掌控感更强,但配置工作量更大。仓库中的 local_setup.md 就是这条路径的完整落地指南,核心步骤如下:
安装与配置:
pip install dbt-duckdb该命令会同时安装dbt-core(核心框架)与dbt-duckdb(DuckDB 适配器)。随后在~/.dbt/profiles.yml中配置连接,注意本仓库已自带 dbt 项目(taxi_rides_ny/),无需运行dbt init:
taxi_rides_ny: target: dev outputs: dev: type: duckdb path: taxi_rides_ny.duckdb schema: dev threads: 1 extensions: - parquet settings: memory_limit: '2GB' preserve_insertion_order: false配置说明:path指定本地 DuckDB 数据库文件;schema区分开发/生产目标;extensions启用 parquet 支持;memory_limit控制内存上限(小于 4GB 内存建议降为 '1GB',16GB 以上可升到 '4GB')。
数据准备与验证:下载 2019-2020 年 yellow/green 出租车数据并转换为 Parquet,写入prodschema;然后用dbt debug验证连接是否正常。所有 dbt 命令必须在taxi_rides_ny/目录内运行。
仓库中的 dbt 项目:理论如何落地
模块结束时,你将构建出这样一套系统(对应课程笔记描述的"项目流程"):
- 原始数据已在仓库中——前几周的行程数据,外加一个演示多源 join 的查找表(taxi zone lookup seed);
- dbt 转换把这些原始数据按 4.1.1 的维度建模思想整理成规范的模型;
- 仪表盘消费最终输出,服务业务决策。
仓库项目 taxi_rides_ny 完整体现了这套流程,其目录结构正是 dbt 的标准三层模型组织方式。
目录结构速览
taxi_rides_ny/ ├── dbt_project.yml # 项目配置:profile、路径、物化默认值、变量 ├── packages.yml # 外部包:dbt_utils、codegen ├── macros/ # 可复用 SQL 宏(如 safe_cast) ├── models/ │ ├── staging/ # 源表 1:1 清理 + 源定义 │ │ ├── sources.yml # 声明 raw 源表(BigQuery/DuckDB 双适配) │ │ ├── schema.yml # 列描述 + 测试(not_null) │ │ ├── stg_green_tripdata.sql │ │ └── stg_yellow_tripdata.sql │ ├── intermediate/ # 非原始、非最终暴露的中间逻辑 │ │ └── int_trips_unioned.sql │ └── marts/ # 面向消费的最终模型 │ ├── fct_trips.sql # 事实表(增量物化) │ ├── dim_zones.sql # 维度表 │ └── schema.yml ├── seeds/ # CSV 查找表(taxi_zone_lookup) ├── snapshots/ # 慢变化维度历史记录 └── tests/ # 自定义 SQL 测试更详细的逐目录说明见课程笔记 4_3_1_dbt_project_structure.md。
分层模型与ref()/source()的区分
- staging 层:sources.yml 声明原始表位置(并用 Jinja 根据
target.type在 BigQuery 的nytaxischema 与 DuckDB 的prodschema 间切换),staging 模型做 1:1 清理——修类型、改名、过滤空行,例如 stg_green_tripdata.sql 中的where vendorid is not null数据质量过滤; - intermediate 层:int_trips_unioned.sql 用
union all合并 yellow 与 green 两个 staging 模型,并为各自打上service_type标签; - marts 层:dim_zones.sql 是简单的维度表透传(从
taxi_zone_lookupseed 读取),fct_trips.sql 则把行程事实表与维度表 LEFT JOIN,形成经典的星型模型。
这里有一个关键区分(详见 4_4_1_dbt_models.md):
{{ source('name', 'table') }}→ 引用源 YAML 中声明的原始表(在 dbt 之外存在);{{ ref('model_name') }}→ 引用另一个 dbt 模型。
ref()还有一个隐藏红利:它自动构建依赖图。如果模型 Bref()了模型 A,dbt 就知道 A 必须先运行,你永远不需要手工维护执行顺序。
星型模型:事实表 + 维度表
遵循 4.1.1 的 Kimball 维度建模,marts 层产出两类表:
- 事实表(fact)——记录业务事件,一行为一个事件,用
fct_前缀(如fct_trips:一行一次行程,yellow + green 合并); - 维度表(dimension)——描述事实的上下文,用
dim_前缀(如dim_zones、dim_vendors)。
星型模型的力量在于"有多少?"类问题变得微不足道:有多少个 zone?→ 对dim_zones做COUNT(*);有多少次行程?→ 对fct_trips做COUNT(*)。简单、聚焦的表,需要复杂分析时再 join。
测试、种子与快照
- 测试:schema.yml 与 staging/schema.yml 中声明
not_null等通用测试(如对vendor_id、pickup_datetime);tests/目录可放自定义 SQL 断言——查询返回多于零行即构建失败; - 种子(seeds):CSV 查找表快速导入,适合 lookup 表与原型验证,
taxi_zone_lookup即用于dim_zones; - 快照(snapshots):当源表列会自我覆盖、但你需要保留历史时使用(如订单状态变更记录)。
外部包声明在 packages.yml:dbt-labs/dbt_utils与dbt-labs/codegen,前者提供通用工具宏,后者可基于现有数据库结构自动生成模型代码。
小结
dbt 的本质是一台"SQL 转换引擎 + 工程化套件":你写SELECT,它负责编译、执行、物化与依赖管理;你享受版本控制、测试、文档、环境隔离与 CI/CD,而不必手写 DDL。无论选择 BigQuery + dbt Cloud 的托管路径,还是 DuckDB + dbt Core 的本地掌控路径,其核心心智模型一致——以 staging → intermediate → marts 的分层组织,用星型模型(fct_/dim_)把原始数据打磨成业务可直接消费的资产。接下来的视频将带你一步步搭建这套体系,本仓库的 taxi_rides_ny 项目正是这条路的完整终点样板。
【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考