4. 维度表设计:维度表结构、退化维度、角色扮演维度、维度表设计原则
维度表,说白了就是数据仓库里的「字典」和「标签本」。它告诉我们事实表中的每一行数据,到底是谁、在哪儿、什么时候、什么产品。没有维度表,事实表就是一串冰冷的数字,毫无意义。
我刚开始做维度建模时,总觉得维度表就是简单的「码表」,把ID翻译成名称就完事了。后来被业务部门追着问「为什么这个月华东区的销售额比华北区高这么多,但你们的数据里连区域层级都查不了」——嗯,那次之后我才真正重视起维度表的设计。
核心观点:维度表的质量,直接决定了数据仓库的可用性和分析深度。一个设计良好的维度表,能让分析师爱不释手;一个糟糕的维度表,会让整个数仓变成「数据垃圾场」。
4.1 维度表的基本结构
维度表的结构其实很简单,就两大部分:
- 主键(Surrogate Key):我习惯用自增整数或雪花ID,不用业务主键。为什么?因为业务主键可能会变,比如产品编码改了、部门合并了,用代理键能隔离这些变化。
- 属性列(Attribute Columns):描述维度的各种特征,比如产品名称、品类、品牌、颜色、尺寸等。
举个例子,一个典型的产品维度表长这样:
CREATE TABLE dim_product (
product_sk INT PRIMARY KEY, -- 代理键
product_id VARCHAR(20), -- 业务主键(保留用于追溯)
product_name VARCHAR(100),
category VARCHAR(50),
sub_category VARCHAR(50),
brand VARCHAR(50),
color VARCHAR(20),
size VARCHAR(10),
unit_price DECIMAL(10,2),
effective_date DATE, -- 缓慢变化维用
expiry_date DATE,
is_current CHAR(1)
);
这里要注意,product_id 是业务主键,我保留它只是为了方便和源系统对账,但维度表的主键永远是 product_sk。这个习惯让我少踩了很多坑。
4.2 退化维度
退化维度这个概念,我第一次听到时觉得挺玄乎。说白了,就是那些「本来应该做成维度表,但属性太少,直接塞进事实表更划算」的字段。
最常见的例子就是订单号。订单号本身就是一个维度,但它通常只有这一个属性(最多加个订单类型)。如果单独建一张订单维度表,里面就一列订单号,然后事实表通过外键关联——这太浪费了。不如直接把订单号放在事实表里,这就是退化维度。
我的经验:判断一个字段是否应该做退化维度,就看它有没有「可分析的属性」。如果除了ID本身,你再也想不出3个以上的描述性属性,那就直接退化到事实表。我在电商项目中,把订单号、物流单号、支付流水号都做成了退化维度,事实表只多了几个字符串字段,但省去了好几张「空壳」维度表的关联开销。
退化维度的优缺点对比:
| 维度 | 优点 | 缺点 |
|---|---|---|
| 退化维度 | 减少关联、查询更快、ETL更简单 | 事实表变宽、无法存储维度属性变化历史 |
| 标准维度表 | 可存储丰富属性、支持SCD、易于复用 | 增加关联、查询性能略低 |
4.3 角色扮演维度
角色扮演维度,名字听着高大上,其实就是一个维度表在事实表中被多次引用,每次扮演不同的「角色」。
最经典的例子就是日期维度。一张订单事实表里,可能有下单日期、付款日期、发货日期、收货日期。难道要为每个日期都建一张维度表?当然不。我们只需要一张日期维度表,然后在事实表里用不同的外键字段去关联它。
看这个例子:
-- 事实表
CREATE TABLE fact_order (
order_sk INT PRIMARY KEY,
order_date_sk INT, -- 关联 dim_date,角色:下单日期
pay_date_sk INT, -- 关联 dim_date,角色:付款日期
ship_date_sk INT, -- 关联 dim_date,角色:发货日期
customer_sk INT,
product_sk INT,
amount DECIMAL(12,2)
);
-- 查询时用别名区分角色
SELECT
d1.full_date AS order_date,
d2.full_date AS pay_date,
d3.full_date AS ship_date,
f.amount
FROM fact_order f
JOIN dim_date d1 ON f.order_date_sk = d1.date_sk
JOIN dim_date d2 ON f.pay_date_sk = d2.date_sk
JOIN dim_date d3 ON f.ship_date_sk = d3.date_sk;
除了日期,客户维度也经常玩角色扮演。比如一笔交易中,有买家、卖家、推荐人,它们本质上都是客户,只是扮演的角色不同。
避坑指南:我曾经在一个项目中,把客户维度复制了三份——dim_buyer、dim_seller、dim_referrer,结果数据量翻了三倍,维护起来想死。后来改成角色扮演,一张表搞定。记住:只要属性结构相同,就别复制表,用角色扮演。
4.4 维度表设计原则
做了这么多年维度建模,我总结了几条铁律。这些原则不是书本上抄来的,是实打实用血泪换来的。
4.4.1 原则一:用代理键,别用业务主键
前面提过,我再强调一次。业务主键会变,代理键不会。代理键是整数,关联性能好。代理键能隔离源系统变化。我见过一个项目,直接用产品编码做维度主键,结果源系统产品编码重构,整个数仓瘫痪了两周。
4.4.2 原则二:维度属性要「可枚举、可分组、可钻取」
每个属性都要能回答业务问题。比如「产品颜色」这个属性,能用来分组统计吗?能用来下钻吗?如果某个属性99%的值都是「黑色」,那它就没有分析价值,可以考虑去掉。
4.4.3 原则三:维度表要「胖」不要「瘦」
维度表宽一点没关系,把能放的描述性属性都放进去。分析师喜欢在维度表里一次性拿到所有标签,而不是关联四五张表才能拼出一个完整的产品信息。我习惯把维度表的字段数控制在20-50个之间,太少了不够用,太多了维护困难。
4.4.4 原则四:处理缓慢变化维(SCD)
维度属性会变,比如客户搬家了、产品改价了。怎么处理?
- SCD Type 1:直接覆盖,不保留历史。适合「改错了」的属性。
- SCD Type 2:新增一行,保留历史。适合需要追溯分析的属性。
- SCD Type 3:加一列存旧值。适合「只关心上一次变化」的场景。
我个人90%的场景用Type 2,因为它最灵活。但要注意,Type 2会让维度表膨胀,需要定期清理过期数据。
4.4.5 原则五:维度表之间不要直接关联
维度表应该通过事实表间接关联,不要搞成「维度表关联维度表」的网状结构。否则你就是在建雪花模型,查询性能会急剧下降。记住:星型模型是王道,雪花模型是邪道(除非你有特殊理由)。
总结一下:代理键、胖维度、可分析属性、SCD策略、星型结构——这五条原则,你只要遵守了,维度表设计就不会出大问题。
4.5 维度表设计核心逻辑图
下面这张图,是我自己梳理的维度表设计决策流程。每次设计新维度时,我都会过一遍这个流程:
这个流程我用了好几年,基本覆盖了日常工作中90%的场景。你按照这个思路走,不会出大错。
4.6 避坑总结
最后,分享几个我踩过的坑,希望你别再踩:
- 别把维度表做成「代码翻译表」:只放ID和名称,不放分析属性。这样的维度表,分析师用一次骂一次。
- 别滥用退化维度:如果一个字段有10个以上的属性,还是老老实实建维度表吧。退化维度只适合「属性极少」的场景。
- 别忘记处理NULL:维度表中的NULL会导致事实表关联丢失。我习惯在维度表中加一行「未知」记录,代理键设为-1,专门处理这种情况。
- 别让维度表无限膨胀:SCD Type 2会让维度表越来越大。定期归档历史数据,或者用拉链表技术控制数据量。
嗯,维度表设计就聊到这儿。记住:维度表是数据仓库的「脸面」,分析师每天打交道最多的就是它。设计得好,人人夸你;设计得烂,天天背锅。