3. 事实表设计(上):事务事实表、周期快照事实表、累积快照事实表
事实表,说白了就是咱们数仓里用来记录「发生了什么」的那张表。它存的是度量值,比如销售额、下单数量、点击次数。维度表告诉你「谁、在哪、什么时候」,事实表告诉你「发生了多少」。
我刚开始做数仓时,觉得事实表不就一张表嘛,有啥好设计的?后来被坑过几次才明白——事实表分三种,每种适用场景完全不同。选错了,后面查询慢、逻辑乱、维护成本高,哭都来不及。
今天咱们就把这三种事实表掰开揉碎讲清楚:事务事实表、周期快照事实表、累积快照事实表。
核心记忆点:事务事实表记「每次发生」,周期快照记「每个周期末的状态」,累积快照记「整个流程的进度」。
3.1 事务事实表(Transaction Fact Table)
这是最常用、最直观的事实表。每一行对应一个业务事件。比如电商里的每一笔订单、每一次点击、每一笔退款。
特点:
- 一行一个事件,粒度最细
- 可以按任意维度做聚合
- 数据量最大,增长最快
- 不支持更新,只追加
举个例子。我在项目中做过一个订单事务事实表,结构大概这样:
CREATE TABLE fact_order_transaction (
order_id BIGINT,
product_id BIGINT,
customer_id BIGINT,
store_id BIGINT,
order_date DATE,
order_amount DECIMAL(10,2),
quantity INT,
discount_amount DECIMAL(10,2),
payment_type_id BIGINT
);
每下一单,就插一条。简单粗暴。但要注意——事务事实表只记录「发生」那一刻。如果订单后来退款了、改金额了,事务表里不会改,你得另建一张退款事务表。
我的经验:事务事实表最适合做「发生了什么」的分析,比如「今天卖了多少」「哪个产品被点击最多」。但别用它做「当前状态」的查询,比如「这个订单现在到哪一步了」——那会查死你。
3.2 周期快照事实表(Periodic Snapshot Fact Table)
周期快照,说白了就是「定期拍照」。比如每天、每周、每月,把某个业务对象的当前状态拍一张照存下来。
特点:
- 一行对应一个周期 + 一个实体
- 记录的是「截至某个时间点的状态」
- 数据量可控,周期越长数据越少
- 支持更新(同一个周期内可以覆盖)
我做过一个库存周期快照表,每天凌晨跑一次:
CREATE TABLE fact_inventory_snapshot (
snapshot_date DATE,
product_id BIGINT,
warehouse_id BIGINT,
quantity_on_hand INT,
quantity_reserved INT,
quantity_available INT,
last_update_time TIMESTAMP
);
每天一条记录,告诉你「今天早上8点,这个仓库里这个产品还剩多少」。你想知道库存趋势?直接按日期聚合就行,不用去翻几十亿条出入库记录。
避坑指南:我曾经在一个项目中,周期快照的粒度没想清楚,把「每天每个产品每个仓库」拍成「每天每个产品」,结果库存对不上。后来加回仓库维度,数据量翻了三倍,但至少对了。记住——快照的粒度必须和业务实体一致。
3.3 累积快照事实表(Accumulating Snapshot Fact Table)
这个稍微复杂点。它记录的是一个业务流程从开始到结束的完整过程。每一行对应一个流程实例,字段里存的是各个关键节点的「时间戳」或「状态」。
特点:
- 一行一个流程实例
- 字段会随着流程推进不断更新
- 适合有明确生命周期的业务(订单、工单、审批)
- 数据量最小,但更新最频繁
拿订单流程举例:
CREATE TABLE fact_order_accumulating (
order_id BIGINT,
order_date DATE,
payment_date DATE,
ship_date DATE,
delivery_date DATE,
return_date DATE,
order_status VARCHAR(20),
total_amount DECIMAL(10,2),
paid_amount DECIMAL(10,2),
refund_amount DECIMAL(10,2)
);
一开始只填 order_id 和 order_date,其他字段为空。用户付款了,更新 payment_date。发货了,更新 ship_date。直到流程结束。
你想想看,如果你想分析「从下单到发货平均要多久」,用累积快照表就一句话:AVG(ship_date - order_date)。用事务表?你得先找到每个订单的「下单事件」和「发货事件」,再关联起来,麻烦多了。
适用场景判断:
- 事务事实表 → 分析「事件发生」的量和趋势
- 周期快照事实表 → 分析「某个时间点」的状态和变化
- 累积快照事实表 → 分析「流程」的效率和瓶颈
3.4 三种事实表对比
我整理了一张表,方便你对照着看:
| 对比维度 | 事务事实表 | 周期快照事实表 | 累积快照事实表 |
|---|---|---|---|
| 粒度 | 每个事件一行 | 每个周期 + 实体一行 | 每个流程实例一行 |
| 数据更新 | 只追加,不更新 | 周期内可覆盖 | 随流程推进不断更新 |
| 数据量 | 最大 | 中等 | 最小 |
| 典型字段 | 度量值 + 维度外键 | 快照时间 + 状态度量 | 各节点时间戳 + 最终状态 |
| 典型查询 | 某时间段内总销售额 | 某天库存量 | 平均处理时长 |
| 业务场景 | 交易、点击、日志 | 库存、余额、账户状态 | 订单、工单、审批流 |
3.5 如何选择?
嗯,这里要注意。很多新手会问:「我到底该用哪种?」
我的建议是三步走:
- 先看业务问题:你想分析「发生次数」还是「当前状态」还是「流程效率」?
- 再看数据源:你拿到的原始数据是事件流、定期快照、还是流程日志?
- 最后看查询模式:用户最常问的问题是什么?
举个例子。如果你做的是电商数仓:
- 订单明细 → 事务事实表
- 每日库存 → 周期快照事实表
- 订单履约流程 → 累积快照事实表
三个可以共存,不冲突。我见过很多成熟数仓,同一套业务同时维护两三种事实表,各司其职。
小技巧:如果你不确定选哪种,先从事务事实表开始。它最灵活,后期可以基于它生成周期快照和累积快照。但反过来就难了——从累积快照倒推事务细节,基本不可能。
3.6 三种事实表的关系图
下面这张图帮你理清三种事实表在数据流中的位置和关系:
3.7 实战中的常见坑
最后,分享几个我踩过的坑:
- 事务表粒度不一致:同一个事实表里,有的行是订单级,有的行是订单行级。查询时聚合结果对不上。我的建议是——建表前先确认粒度,写进文档。
- 周期快照的周期选错:库存变化快,你选月快照,中间缺货了都不知道。选日快照,数据量又太大。折中方案是——先按日跑,跑一段时间后看数据量,再决定是否降频。
- 累积快照更新不及时:ETL 流程没处理好,订单都签收了,累积表里还是「已发货」。这会导致流程分析全错。我的做法是——给累积表加一个
last_updated字段,监控更新延迟。
重要提醒:累积快照事实表虽然好用,但只适合有明确开始和结束的流程。如果你的业务流程没有固定终点(比如用户行为轨迹),别用累积快照,老老实实用事务事实表。