陷阱二:粒度声明陷阱——事实表粒度不明确,导致数据重复或丢失
好,咱们接着聊第二个坑。这个坑,说实在的,我见过太多次了。
很多刚入行的朋友,建事实表的时候,上来就写 INSERT INTO fact_sales,然后从源系统里把订单数据一股脑全拉过来。结果呢?跑出来的报表,金额总和要么翻倍,要么少一截。老板一问,谁都不敢拍胸脯说数据是对的。
为什么会这样?根本原因就是:事实表的粒度没有说清楚。
什么是粒度?一句话讲明白
粒度,说白了就是一行事实数据代表什么。
你想想看,一张销售事实表,如果一行代表「一笔订单」,那跟一行代表「一笔订单中的一个商品」,统计出来的总金额能一样吗?
我个人的习惯是,在开始建模之前,先拿笔在纸上写一句话:
「这张表里,每一行记录的是……的一次度量。」
这句话写不出来,或者写出来模棱两可,那后面肯定要出问题。
粒度声明公式:
事实表粒度 = 业务事件发生的自然单位 + 维度组合的唯一性
粒度不明确的三种典型症状
我在项目中遇到过几种常见情况,你可以对照看看自己中招了没:
- 「一行多意」型——同一张表里,有的行代表订单头,有的行代表订单行。数据源不同,ETL 合并时没做统一。
- 「粒度漂移」型——一开始说好是「每日店铺汇总」,后来业务要「每笔交易明细」,直接在原表上加字段,结果既有汇总又有明细。
- 「隐式聚合」型——源系统已经做了预聚合,ETL 没注意,又做了一次 sum,数据直接翻倍。
⚠️ 避坑指南:我曾经接手过一个项目,销售事实表里既有订单金额又有退款金额。开发同学把退款也当成正向订单加载进来,结果月报里「净销售额」比实际多了两倍。查了三天才发现,是粒度声明里没区分「正向事件」和「逆向事件」。
粒度声明的最佳实践
怎么避免这个坑?我建议你按下面几步来:
| 步骤 | 动作 | 产出物 |
|---|---|---|
| 1 | 明确业务事件 | 「下单」「支付」「退款」「发货」 |
| 2 | 确定自然粒度 | 「一次支付行为」vs「一笔订单中的一次支付」 |
| 3 | 列出所有维度 | 时间、产品、客户、门店、渠道 |
| 4 | 验证唯一性 | 这些维度组合在一起,能唯一确定一行吗? |
| 5 | 写进设计文档 | 「fact_sales 每行代表一次订单行级别的销售事件」 |
嗯,这里要注意,第 4 步特别关键。你想想看,如果维度组合不能唯一确定一行,那数据加载的时候要么丢数据,要么重复。这就是事实表最常见的「多对多」问题。
一个真实的粒度声明案例
咱们来看一个具体的例子。假设你要建一张「电商订单事实表」。
错误做法:
-- 没有粒度声明,直接拉数据
INSERT INTO fact_order
SELECT
order_id,
product_id,
order_amount,
discount_amount,
shipping_fee
FROM source_orders;
这里的问题在哪?order_amount 是整单金额,但 product_id 是商品级别的。如果一笔订单买了 3 个商品,那 order_amount 就会被重复计算 3 次。
正确做法:
-- 先声明粒度:每行代表一个订单行(Order Line Item)
-- 维度组合:order_id + product_id 唯一确定一行
INSERT INTO fact_order_line
SELECT
order_id,
product_id,
line_item_amount, -- 单个商品的金额
discount_amount, -- 单个商品的折扣
shipping_fee -- 运费分摊到行
FROM source_order_lines;
💡 小技巧:我习惯在事实表的注释里直接写明粒度声明。比如:
COMMENT ON TABLE fact_order_line IS
'粒度:每行代表一次订单行级别的销售事件。
唯一键:order_id + product_id。
度量:line_item_amount, discount_amount, shipping_fee。'
这样后来的人一看就明白,不会乱用。
粒度声明与数据重复/丢失的关系
咱们用一张图来理清这个逻辑:
常见粒度声明场景对照表
我把实际工作中最常见的几种场景整理了一下,你可以直接拿来参考:
| 业务场景 | 推荐粒度 | 唯一键 | 典型度量 |
|---|---|---|---|
| 电商订单 | 订单行级别 | order_id + product_id | 销售额、折扣、数量 |
| 银行交易 | 单笔交易级别 | transaction_id | 交易金额、手续费 |
| 库存快照 | 每日商品级别 | date + product_id + warehouse_id | 库存数量、库存金额 |
| 用户行为日志 | 单次事件级别 | session_id + event_seq | 停留时长、点击次数 |
| 财务流水 | 单笔凭证级别 | voucher_id + line_item | 借贷金额、余额 |
如何检查粒度是否声明正确?
给你一个我常用的自检清单:
- 自检 1:随便抽两行数据,它们的维度组合能一样吗?如果能,说明粒度太粗。
- 自检 2:对度量字段做
SELECT SUM,然后跟源系统对账。对不上,说明粒度有问题。 - 自检 3:如果同一个事实表里既有金额又有数量,检查一下它们是不是在同一粒度上。比如金额是订单级,数量是商品级,那就出问题了。
- 自检 4:问问自己:「如果业务方问这一行数据是什么意思,我能用一句话说清楚吗?」
我的个人经验:每次建新事实表,我都会先写一段注释,把粒度声明写清楚。然后让团队里的另一个人 review 一下。很多时候,别人一眼就能看出你声明里的漏洞。这比上线后出问题再回头查,成本低太多了。
小结
粒度声明这件事,听起来简单,做起来容易忽略。但它是事实表设计的基石。基石不稳,上面盖的楼再漂亮也没用。
记住一句话:「先声明,后建模;声明不清,模型必崩。」
嗯,这个坑咱们就聊到这儿。你只要每次建表前多花 5 分钟把粒度想清楚,就能避开 80% 的数据质量问题。