维度建模

在关系数据库管理系统中实现的维度模型称为星型模式,因为其结构类似星型结构。在多维数据库环境中实现的维度模型通常称为联机分析处理(OnLine Analytical Processing,OLAP)多维数据库

事实表

维度模型中的事实表存储组织机构业务过程事件的性能度量结果。应该尽量将来源于同一个业务过程的底层度量结果存储于一个维度模型中。

"事实"这一术语表示某个业务度量。

事实表中的每行对应一个度量事件。每行中的数据是一个特定定级别的细节数据,称为粒度。例如,销售事务中用一行来表示每个卖出的产品。维度建模的核心原则之一是同一事实表中的所有度量行必须具有相同的粒度。牢记建立事实表时使用统一的细节级别这一原则可以确保不会出现重复计算度量的问题。

最实用的事实是数值类型和可加类型事实,可加性是至关重要的。可能也会遇到一些半可加,甚至是不可加的事实。半可加事实(例 如,账户节余)不能按时间维度执行汇总操作。不可加事实(例如,单位价格)不可相加。面对这种情况时,不得不进行计数或者取平均值操作,或者简化为一次输出一个事实行。

事实通常以连续值描述,这样做有助于区分到底是事实还是是维度属性的问题。

理论上,以文本方式表示度量事实是可行的。多数情况下,文本型度量是对某些事情的描述,设计者应该尽最大可能将文本数据 放入维度中,将它们有效地关联到其他文本维度属性上,以减少空间开销。不要在事实表中存储冗余的文本信息。除非对事实表中的每个行来说,其文本是唯一的,否则,应将其放入维度表中。

将所有事实表的粒度可划分为三类:事务、周期性快照和累积快照。事务粒度级别的事实表最常见。

一般事实表具有两个或更多个外键与维度表的主键关联。可以通过维度表使用JOIN操作来实现对事实表的访问。 事实表通常有包含外键集合的主键。事实表的主键常称为组合键,具有组合键的表称为事实表。事实表表示多对多关系。其他表称为维度表。

维度表

维度表是事实表不可或缺的组成部分。维度表包含与业务过程度量事件有关的文本环境。它们用于描述与"谁、什么、哪里、何时、如何、为什么"有关的事件。维度表通常有多列,或者说包含多个属性。 维度属性可作为查询约束、分组、报表标识的主要来源。

在分析操作型源数据时,有时不清楚一个数值数据元素应该该是事实属性还是维度属性。可以通过分析该列是否是一种包含多个值并作为计算的多参与者的度量,这种情况下该列往往可能是事实;或者该列是对具体值的描述,是一个常量、某一约束和行标识的参与者,此时该属性往往是维度属性。例如,产品的标价看起来像像一个产品的常量属性,但它经常会发生变化,因此它更可能是一种度量事实。偶尔,由于很难确定如何进行分类,需要根据设计者所处的环境以不同的方式建模数据元素。

一个数字量到底是事实还是维度属性,对设计者来说是一个两难的问题,很难做出决策。连续值数字基本上可以认为属于事实,来自于一个不太大大的列表的离散数字基本可认为是维度属性。

维度模型表示每个业务过程包含事实表,事实表存储事件的数值化度量,围绕事实表的是多个维度表,维度表包含事件发生时实际存在的文本环境。

粒度最小的数据或原子数据具有最多的维度。尚未聚集的原子数据是最具有可表达性的数据。这些原子数据是构建能满足用户提出任意查询的事实表的设计基础。对维度模型来说,可以将全新维度增加到模式中,只要该该维度的单一值被定义到已经存在的事实表行中。同样,可以将新的事实增加到事实表中,前提是其细节级别与当前事实表保持一致。可以向已存在的维度表添加新属性。对上述情况,可以通过简单增加数据

另一种体会事实表与维度表互为补充的方式似乎可以考察将它们转化为报表。维度属性支持报表过滤和标识,事实表支持报表中的数字值。

清洗数据(消除拼写错误、解决领域冲突、处理错误的元素、解析为标准格式)

维度模型应该围绕业务过程组织,例如,订单、发货、服务调用等,而不是按照组织中部门的职责划分。

维度建模流程

  • 选择业务过程
  • 声明粒度
  • 确认维度
  • 确认事实

业务过程

业务过程是组织完成的操作型活动,例如,获得订单、处理保险索赔、学生课程注册或每个月每个账单的快照等。业务过程事件建立或获取性能度量,并转换为事实表中的事实。多数事实表关注某一业务过程的结果。过程的选择是非常常重要的,因为过程定义了特定的设计目标以及对粒度、维度、事实的定义。每个业务过程程对应企业数据仓库总线矩阵的一行。

粒度

声明粒度是维度设计的重要步骤。粒度用于确定某一事实表中中的行表示什么。粒度声明是设计必须履行的合同。在选择维度或事实前必须声明粒度度,因为每个候选维度或事实必须与定义的粒度保持一致。在所有维度设计中强制实行一致性是保证BI应用性能和易用性的关键。在从给定的业务过程获取数据时,原子粒度是最低低级别的粒度。我们强烈建议从关注原子级别粒度数据开始设计,因为原子粒度数据能够承受无法预期的用户查询。上卷汇总粒度对性能调整来说非常重要,但这样的粒度往往要猜测业务公共问题。针对不同的事实表粒度,要建立不同的物理表,在同一事实表中不要混用多种不同的粒度。

维度

维度提供围绕某一业务过程事件所涉及的“谁、什么、何处、何时、为什么、如何”等背景。维度表包含BI应用所需要的用于过滤及分类事实的描述性属性。牢牢掌握事实表的粒度,就能够将所有可能存在的维度区分开。当与给定事实表行关联时,任何情况下都 应使维度保持单一值。维度表有时被称为数据仓库的“灵魂”,因为维度表包含确保DW/BI系统能够被用作业务分析的入口和描述性标识。主要的工作都放在数据管理与维度表的开发方面,因为它们是用户BI经验的驱动者。

事实

事实涉及来自业务过程事件的度量,基本上都是以数量值表示示。一个事实表行与按照事实表粒度描述的度量事件之间存在一对一关系,因此事实表对应一个物理可观察的事件。在事实表内,所有事实只允许与声明的粒度保持一致。例如,在零售事务中,销售产品的 数量与其总额是良好的事实,然而商店经理的工资不允许存在于零售事务中。

事实表

发生在现实世界中的操作型事件,其所产生的可度量数值,存储在事实表中。除数字度量外,事实表总是包含外键,用 于关联与之相关的维度,也包含可选的退化维度健和日期/时间戳。查询请求的主要目标是基于事实表开展计算和聚集操作。

事实表中的数字度量可划分为三类。最灵活、最有用的事实是完全可加,可加性度量可以按照与事实表关联的任意维度汇总。半可加度量可以对某些维度汇总,但不能对所有维度汇总。差额是常见的半可加事实,除了时间维度外,它们可以跨所有维度进行加法操作。另外,一些度量是完全不可加的,例如,比率。对非可加事实,一种好的方法是,尽可能存储非可加度量的完全可加的分量,并在计算出最终的非可加事实前,将这些分量汇总到最终的结果集合中。最终计算通常发生在BI层或OLAP多维数据库层。

  • 事务事实表。事务事实表的一行对应空间或时间上某点的度量事件。原子事事务粒度事实表是维度化及可表达的事实表,这类健壮的维度确保对事务数据的最大化分片和分块。
  • 周期快照事实表。周期快照事实表中的每行汇总了发生在某一标准周期,如某一天、某周、某月的多个度量事件。粒度是周期性的,而不是个体的事务。周期快照事实表通常包含许多事实,因为任何与事实表粒度一致的度量事件都是被允许存在的。这些事实表其外键的密度是均匀的,因为即使周期内没有活动发生,也会在事实表中为每个事实插入包含0或空值的行。
  • 累积快照事实表。累积快照事实表的行汇总了发生在过程开始和结束之间可预测步骤内的度量事件。通常在事实表中针对过程中的关键步骤都包含日期外键。累积快照事实表中的一行,对应某一具体的订单,当订单产生时会插入一行。当管道过程发生时,累积事实表行被访问并修改。这种对累积快照事实表行的一致性修改在三种类型事实表中具有特性,除了日期外键与每个关键过程步骤关联外,累积快照事实表包含其他维度和可选退化维度的外键。通常包含数字化的与粒度保持一致的,符合里程碑完成计数的滞后性度量。
  • 无事实的事实表。
  • 聚集事实表。聚集事实表是对原子粒度事实表数据进行简单的数字化上卷操作,目的是为了提高查询性能
  • 合并事实表。通常将来自多个过程的,以相同粒度表示的事实合并为一个单一的合并事实表,这样做能够带来方便

维度表

缓慢变化维(SCD)

  • 不动。不处理后续变化,只记录第一次写入时的值,丢弃后续的更新
  • 覆盖重写。将后续的变化更新到数据中,记录最新的值
  • 拉链表。在维度表中新增一行,新的数据中记录变更后的属性值。拉链表因为记录了多行,不能使用数据本身的业务主键如用户id,需考虑主键重复问题
    • 增加开始时间、结束时间。
    • 标记当前行。可通过设置结束时间为 9999-12-31 23:59:59 标记
  • 增加新字段,记录修改后的值。如增加字段 previous_value,因为只记录之前修改的值,如果发生多次修改,需新增字段,不够灵活,很少使用

维度层次

部分维度具有层次,如部门组织架构、电商商品类目。

一般是做打平处理,如 运营中心/直播运营/抖音组/投流。在实际业务中,类目一般是 3 级、4 级,近来也有部分公司在使用 5 级类目,组织架构的层级一般不做限制,可无限创建,但实际业务中一般在 5 层左右。

如果是固定深度的维度,直接打平。如果是可变深度,但深度可预测,可做数据探查,确定最大深度层次,然后打平。

如果是深度不确定的层次:

路径枚举

CREATE TABLE dim_category (
    category_id STRING,
    category_name STRING,
    path STRING,   -- 例如:"/1/10/105/"
    level INT,     -- 当前节点层级
    -- 其他属性
);

桥接表

CREATE TABLE bridge_department (
    ancestor_id   STRING,   -- 祖先节点ID
    descendant_id STRING,   -- 子孙节点ID
    depth         INT,      -- 层级深度(0表示自己,1表示父子,2表示爷孙...)
    is_leaf       BOOLEAN,  -- 是否叶子节点
    path          STRING,   -- 可选:完整路径,如 '/1/10/105/'
    weight        DECIMAL,  -- 可选:权重系数(用于分摊)
    valid_from    DATE,     -- 可选:有效期开始
    valid_to      DATE,     -- 可选:有效期结束
    is_current    BOOLEAN   -- 可选:是否当前有效
);

假设组织架构

1 [集团] ├── 10 [事业部A] │ ├── 101 [销售一部] │ └── 102 [销售二部] └── 20 [事业部B] └── 201 [产品部]

桥接表数据

ancestor_iddescendant_iddepthis_leaf
110false
1101false
11012true
11022true
1201false
12012true
10100false
101011true
101021true
20200false
202011true
1011010true

使用方式:

事实表通过 descendant_id 关联桥接表,维度表通过 ancestor_id 关联桥接表。

汇总查询时,事实表 JOIN 桥接表 ON 事实表.id = 桥接表.descendant_id,再 JOIN 维度表 ON 桥接表.ancestor_id = 维度表.id

-- 统计集团下所有部门的销售额
SELECT 
    d.dept_name,
    SUM(f.amount) AS total_amount
FROM fact_sales f
JOIN bridge_department b ON f.dept_id = b.descendant_id
JOIN dim_department d ON b.ancestor_id = d.dept_id
WHERE d.dept_id = '1'  -- 集团ID
  AND b.depth >= 0      -- 包含所有层级
GROUP BY d.dept_name;

-- 统计所有一级部门(事业部)的销售额
SELECT 
    d.dept_name,
    SUM(f.amount) AS total_amount
FROM fact_sales f
JOIN bridge_department b ON f.dept_id = b.descendant_id
JOIN dim_department d ON b.ancestor_id = d.dept_id
WHERE b.depth = 1  -- 只取直接下级(事业部)
GROUP BY d.dept_name;