一、维度设计之维度变化
1、数据仓库的重要特点之一是反映历史变化,,**维度的属性并不是静态的,它会随着时间的流逝发生缓慢的变化与数据增长较为快速的事实表相比,维度变化相对缓慢。在一些情况下,我们需要保留历史数进行分析。所以我们需要对缓慢变化的维度进行设计和处理。**在 Kimball 的理论中,种处理缓慢变化维的方式,下面通过简单的实例进行说明,具体细节请翻阅 Kimball 的相关书籍。
2、处理缓慢变化维度一般存在三种方式
(1)、重写维度值
此种方式,不保留历史数据,始终取最新数据。相当于保留最新的数据
(2)、插人新的维度行
此种方式,保留历史数据,维度值变化前的事实和过去的维度值关联。维度值变化后的事实表和当前变化后的维度值关联。(这个方式痛点不能将变化前后的记录的事实归一为变化前的维度或者变化后的维度)
(3)、添加新的维度列
此种方式是再原理的维度表增加一个维度用于区分变化前后的维度。这样就能很好解决变化前后的事实不能归一为 不同新旧维度的问题
3、快照维表
处理缓慢变化的维度问题,可以次啊用快照的方式(),。数据仓库的计算周期 般是每天一次,基于此周期,处理维度变化的方式就是每天保留一份全量快照数据。比如商品的维度,每天保留一份全量的商品快照数据,任意一天的事实均可获取当天的商品信息。也可以获取到最新的商品信息。通过限定日期,采用自然键进行关联即可。
优点:简单高效维护成本低。使用方便理解性好
缺点:会消耗更多存储资源。如果一定要杜绝此种方法,必须涉及规则清除无用数据
综合上面三种来看,由于现在的存储成本远低于cpu 、内存等成本,,此方法弊大于利。那么是否有方法既可以实现上面的优点,同时又可以很好地降低存储呢,阿里巴巴的极限存储。很大程度解决了这种问题。
4、极限存储
首先来看历史拉链存储,历史拉链存储是指利用维度模型中缓慢变化维的第二种处理方式,这种处理方式是通过新增两个时间戳字段(sta rt_dt end_dt ),将所有的以天为粒度的变更数据都记录下来,通常分区字段也是时间戳字段。
举个例子 ,例如电商数据中卖家A 1月1日在淘宝网发布了 两个商品,前端商品表将生成两条记录t1,t2,1月2日,卖家A商品下架了B,同时又发布了商品 D,前端商品表将更新记录 ti ,又新生成记录 t3。采
用全量存储方式,在 1日这个分区中存储 tl ,2 两条记录,在1月2日这个分区中存储更新后的 tl 以及 t2 t3 记录。数据存储记录如表结构
如果此种情况下我们采用历史拉链表存储,数据存储记录会出现对于不变的数据,不再重复存储。数据结构如下图:
在这样(拉链表)的情况下,我们可以通过限制时间戳字段来获取历史数据(例如我们想访问1月1日的全量数据只要我们限制 stat_dt<=20160101 and end_dt>20160101)
但是基于上面的存储方式,下游在使用的时候理解可能有一定的障碍,特别ODS 数据面向的下游用户包括数据分析师、前端开发人员等,他们不怎么理解维度模型的概念,因此会存在较高的解释成本。另外,这种储方式用 start_dt和end_dt 做分区,随 时间的推移,分区数量会极度膨胀,而现行的数据库系统都有分区数量限制。
为了解决上面两个问题。阿里也提出了相应的处理方式,即通过极限存储方式来处理。
具体为包括 1、透明化 2、分月做历史拉链表。
1、透明化
底层的数据还是历史拉链存储,但是上层做了一个视图操作,或者hive里面做一个 hook,通过分析语句的语法树,把对极限存储前的表的查询转换成对极限存储表的查询。屏蔽下游用户的使用难度。即极限存储表和全量存储使用方式是一样的。
Select * from A where ds =20160101;
等家于:
Select * from A EXST where start dt <=20160101 and end dt
>20160101 ;
2、分月做历史拉链表
假设用 start_dt和 end dt 做分区,并且不做限制,那么可以计算出一年历史拉链表最多可能产生的分区数是 365*364/2=66430 。如果每个月月初重新开始做历史拉链表,目录结构如下
再计算一年最多可能产生的分区数是: 12 ×( 1 +(30+29)/2)=5232 个。采用极限存储的处理方式,极大地压缩了全量存储的成本,又可以达到对下游用户透明的效果,是一种比较理想的存储方式。但是其本身也有一定 的局限性,首先,其产出效率很低,大部分极限存储通常需要t-2 其次,对于变化频率高的数据并不能达到节约成本的效果。因此在实际生产中,做极限存储需要进行一些额外的处理。
(1)、在做极限存储前有一个全量存储表,全量存储表仅保留最近一段时间的全量分区数据,历史数据通过映射的方式关联到极限存储表。即用户只访问全量存储表,所以对用户来说极限存储是不可
见的。
(2)、对于部分变化频率频繁的字段需要过滤。例如,用户表中存在用户积分字段,这种字段的值每天都在发生变化,如果不过滤的化,极限存储就相当于每个分区存储一分全量的数据,起不到节约存储成本的效果。
二、维度设计值特殊维度
在我们进行维度设计过程中,我们了解的除了普通的维度外还有很多特殊维度用平常的维度建模思想可能不太适用,其中包含我们常见的维度包含递归层次、行为维度、多值维度、多值属性和杂项维度等。
上面我们学习了维度的层次结构,即维度属性以层次方式或一对多的方式互相关联;或者描述为不同维度之间的主从关系 ,比如商品和类目的关系、商品和品牌的关系等。本次我们所指的是某维度的实例值的层次关系,比如某宝的类目体系
维度的递归层次,按照层级是否固定分为均衡层次结构和非均衡层次结构。比如类目,有固定数量的级别,分别是叶子类目、五级类 目、四级类目、三级类目、二级类目、 级类曰:地区,分别是乡镇/街道
区县、城市、省份、国家。对于这种具有固定数量级别的递归层次,我们称之为均衡层次结构。比如公司之间的关系,每个公司可能存在 个母公司,但可能没有固定的 级、 级等层级关系。对于这种数量级别不固定的递归层次,称为“非均衡层次结构”。
1、递归层次维表处理
在我们数据仓库中,遇到包含层次结构的的维度时,我们处理的方式包含了将层次结构扁平化和利用层次桥接表 来解决用户使用递归 SQL 的成本较高的问题。
(1)、层次结构扁平化
降低递归层次使用复杂度的最简单和有效的方法是层次结构的扁平化,通过建立维度 的固定数
级别的属性来实现,可以在一定程度上解决上钻和下钻的问题。对于均衡层次结构,采用扁平化最有
效。
例如对于某宝商品类目,通过层次记过扁平化之后,每个类目保存一条记录。并将其所属的各类目层级属性化。其中,对于高层级类目,由于其无低层级类日,则低层级类 目置为空值。其具体的存储结构方式为:
如果统计类目 ID 21 的最近一天的 GMV ,将和交易事实表通过叶子类目和类目维表的类目ID 关联之后,然后限制限制一级类目ID等于21之后进行汇总统计,即可以得到类目ID等于21 的最近一天的GMV,可以知道其使用的方便性等到大大的提升。当相应的会出现如下问题:
1)、针对某类目的上钻和下钻之前,必须知道其所属的类目层级,然后才能决定限制哪一级类目。如上所述。需要先要知道ID 在一级类目,然后在对其进行限制。
2)、假设分三级类目统计最近一天的GMV,由于某些叶子类目直接是一级类目和二级类目(比如类目 ID 等于 121456022 的类目,其是叶子类目),和交易事实表关联之后,其对应的三级类目为空,导致根据三级类目统计最近一天的 GMV 时,类目ID 等于121456022 的交易无法被统计到。下游数据统计时,为了规避此问题,如果此类目对应的三级类目为空,则取二级类目,如果二级类目也为空则取一级类目。sql 会比较复杂。
所以针对此问题,下游数据统计时,类目层次结构扁平化的另方式是回填,将类目向下虚拟,具体数据存储示例如下 所示,其中粗体部分为回填内容
对于扁平化仅包含固定数量的级别,对于非平衡层次结构,可以通过预留级别的方式来解决,但扩展性较差。
(2)、层次桥接表(不太常用,知道概念)
针对层次结构扁平化所存在的问题,可以采用桥接表的方式来解决,不需要预先知道所属层级,不需要回填,也可解决非均衡层次结构的问题。与扁平化方法相比,该方法适合解决更宽泛的分析问题,灵活性好;但复杂性高,使用成本高。,而且由于事实表和桥接表的多对关系 而带来了双重计算的隐患 。在实际应用中可以根据具体的业务需求来选择技术方案,比如在统计分析或报表中, 般都是按照固定级别进行,如按照 级类目统计 GMV 、按照省份统计 GMV 等,现在的扁化设计完全可以满足需求 ,而不需要引人复杂的桥接表。很多时候,简单、直接的技术方案却是最好的解决方案。
2、行为维度
在数据仓库中,一般我们存很多维表,如卖家主营类目维度、卖家主营品牌维度、用户常用地址维度等。其中卖家主营类目和主营品牌维度和主营品牌通过卖家的商品分布和交易分布情况,采用算法计算得到;卖家常用地址通过最近 段时间内物流中卖家的发货地址和买家的收货地址进行等到。类似
行统计得到。类似的维度,都和事实相关,如交易、物流等,称之为行为维度或事实衍生的维度。
按照加工方式,行为的维度可以划分为以下几种:
(1)、另一个维度 的过去行为,如买家最近一次访问淘宝的时间、 买家最近 次发生淘宝交易的时间等。
(2)、快照事实行为维度,如买家从年初截止至当前的淘宝交易金额、买家信用分值、卖家信用分值等。
(3)、分组事实行为维度 ,将数值型事实转换为枚举值。如买家从年初截至当前的淘宝交易金额按照金额划分的等级 买家信用分值按照分数划分得到的信用等级等。
(4)、复杂逻辑事实行为维度,通过复杂算法加工或多个事实综合加工得到。如前面提到的卖家主营类目,商品热度根据访问、收藏、加人购物车、交易等情况综合计算得到。
对于行为维度,有两种处理方式,其中一种是将其冗余至现有的维度表中,如将卖家信用等级冗余至卖家维表中 另一种是 工成单独的行为维表,如卖家主营类目。具体采用哪种方式主要参考如下两个原则:
第一: 避免维度过快增长。比如对商品表进行了极限存储,如果将商品认读加入现有的维表中。则可能会使得每日商品变更占比过高,从而导致极限存储效果较差。
第二:避免耦合度过高,比如卖家主营类目,加工逻辑异常复杂。如果融合进现有的卖家维表中,那么过多的业务稠合会导致卖家维表刷新逻辑复杂、维护性差、产出延迟等。
3、多值维度
对于多值维度, 种情况是事实表的 条记录在某维表中有多条记录与之对应。比如对于淘宝交易订单,买家一次购买了多种商品,如件毛衣和两双袜子,称为交易父订单 对于每种商品的交易,称为交易子订单:此交易父订单有两个子订单与之对应。假设设计交易父订单事实表,则对于此事实表的每一条记录,在商品表中都有 到多条记录与之对应。
针对多值维度,常见的处理方式有三种,可以根据业务的表现形式和统计分析需求进行选择。
第一种处理方式是降低事实表的粒度。在淘宝交易 中,前台业务和商业智能关注交易子订单 ,所以在数据仓库模型设计中,将交易订单设计为子订单粒度,对于每个子订单,只有 种商品与之对应。对于其中的事实,则采用分摊到子订单的方式来解决。但很多时候,事实表的度是不能降低的,多值维度的出现是无法避免的。
第二种处理方式是采用多字段。比如在房地产销售中,每次合同订都可能存在多个买受方的情况,如夫妻合买等。对于合同签订事实表,每条记录可能对应多个买受方,而合同已经是此事实中的最细粒度 ,无法通过降低粒度的方式来解决。由于合同签订的买受人 般不会太多,所以 般采用多字段方式。考虑到扩展性,可以通过预留宇段的方式,如超过三个买受方时,其余买受方填写至“其他买受方”字段。模型计如图所示:
第三种处理方式是采用较为通用的桥接表。桥接表方式更加灵活、扩展性更好,但逻辑复杂、开发和维护成本较高,可能带来双重计算的风险,选择此方式需慎重。通过在事实表和维表之间开发 个分组表,
通过此分组表建立连接。(自己很少用)
4、多值属性
维表中的某个属性字段同时有多个值,称之为“多值属性”。它是多值维度的另一种表现形式。如商品SKU维度表、商品属性维表、商品标签维表等。每个商品均有一到多个SKU、一到多个属性和一到多个标签所以商品和 SKU 、属性、标签都是多对多的关系。
如商品的尺寸和颜色等:
对于多值属性,常见的处理方式有三种,可以根据具体情况进行选择。
第一种处理方式是保持维度主键不变,将多值属性放在维度的一个属性字段 中。比如对于商品属性(注:此属性是业务上的含义,和维度建模中的维度属性含义不同),可以通过 k-v 对的形式放在 property字段中数据示例如下: 10281239: 156426871; 137396765:29229; 137400766: 3226633 。此种处理方式扩展性好,但数据使用较为麻烦。
第二种处理方式也是保持维度主键不变,但将多值属性放在维度的多个属性字段中。比如卖家主营类目,由于卖家店铺中可能同时会销售男装、女装、内衣等,所以卖家主营类目可能有多个,但业务需求是只取根据算法计算得到的 TOP3 。针对此种情况,维度的多值属性字段具体值的数量固定,可以采用多个属性字段进行存储,方便数据统计分析和报表展示。
如果多值属性字段具体值的数量不固定,则可以采用预留字段的方式,但扩展性较差。卖家主营类目维度设计如图
第三种:处理方式是维度主键发生变化。一个维度值存放多条记录。比如商品的sku维表,对于每个商品有多少sku,就有多少条记录,主键是商品ID 和SKU的 ID。这种处理方式扩展性好,使用方便,但需要考虑数据的急剧膨胀情况。比如商品属性表采用了此种处理方式,数据记录达到几百亿的级别。
5、杂项维度
项维度是由操作型系统中的指示符或者标志宇段组合而成的,一般不在一致性维度之列。比如淘宝交易订单的交易类型宇段,包括话费充值、司法拍卖、航旅等类型 :支付状态、物流状态等,它们在源系统中直接保存在交易表中。(但有时会出现事实表占用空间过大问题,通过拆分杂项维度方式解决。但自己应用很少)
综述:对于缓慢变化维度处理我们一般采用拉链表的方式,结合极限存储。处理维度变化的情况。对于特殊维度,应用比较多的层次维度次采用维度压平加空值回填父级维度的方式处理,对于行为维度经常会对卖家主营类目维度、卖家主营品牌维度、用户常用地址维度等有价值的信息定期分析计算,形成特定行为维表。多值属性和多值维度处理,则一般采用 一个字段中KV 形式或 具体数量采用多个维度和多条记录三种方式根据业务情形来具体做出选择。