3. 退化维度陷阱:将业务主键错误处理为事实,丢失维度属性

这个陷阱,我见过太多次了。

很多刚接触维度建模的同事,看到订单号、交易流水号、发票号这类字段,第一反应就是:「这不就是个ID嘛,扔到事实表里当度量好了。」

嗯,表面上看确实是个ID。但你仔细想想——业务主键背后,往往藏着丰富的维度属性。一旦你把它当事实处理,这些属性就全丢了。

3.1 什么是退化维度?

先明确概念。退化维度(Degenerate Dimension),指的是那些没有独立维度表、但包含维度属性的维度。它通常以业务主键的形式出现在事实表中。

举个例子:

  • 订单号本身不是度量,它不能求和、不能平均
  • 但订单号可以关联出:下单时间、客户ID、门店ID、支付方式
  • 这些属性如果被丢弃,后续分析就断了

说白了,退化维度是「有维度属性,但没建维度表」的特殊维度。它应该留在事实表中,但要以维度字段的身份存在,而不是度量。

核心判断标准:

  • 能参与聚合运算(SUM、AVG)→ 事实
  • 不能参与聚合,但能分组、筛选、关联 → 维度
  • 业务主键几乎永远是维度,不是事实

3.2 错误处理的典型场景

我在项目中遇到过这样一个案例:

某电商团队做销售分析,把「订单号」直接放进了事实表的度量区。结果呢?

  • 订单号被当成数值型度量,系统自动做了SUM
  • SUM(订单号) 毫无意义,还占用了大量存储
  • 更糟的是,订单相关的门店、渠道、促销活动信息全丢了

后来他们想分析「不同门店的订单分布」,发现根本做不了——因为门店信息只存在于订单维度,而订单维度已经被「优化」掉了。

⚠️ 常见错误模式:

  1. 把业务主键放在事实表的度量列(numeric类型)
  2. 为了「节省空间」删除订单维度表,只保留订单号
  3. 把订单号当作退化维度,但没保留任何维度属性
  4. 在ETL中直接丢弃业务主键,认为「反正用不到」

3.3 正确做法:保留退化维度,但不丢失属性

正确的处理方式其实不复杂:

  1. 业务主键留在事实表,但作为维度字段(varchar类型)
  2. 提取关键维度属性,建立退化维度表或直接保留在事实表
  3. 区分核心属性与冗余属性,避免过度建模

我个人的习惯是:

  • 如果业务主键关联的属性少于3个,直接放在事实表中
  • 如果属性超过3个,建一个独立的退化维度表
  • 永远保留业务主键本身,它是数据血缘的锚点

💡 实用技巧:

订单号、交易流水号这类字段,建议保留为varchar类型。即使你暂时用不到它的属性,未来做数据回溯、问题排查时,它都是救命稻草。

3.4 代码示例:错误 vs 正确

先看错误做法:

-- ❌ 错误:订单号被当作度量
CREATE TABLE fact_sales (
    sale_id INT PRIMARY KEY,
    order_id BIGINT,        -- 当作数值度量
    product_id INT,
    amount DECIMAL(10,2),
    quantity INT
);

-- 查询时:SUM(order_id) 毫无意义
SELECT SUM(order_id) FROM fact_sales;

再看正确做法:

-- ✅ 正确:订单号作为退化维度字段
CREATE TABLE fact_sales (
    sale_id INT PRIMARY KEY,
    order_id VARCHAR(50),   -- 维度字段,varchar类型
    product_id INT,
    amount DECIMAL(10,2),
    quantity INT,
    order_date DATE,        -- 退化维度属性
    store_id INT,           -- 退化维度属性
    channel VARCHAR(20)     -- 退化维度属性
);

-- 查询时:按订单维度分组
SELECT 
    store_id,
    channel,
    COUNT(DISTINCT order_id) AS order_count,
    SUM(amount) AS total_amount
FROM fact_sales
GROUP BY store_id, channel;

3.5 什么时候可以「退化」?

不是所有业务主键都需要建维度表。我总结了几条经验:

场景 建议处理方式 原因
订单号(属性多) 建退化维度表 关联客户、门店、促销等5+属性
交易流水号(属性少) 直接保留在事实表 通常只有时间、渠道2-3个属性
发票号(属性中等) 视情况而定 如果只关联开票日期和金额,放事实表即可
物流单号(属性多) 必须建维度表 涉及物流商、仓库、配送状态等复杂属性

3.6 避坑指南

我曾经踩过的坑:

  • 为了「性能优化」把订单号从事实表删掉,结果数据对账花了3天
  • 把订单号设成int类型,导致后续关联时类型不匹配
  • 以为「订单维度表」可以完全替代事实表中的订单号,结果ETL链路断了就全完了

所以我的建议是:

  • 业务主键永远保留,哪怕你觉得没用
  • 类型用varchar,别用int或bigint
  • 属性提取要克制,只保留分析确实需要的字段
  • 定期检查退化维度,看看有没有属性被遗漏

3.7 核心逻辑图

下面这张图展示了退化维度的处理流程:

退化维度处理流程 退化维度处理决策流程 业务主键出现 是否包含维度属性? 否 作为事实度量 是 作为退化维度 属性数量评估 (≤3个 / >3个) ≤3个 直接保留在事实表 >3个 建独立退化维度表 ⚠️ 业务主键本身 必须保留在事实表

3.8 总结要点

退化维度陷阱的本质,是混淆了「业务标识」和「业务度量」。业务主键是用来标识一件事的,不是用来计算数值的。

记住三条原则:

  • 业务主键永远是维度,不是事实
  • 保留主键本身,它是数据质量的最后防线
  • 属性提取要适度,别为了建模而建模

嗯,这个坑其实很好避免。只要你在设计事实表时,多问自己一句:「这个字段能求和吗?」——答案如果是「不能」,那它大概率是个维度。