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

source:划清原始数据的责任边界

目标:用 source 声明已有原始表,并解释为什么声明来源不会替你装载数据。

第 4 课的 ref('raw_orders') 指向本项目管理的 seed。真实团队的原始订单通常由采集系统写入;dbt 应当知道它在哪里,却不应该因为一次模型构建就去重新采集订单。source() 用来表达这个边界:这是转换流程依赖的外部入口。

为节省安装工作,本课仍让 seed 扮演“外部装载器”,表的位置也不变。只改变 staging 如何描述入口。这是教学模拟:一个物理表同时有 seed 和 source 两个资源名字,dbt 不会据此自动推断它们之间的依赖。

声明位置,再使用位置

创建 models/staging/sources.yml:

version: 2
sources:
  - name: shop
    schema: main
    description: 小商店原始输入;本地由 seed 模拟上游装载。
    tables:
      - name: orders
        identifier: raw_orders
      - name: customers
        identifier: raw_customers

shop 是逻辑来源名,orders 是逻辑表名,identifier 是数据库里的实际对象名。schema: main 指定命名空间。这里省略 database,让它使用当前连接的数据库,便于第 13 课切换到另一个练习数据库文件。开头的 version: 2 是属性文件格式,与项目版本、引擎版本均不同。

将 models/staging/stg_customers.sql 完整替换为:

select
    cast(customer_id as integer) as customer_id,
    trim(customer_name) as customer_name
from {{ source('shop', 'customers') }}

将 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 {{ source('shop', 'orders') }}

转换逻辑没有变化,只替换了最后一行。先预测金额会不会变,再运行:

uv run dbt seed --profiles-dir .
uv run dbt run --profiles-dir . --select stg_customers stg_orders
uv run dbt ls --profiles-dir . --select +stg_orders
uv run dbt show --profiles-dir . --inline "select count(*) as n, sum(amount_cents) as cents from {{ ref('stg_orders') }}"

预期查询是 n = 5、cents = 6600,这里没有筛选状态,所以是所有订单的总额。ls 的祖先现在包含 source shop.orders,不再包含 seed raw_orders。可以把图理解为:

先独立执行 seed,准备物理原始表
source shop.orders → stg_orders → 后续业务模型

为什么后续要分两步运行

source 声明不会创建、加载或自动刷新那张表。因此后续统一先 dbt seed,再构建其他资源;需要全项目 build 时使用 --exclude resource_type:seed,避免把教学装载器混入另一条没有依赖箭头的运行路径。

不要因为本机已有表、一次全项目 build 恰好成功,就认为它在空数据库也会正确。依赖没有写进图的任务,其先后关系需要由流程显式保证。

引导练习:

  1. 把 sources.yml 里的 identifier: raw_orders 临时改成 identifier: raw_orders_missing。
  2. 再运行本课的 run 命令,应因找不到原始表失败。
  3. 恢复 identifier,重新 run,查询又应得到五行。

独立练习:把来源里的逻辑表名 orders 改成 purchases,但保持物理表不动。需要同步改哪个文件的哪一处?完成后运行并恢复主版本。

参考变换

sources.yml 中仅把 - name: orders 改成 - name: purchases,保留 identifier: raw_orders。然后将 stg_orders 最后一行改为:

from {{ source('shop', 'purchases') }}

这是基于上面完整文件的单行替换片段。模型结果仍为五行,因为实际查询的物理表未变。完成后把两个逻辑名称改回 orders;stg_customers 不需修改。

若报 source 未定义,核对 source() 的两个逻辑参数;若报数据库对象不存在,核对 identifier、schema、当前数据库,以及是否先 seed。两类错误发生在不同层,不要只反复运行同一个命令。

本课保留 sources.yml 和两个改用 source 的 staging 模型。CSV 继续作为本地模拟上游。已有的数据不会因为增加来源声明而改变。

参考资料