「dbt+SQLServer构建数据仓库」是一套基于真实项目的实战学习笔记,记录我从零开始在 SQL Server 上用 dbt 搭建分层式数仓的完整过程。本文是第 1 篇,回答两个问题:dbt 到底是什么 、一个 dbt 项目从开发到上线经历哪些环节。
一、dbt 的定位
SQL Server 在国内企业数仓里占有率很高,围绕它的传统数仓栈已经非常成熟:SSIS 做 ETL、存储过程做转换、SQL Server Agent 做调度、SSAS 做 OLAP、SSRS/Power BI 做报表。这套栈运行了二十年,稳定可靠。
但近年 dbt 异军突起,把"数据转换"这件事重新定义成了软件工程问题。很多团队在问:
- dbt 是来替代 SSIS 的吗?
- 我们已经有一堆存储过程,迁到 dbt 值得吗?
- dbt 在 SQL Server 上能跑通吗?
要回答这些对比性问题,得先理清 dbt 的定位和工作方式。本文专注于把 dbt 本身讲透,对比和迁移决策留到下篇。
二、dbt 是什么
2.1 一句话定位
dbt 是一个让数据分析师用软件工程的方式编写数据转换的命令行工具。
关键词拆解:
- 数据转换:dbt 只管 "ELT" 里的 "T"(Transform),不管 E(Extract)和 L(Load)。数据怎么进库,dbt 不管;进了库之后怎么加工成业务表,dbt 管。
- 软件工程方式:版本控制、模块化、测试、文档、CI/CD,这些软件工程师习以为常的东西,dbt 把它们带进了 SQL 世界。
- 命令行工具 :dbt 是 CLI,不是 IDE、不是服务、不是数据库。
pip install dbt-core装好就能跑。
2.2 dbt 在数据栈中的位置
┌──────────────────────────────────────────────────────────┐
│ 数据源: MySQL / Oracle / API / 文件 / 日志 │
└──────────────────────────────────────────────────────────┘
│
│ E (Extract) + L (Load)
│ 由 Fivetran / Airbyte / 自研 ETL 负责
▼
┌──────────────────────────────────────────────────────────┐
│ 数据仓库 / 数据湖: SQL Server / Snowflake / BigQuery │
│ (原始数据已落库) │
└──────────────────────────────────────────────────────────┘
│
│ T (Transform) ← dbt 在这里!
▼
┌──────────────────────────────────────────────────────────┐
│ 转换后的数据: dim/fact 表, 供 BI 消费 │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ BI 工具: Power BI / Tableau / Looker / Metabase │
└──────────────────────────────────────────────────────────┘
关键认知 :dbt 不替代数据库,也不替代 ETL 工具,它只替代"在数据库里写转换逻辑"这一段。所以 dbt 是增量引入而非全盘替换,这是它相对 SSIS 的重要差异------具体对比见下篇。
2.3 核心概念速览
| 概念 | 含义 | 传统 SQL Server 类比 |
|---|---|---|
| model | 一个 .sql 文件 = 一个转换 |
一个存储过程 / 一个视图 |
| ref() | 引用另一个 model,自动建依赖 | 手写 INSERT INTO ... SELECT FROM ... |
| source() | 声明外部源表 | 直接 FROM schema.table |
| materialization | 物化策略(view/table/incremental) | 手动决定建视图还是建表 |
| test | YAML 声明数据质量校验 | 手写 IF EXISTS ... RAISERROR |
| snapshot | SCD2 历史拉链表 | 手写 MERGE + 历史表 |
| macro | 可复用 Jinja 片段 | 标量函数 / 动态 SQL |
| seed | CSV 加载成表 | BULK INSERT / BCP |
这套概念体系是 dbt 的"语法骨架",后续所有工程化能力(测试、文档、CI)都建立在这之上。
三、dbt 项目的工作流程
3.1 开发循环(Develop Loop)
dbt 的日常开发是一个紧凑的反馈环:
┌─────────────────────────────────────┐
│ 1. 写/改 model SQL (含 ref/source) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. dbt run --select <model> │ ← 只跑改动的模型
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. dbt test --select <model> │ ← 验证数据质量
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 4. 查结果 / 看编译 SQL / 改 schema │
└─────────────────────────────────────┘
│
└──→ 回到 1
这个循环通常每人每天跑几十次,每次几秒到几十秒。与传统"改存储过程 → 部署 → 等调度 → 看结果"的小时级反馈相比,体验是数量级的提升(反馈速度的详细对比见下篇第五节)。
3.2 项目生命周期
一个 dbt 项目从诞生到上线,经历这几个阶段:
阶段一:初始化
bash
dbt init my_project # 或手动建目录
# 配置 ~/.dbt/profiles.yml
dbt debug # 验证连接
产物:dbt_project.yml + 目录骨架。
阶段二:开发
按分层架构写 model:
models/
├── staging/ ← 1:1 投影源表, 重命名+类型
├── intermediate/ ← (可选) 中间计算
└── marts/ ← 业务维度/事实表
每个 model 是一个 .sql 文件,用 {{ ref('xxx') }} 串起来。dbt 解析所有 ref() 自动生成 DAG。
阶段三:测试
在 schema.yml 里声明测试:
yaml
models:
- name: dim_customers
columns:
- name: customer_id
tests: [unique, not_null]
dbt test 自动生成校验 SQL 并执行。失败的测试会阻断 CI(如果配了)。
阶段四:文档
bash
dbt docs generate
dbt docs serve
生成一个可点击的网站,含模型描述、字段说明、DAG 血缘图。文档从代码生成,永远和代码同步。
阶段五:部署
dbt 本身不调度,部署方式有三种:
- 手动 CLI :
dbt run在服务器上 cron 跑(简单项目) - dbt Cloud:官方托管,自带调度、CI、文档(省心但收费)
- 外部编排 :Airflow / Prefect / Dagster 调用
dbt run(企业主流)
阶段六:监控
dbt run --store-failures:测试失败的行存到表里供排查- 解析
target/run_results.json:拿运行时长、状态做监控大盘 - 接入 Sentry / Datadog:异常告警
3.3 一次 dbt run 内部发生了什么
理解 dbt 的执行模型,有助于和传统方案对比。当你敲下 dbt run:
1. 解析阶段 (parse)
├─ 读 dbt_project.yml + 所有 .sql/.yml
├─ 解析 ref() / source() 依赖
└─ 构建 DAG (有向无环图)
2. 规划阶段 (plan)
├─ 拓扑排序 DAG
├─ 根据 --select 过滤要跑的节点
└─ 按 threads 并发分组
3. 编译阶段 (compile)
├─ Jinja 渲染 {{ ref('x') }} → 实际 schema.table
├─ 适配器方言转换 (SQL Server 的 T-SQL)
└─ 写入 target/compiled/
4. 执行阶段 (execute)
├─ 通过 adapter (pyodbc) 连数据库
├─ 按 DAG 顺序执行: create view / create table as ...
├─ 记录每个节点的状态/时长
└─ 写入 target/run_results.json
关键点:第 3 步编译是 dbt 的核心魔法------你写的 SQL 是"模板",dbt 把它编译成目标库的真实方言。这就是为什么同一个 dbt 项目能在 SQL Server / Snowflake / BigQuery 之间相对容易地迁移。
四、小结
本文把 dbt 的定位和工作流程讲清楚了:
- dbt 只做 ELT 里的 T,不替代数据库也不替代 ETL 工具,是增量引入而非全盘替换。
- 核心概念八件套(model/ref/source/materialization/test/snapshot/macro/seed)构成了 dbt 的语法骨架。
- 开发循环是秒级反馈 :
run --select+test --select每天几十次,体验远超传统方案。 dbt run内部四步(parse → plan → compile → execute)中,compile 阶段的方言编译是跨库迁移的关键。
但这只是 dbt 单方面的故事。要决定"要不要用 dbt",还得把它和团队现有的 SQL Server 传统方案摆在一起逐维度对照------这正是下一篇要做的事。