引言
在数据仓库的世界里,维度表就像一本“说明书”,记录了业务中重要实体的详细信息,比如客户是谁、产品是什么、时间是哪天。这些信息帮助我们理解和分析数据。不过,现实生活中,事情总在变化,比如客户搬家了、产品改名了,这些变化虽然不频繁,但确实会发生,我们称之为“缓慢变化维度”(Slowly Changing Dimensions, SCD)。如何处理这些变化,既要保持数据的准确性,又要保留历史的痕迹,是数据仓库设计中的一大挑战。
除了变化的属性,维度表里的信息还可能有层次关系,比如时间可以分成年、月、日,地址可以分成国家、省份、城市。怎么组织这些层次关系,也是个关键问题。这篇文章将用简单易懂的语言,结合《数据仓库工具箱》第三版第二章的内容,详细讲解什么是缓慢变化维度,怎么处理它的属性变化,以及如何处理维度层次关系,最后通过一个零售案例把这些概念讲透彻。
1. 什么是缓慢变化维度(SCD)?
1.1 维度和维度属性
先来说说维度表是什么。想象你在超市买东西,结账时收银员会记录你的购买信息:买了什么、什么时候买的、你是哪位顾客。这些信息里,“买了什么”是产品维度,“什么时候”是时间维度,“你是哪位”是客户维度。每个维度表就像一个档案夹,里面装满了描述性的信息,比如客户维度表可能有你的姓名、地址、电话号码等,这些就是“维度属性”。
1.2 缓慢变化维度的定义
但生活不是静止的,你的地址可能会因为搬家而变,电话号码可能会因为换号而改。这种变化不常发生,但一旦发生,就需要数据仓库有所反应。这种“慢慢变动的维度属性”,就被称为缓慢变化维度(SCD)。SCD的核心问题是:当这些属性变了,我们是直接覆盖掉旧信息,还是保留历史记录?不同的处理方式,会影响到数据仓库的功能和使用。
2. 缓慢变化维度的类型及处理方法
处理缓慢变化维度时,有好几种方法,每种方法适合不同的场景。下面我们一一来看。
2.1 Type 0:不变
什么是Type 0?
有些属性天生就不会变,比如你的出生日期,一旦录入,就永远是那个值。这种情况我们叫Type 0。
怎么处理?
很简单,不用管它,录进去就完事了,之后不会改动。
适合哪里?
像出生日期、性别(在某些业务场景下)这种固定不变的属性,用Type 0就够了。
2.2 Type 1:覆盖
什么是Type 1?
如果一个属性变了,我们不在乎它的历史记录,直接用新值把旧值盖掉,这就是Type 1。比如你的电话号码变了,我们只关心你现在的号码,之前的号码不重要。
怎么处理?
在维度表里找到对应的记录,把旧值更新成新值。比如客户A的电话从“123-4567”变成“987-6543”,我们直接把表里的电话号码改成“987-6543”,旧号码就没了。
优缺点:
- 优点:简单粗暴,表里数据量不会增加。
- 缺点:历史记录丢了,想查以前的号码查不到。
适合哪里?
适合那些历史不重要、只关心当前状态的属性,比如电话号码、邮箱地址。
小案例:
假设有个客户表:
客户ID | 姓名 | 电话号码 |
---|---|---|
A | 张三 | 123-4567 |
张三换了号码变成“987-6543”,用Type 1处理后,表变成:
客户ID | 姓名 | 电话号码 |
---|---|---|
A | 张三 | 987-6543 |
旧号码“123-4567”没了。 |
2.3 Type 2:添加新行
什么是Type 2?
如果我们既想要最新的信息,又想保留历史记录,就用Type 2。每次属性变了,不改原来的记录,而是加一行新的,把变化后的信息记下来。
怎么处理?
- 给维度表加一个“代理键”(surrogate key),用来区分不同的记录行。
- 属性变了,就插一行新记录,写上新值和新的代理键。
- 为了知道哪行是当前的、哪行是历史的,可以加“生效日期”(effective date)和“失效日期”(expired date),或者加个“当前标志”(current flag)。
举个例子:
还是客户表,假设张三搬家了,地址从“旧地址”变成“新地址”。原始表是:
代理键 | 客户ID | 姓名 | 地址 |
---|---|---|---|
1 | A | 张三 | 旧地址 |
用Type 2处理后,地址变了,表变成:
代理键 | 客户ID | 姓名 | 地址 | 生效日期 | 失效日期 | 当前标志 |
---|---|---|---|---|---|---|
1 | A | 张三 | 旧地址 | 2020-01-01 | 2023-01-01 | N |
2 | A | 张三 | 新地址 | 2023-01-01 | NULL | Y |
- 旧记录的失效日期定为2023-01-01,当前标志改成“N”(不是当前)。
- 新记录的生效日期是2023-01-01,失效日期暂时为空,当前标志是“Y”(是当前)。
优缺点:
- 优点:历史记录全保留,想查张三以前住哪都能查到。
- 缺点:表里的行数会变多,可能拖慢查询速度。
适合哪里?
适合需要跟踪历史变化的属性,比如地址、职位、婚姻状况。
2.4 Type 3:添加新列
什么是Type 3?
如果历史记录不用全保留,只想记最近的几次变化,可以用Type 3。属性变了,不加新行,而是在表里加一列,把旧值挪过去。
怎么处理?
- 在维度表里为这个属性多加一列,比如“前一次值”。
- 属性变了,把当前值移到“前一次值”列,新值写到当前列。
举个例子:
客户表有婚姻状况,张三从“单身”变成“已婚”。原始表:
客户ID | 姓名 | 婚姻状况 |
---|---|---|
A | 张三 | 单身 |
改成Type 3结构后:
客户ID | 姓名 | 当前婚姻状况 | 前一次婚姻状况 |
---|---|---|---|
A | 张三 | 已婚 | 单身 |
优缺点:
- 优点:能保留有限历史,表行数不会增加。
- 缺点:只能记固定次数的变化(看你加了几列),多了就记不下了。
适合哪里?
适合只关心最近几次变化的属性,比如会员等级、婚姻状况。
2.5 混合方法
现实中,一个维度表里可能有好几种属性,有的用Type 1,有的用Type 2,有的用Type 3。比如客户表里,电话号码用Type 1覆盖,地址用Type 2加新行,会员等级用Type 3加新列。这样可以根据业务需求灵活搭配。
3. 维度层次关系
维度表里的属性有时候不是平铺的,而是有层次关系。比如时间维度有年、季度、月、日,地理维度有国家、省份、城市。怎么组织这些层次,有两种常见方法:星型模式和雪花模式。
3.1 星型模式(Star Schema)
什么是星型模式?
把所有层次的属性都塞到一个表里,不管它们有没有关系。比如时间维度表里,直接放年、季度、月、日。
举个例子:
时间维度表:
时间ID | 年 | 季度 | 月 | 日 |
---|---|---|---|---|
1 | 2023 | Q1 | 1月 | 1日 |
优缺点:
- 优点:查起来快,因为不用连很多表。
- 缺点:数据可能会重复,比如“2023”在每行都出现。
适合哪里?
大部分数据仓库都爱用星型模式,尤其是需要快速查询的时候。
3.2 雪花模式(Snowflake Schema)
什么是雪花模式?
把层次拆开,分别存到不同的表里,表之间用键连起来,像雪花一样。比如地理维度分成国家表、省份表、城市表。
举个例子:
国家表:
国家ID | 国家名称 |
---|---|
1 | 中国 |
省份表:
省份ID | 省份名称 | 国家ID |
---|---|---|
1 | 广东 | 1 |
城市表:
城市ID | 城市名称 | 省份ID |
---|---|---|
1 | 广州 | 1 |
优缺点:
- 优点:数据不重复,规范化强。
- 缺点:查的时候要连好几个表,速度慢。
适合哪里?
适合特别在意数据冗余,或者层次关系很复杂的场景。
3.3 选哪个模式?
星型模式因为查得快,用得最多,尤其是在分析型数据库里。但如果数据量巨大,或者业务要求严格规范化,雪花模式可能更好。具体选哪个,得看你的需求。
4. 具体案例分析:零售业务的数据仓库
为了把这些概念讲明白,我们来看一个零售公司的例子,设计它的客户维度和产品维度。
4.1 业务背景
假设我们为一家卖日用品的公司建数据仓库,公司想分析销售情况、客户行为。数据仓库里有客户维度、产品维度和销售事实表。
4.2 维度设计
客户维度:
- 属性:客户ID、姓名、地址、电话号码、会员等级
- 变化情况:
- 姓名:基本不变(Type 0)
- 地址:搬家会变,需保留历史(Type 2)
- 电话号码:换号会变,历史不重要(Type 1)
- 会员等级:消费多会升级,保留前一次等级(Type 3)
产品维度:
- 属性:产品ID、产品名称、类别、子类别、供应商
- 变化情况:
- 产品名称:品牌重塑会变,需保留历史(Type 2)
- 类别和子类别:业务调整会变,需保留历史(Type 2)
- 供应商:合同变了会换,需保留历史(Type 2)
4.3 处理缓慢变化维度
客户维度表:
代理键 | 客户ID | 姓名 | 地址 | 电话号码 | 当前会员等级 | 前会员等级 | 生效日期 | 失效日期 | 当前标志 |
---|---|---|---|---|---|---|---|---|---|
1 | A | 张三 | 旧地址 | 123-4567 | 银卡 | 无 | 2020-01-01 | 2023-01-01 | N |
2 | A | 张三 | 新地址 | 987-6543 | 金卡 | 银卡 | 2023-01-01 | NULL | Y |
- 姓名:Type 0,不变。
- 地址:Type 2,搬家后加新行。
- 电话号码:Type 1,直接覆盖。
- 会员等级:Type 3,加列记前一次等级。
产品维度表:
代理键 | 产品ID | 产品名称 | 类别 | 子类别 | 供应商 | 生效日期 | 失效日期 | 当前标志 |
---|---|---|---|---|---|---|---|---|
1 | P1 | 旧牙膏 | 日化 | 牙膏 | 厂商A | 2020-01-01 | 2023-01-01 | N |
2 | P1 | 新牙膏 | 日化 | 牙膏 | 厂商B | 2023-01-01 | NULL | Y |
- 产品名称、类别、子类别、供应商:全用Type 2,变化时加新行。
4.4 处理维度层次关系
客户维度:
地址有国家、省份、城市层次。为查得快,用星型模式,全放一个表:
代理键 | 客户ID | 姓名 | 国家 | 省份 | 城市 | 电话号码 | 当前会员等级 | 前会员等级 | 生效日期 | 失效日期 | 当前标志 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | A | 张三 | 中国 | 广东 | 广州 | 123-4567 | 银卡 | 无 | 2020-01-01 | 2023-01-01 | N |
产品维度:
类别和子类别有层次,也用星型模式:
代理键 | 产品ID | 产品名称 | 类别 | 子类别 | 供应商 | 生效日期 | 失效日期 | 当前标志 |
---|---|---|---|---|---|---|---|---|
1 | P1 | 旧牙膏 | 日化 | 牙膏 | 厂商A | 2020-01-01 | 2023-01-01 | N |
4.5 事实表设计
销售事实表用代理键连维度表,支持Type 2的历史记录:
销售ID | 客户代理键 | 产品代理键 | 销售日期 | 销售额 |
---|---|---|---|---|
1 | 1 | 1 | 2022-06-01 | 100 |
2 | 2 | 2 | 2023-06-01 | 150 |
这样就能查到张三搬家前后的购买记录,和牙膏改名后的销售情况。
5. 总结
缓慢变化维度是数据仓库里处理变化的“法宝”。Type 1简单覆盖,适合历史不重要的场景;Type 2加新行,适合需要历史追踪;Type 3加新列,适合记有限变化。实际中可以混用,灵活应对。
维度层次关系上,星型模式查得快,用得广;雪花模式更规范,但查得慢。零售案例里,我们用混合SCD和星型模式,既保留了历史,又保证了效率。希望这篇文章让你对SCD和维度层次关系有个清晰的认识!