数据仓库之维度模型的核心理念

10分钟简读经典著作 Data Warehouse Toolkit

Posted by Yezhiwei on August 14, 2020

原文链接:https://www.yejianye.com/2018/05/21/data-warehouse-toolkit/

背景

简读经典著作 Data Warehouse Toolkit,带着问题去读书可以事半功倍。先说说我(这里的“我”指原文作者)读这本书的背景,在 Glow 我们很重视各类数据的采集,也很喜欢用数据说话。在以前的文章中提过,我们目前用于各类产品分析的 Dashboard 有几十个,总共包含了上千张图表。虽然产品经理与分析师们用着还不错,但我觉得在底层的数据组织上太随意,没有一套理论与规范来支撑,长此下去会越搞越乱。读这本书之前,我知道数据仓库中最常用的是维度模型,但处于一知半解的状态。所以我是带着下面 4 个问题开始看这本书

  • 为什么维度模型适用于商业分析?
  • 维度模型的基本设计理念是怎样的?
  • 如何记录所有的历史数据与变更?
  • 如何把控数据的质量?

对我来说,这本书的灵魂在第1, 2, 19章,前两章已经把维度建模的主要方法都介绍了,回答了我的前三个问题。第 3 - 18 章列举了这套方法在不同行业的实际应用,我根据工作相关性以及兴趣,选读了第3,5,8,14 与 15 章。第 19 章讲述了 ETL 中包含的各个子系统,其中有一个部分专门讲数据质量的管理,回答了我的第 4 个问题。很佩服 Ralph 老爷子竟然可以在 ETL 中分出 34 个子系统,这也是为什么我强调这本书不能硬刚,要跳读。

当然,每个人的问题可能都不太一样,所以下面还是以科普数据仓库的基本知识为主脉络,并在相应的模块回答之前的那几个问题。

数据仓库由哪些部分组成?

dw-architecture

在最底层,我们需要将业务场景中的各类数据导入到数据仓库中,包括业务数据库、日志文件、第三方服务数据等等。这一层的目的是将企业的各类数据打通,统一存储在一个系统中。在这一层我们对源数据的结构只做少量处理。做为数据仓库最底层的原始数据,它们不应该被分析人员直接查询。

中间层是数仓的设计者根据业务流程设计的维度模型。由于业务数据库的主要任务是支持日常业务中的增删查改,而数据仓库的主要任务是支持大规模的商业分析。所以数仓底层的原始数据必须转换为更适合统计分析的维度模型才能发挥价值。我们会在下面的章节中详细介绍维度模型。

最上层是构建在数仓之上的各类数据应用场景,包括在维度模型上直接进行 Ad-hoc 查询,建立统计报表,或是做数据挖掘。

若要深入了解,可以阅读第一章中的 Kimball’s DW/BI Architecture

什么是维度模型?

维度模型是专为统计分析优化的数据模型,维度模型的设计由业务流程驱动,每一个业务流程对应一张事实表以及若干维度表。在对每项业务做统计分析时,我们所关心的指标记录在事实表中,如交易金额、利润、销售量等。过滤与分组的条件则记录在维度表中,如交易时间,地区,商品种类等。在事实表的每条记录中,除了指标数据,还保存连接各维度表的外键。这个模型足够简单直观,对于非技术的业务人员来说理解起来也不困难。下图是一个维度模型的例子

dimensional-model-example

这个模型很好的均衡了查询复杂度与数据的维护成本。

对于查询来说,最简单的模型是一张超宽的业务表,所有的查询条件都有对应的表内字段,不需要任何 join 。但坏处是这张表的维护成本很高,有大量的冗余数据,经常要修改表结构或是现有数据来适应业务的变化。对于数据库管理员来说,这张表就是一场灾难。而经典的 ER 模型是另一个极端,表的维护成本低,没有冗余数据,但对于统计查询不友好。业务人员不但要理解错综复杂的表与表之间的网状关系,而且构建统计查询的 SQL 语句的难度也很大,还要忍受复杂 SQL 语句低下的执行效率。

维度模型就是这两者之间的一个折中。对业务人员来说,查询时有统一的语句结构,并且最多只有一级 join。对维护人员来说,为应对业务需求的变化,大部分数据表的更改都发生在数据量较小的维度表上。这是一个非常棒的设计!

关于维度表的详细介绍,可以阅读书中第一章的 Dimensional Modeling Introduction

事实表设计

事实表是维度模型的中枢,在设计事实表时最主要有两个问题

  • 定义指标
  • 确定每条记录的粒度

指标大部分情况下都是数字,如果出现文本的指标,则该考虑一下它到底是指标还是维度。在分析统计中,我们会对指标数据做各种聚合,比如计算总和,平均数,中位数等等,根据对聚合操作的支持,我们可以把指标分为可加,半可加与不可加。可加的指标是最好用的,比如交易金额与销售量。无论如何切分数据,交易金额总和与销售量总和都是有意义的。半可加的指标只在某些数据切分下可以求总,比如账户余额,在多个账户之间可以求总,但若将账户以时间维度切分,就总就是没有意义的。不可加指标的例子包括销售单价或是税率。许多时候,半可加或是不可加指标是可以通过可加指标计算得到的,比如销售单价 = 交易金额 / 销售数量。在设计事实表中,一个原则就是我们要尽量存储可加的指标。

事实表中每条记录的粒度越小,在其之上的统计分析就越精细。所以一般来讲,我们都会以业务中最小的原子事务为粒度来构建事实表。比如在常见的购物场景中,最小的粒度并不是一个订单,而是订单中的一个子项,即购买某某商品 N 件,所以事实表的每条记录应是订单中的一个子项。事实表设计的另一个重要原则是要保证表中每一条记录的粒度是相同的。比如一个事实表中不能有的记录是单笔订单,有的记录是订单中的一个子项。因为这样就破坏了指标的可加性,在过滤条件设置不当时,很容易得出错误的统计结果。

除了以事务为粒度的事实表外,另一种常见的类型是周期快照事实表。周期的粒度可以是日,周或是月。它是对一个周期内发生的所有事务聚合得到的。它不能代替事务事实表,但可以让许多商业分析更快速便捷。毕竟,除了少数分析需要精确到每笔交易,以自然日为粒度的统计可以满足大部分需求。

有时一个业务的流程需要很多步骤,整个过程会持续较长的时间。比如一次网购,它由下单、付款、货物发出、签收等步骤组成,从头至尾可能要许多天的时间。如果我们要分析每一步到下步之间的时长、流失率等指标,最好的事实表结构是把一次网购的整个流程作为一条记录保存,各个时间点分别存在该记录的不同字段中,这就是累积快照事实表。之前介绍的两种事实表,一条记录一旦写入后,基本不会再被更新,而累积快照事实表的每条记录在业务流程进行的过程中会被多次更新。值得注意的是,相比插入新记录,在数据仓库中完成大批量记录更新的技术难度会更高。

关于事实表的详细介绍,可以阅读书中第二章的 Basic Fact Table Techniques

维度表的设计

维度表用于辅助描述事实发生的场景,为事实增添了 who, what, where, when, how 和 why 这些细节,主要用于过滤与分组。维度属性设计是数据仓库质量的关键。

乍看之下,维度表和 ER 模型中的 Entity 表有许多相似之处,比如客户表,商品表等都是很常见的维度表。那么维度表在设计时,与设计业务数据库的表有哪些关键性的不同呢?

更多使用描述性文字

在业务数据库中,我们常常用标志位,布尔值或是操作代码做为字段的内容。在维度表,我们更推荐使用描述性的文字。比如,在客户表中有一个 membership 字段来表示该客户是否为会员。在业务表中,该字段会用布尔型的 0/1 表示。但是在维度表中,该字段应该为文本型。可以用 Yes/No 做为属性值,但更好的选择是用 Member/Non-member 做为属性值。当你做分组或是透视表时就可以看出差别,由后者产生的报表描述更清晰。

dim-table-text-attr

扁平的层级

在业务数据库中,我们在设计表时会遵循第三范式,尽量避免冗余,但也会因此产生更多的表与层级,而这恰恰是维度表所要避免的。例如,一个大企业之下会有若干品牌,每个品牌之下有商品分类,每个分类之下再有单类的商品。以 ER 模型的设计思路,品牌,分类以及商品都应该是单独的表,商品用外键连接分类,分类再用外键连接品牌。而在设计维度表时,我们应该只建一张商品表,将品牌名,分类名直接保存在商品表中。虽然这会导致大量的数据冗余,但这张维度表对分析人员来说更简单易用。

日期维度表

“日期在事实表中用一个字段存不就好了,有必要做成一个维度表吗?”这可能是很多人第一次听到日期维度表时的想法,至少我是这样。但读了这本书之后,就觉得日期维度确实有其精妙之处。一年只有365天,所以即使存100年,日期维度表也只有不到 4 万条记录。但它除了常规的日、月、年之外,还可以有许多有趣的属性,可以存星期几,是工作日还是休息日,是否是法定节日,是哪一个财年,哪一个季度等等。这使你的事实表可以在更丰富的时间维度上做分析,例如对比节假日与非节假日的销量,或是计算财报等。

关于维度表的详细介绍,可阅读第二章的 Basic Dimension Table Techniques。

维度表的更新

在业务数据库中,当更新一条记录时,我们一般采用的是覆盖的方式。虽然在有些维度表上,我们也可以用这种方式,但数据仓库的一个重要职责就是完整记录所有的历史数据。所以这一节我们来谈谈更新维度表时如何保留历史,在这本书中给出了 6 种方法,详情可阅读第二章中的 Dealing with Slowly Changing Dimension Attributes。

这里我只介绍一种方法,在书中被称为 Type 2: Add New Row,也就是为每一次更新创建一条新记录。这个方法的通用性最强,并且在更新时不用修改事实表。

要使用这个方法,首先在维度表中就不能以自然键为主键,因为同一个自然键可能对应多条记录。这就需要创建一个代理键做为主键,可以是一个简单的自增序列。其次要额外添加三个字段,分别是记录的生效日期(Row Effective Date),记录的失效日期(Row Expiration Date), 以及是否为当前记录的标志位(Current Row Indicator)。

在事实表新增记录时,我们可以通过维度表的自然键和当前标志位来找到对应的维度记录,将该记录的代理键做为外键存入事实表。当维度表更新记录时,将当前记录的标志位置否,记录失效日期设为当前时间。并增加一条新记录,新记录的生效日期为当前时间,失效日期设为一个最大日期的常数,并置上当前标志位。

要在维度表中查找某个历史时刻 hist_date 对应的记录时,只需通过以下 SQL 语句

select * from example_table
where 
    hist_date >= row_effective_date
    and hist_date < row_expiration_date
    and [other conditions...]

数据质量管理

关于数据质量管理,我非常赞同书中的一句话 “Seemingly small data quality issues are, in reality, important indications of broken business processes”。比如数据录入的错误或是缺失是常见的质量问题,为什么会出发生这种情况?很可能这些数据的录入,对于业务人员来说,只是一种负担,但对他们的工作并没有帮助,那就说明业务流程出现了问题。

建立一套数据质量管理体系需要

  1. 建立一组数据质量测试
  2. 对每条数据,记录质量审计结果

数据质量测试根据范围大小可以分为三层

  • 字段测试最为简单,比如检查一个字段的值是否超出了正常范围,或是出现了不该有的空值。
  • 结构测试需验证字段之间的关系,一个典型的例子是测试外键的有效性。(数仓一般不会强制外键关系)
  • 商业逻辑测试相对来说更复杂,比如对一家航空公司来说,验证金牌会员字段的正确性,需计算该客户过去一年的总里程数是否超过特定阈值。

有了质量测试之后,要创建一张质量错误事实表,记录每一个质量错误,并定期审查。另一方面,无论一条数据是否通过质量测试,我们都可以打上一组质量审计标签,比如总体质量评分,各类质量错误的标志位,ETL 程序的版本号,ETL 的执行序号等等,这与实体产品生产标签上的日期与批次号有异曲同工之妙。

在数据仓库中,质量把控是很重要但又容易被忽略的环节,有兴趣深入了解的朋友可阅读书中第 19 章的 Cleaning and Conforming Data

小结

本文简读了《 Data Warehouse Toolkit 》一书中的一些关键概念。虽然数据仓库在实践中有许多细节,但维度模型的核心理念却是非常简单的,所以希望这个简读能对大家有所帮助。有什么建议意见,欢迎留言。

如果觉得还有帮助的话,你的关注和转发是对我最大的支持,O(∩_∩)O :)

关注公众号后台回复「数仓」获取「数据仓库工具箱-维度建模权威指南(第三版)」「阿里巴巴大数据实践」等经典大数据和数据仓库电子书