统一星型模式(Unified Star Schema)由 Francesco Puppini(弗朗切斯科·普皮尼)在他于 2020 年推出的与 Bill Inmon 合著的 The Unified Star Schema 一书中提出。
以早期的电话拨号服务来比喻,业务人员相当于电话用户,传统的服务方式是应用层的数据开发人员充当电话接线员,用户通过接线员去实现拨号;而 USS 的作用就是实现用户自助拨号,其中桥接表就相当于电话交换机,交换机会自动寻址自动拨号而不再需要接线员。
基本观点
传统维度建模中的字段划分偏向数据开发视角,而统一星型模式中的字段划分偏向数据分析视角。比如产品维表中可能有库存量(units in stock)字段,这个字段在传统维度建模中一般被视作产品维度的属性字段,而在统一星型模式中则被视作是度量字段(可被聚合、用于Y轴呈现等)。
- 除非有性能提升(提前聚合数据量大的表)或逻辑复用(复杂逻辑分步实现)的考量,业务逻辑尽可能都在展示层(BI层)实现,提高“自助率”。
这样做的好处有:①能够减少数仓中应用层的维护成本②降低数据处理错误或不一致的风险③减少对开发人员的依赖。
- 从数据处理的角度,数据分析的过程就是对数据表进行去规范化(denormalizing)、扁平化(flattening)的过程,而这个过程可能存在一些导致数据结果有误的“陷阱”。
这些“陷阱”可分为两类:①数据的丢失,内连接和左右连接都大概率会丢失数据;②度量数据的发散,有“风扇陷阱”(fan trap)和“裂缝陷阱”(chasm trap)两种。
对于数据的发散,只需要关注度量数据的发散,因为维度数据的发散不会影响聚合,进而不会影响数据结果。
专业的开发人员往往知道如何避免这些“陷阱”而得到正确的数据结果。而统一星型模式能够从根本上杜绝这些“陷阱”,这样数据处理工作可以直接交由分析人员来做(也不用担心数据结果出错的问题),从而实现真正的”自助分析”。
- 在联合处理多对多或粒度不一致的两张表时,堆叠(UNION ALL)的方式要优于连接(JOIN)。
比如需要多张事实表联合处理时,应尽量选择堆叠的处理方式,性能更好还不丢失数据;对于粒度不一致时,统一星型模式可以通过局部再规范化(re-normalizing)的措施来解决,这也优于传统先聚合汇总再连接的处理方式。
连接是拼接字段,性能一般并且有数据发散和丢失数据(全连接不丢失数据但性能不好)的风险;而堆叠是拼接记录,算力消耗小(是高性能的)并且无数据发散(是无冗余的)、不丢失数据(是无损的),唯一的小缺点是SQL代码会难写一点(需要对齐字段,若用编程语言则很容易)
- 对于多表数据的合并,现代的BI工具除了支持传统的连接(join)合并方式,普遍也还支持关联(association)合并方式,统一星型模式在两种方式体系下都具备完整的功能。
关联在某些BI工具中可能叫做关系(relationship)。关联在单次的(或说合并逻辑简单的)数据处理场景中比连接更为灵活好用且对数据丢失、数据发散免疫,因而十分适合在BI工具中使用。
当然,有了关联并不是说就不需要统一星型模式了,关联与传统的连接一样需要人工绘制数据模型图(制定连接或关联规则),这就留下了千人千面的处理空间,存在不同人数据处理结果不一致的隐患。而统一星型模式使用统一的、简化的数据模型,对分析人员友好并且杜绝了数据结果不一致的隐患。
最理想的数据分析方案是既采用统一星型模式,同时也使用关联的表合并方式;如果BI工具没有关联功能,那么退而求其次在统一星型模式的基础上使用连接,也能实现全部的功能,尽管数据处理会麻烦一点(需要注意数据重复的问题)。
使用 USS 需要注意的事项
-
(桥接表的构建)由于 USS 的桥接表实现的都是两表之间的直连,如果原始模型中两张表需要通过中介表才能连接(即间接连接),那么应该基于中介表在桥接表中添加所有可能的派生外键。(具体做法参见〖Northwind样例数据实战(USS)〗)
-
(在基于连接的使用场景下)USS 的桥接表中可能存在多个 Stage 指向同一张事实表的情况(都有该表的外键),如果这些 Stage 被同时用于聚合的话就出现了数据膨胀,因此在书写聚合公式的时候需要对 Stage 进行过滤(只将相关的 Stage 纳入聚合范围),比如QlikView中的写法
Sum({1<Stage={'Sales'}>}SalesAmount)
。
如果是在基于关联(association)的使用场景下,则不会有这个问题,因为关联的聚合都是只作用于单张来源表,永远对发散问题免疫
- (在基于连接的使用场景下)USS 的桥接表中存在大量的
NULL
值是常态,因此在BI工具中书写条件或聚合语句时需要避免误算,比如sum(if(quantity<5, 0, 1))
的写法使NULL
被标记成1
可能导致聚合值有误,可改用sum(if(quantity>5, 1, 0))
的写法。