1. 维度建模基础概念陷阱:混淆事实表与维度表

说实话,我见过太多数据团队在这个坑里摔得鼻青脸肿。明明业务需求梳理得挺清楚,一到建表就乱了套——把用户信息塞进事实表,把交易金额扔进维度表。结果呢?模型跑起来慢得像蜗牛,业务方要个报表得等半天。

为什么会这样?说白了,就是没搞明白事实表和维度表各自的定位。我刚开始做数仓时也犯过这毛病,后来被一个老前辈点醒,才真正理解这两者的区别。

事实表 vs 维度表:一张表说清楚

对比维度 事实表 维度表
核心作用 记录业务事件 描述业务对象
数据特征 数值型、可加性 文本型、描述性
数据量 快速增长,通常很大 相对稳定,变化缓慢
主键 复合主键(外键组合) 单一主键(代理键)
更新方式 追加写入,极少修改 缓慢变化维(SCD)
典型例子 订单金额、销售数量 客户姓名、产品分类

记住这个口诀:事实表回答"发生了什么",维度表回答"谁、什么、在哪、何时"。

最常见的混淆场景

我在项目中遇到过好几次这样的场景:业务方说"我要看每个客户的购买金额",结果建模的同学直接把客户姓名、年龄、性别全塞进了事实表。你想想看,这样做会有什么后果?

  • 数据冗余爆炸:同一个客户买100次,他的信息就重复存100次
  • 更新维护噩梦:客户改个手机号,得扫全表更新,性能直接崩
  • 查询效率低下:事实表膨胀得飞快,扫描成本成倍增加

嗯,这里要注意,不是所有数值都该进事实表。比如产品单价,虽然是个数字,但它描述的是产品属性,应该放在产品维度表里。只有那些随业务事件变化的数值,比如成交金额、折扣金额,才属于事实表。

一个真实的错误案例

我记得有个电商项目,团队把订单明细和商品信息混在一个表里。表结构大概长这样:

-- 错误的做法:事实和维度混在一起
CREATE TABLE order_mixed (
    order_id        STRING,
    customer_name   STRING,    -- 维度属性
    customer_level  STRING,    -- 维度属性
    product_name    STRING,    -- 维度属性
    product_price   DECIMAL,   -- 维度属性
    order_amount    DECIMAL,   -- 事实度量
    order_qty       INT,       -- 事实度量
    order_date      DATE       -- 维度属性
);

这个表跑了一个月,数据量从100万涨到5000万。每次查某个客户的购买记录,都得扫全表。更可怕的是,商品调价后,历史数据全得跟着改——你敢信?

正确的做法应该是拆成两张表:

-- 维度表:客户
CREATE TABLE dim_customer (
    customer_sk      INT PRIMARY KEY,
    customer_name    STRING,
    customer_level   STRING,
    effective_date   DATE,
    expired_date     DATE
);

-- 维度表:产品
CREATE TABLE dim_product (
    product_sk       INT PRIMARY KEY,
    product_name     STRING,
    product_price    DECIMAL,
    category         STRING
);

-- 事实表:订单
CREATE TABLE fact_order (
    order_id         STRING,
    customer_sk      INT,       -- 外键
    product_sk       INT,       -- 外键
    order_amount     DECIMAL,   -- 事实
    order_qty        INT,       -- 事实
    order_date       DATE,
    FOREIGN KEY (customer_sk) REFERENCES dim_customer(customer_sk),
    FOREIGN KEY (product_sk) REFERENCES dim_product(product_sk)
);

个人习惯:我每次建模前都会先问自己三个问题——这个数据是描述业务对象的吗?它会频繁变化吗?它是不是文本描述?如果三个都是,那它大概率是维度属性。

核心知识体系

下面这张图是我自己总结的,帮你一眼看透事实表和维度表的关系:

维度建模核心结构 事实表 记录业务事件 时间维度 年/月/日/周 客户维度 姓名/等级/地区 产品维度 名称/分类/价格 关键原则 1. 事实表通过外键与维度表关联 2. 维度表提供上下文描述信息 3. 事实表只存度量值和外键,不存描述性字段 ← 维度表提供"谁、什么、何时" → 事实表记录"发生了什么" →

避坑指南

我曾经踩过的坑:有个金融项目,我把交易渠道的描述信息(渠道名称、渠道类型、渠道负责人)全塞进了交易事实表。结果渠道改名时,要更新上亿条历史数据,跑了一整晚还没跑完。后来改成维度表,只改一条记录就搞定了。

给你几个实用的判断方法:

  1. 看数据变化频率:每天变几次的,大概率是事实;几个月才变一次的,大概率是维度
  2. 看数据用途:用来做聚合计算的,进事实表;用来做筛选分组的,进维度表
  3. 看数据来源:来自业务流水表的,进事实表;来自基础信息表的,进维度表

我的小技巧:建模时先画一张星型模型草图,把业务事件放在中间,周围画上相关的业务对象。如果某个字段你犹豫不决,就问自己——"这个字段会随着每次交易变化吗?" 会,就放事实表;不会,就放维度表。

记住,维度建模不是死板的教条。但事实表和维度表的边界,是不能模糊的底线。搞混了,后面所有ETL、报表、分析都会跟着乱。我见过太多项目因为这个基础问题,最后不得不推倒重来。