未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适用于已有 dbt 项目、可用的客户 staging 模型及 SCD 基础的读者,提供 seed 加载、快照运行和历史查询的组合练习,并非可直接部署的完整项目。当前“创建 Snapshot”和 timestamp 策略示例仍在 {{ config(...) }} 的 Jinja 表达式内部使用 SQL 的 -- 行注释,不能原样执行;check 策略示例的注释已置于表达式外,两个 CSV 示例也已直接以表头开始,不再存在旧说明所述的问题。等级变化查询需匹配数据库的数组语法,并假设等级值属于 bronze、silver、gold、platinum;timestamp 策略要求所追踪字段变化时 updated_at 可靠更新。快照仅保存运行时捕获的状态,不补回启用前或两次运行之间未捕获的历史;seed 类型、容量及历史表性能需结合适配器和运行环境评估。“维度建模基础”仅为查找名称,不提供跳转,也不能代替前置知识。本次仅作全文结构审阅,未执行代码或进行外部事实核验。 已取消无法确定目标的可点击引用「维度建模基础」,保留原提及;Missing link target: 维度建模基础
dbt Seeds 与 Snapshots - 维度表与历史数据管理
abstract 本文核心
- Seeds:用 CSV 文件管理小型维度映射表和配置数据,让这类数据也纳入版本控制
- Snapshots:捕获数据的历史变更,实现 SCD Type 2(缓慢变化维),让你能查询"昨天这个客户是什么分层"
Seeds:管理配置数据的正确方式
什么是 Seed
Seed 就是 seeds/ 目录下的 CSV 文件。dbt seed 命令会把这些 CSV 文件加载到数据库中,变成可以被模型 ref() 引用的表。
适合用 Seed 管理的数据
| 数据类型 | 示例 | 说明 |
|---|---|---|
| 维度映射表 | 城市代码 → 城市名 | 数据量小,变化少 |
| 状态码描述 | 1=待付款, 2=已付款 | 枚举值的描述 |
| 汇率配置 | USD→CNY: 7.2 | 静态配置(不适合实时汇率) |
| 业务规则 | 不同区域的费率 | 硬编码在代码里难维护 |
| 测试数据 | 已知的测试订单 | 用于对比测试 |
warning Seed 不适合的场景
- 数据量较大 —— 应结合文件大小、加载成本和适配器能力评估,通常更适合从数据库加载;几千行不是统一硬性上限
- 频繁更新 —— 每次更新都需要改 CSV 并重新 commit,成本较高
- 敏感数据 —— CSV 会在 Git 仓库中明文存储
创建和使用 Seed
country_code,country_name,region,currency
CN,中国,Asia,CNY
US,美国,Americas,USD
GB,英国,Europe,GBP
JP,日本,Asia,JPY
DE,德国,Europe,EUR
# dbt_project.yml 中配置 seed(可选)
seeds:
my_analytics_project:
country_codes:
+column_types:
country_code: varchar(2) # 手动指定列类型(默认类型由 CSV 内容推断,具体类型取决于适配器)
country_name: varchar(100)
# 加载所有 seed 到数据库
dbt seed
# 只加载特定 seed
dbt seed --select country_codes
# 全量重新加载(seed 变化时)
dbt seed --full-refresh
在模型中使用:
-- models/marts/core/fct_orders.sql
SELECT
o.order_id,
o.customer_id,
o.total_price_cents,
c.country_name, -- 来自 seed
c.region,
c.currency
FROM {{ ref('fct_orders_base') }} o
LEFT JOIN {{ ref('country_codes') }} c -- 直接 ref() 引用 seed
ON o.shipping_country_code = c.country_code
复杂的业务规则映射表
min_ltv_cents,max_ltv_cents,tier_name,discount_percentage,support_level
0,9999,bronze,0,standard
10000,49999,silver,5,priority
50000,199999,gold,10,dedicated
200000,999999999,platinum,15,white-glove
-- models/marts/dim_customers.sql
-- 使用 seed 中的规则动态计算客户等级
SELECT
c.customer_id,
c.email,
c.lifetime_value_cents,
t.tier_name,
t.discount_percentage,
t.support_level
FROM {{ ref('stg_customers') }} c
LEFT JOIN {{ ref('customer_tier_rules') }} t
ON c.lifetime_value_cents BETWEEN t.min_ltv_cents AND t.max_ltv_cents
这种做法的好处:如果客户等级规则变了,只需修改 CSV 文件,提交 Git,重新运行,无需改 SQL。
Snapshots:捕获历史变更
为什么需要 Snapshot
假设你有一张客户维度表:
今天的 dim_customers:
customer_id | segment | lifetime_value_cents
1001 | gold | 55000
三个月后,这个客户可能:
customer_id | segment | lifetime_value_cents
1001 | silver | 48000 ← 消费减少,降级了
如果你只保存最新状态,你永远不知道这个客户以前是什么等级。这在分析"客户流失"、"等级变化"时会造成严重问题。
Snapshot 解决的就是这个问题:记录每次运行时捕获的数据状态变化历史,不会自动补回启用前历史,也不保证捕获两次运行之间的所有变化。
这是 维度建模基础 中 SCD(Slowly Changing Dimension,缓慢变化维)Type 2 的标准实现方式。
Snapshot 的工作原理
每次运行 dbt snapshot 时,dbt 会:
- 查询源数据
- 与快照表中的最新记录对比
- 如果发现变化,关闭旧记录(填入
dbt_valid_to),插入新记录
快照表的结构:
dbt_scd_id | customer_id | segment | dbt_valid_from | dbt_valid_to
abc123 | 1001 | silver | 2024-01-01 00:00:00 | 2024-06-15 10:00:00 ← 已失效
def456 | 1001 | gold | 2024-06-15 10:00:00 | 2024-09-01 14:30:00 ← 已失效
ghi789 | 1001 | silver | 2024-09-01 14:30:00 | null ← 当前有效
通过 dbt_valid_to IS NULL 可以查到每个记录的当前最新状态。
创建 Snapshot
-- snapshots/snap_customers.sql
{% snapshot snap_customers %}
{{
config(
target_schema='snapshots', -- 快照表写入哪个 schema
strategy='timestamp', -- 检测变化的策略
unique_key='customer_id', -- 哪个字段是主键
updated_at='updated_at', -- 用哪个字段判断记录是否更新
)
}}
-- 快照的来源查询:通常是 staging 层
SELECT
customer_id,
email,
first_name,
last_name,
customer_segment,
lifetime_value_cents,
updated_at
FROM {{ ref('stg_customers') }}
{% endsnapshot %}
两种变化检测策略
strategy = 'timestamp'(推荐)
{{
config(
strategy='timestamp',
unique_key='customer_id',
updated_at='updated_at', -- 通过 updated_at 字段的变化来检测
)
}}
- 优点:性能好,准确
- 前提:源数据有可靠的
updated_at字段 - 适用:大多数业务表(有 updated_at 维护的)
strategy = 'check'(兜底方案)
-- 监控 customer_segment、email 和 lifetime_value_cents 的变化
-- 可将 check_cols 改为 'all' 以监控所有字段;性能取决于数据规模、字段数量和数据库
{{
config(
strategy='check',
unique_key='customer_id',
check_cols=['customer_segment', 'email', 'lifetime_value_cents']
)
}}
- 优点:不依赖 updated_at
- 缺点:每次运行需要对比查询结果中记录的 check_cols 指定字段;使用 check_cols='all' 时对比所有字段,实际性能取决于数据规模、字段数量和数据库。
- 适用:源数据没有 updated_at,或者 updated_at 不可信
运行 Snapshot
# 运行所有 snapshot
dbt snapshot
# 只运行特定 snapshot
dbt snapshot --select snap_customers
# 同时运行模型、测试、snapshot
dbt build
查询快照数据
-- 查询所有客户的当前状态
SELECT *
FROM snapshots.snap_customers
WHERE dbt_valid_to IS NULL -- 当前有效的记录
-- 查询某客户的历史状态
SELECT
customer_id,
customer_segment,
lifetime_value_cents,
dbt_valid_from,
dbt_valid_to
FROM snapshots.snap_customers
WHERE customer_id = 1001
ORDER BY dbt_valid_from
-- 时间旅行:某客户在 2024-06-01 的状态是什么?
SELECT *
FROM snapshots.snap_customers
WHERE customer_id = 1001
AND dbt_valid_from <= '2024-06-01'
AND (dbt_valid_to > '2024-06-01' OR dbt_valid_to IS NULL)
在模型中使用快照
快照数据可以被 ref() 引用:
-- models/marts/fct_customer_tier_changes.sql
-- 分析客户等级变化
WITH tier_history AS (
SELECT
customer_id,
customer_segment AS new_tier,
LAG(customer_segment) OVER (
PARTITION BY customer_id
ORDER BY dbt_valid_from
) AS prev_tier,
dbt_valid_from AS changed_at
FROM {{ ref('snap_customers') }}
)
SELECT
customer_id,
prev_tier,
new_tier,
changed_at,
CASE
WHEN prev_tier IS NULL THEN 'new_customer'
WHEN (
ARRAY_POSITION(ARRAY['bronze','silver','gold','platinum'], new_tier) >
ARRAY_POSITION(ARRAY['bronze','silver','gold','platinum'], prev_tier)
) THEN 'upgrade'
ELSE 'downgrade'
END AS change_type
FROM tier_history
WHERE prev_tier IS NOT NULL -- 排除初次出现的记录
AND prev_tier != new_tier -- 排除没有变化的记录
实战案例:完整的维度管理
一个电商项目中 seeds + snapshots 的典型用法:
seeds/
├── country_codes.csv # 国家代码映射
├── customer_tier_rules.csv # 客户等级规则
├── product_categories.csv # 产品分类映射
└── holiday_calendar.csv # 节假日日历
snapshots/
├── snap_customers.sql # 客户信息变化历史
├── snap_products.sql # 产品信息变化历史(价格、分类调整)
└── snap_vendor_contracts.sql # 供应商合同条款变化
总结
| 功能 | Seeds | Snapshots |
|---|---|---|
| 解决的问题 | 静态配置/映射表的版本控制 | 历史变更追踪(SCD Type 2) |
| 数据来源 | CSV 文件 | 数据库中的已有表 |
| 使用命令 | dbt seed |
dbt snapshot |
| 引用方式 | ref('seed_name') |
ref('snapshot_name') |
| 数据量考虑 | 适合小型静态数据,容量需结合文件大小和加载成本评估 | 历史表持续增长,容量与性能受数据库、运行频率及变更规模影响 |
| 更新方式 | 修改 CSV,重新 seed | 每次 snapshot 自动追加变更 |
下一步
10-dbt与调度集成 - 学习如何把 dbt 命令接入 Airflow 和 CI/CD,实现自动化运行
最后更新:2026-03-18
#dbt #seed #snapshot #SCD #缓慢变化维 #维度建模
info 前置知识
- 掌握 dbt Model 和三层分层架构,参考 数据建模分层
- 了解维度建模的基本概念(事实表、维度表、SCD)
- 理解缓慢变化维(SCD)的业务场景:客户地址变更、产品价格调整等
掌握检查
- [ ] 能创建一个 seed 文件(CSV 格式),用
dbt seed加载到数据库,并在 Model 中通过ref('seed_name')引用 - [ ] 能说出 seed 的使用限制:适合小型静态配置数据,具体容量需结合文件大小、加载成本和适配器能力评估,不适合频繁变化的数据
- [ ] 能写一个 snapshot 文件,配置
strategy(timestamp 或 check)、unique_key、updated_at,运行dbt snapshot后在数据库中查到dbt_scd_id、dbt_valid_from、dbt_valid_to字段 - [ ] 理解 SCD Type 2 的数据结构:当前有效行的
dbt_valid_to为 NULL,历史行的dbt_valid_to为变更时间 - [ ] 知道 snapshot 和增量模型的本质区别:snapshot 按变化检测策略追踪运行时捕获的历史版本;增量模型是否保留历史取决于模型逻辑和增量策略,并非只能保留最新状态
实践练习
基础题:创建一个 seed 文件 customer_segments.csv,包含客户等级(bronze/silver/gold)和对应的折扣率。用 dbt seed 加载,在 dim_customers.sql 中通过 ref('customer_segments') 关联,给每个客户打上等级标签。
进阶题:为客户表创建一个 snapshot,追踪客户 email 和 tier 字段的历史变化。运行 dbt snapshot 首次创建,然后在原始表中修改某个客户的 tier,再次运行 dbt snapshot,查询 snapshot 表验证历史记录是否正确保存(旧记录的 dbt_valid_to 应该被填上变更时间)。