Course Builder个人学习工作台 · 本地模式
dbt 入门:把 SQL 变成可信的数据流水线

结课实战:从空库重建并修复一次故障

目标:仅依靠项目源文件重建可信报表,并完成一次发现、定位、修复、复验的闭环。

你的项目现在具备一条完整主线:外部输入声明 → staging 整理 → 客户汇总;还有一条独立的订单增量练习路径。最后一课不引入新框架,而是检验这些组件能否在没有旧数据库帮忙时正常工作。

能在旧环境跑通,只能说明“此刻已有状态足够”;能在空目标重建,才更能说明项目把输入和依赖表达完整。CI(持续集成)的一种常见用途,就是在隔离环境中运行这样的构建检查。本课的 ci 只是本地目标名称,没有连接任何远程 CI 服务。

先核对你已经拥有的文件

以下是主线文件;所有内容均在此前课文中创建,不要求临时补实现。

shop_dbt/
├── .python-version
├── pyproject.toml
├── uv.lock
├── dbt_project.yml
├── profiles.yml
├── run_pipeline.sh
├── seeds/
│   ├── raw_customers.csv
│   └── raw_orders.csv
├── models/
│   ├── hello.sql
│   ├── staging/
│   │   ├── sources.yml
│   │   ├── schema.yml
│   │   ├── stg_customers.sql
│   │   └── stg_orders.sql
│   └── marts/
│       ├── schema.yml
│       ├── customer_summary.sql
│       ├── order_ledger.sql
│       └── order_ledger.yml
├── macros/
│   └── cents_to_currency.sql
└── tests/
    ├── assert_nonnegative_order_amount.sql
    └── assert_revenue_reconciles.sql

CSV 必须恢复第 3 课的四位客户、五笔订单;第 8 课的额外练习测试已删除;第 12、13 课要求的增量恢复已完成。本课使用尚未建立的 shop_ci.duckdb,不删除 dev 或 prod。

从空目标开始

先用下面的一行 Python 做存在性断言;uv run python 使用第 2 课准备的解释器,-c 执行引号里的代码。该命令只检查文件,不修改它。

uv run python -c "from pathlib import Path; assert not Path('shop_ci.duckdb').exists(), 'ci database already exists; choose a new ci path before the empty-database exercise'"
bash run_pipeline.sh ci

第一次断言应安静通过。若你重做本课、文件已经存在,在 profiles.yml 中为 ci 换一个未用过的文件名,并同步上面断言检查的名称,不必删除有价值的旧数据库。

预期 seed 装载 4 位客户与 5 笔订单,build 建立 5 个模型并通过 17 项测试,随后成功生成文档。不要只看最后一个 PASS;应确认整条脚本成功返回。产出的 5 个模型分别是 hello、两个 staging、customer_summary、order_ledger。

先写出你预计的验收数字,再运行:

uv run dbt show --profiles-dir . --target ci --inline "select count(*) as customers, sum(completed_order_count) as orders, sum(revenue_cents) as cents from {{ ref('customer_summary') }}"
uv run dbt show --profiles-dir . --target ci --inline "select count(*) as ledger_rows, count(distinct order_id) as distinct_orders from {{ ref('order_ledger') }}"
bash run_pipeline.sh ci

第一次查询应为 customers=4, orders=3, cents=5200;第二次是 ledger_rows=5, distinct_orders=5。最后再次运行完整流程,数字应保持一致。这是在当前固定输入与业务规则下的可重复运行,不表示任意坏输入都能自动恢复。

引导演练:一条负金额订单

  1. 把 raw_orders.csv 中 105 的金额从 600 改成 -600,并把 updated_at 改为 2026-01-04 12:00:00。
  2. 执行 bash run_pipeline.sh ci,预期在非负金额测试处失败,并且不继续到文档生成。
  3. 用已有查询能力定位反例:
uv run dbt show --profiles-dir . --target ci --inline "select order_id, amount_cents from {{ ref('stg_orders') }} where amount_cents < 0"

应看到订单 105、金额 -600。它没有计入 completed 收入,所以仅看 5200 分总额可能发现不了异常。不同测试分别保护不同假设。

  1. 把 raw_orders.csv 恢复第 3 课完整版本。为消除本次错误输入可能留下的增量状态,执行:
uv run dbt seed --profiles-dir . --target ci
uv run dbt build --profiles-dir . --target ci --exclude resource_type:seed --full-refresh
bash run_pipeline.sh ci

应重新全绿,且验收数字仍为 4、3、5200 和 5、5。修复输入之后,要复验受影响路径,而不只是确认文件看起来对了。

独立交付:用自己的话说明系统

不翻前文,写一段给接手同事的说明,回答以下问题:入口表在哪里,谁负责加载?一行客户汇总代表什么?金额哪些状态算、单位是什么?原始订单改了,应该执行哪段流程?如果一个更正没有更新 updated_at,哪条路径会有风险?

然后用这段说明判断下面三种症状的排查顺序:

症状你首先应该核对的对象
空库运行提示 raw_orders 不存在来源位置和上游装载步骤
客户 4 消失,总额仍为 5200 分客户汇总的粒度和连接方式
stg_orders 是新金额,order_ledger 是旧金额增量筛选、水位和更新时间
一份可交付的参考说明

本项目在每个目标数据库的 main schema 读取 raw_orders 与 raw_customers。教学环境先运行 seed 模拟上游装载,source 声明本身不导入数据。staging 统一字段与类型,customer_summary 以客户为粒度,仅统计 completed 订单,保留零消费客户;revenue_cents 是整数分,revenue 是两位小数的元。

输入改变后,在指定目标运行 run_pipeline.sh。订单增量表 order_ledger 是独立练习分支,依赖 updated_at 识别变化;未更新时间或超出水位范围的历史更正可能被漏掉。需要调整变更识别策略或有计划地全量重建。任何失败都先检查日志与反例,修复后重新构建和验证,不因某个总额正确就宣布全部正确。

验收时,源文件能在新的目标数据库生成 5 个模型、17 项通过的测试,客户数为 4、成交笔数为 3、消费总额为 5200 分,增量表中 5 个唯一订单。再次运行保持这些数字。

学到这里之后

现在你已经能把一组 SQL 变成有依赖、有业务含义、能检查和重跑的项目。下一步应由实际问题决定:想验证复杂 SQL 的边界分支,学 unit tests;想知道源数据是否按时到达,学 source freshness;想保留客户地址等字段的历史版本,学 snapshots。增量表只保留最新值,不能代替历史快照。

当本地流程清楚之后,再接真实数仓、团队 Git 审查与调度系统。更换数据库需要核对适配器配置、SQL 方言和增量策略;阅读 v2/Fusion 资料时也要重新确认运行时差异。课程中的 source、ref、粒度和业务测试思路,仍是理解新环境的基础。

参考资料