dbt+SQLServer构建数据仓库(1):认识dbt与项目工作流程

「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 项目从诞生到上线,经历这几个阶段:

阶段一:初始化

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 里声明测试:

models:
  - name: dim_customers
    columns:
      - name: customer_id
        tests: [unique, not_null]

dbt test 自动生成校验 SQL 并执行。失败的测试会阻断 CI(如果配了)。

阶段四:文档

dbt docs generate
dbt docs serve

生成一个可点击的网站,含模型描述、字段说明、DAG 血缘图。文档从代码生成,永远和代码同步

阶段五:部署

dbt 本身不调度,部署方式有三种:

  1. 手动 CLI:dbt run 在服务器上 cron 跑(简单项目)
  2. dbt Cloud:官方托管,自带调度、CI、文档(省心但收费)
  3. 外部编排: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 的定位和工作流程讲清楚了:

  1. dbt 只做 ELT 里的 T,不替代数据库也不替代 ETL 工具,是增量引入而非全盘替换。
  2. 核心概念八件套(model/ref/source/materialization/test/snapshot/macro/seed)构成了 dbt 的语法骨架。
  3. 开发循环是秒级反馈:run --select + test --select 每天几十次,体验远超传统方案。
  4. dbt run 内部四步(parse → plan → compile → execute)中,compile 阶段的方言编译是跨库迁移的关键。

但这只是 dbt 单方面的故事。要决定"要不要用 dbt",还得把它和团队现有的 SQL Server 传统方案摆在一起逐维度对照——这正是下一篇要做的事。

posted on 2026-08-04 10:40  哥本哈士奇(aspnetx)  阅读(58)  评论(0)    收藏  举报

导航