














本文接上篇的 dbt 定位与工作流程, 讲述:dbt 和 SQL Server 上传统数仓开发相比有哪些范式差异和取舍。
上篇我们弄清了 dbt 是什么、一个 dbt 项目怎么跑起来。但"dbt 好不好"是个伪命题——好坏是相对的,得有参照系。这个参照系就是团队现在用的 SQL Server 传统数仓栈。
所以本文先描绘传统方案的典型形态和真实痛点,再从六个维度与 dbt 系统对比,最后给决策矩阵和迁移建议。本文不站队,目标是把两种范式的差异讲透,让你能根据团队情况做判断。
| 层 | 工具 | 作用 |
|---|---|---|
| ETL | SSIS (SQL Server Integration Services) | 抽取、加载、转换一体化 |
| 转换 | 存储过程 (T-SQL) | 复杂业务逻辑 |
| 调度 | SQL Server Agent | 定时跑 SSIS 包 / 存储过程 |
| OLAP | SSAS (Analysis Services) | 多维模型 / 计算 |
| 报表 | SSRS / Power BI | 报表与可视化 |
| IDE | SSMS (SQL Server Management Studio) | 主要开发环境 |
| 版本控制 | 数据库项目 + SSDT(可选) | 较弱,很多人不用 |
一个新需求的传统流程:
CREATE TABLE ...,DDL 散落各处sp_dim_customer_build 之类的过程,几百上千行 T-SQL这些不是理论问题,是项目里实打实遇到的:
IF EXISTS 检查,数据质量靠业务方投诉发现。ALTER,历史靠数据库备份。SSIS 的 .dtsx 是二进制 XML,diff 几乎不可读。下面从六个维度系统对比。每个维度都给出两者的具体形态和取舍。
这是最根本的差异。
传统存储过程(命令式):
CREATE PROCEDURE sp_build_dim_customer AS
BEGIN
TRUNCATE TABLE dim_customer;
INSERT INTO dim_customer (customer_id, first_name, last_name, ltv)
SELECT c.id, c.first_name, c.last_name, SUM(p.amount)
FROM raw_customer c
LEFT JOIN raw_order o ON c.id = o.user_id
LEFT JOIN raw_payment p ON o.id = p.order_id
WHERE p.status = 'completed'
GROUP BY c.id, c.first_name, c.last_name;
-- 手动写日志
INSERT INTO etl_log (proc_name, rows_affected, run_at)
VALUES ('sp_build_dim_customer', @@ROWCOUNT, GETDATE());
END
你显式控制每一步:清表、插入、记日志、报错处理。灵活,但样板代码多,每个过程都要重复。
dbt model(声明式):
-- models/marts/dim_customers.sql
with customers as (select * from {{ ref('stg_customers') }}),
payments as (
select customer_id, sum(amount) as ltv
from {{ ref('stg_payments') }}
where status = 'completed'
group by customer_id
)
select c.customer_id, c.first_name, c.last_name,
coalesce(p.ltv, 0) as ltv
from customers c
left join payments p on c.customer_id = p.customer_id
# dbt_project.yml 里声明物化
models:
my_project:
marts:
+materialized: table # dbt 自动处理 truncate + insert
你只声明转换逻辑和物化意图,dbt 负责:清表、建表、依赖顺序、日志、错误处理。
取舍:
pre-hook/post-hook 注入命令式逻辑,两者并非水火不容| 能力 | 传统方案 | dbt |
|---|---|---|
| 版本控制 | SSDT 数据库项目(弱采用)或裸脚本 | 原生 git,每个 .sql/.yml 都是文本 |
| 依赖管理 | 人工维护调用链 | ref() 自动解析 DAG |
| 单元/数据测试 | 手写 IF EXISTS 检查 |
内建 unique/not_null/relationships/accepted_values + 自定义 |
| CI/CD | 几乎没有,靠手动部署 | GitHub Actions/GitLab CI 跑 dbt build,PR 自动验证 |
| 文档 | Word/Confluence,与代码脱节 | dbt docs 从代码生成,含 DAG 血缘图 |
| 环境隔离 | 鄙视链:Dev < QA < Prod,迁移靠脚本 | --target dev/prod + profile 切换,schema 前缀天然隔离 |
| 代码复用 | 复制存储过程,或写标量函数(性能差) | macro 复用,编译期内联,无运行时开销 |
| 可重现性 | 同样的脚本在不同库可能跑出不同结果 | 同样的代码 + 同样的 seed → 完全一致 |
这一栏是 dbt 最大的优势所在。传统方案不是不能做这些事,而是每件都要自己搭:自己写测试框架、自己写文档生成器、自己搞 CI 脚本。dbt 开箱即用。
| 维度 | 传统方案 (SSMS + 存储过程) | dbt (CLI + 任意编辑器) |
|---|---|---|
| 编辑器 | SSMS(只能 Windows) | VS Code / Vim / 任意编辑器(跨平台) |
| 反馈速度 | 改完 → 部署 → 等调度 → 看结果(分钟~小时) | dbt run --select x(秒级) |
| 调试 | PRINT 语句 + 临时表 |
dbt compile 看生成的 SQL + dbt show --inline |
| 改动范围 | 全文搜索存储过程名找调用方 | dbt ls --select x+ 一键列出所有下游 |
| 学习材料 | MSDN 文档 + 公司内部 Wiki | 官方文档 + 社区 + 大量博客/课程 |
| 团队协作 | 锁对象、合并冲突难 | git 标准 workflow,PR review |
开发体验的差异是最直观的。dbt 让数据工程师能像软件工程师一样工作——这是它能在创业公司和云原生团队迅速普及的根本原因。
| 维度 | 传统方案 | dbt |
|---|---|---|
| 调度器 | SQL Server Agent(成熟、企业级、UI 全) | 不内置,需外接(Airflow/dbt Cloud/cron) |
| 并发执行 | Agent Job 多步串行为主 | threads: N 并发跑独立节点 |
| 增量加载 | 存储过程 + MERGE/IDENTITY 手写 | materialized=incremental + unique_key 声明式 |
| 事务 | 完整 T-SQL 事务控制(默认) | 默认 auto-commit,需 flag 启用 BEGIN/COMMIT |
| 性能调优 | 索引、分区、列存、内存优化表全可控 | 同样可控,但需通过 post-hook/原生 SQL 注入 |
| 资源隔离 | Agent Job 可绑 CPU/内存 | 依赖数据库侧的资源池 |
结论:
这是我曾经踩过的坑,值得单独提。
传统方案:直接写 T-SQL,方言就是方言,无适配问题。
dbt-sqlserver:dbt-core 是方言无关的,适配器层负责翻译。但 dbt-sqlserver 有几个 legacy 行为与 dbt-core 默认不一致:
| 行为 | dbt-core 默认 | dbt-sqlserver 默认 | 启用标准行为的 flag |
|---|---|---|---|
| schema 拼接 | target.schema + '_' + custom |
custom 直接用(丢前缀) |
dbt_sqlserver_use_default_schema_concat: true |
| 事务 | emit BEGIN TRAN/COMMIT |
auto-commit,失败不回滚 | dbt_sqlserver_use_dbt_transactions: true |
| 字符串类型 | STRING → VARCHAR(MAX) |
STRING → VARCHAR(8000) |
dbt_sqlserver_use_native_string_types: true |
我在实际使用的时候,就因为第一个坑(schema 拼接)第一次 dbt run 报错。解决方案是在 dbt_project.yml 加:
flags:
dbt_sqlserver_use_default_schema_concat: true
启示:跨适配器迁移时,generate_schema_name 这类 macro 是高风险点,务必审阅 dbt 输出的所有 WARNING。
| 维度 | 传统方案 | dbt |
|---|---|---|
| 前置知识 | T-SQL + SSIS + Agent(都是微软生态) | SQL + Jinja + YAML + 命令行 |
| 学习曲线 | SSIS 拖拽上手快,深水区深 | dbt 本身简单,Jinja/macros 是进阶难点 |
| 团队背景匹配 | DBA / 数据库开发熟悉的工具链 | 数据分析师 / 软件工程师熟悉的工具链 |
| 招聘 | 国内 SQL Server DBA 池子稳定但缩水 | dbt 人才稀缺但增长快 |
| 培训成本 | 老团队零成本 | 老团队需 1-2 个月过渡 |
| 生态惯性 | 已有 SSIS 包 / Agent Job 的历史包袱 | 新项目零负担 |
关键判断:如果团队是 DBA 主导、已有大量 SSIS 资产,全面迁移 dbt 不现实;如果是数据工程师/分析师主导、新项目起步,dbt 几乎是默认选择。
基于上面的对比,给一个决策矩阵:
是新项目吗?
├─ 是 → 团队背景?
│ ├─ 数据工程师/分析师为主 → dbt
│ └─ DBA 为主 → 传统方案 或 dbt(视学习意愿)
└─ 否(已有资产) → 资产规模?
├─ 小(<50 个过程/包) → 可考虑全量迁 dbt
├─ 中 (50-500) → 混合方案, 新需求用 dbt, 老的留着
└─ 大 (>500) → 传统方案维持, 仅新模块评估 dbt
实际项目里,纯 dbt 或纯传统方案都少见,混合方案是主流:
┌─────────────────────────────────────────────────────┐
│ 数据源 (MySQL / API / 文件) │
└─────────────────────────────────────────────────────┘
│
│ SSIS / Airbyte 做 E+L (老资产复用)
▼
┌─────────────────────────────────────────────────────┐
│ SQL Server 原始库 (raw schema) │
└─────────────────────────────────────────────────────┘
│
│ dbt 做 T (转换层, 新方式)
│ dbt_project.yml + models/
▼
┌─────────────────────────────────────────────────────┐
│ SQL Server 数仓层 (staging/marts schema) │
└─────────────────────────────────────────────────────┘
│
│ SQL Server Agent / Airflow 调度 dbt
▼
┌─────────────────────────────────────────────────────┐
│ Power BI / SSRS 消费 │
└─────────────────────────────────────────────────────┘
落地策略:
dbt run --target prod,复用成熟告警这样既享受 dbt 的工程化红利,又不浪费已有投资。
如果团队决定从传统方案迁 dbt,建议:
迁移前先在 dbt 里搭好 raw / staging / marts 三层骨架。老存储过程通常是"一锅炖"——清洗、聚合、业务逻辑混在一起。迁移时趁机分层,质量会提升一档。
迁一个 model 就配一个 schema.yml 测试。传统方案缺测试,迁移是补测试的最佳时机。没有测试的迁移等于没迁——你无法证明新逻辑和老逻辑等价。
为避免踩 schema 拼接的坑。建议 dbt-sqlserver 项目初始就把三个 flag 配上:
flags:
dbt_sqlserver_use_default_schema_concat: true
dbt_sqlserver_use_dbt_transactions: true
dbt_sqlserver_use_native_string_types: true
避免后续逐个踩雷。
第一阶段:SQL Server Agent 调 dbt run(命令行包一层)
第二阶段:引入 Airflow / Dagster,统一调度新老资产
第三阶段:老存储过程逐步迁成 dbt model,最终下线 Agent Job
把全文浓缩成几条:
dbt_project.yml 配齐三个 flag。| 维度 | 传统 SQL Server 方案 | dbt 方案 |
|---|---|---|
| 转换单元 | 存储过程 / 视图 | model (.sql) |
| 依赖管理 | 人工维护 | ref() 自动 DAG |
| 物化决策 | 手写 DDL | +materialized 配置 |
| 数据测试 | 手写 IF EXISTS |
YAML 声明 + 内建 generic tests |
| 增量加载 | 手写 MERGE | materialized=incremental |
| 历史拉链 | 手写 SCD2 表 + MERGE | snapshot 资源 |
| 代码复用 | 复制 / 标量函数 | macro (Jinja) |
| 调度 | SQL Server Agent | 外接 (Airflow / dbt Cloud) |
| 版本控制 | SSDT(弱) | git 原生 |
| 文档 | Word/Confluence | dbt docs 自动生成 |
| IDE | SSMS (Windows only) | 任意编辑器 (跨平台) |
| 调试 | PRINT + 临时表 | dbt compile + dbt show |
| 环境隔离 | 多库实例 | --target + schema 前缀 |
| 方言 | T-SQL 原生 | 适配器翻译(有 legacy 差异) |
| OLAP | SSAS 多维模型 | 不覆盖 |
| 报表 | SSRS / Power BI | 不覆盖(交给 BI 工具) |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。