零案例:
用户维度表(不使用代理主键):
用户id | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 男 | 北京 | 3 | 财务部 |
采购事实表:
订单id | 用户id | 产品id | 时间 | 金额 |
1 | 张小花 | 乌龟 | 2000-01-01 | 15 |
2 | 张小花 | 王八 | 2000-02-01 | 25 |
性别汇总表,每年一个分区:
年度 | 性别 | 金额 |
2000 | 男 | 40 |
一 . 第一种方式:直接修改维度值
变化属性:张小花性别从男变成女
修改后的维度属性:
用户id | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 女 | 北京 | 3 | 财务部 |
优点: 简单
缺点: 丢历史, 所有数据都挂在最新维度值上。其实也算不得缺点,可能就是要这个效果。
适用场景:适合修正错误属性,或者确定目前和以后不需要历史的属性。比如用户性别,如果需要修改,肯定是以前弄错了。
隐含的成本: 汇总必须和明细保持一致。 如果有基于直接修改的维度属性的汇总,维度属性修改后,必须重新汇总数据。上面表格,性别汇总表的汇总数据和最新的明细数据已经不一致了, 得全量刷新一下(所有的历史分区都得刷新, 而且也很难判定是不是有属性更新了,干脆每次调度刷全部历史分区)。
再看优点:维度表本身处理简单了, 但是以直接修改的维度属性进行汇总的所有汇总表, 都得进行全量更新。 代价也不小。
经验: 非必要不汇总, 即使汇总也最好基于key汇总,比如基于用户id 进行汇总,虽然性能不如基于性别的好,但是维护成本低。性能达标即可,没有必要追求极致,成本非常高。
二. 第二种方式,加新行
变化属性:张小花在2000-01-10 ,从财务部调到后勤部。 订单用来计算成本或者效益,当时在哪个部门发生的,归属哪个部门。
拉链维度表(没用代理主键):
用户id | 开始时间 | 结束时间 | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 0001-01-01 | 2000-01-10 | 男 | 北京 | 3 | 财务部 |
张小花 | 2000-01-10 | 9999-01-01 | 男 | 北京 | 3 | 后勤部 |
缺点: 得比对需要记录历史的属性,如果发生变更,生成一个新行。
优点:能准确描述事实表的历史环境。历史汇总无需重新刷新。
可能导致的问题:多个不相关属性缓慢变化, 结果导致维度表整体快速变化。 或者一些属性变化频率明显快与其他属性。 极值:用户的最后下单时间。 这个说起来是用户属性,但是却是用户每次下单都不一样。放在用户维度,会导致用户维表变成快变维度。所以得当成退化维度或者单独建维度表。适可而止,别过分,太过分的话,用户表的可变属性都得拆出去,会生成太多的小维表。用户表的变化频率在可接受范围内就行了。
三. 第一种方式和第二种方式比较
第一种方式其实就是修正维度属性, 顺便修正相关的下游表(根据维度属性进行汇总的表)。
第二种方式是记录正确数据的历史变更。
拿到一个维度表,首先分一下, 哪些属性的变更是修正数据,哪些属性的变更是正常的变更。
有必要,再分一下属性的变化频率,重点关注一下变化特别快的属性。
不能盲目的对所有属性采用第一种或者第二种方法。
四. 其他方式
五. 每天一个最新快照
这个应该是大数据里用的最普遍的一种方式, 可能也是问题最多的方式。
1. 如果关联最新分区的维度表, 其实就是第一种方式,完全丢了历史。
2.如果根据业务时间关联对应分区的维度, 维度表其实就相当于对全表每天一条拉链, 而忽略有些属性是需要第一种方式的。如上面表格的性别属性, 事实表 和维度表通过 用户id 和etldt 进行关联,有一段时间关联出来的维度属性就不正确。 这个不是历史,是需要被修正的。还有,如果业务日期足够久远,维度表可能提供不了那么久远的快照。
优点: 简单
缺点: 浪费一点存储, 更关键的是他全行采用第一种或者第二种方法,没有进行拆分。 不一定满足需求
六. 第一种方式+ 第二种方式
这两种方式分别有适用对象,而一个维度表通常包含多个类型的属性,所以通常我们要两种方式结合使用。
用户id | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 男 | 北京 | 3 | 财务部 |
拿这个举例,
第一步,划分一下属性, 用户性别适合第一类; 用户地址, 用户等级, 部门适合第二类
假设, 2001-01-01 ,张小花从财务部调了 后勤部
2001-02-01 ,发现张小花的性别搞错了,
生成的维度表应该是这样子:
1. 初始
用户id | 开始时间 | 结束时间 | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 0001-01-1 | 9999-01-01 | 男 | 北京 | 3 | 财务部 |
2. 2001-01-01 ,张小花从财务部调了 后勤部
用户id | 开始时间 | 结束时间 | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 0001-01-1 | 2001-01-01 | 男 | 北京 | 3 | 财务部 |
张小花 | 2001-01-01 | 9999-01-01 | 男 | 北京 | 3 | 后勤部 |
3. 2001-02-01 ,发现张小花的性别搞错了,
用户id | 开始时间 | 结束时间 | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 0001-01-1 | 2001-01-01 | 女 | 北京 | 3 | 财务部 |
张小花 | 2001-01-01 | 9999-01-01 | 女 | 北京 | 3 | 后勤部 |
注意, 这里把已经关链的数据的性别也进行了修正。但是需要关注历史的字段没有改变,所以没有生成新的行。
可以增加一个cdc字段, 把需要记录历史的字段生成一个cdc, 当cdc变化时,关闭旧链,生成新链。
其他字段,每次都用最新值提花以前的旧值, 包括已经关链的行。 关链,只是对需要记录历史的属性关联,不再改变。 需要修正的数据,每次都是全表更新。
再啰嗦一句, 如果已经对维度表的属性进行了划分,哪些适用第一种方式,哪些适用第二种方式, 千万别根据第一种方式的属性进行汇总,千千万!! 除非你确认他绝对正确。 否则就需要每次对这个汇总表的所有历史分区重新计算。
七. 第一种方式+第二种方式变种
用户看数的需求是多样的,也不该由数仓去约束人家看数方式对不对,只能尽量去满足。
用户id | 用户性别 | 用户地址 | 用户等级 | 部门 |
张小花 | 女 | 北京 | 3 | 财务部 |
比如,明知道用户性别不存在历史只有对错, 但是数据已经对外输出了,不可更改。然后又想看看真实的数据。
实现方式: 加列, 一列性别记录历史做拉链, 一列保留最新值。想用那列用哪列,想咋看咋看。
这块就没止境了, 维度就是想各种办法, 满足用户看数的需求。