在数据分析业务中,存在时间,机构,产品等平衡维度,同时,也存在不少非平衡维度,由于非平衡维度没有固定数量的层次,因此,通常设计成父子维度。本文介绍父子维度及其实现方法。
在子公司成本分析的场景中,存在公司维度表,如图1所示,每个节点表示一个公司,节点中的数字表示公司ID,从图1中可以看到,公司1拥有子公司2和3,公司3拥有子公司4和5,表1是其对应的关系数据库表形式,在成本分析多维模型中,公司1的成本=公司1自身成本+公司2成本+公司3成本,公司3成本=公司3自身成本+公司4成本+公司5成本;
图1
表1
在此场景下,计算公司1的成本,需要用SQL递归获取公司1的子公司2和3,然后获取公司3的子公司4和5。很明显,这种SQL获取的代价是极高的,尤其是同时查询大量公司成本时,会产生不可控数量的SQL查询,严重占用数据库资源,拖累数据服务的响应时间。此方法的优点是逻辑清晰,不需要使用复杂SQL语句和特定函数。
为了解决产生过多SQL查询的问题,可以把整个维度表全量加载到内存中,递归查找过程在内存中完成,但是,此方案只能在数据量少的维度表上使用,当维度表数据量大时,数据无法完全加载到内存中,同时,数据缓存在内存中,需要解决数据刷新、数据一致性等问题,还存在第一次加载慢的问题,随着后端工具比如Kylin等加速工具的应用,大量数据堆积在内存中只会增加数据服务工具的复杂度和开发工作量,并没有明显的收益。
采用桥接表是另外一种解决产生过多SQL查询问题的办法。表2是图1对应的桥接表,对于每个公司,记录包括自己在内的所有层次子公司,比如在图1中,公司1至5的祖先都是公司1,因此,在表2中包含5行以公司1为祖先节点的数据。如此,汇总公司1的数据时,不需要递归SQL查询公司1包含的全部层次子公司,只需要一条SQL语句,将公司维度表和事实表关联,并将superior id=1作为过滤条件即可。
表2
在数据服务工具开发中,表2对用户是透明的,用户看到的是表1与事实表的关联,由工具判断何时关联表2。这是因为,将表2直接暴露给用户,与事实表关联,容易造成重复计算。由于表2中包含两行id为2的公司,,因此,id列与事实表关联,并且用id=2作为过滤条件时,计算结果是正确数据的两倍,也就是说公司2被重复计算了两次。
存在部分场景,只有叶子节点才存在真实数据,非叶子节点都是逻辑节点,不存在真实交易数据,即在图1中,公司1和3是逻辑节点,2,4,5是实际数据节点,则公司1的成本=公司2+公司4+公司5,公司3的成本=公司4+公司5,此时,需要在表2中添加一列,用于标记每个节点是否为叶子节点,如表3所示。在SQL查询时,where条件需要加上is leaf的条件判断。
表3
也可以不采用桥接表的设计方法,直接存放子节点列表,如表4所示,或者采用全路径存放,如表5所示。表5中,每行path为从根节点到当前节点的全路径,加号结尾表示非叶子节点,点号结尾表示叶子节点。做过关系数据库设计的同学,可能觉得表4和表5连第一范式都不满足,但是,在数据分析类系统设计中,几乎不会考虑规范化这个因素。表4和5共同的缺点一是节点的变动,可能导致全部路径重算,二是需要专门的函数支持表达式计算和路径搜索,适应性差,操作复杂。
表4
表5
减法是加法的一种,因此,为了使桥接表支持减法运算,需要在表3的基础上添加计算因子列,加法的计算因子为1,减法的计算因子为-1,如表6所示。其中,1=2+3,3=4-5。
表6
以上所有的设计方法都只支持加法和减法操作,无法满足乘法和除法的使用场景。对表4进行扩展,得到表7所示的设计模式,其中,1=2/3,3=4+5,即可以支持乘法和除法操作。但是,与表4的方案缺点一样,表7同样需要专门的函数支持表达式计算和路径搜索。同时,表4、5和7都存在钻取和切片实现复杂的问题。
表7
本文介绍了父子维度的几种设计方法和优缺点。当使用了父子维度的设计方案后,计算顺序成为另外一个需要重点解决的问题,此问题将在后续文章中介绍。
领取专属 10元无门槛券
私享最新 技术干货