ref 与 staging:把查询连接成依赖图
目标:建立两个整理输入的模型,并用 ref 声明它们对 seed 的依赖。
上一课把 SQL 写进命令行,适合探索,却不便于多人复用。现在把稳定的整理规则放入模型文件。staging 层尽量保留输入粒度:订单模型仍是一行一笔订单,客户模型仍是一行一位客户。它负责列名、类型和简单标准化;消费总额留到后面计算。
SQL 的 cast(x as type) 明确值的类型;integer 是整数,date 是日期,timestamp 带时间,decimal(12,2) 最多十二位数字、其中两位在小数点后。显式类型能避免不同样本触发自动推断后,后续代码得到不同类型。
将转换保存为模型
创建 models/staging/stg_customers.sql:
select
cast(customer_id as integer) as customer_id,
trim(customer_name) as customer_name
from {{ ref('raw_customers') }}
trim 去除首尾空格。创建 models/staging/stg_orders.sql:
select
cast(order_id as integer) as order_id,
cast(customer_id as integer) as customer_id,
cast(order_date as date) as order_date,
lower(trim(status)) as status,
cast(amount_cents as integer) as amount_cents,
cast(amount_cents / 100.0 as decimal(12,2)) as amount,
cast(updated_at as timestamp) as updated_at
from {{ ref('raw_orders') }}
lower(trim(status)) 先去空格,再转小写。我们同时保留整数分和展示金额:整数分方便精确求和,两位小数方便阅读。这里没有筛选状态,因为其他分析可能仍然需要退货订单。
{{ ... }} 是模板表达式,不是数据库的原生 SQL。ref('raw_orders') 用资源名查找对象,并记录依赖:必须先有 raw_orders,才可以构建 stg_orders。dbt 不靠文件名中的数字排序。所有依赖组合成 DAG,即有方向、没有循环的图;如果 A 依赖 B,B 又依赖 A,就没有合法的先后顺序。
先查看会选中什么
先预测:+stg_orders 应包含什么?再运行:
uv run dbt ls --profiles-dir . --select +stg_orders
uv run dbt build --profiles-dir . --select +stg_orders +stg_customers
uv run dbt show --profiles-dir . --inline "select order_id, status, amount from {{ ref('stg_orders') }} order by order_id" --limit 10
ls 列出资源;选择器前面的 + 表示同时选择祖先。此时订单路径包含 seed raw_orders 与模型 stg_orders。build 可以按图顺序装载选中的 seed、构建模型并执行选中的测试;本课还没添加测试。
预期查询包含五行,金额分别为 12.00、8.00、25.00、15.00、6.00,状态顺序为 completed、returned、completed、completed、placed。小数显示是否补零由预览格式决定,数值应一致。
raw_customers → stg_customers
raw_orders → stg_orders
这是本课的依赖图。stg_ 只是命名习惯;真正让箭头存在的是 ref。
练习标准化,而不是删除问题
- 把 CSV 中 101 的状态改成
COMPLETED,保留首尾空格。 - 重新执行上面的
build。它包含 seed,因此会加载修改后的 CSV。 - 预览 stg_orders,101 仍应是 completed。输入清洗规则被集中执行了。
- 恢复 CSV 为原来的 completed,再 build,保持主版本一致。
独立练习:把 raw_customers 中 Ada 改成 Ada。预测原始表与 staging 的客户名称是否相同,用查询验证后恢复 CSV。
验证命令与参考答案
uv run dbt build --profiles-dir . --select +stg_customers
uv run dbt show --profiles-dir . --inline "select customer_id, length(customer_name) as name_length from {{ ref('stg_customers') }} where customer_id = 1"
staging 的长度应为 3,未清洗原始表里的长度为 5。恢复 Ada 后再次 build。清洗首尾空格是明确规则;如果出现两个相同 customer_id,不能顺手删一行假装它合理,下一阶段要用测试发现问题。
若提示找不到 ref 目标,检查参数用的是文件名去掉扩展名后的名称;若类型转换失败,找到原始 CSV 里的非数字值,不要立即用空值吞掉错误。--select stg_orders 只选模型,不保证重装它的 seed;要重建当前整条路径,使用本课前缀 +。
本课保留两份 staging SQL;没有改动 hello.sql。下一课会把 staging 的输入改成 source,明确生产环境里外部数据的责任边界。