三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

dbt+SQLServer构建数据仓库(7):进阶扩展

dbt+SQLServer构建数据仓库(7):进阶扩展

dbt+SQLServer构建数据仓库(7):进阶扩展

dbt+SQLServer构建数据仓库(7):进阶扩展

本文讲述项目从"能跑"走向"生产级"的四个进阶能力:增量模型只跑新增数据、快照做 SCD2 历史拉链、dbt docs 自动生成文档与血缘、CI/CD 让每次提交自动验证。这些功能本项目暂未实现,本文给出可直接落地的代码示例。

一、引言

前 6 篇我们走完了从认知到建模到测试的完整闭环,但"能跑"离"生产级"还有四个能力缺口:

能力 解决的问题 本文章节
增量模型 大数据量全量重建太慢 第二节
快照(snapshot) 历史状态丢失,无法追溯 第三节
dbt docs 文档靠口口相传,新人难上手 第四节
CI/CD 提交即验证,质量门禁缺失 第五节

本文给出每个能力的可落地代码示例,可直接加到本项目的 dbtms/ 目录里。需要说明:这些功能本项目暂未实现,目的是把"如何落地"讲清楚,供你按需引入。

二、增量模型(incremental)

2.1 问题:全量重建的代价

本项目 [fct_orders.sql]现在用 table 物化(见 [dbt_project.yml] 第 33 行 marts: +materialized: table)。每次 dbt run 都会把整张表 drop 再 create,全量重算。

数据量小(几千几万行)没问题,一旦源表到了百万行、千万行,每次跑几分钟甚至几十分钟,日跑批就扛不住了。

2.2 解决:改用 incremental 物化

incremental 物化的核心思想:首次全量构建,后续只处理新增数据。改造 [fct_orders.sql]

{{ config(materialized='incremental',unique_key='order_id',on_schema_change='append_new_columns'
) }}with orders as (select * from {{ ref('stg_orders') }}{% if is_incremental() %}where order_date > (select max(order_date) from {{ this }}){% endif %}
),payments as (selectorder_id,sum(amount) as total_amountfrom {{ ref('stg_payments') }}where status = 'completed'group by order_id
)selecto.order_id,o.customer_id,o.order_date,o.status,coalesce(p.total_amount, 0) as amount
from orders o
left join payments pon o.order_id = p.order_id

2.3 三个关键配置

配置 作用
materialized='incremental' 启用增量物化,模型从 table 变为增量表
unique_key='order_id' 去重键。重复跑同一天的数据不会产生重复行(SQL Server 上走 MERGE INTO)
is_incremental() Jinja 宏。首次构建返回 false(走全量),后续返回 true(走 where 过滤)

{% if is_incremental() %} 块里的 where order_date > (select max(order_date) from {{ this }}) 是增量的灵魂:{{ this }} 指向当前模型已存在的表,只挑出比已有最大日期更新的数据。

2.4 增量策略(SQL Server)

dbt-sqlserver 支持两种增量策略:

策略 行为 适用场景
append(默认) 直接 INSERT 新数据 源数据只新增、绝不修改历史
merge MERGE INTO,基于 unique_key 去重 upsert 源数据可能更新已有行

如需用 merge,在 config 里加 incremental_strategy='merge':

{{ config(materialized='incremental',unique_key='order_id',incremental_strategy='merge',on_schema_change='append_new_columns'
) }}

三、快照(snapshot/SCD2)

3.1 问题:历史状态丢失

[fct_orders.sql] 只反映当前状态。客户改了名字、订单从 pending 变成 completed,旧状态就丢了——而审计、对账往往要问"上周三这个订单是什么状态"。

3.2 解决:snapshot 做 SCD2

snapshot 自动实现 Type 2 Slowly Changing Dimension(SCD2):每次源数据变化,就追加一行新版本,旧行打上失效时间,形成"历史拉链表"。

在项目根目录新建 snapshots/snap_orders.sql:

{% snapshot snap_orders %}
{{config(target_schema='dbt_dev_snapshots',unique_key='order_id',strategy='timestamp',updated_at='order_date',)
}}
select * from {{ source('raw', 'raw_orders') }}
{% endsnapshot %}

3.3 四个关键配置

配置 作用
target_schema 快照表存放的 schema,本项目约定 dbt_dev_snapshots
unique_key 主键,用于识别"同一行"在不同时点的版本
strategy='timestamp' 基于时间戳判断是否变化(另一种是 check,比对指定字段)
updated_at='order_date' 时间戳字段,dbt 用它判断该行是否比上次快照更新

3.4 执行与产出

dbt snapshot

dbt 会自动给快照表多加几个元数据列:

列名 含义
dbt_scd_id 每个版本行的唯一标识(哈希)
dbt_updated_at 本次变更发生的时间
dbt_valid_from 本版本生效起始时间
dbt_valid_to 本版本失效时间(当前版本为 NULL)

查询"某订单的历史状态流转":

select order_id, status, dbt_valid_from, dbt_valid_to
from dbt_dev_snapshots.snap_orders
where order_id = 1001
order by dbt_valid_from;

3.5 使用场景

  • 客户改名历史:追溯任意时点的客户名称
  • 订单状态流转:pending → paid → shipped → completed 的时间线
  • 审计追溯:合规检查需要"某天某时刻的状态"

四、dbt docs:自动生成文档与血缘

4.1 两条命令

dbt docs generate   # 生成文档(写入 target/ 目录)
dbt docs serve      # 启动本地网站(默认 8080 端口)

4.2 产出一个可点击的网站

打开 http://localhost:8080,你会看到:

  • 模型列表与描述:所有 model 一目了然
  • 每个模型的 SQL、编译后 SQL、字段说明:点开模型即可看
  • DAG 血缘图:可视化展示模型依赖,节点可点击钻取
  • 源表(source)描述:raw 层来源说明

4.3 文档来源:schema.yml

文档不是手写的,而是从 schema.yml 里的 descriptioncolumns 自动抽取。本项目已有的 schema.yml 越完整,生成的 docs 就越丰富。例如:

models:- name: fct_ordersdescription: 订单事实表,每个订单一行,关联客户与已完成支付的金额columns:- name: order_iddescription: 订单主键tests: [unique, not_null]- name: customer_iddescription: 关联 dim_customers 的外键- name: amountdescription: 已完成支付的金额总额,未支付为 0

4.4 优势与团队协作价值

  • 文档从代码生成:永远和代码同步,不像 Word 文档会腐烂
  • 新人友好:看 docs 网站就能理解数仓结构,不用读代码
  • 血缘可视:依赖关系一眼看清,改上游能立刻看到下游影响

五、CI/CD:每次提交自动验证

5.1 GitHub Actions 示例

在项目根目录新建 .github/workflows/dbt_ci.yml:

name: dbt CI
on: [pull_request]
jobs:dbt:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: actions/setup-python@v5with: { python-version: '3.11' }- run: pip install dbt-core dbt-sqlserver- run: dbt deps- run: dbt parse- run: dbt build --target cienv:DBT_SQLSERVER_HOST: ${{ secrets.CI_DB_HOST }}DBT_SQLSERVER_USER: ${{ secrets.CI_DB_USER }}DBT_SQLSERVER_PASSWORD: ${{ secrets.CI_DB_PASSWORD }}

5.2 CI 流程

PR 提交 → dbt parse(语法检查) → dbt build(run + test) → 全过才能 merge。

  • dbt parse:只解析不执行,秒级反馈语法错误
  • dbt build:第 5 篇讲过,一次跑完 run + test,任一失败 CI 红

5.3 环境隔离

CI 用独立的 ci target(在 profiles.yml 里配 schema: dbt_ci_),不污染 dev/prod。每个 PR 跑在自己的 schema 前缀下,互不干扰。

5.4 调度生产化

CI 解决的是"提交时验证",生产调度是另一回事。两种方案:

方案 做法 适合
方案 A SQL Server Agent 调 dbt run --target prod 已有 Agent 体系,复用现有调度
方案 B Airflow/Dagster 编排(cosmos 或 dbt operator) 需要复杂依赖、重试、告警

方案 A 简单:在 Agent Job 里加一步 cmdexec,调用 dbt run --target prod。方案 B 更强大:能可视化 DAG、失败重试、上下游联动告警,但引入了新的编排系统。

六、生产化检查清单

从开发到生产,逐项对照:

维度 检查项 本系列参考篇
配置 dbt_project.yml flags 配齐 第3篇
建模 分层清晰,staging 1:1,marts 聚合 第4篇
测试 主键 unique+not_null,外键 relationships 第5篇
模板 ref/source 正确,无硬编码表名 第6篇
增量 大表用 incremental,有 unique_key 本文第二节
快照 需要历史追踪的表配 snapshot 本文第三节
文档 dbt docs 可生成,schema.yml 描述完整 本文第四节
CI PR 自动跑 dbt build 本文第五节
调度 Agent/Airflow 定时调 dbt run --target prod 本文第五节
监控 run_results.json 解析,测试失败告警 第1篇

清单不是死规定,按团队实际情况裁剪。核心是:配置、建模、测试、模板四项是底线,增量/快照/CI 按需引入。

七、下一步

下一步将借助AI的能力来进一步扩展dbt的能力,我们会搭建一套vibe coding的环境,来自动构建数据仓库。

八、小结

本文讲了项目从"能跑"走向"生产级"的四个进阶能力:

  1. 增量模型:大数据量只跑新增,is_incremental() + unique_key 是核心
  2. 快照:SCD2 历史拉链,strategy='timestamp' + updated_at 是核心
  3. dbt docs:文档从代码生成,dbt docs generate + serve 两条命令搞定
  4. CI/CD:GitHub Actions 跑 dbt build --target ci,PR 即门禁

这四个能力的共同点:都是 dbt 原生支持,不用额外买工具。增量靠 config,快照靠 snapshot 资源类型,文档靠 docs 命令,CI 靠标准 YAML——这正是 dbt 作为"工程化框架"的价值。

← 返回列表