零售营销-数据仓库建模

来源:互联网 发布:银行数据质量管理办法 编辑:程序博客网 时间:2024/04/28 07:43

2章  零   

理解维度建模原理的最佳途径,是通过一系列切实的例子去进行实践。通过观察实际的实例,就能使设计方面的挑战与解决办法了然于心,这比仅仅通过抽象的表述进行学习要有效得多。本书中采用了大量取自诸多行业方面的实例,目的在于使读者不要为自己的业务细节所困扰而得到恰当的设计。

如果打算学习维度建模方面的知识,请不妨通读本书各章,即使不从事零售业务或者不在一个电信公司工作也要这样做。本书并不打算成为向某具体产业或者业务提供全面的解决办法的手册,各章以几乎在每种业务的维度建模中都会遇到的典型问题集的比喻形式进行内容的叙述。大学、保险公司、银行以及航空业等都几乎无一例外地需要本章所述的零售方面所使用的技能。此外,可以设想一下,如果一个人的业务总在随时间发生变化,那么会需要什么呢?当处理从自己公司获得的数据时,很容易受过去经历的复杂性影响而使事情变糟。通过走出去,然后带一两个经过充分考虑的设计原理回来,就能做到在进入纷繁的业务细节处理时,仍然能够记住设计原理的宗旨。

Ñ本章概念:

n 设计维度模型的四步过程

n 事务级事实表

n 可加性与非可加性事实

n 样本维度表属性

n 诸如促销这样的因果维度

n 诸如交易票据编号这样的合并维度

n 维度模型的扩展

n “使用过多维度”陷阱的避免

n 代理关键字

n 市场容量分析

◣ 2.1  四步维度设计过程

整本书考虑一致地按照具有一定顺序的四个步骤的方式进行维度数据库的设计。这四个步骤的含义会随着各种不同设计的进行逐渐变得更加清晰起来,不过首先还是给出一些初始说明。

1)选取要建模的业务处理过程。

业务处理过程是机构中进行的一般都由源数据收集系统提供支持的自然业务活动。听取用户的意见是选取业务处理过程的效率最高的方式。用户叫嚷着要在数据仓库中进行分析的性能度量值是从业务评测处理过程得来的。典型的业务处理过程包括原材料购买、订货、运输、开票、库存与账目管理等。要记住的重要一点是,这里谈到的业务处理过程并不是指业务部门或者职能。比如,可以建立一个用来处理订单数据的单一维度模型,而不应为要存取订单数据的销售与市场部门建立单独的模型。通过将注意力集中放在业务处理过程方面,而不是业务部门方面,就能在机构范围内更加经济地提交一致的数据。如果建立的维度模型是同部门捆绑在一起的,就无法避免出现具有不同标记与术语的数据拷贝的可能性。多重数据流向单独的维度模型,会使用户在应付不一致性的问题方面显得很脆弱。确保一致性的最佳办法是对数据进行一次性地发布。单一的发布过程还能减少ETL的开发量,以及后续数据管理与磁盘存储方面的负担。

2)定义业务处理的粒度。

粒度定义意味着对各事实表行实际代表的内容给出明确的说明。粒度传递了同事实表度量值相联系的细节所达到的程度方面的信息。它给出了后面这个问题的答案:“如何描述事实表的单个行?”。

典型的粒度定义包括:

o 顾客购物券上扫描设备一次拾取的分列项内容

o 医生开出的单据项目内容

o 个人登机通行证内容

o 仓库中每种产品库存水平的日快照

o 每个银行账号的月快照

数据仓库团队经常将这个看起来似乎不必要的步骤绕了过去。请不要这样做!对于设计团队的每个人来说,能够在事实表粒度上做到一致是很重要的。没有粒度的定义实际上是不可能达到下面第3步中提出的要求的。需要引起注意的是,一个不合适的粒度定义会使数据仓库的实现令人摸不着头脑。粒度定义是不容轻视的至关重要的步骤。说到这里,你应该能够发现在第3步或第4步中给出的粒度说明是错误的。好了,还是先回到第2步重新给出粒度的正确定义,而后再看第3步或第4步的内容。

3)选定用于每个事实表行的维度。

维度所引出的问题是,“业务人员将如何描述从业务处理过程得到的数据?”应该用一组在每个度量上下文中取单一值而代表了所有可能情况的丰富描述,将事实表装扮起来。如果对粒度方面的内容很清楚,那么维度的确定一般是非常容易的。通过维度的选定,可以列出那些使每个维度表丰满起来的离散的文本属性。常见维度的例子包括日期、产品、顾客、事务类型和状况等。

4)确定用于形成每个事实表行的数字型事实。

2.1  四步骤维度设计过程的关键输入内容

事实的确定可以通过回答“要对什么内容进行评测”这个问题来进行。业务用户在这些业务处理性能度量值的分析方面具有浓厚的兴趣。设计中所有供选取的信息必须满足在第2步中定义的粒度要求。明显属于不同粒度的事实必须放在单独的事实表中。典型的事实是诸如订货量或者支出额这样的可加性数字数据。

整本书在开发各个实例研究时,都将按这样的四个步骤来展开,并以用户对业务的理解作为确定维度模型所需维度与事实的内容的依据。很显然,在按照如图2.1所示的四个步骤确定相关内容时,需要同时考虑业务用户需求和源数据本身。千万要克服只看看源数据文件就对数据进行建模的偏向。虽然说,一头扎进文件设计图与复写簿中去搜集数据比采访业务人员具有少得多的风险,但这不能代替用户的介入。遗憾的是,许多机构仍然企图使用这种受数据驱动的最省力的方法去建模,结果是很少有成功的。

◣ 2.2  零售实例的研究

这里先对在本实例研究中要使用的零售业务进行简要的描述,以使建立的维度与事实表更容易理解。之所以从这个行业入手,是因为它与大家都是密切相关的。设想一下在一家大型杂货连锁店总部工作的情形,其业务涵盖分布在5个州范围内的100多家杂货店。每个商店都有完整的配套部门,包括杂货、冷冻食品、奶制品、肉制品、农产品、面包店、花卉门市以及卫生/美术方面的辅助人员等,并有大致6万多个品种的产品放在货架上。每个品种的产品被称做库存储藏单位(SKUsStock Keeping Units)。大约55 000SKUs来自外部的生产厂家,并在包装上印有条形码。这些条形码被叫做统一产品编码(UPCsUniversal Product Codes)。UPCs具有与单个SKUs相同的粒度。一个产品的不同包装类型具有一个单独的UPC,因而也有一个单独的SKU

剩下的5 000SKUs从诸如肉制品、农产品、面包店或者花卉门市等部门获取。虽然这些产品具有举国一致的可识别UPCs,杂货连锁店仍旧可以给它们分配SKU编号。既然杂货店是高度自动化的,那么完全可以为这些从其他部门取来的许多项目贴上扫描标记。尽管条形码不是UPCs,但它是确定无疑的SKU编号。

数据是从杂货店中多个令人感兴趣的地方收集得到的,其中一些最有用途的数据是在顾客购买产品时从收银机那里收集的。现代杂货店直接将条形码扫描到销售点(POSPoint-Of-Sale)系统中去,POS系统放在杂货店中对顾客外卖食品进行检测的出口处。厂家发货的后门是另外一个令人感兴趣的数据收集点。

在杂货店,管理方面所关注的是如何使产品的订购、储存与销售运作能最大限度地实现利润而开展后勤工作。利润最终要靠尽可能施加到每种产品上的管理职责、产品采购成本与额外开销的降低、以及在竞争激烈的价格战环境中吸引尽可能多的顾客等方面的一系列工作来获取。最重要的管理决策应该是关于定价与促销工作方面的。产品促销包括临时降价、在报纸与报纸夹页中加入广告内容、杂货店的陈设(包括廊端展销)和优惠券发行等工作。掀起产品销售量浪潮的最直接与最有效的方式是大幅度地降低产品的价格。

将纸巾降价一半,并为此打出配套广告和召开展销会,就可以使纸巾的销售量一下子提升10个点。遗憾的是,如此大规模的降价通常是经受不住的,因为这样的纸巾销售很可能是亏本的。这些问题说明,如何使各种形式的促销活动所产生的效能清晰可见是杂货店运营情况分析的重要部分。

在对业务实例研究进行描述之后,现在就可以开始维度建模的设计工作了。

2.2.1  第一步:选取业务处理

设计工作的第一步是,通过将对业务需求的理解与对可用数据的理解组合起来而确定建模的业务处理内容。

¯建立的第一个维度模型应该是一个最有影响的模型——它应该对最紧迫的业务问题做出回答,并且对数据的抽取来说是容易访问的。

在这个零售实例研究中,管理方面要做的事情就是更好地理解像POS系统记录的顾客购买情况。于是,建模所提供的业务处理就相应成为一个POS零售业务。这类数据可以用来分析出什么促销条件下的什么日子里,在什么商店正在销售什么样的产品等方面的内容。

2.2.2  第二步:定义粒度

一旦将业务处理确定下来,数据仓库团队下一个就面临关于粒度确定的严肃课题。应该在维度模型中给出何种详细程度的细节内容?这就引出了关于设计的一个重要提示。

¯应优先考虑为业务处理获取最有原子性的信息而开发维度模型。原子型数据是所收集的最详细的信息,这样的数据不能再做更进一步的细分。

通过在最低层面上装配数据,大多原子粒度在具有多个前端的应用场合显示出其价值所在。原子型数据是高度维结构化的。事实度量值越细微并具有原子性,就越能够确切地知道更多的事情,所有那些确切知道的事情都转换为维度。在这点上,原子型数据可以说是维度方法的一个极佳匹配。

原子型数据可为分析方面提供最大限度的灵活性,因为它可以接受任何可能形式的约束,并可以以任何可能的形式出现。维度模型的细节性数据是安如泰山的,并随时准备接受业务用户的特殊攻击。

当然,可以总是给业务处理定义较高层面的粒度,这种粒度表示最具有原子性的数据的聚集。不过,只要选取较高层面的粒度,就意味着将自己限制到更少或者细节性可能更小的维度上了。具有较少粒度性的模型容易直接遭到深入到细节内容的不可预见的用户请求的攻击。如果不让用户存取原子型数据,则他将不可避免地在分析方面撞上南墙。就如将在第16章所见到的那样,聚集概要性数据作为调整性能的一种手段起着非常重要的作用,但它绝对不能作为用户存取最低层面的细节内容的替代品。遗憾的是,有些实业界的权威人士在这方面一直显得含糊不清。他们宣称维度模型只适合于总结性数据,并批评那些认为维度建模方法可以满足预测业务需求的看法。这样的误解会随着细节性的原子型数据在维度模型中的出现而慢慢地消逝。

在该实例研究中,最佳粒度的数据是POS事务的单个分列项。为了确保得到最大限度的维度性和灵活性,所有讨论都将在这个粒度上展开。将这个粒度的定义针对第一版的原文做出修改是毫无价值的。以前,我们也将注意力集中在POS数据上,但不是考虑如何在维度模型中对事务分列项目细节进行表示,而是注重提供一天中某个商场所堆积的产品与促销方面的数据。在当时,这些每日产品总量反映了辛迪加零售数据库的技术状况。指望当时的硬件与软件能够有效地处理与各个POS事务分列项相关的数据海量的想法,是很不合时宜的。

通过访问POS事务信息,能够得出一个关于商场销售非常详细的概况。虽然用户或许对与特定POS事务相联系的单个项目的分析并不是很感兴趣,但数据库团队仍然无法预知出他们抽取数据的各种可能方式。例如,他们可能想弄清星期一相对于星期日在销售上的不同情况,或者想评估一下是否值得为诸如谷物一类的物品准备那么多不同大小的商标,或者想了解有多少购物者会对优惠50%的洗发精促销活动特别有兴趣,或者想确定如果对一个竞争很激烈的饮用苏打产品在经过了大力的促销宣传以后进行降价销售会造成什么样的影响等。虽然这些查询没有一个只对来自某单个特定事务的数据存在要求,但它们都是些需要精确切割的细节性数据的涉及面很广的问题。如果用户只能存取总结性数据,则不能回答其中的任何一个问题。

¯数据仓库几乎总是要求在每个维度可能得到的最低粒度上对数据进行表示的原因,并不是因为查询想看到每个低层面的行,而是因为查询希望以很精确的方式对细节知识进行抽取。

2.2.3  第三步:选定维度

一旦事实表的粒度被选定,则时期、产品与商店方面的维度就应该随之被确定下来。可以假定,日历日期是由POS系统提供的日期值。后面可以看到,随日期给出针对每一天的时间其情形又是怎么样的。在基本维度框架范围内,可能需要知道其他诸如针对某种产品的促销这样的维度是否可以分配数据。这个内容可表示为另外的一个设计原则。

¯一个经过仔细考虑的粒度定义确定了事实表的基本维度特性。同时,经常也可能向事实表的基本粒度加入更多的维度,而这些附加的维度会在基本维度的每个组合值方面自然地取得惟一的值。如果附加的维度因为导致生成另外的事实行而违背了这个基本的粒度定义,那么必须对粒度定义进行修改以适应这个维度的情形。

在实例研究中,已经选定的描述性维度有:日期、产品、商店与促销等。此外,还会将POS事务票据编号作为一个特殊的维度包括进来。这方面更多的内容将在本章的后面进行介绍。

现在可以开始考察如图2.2所示的初步方案了。在着手利用描述属性填充维度表之前,还是先完成设计过程的最后一步吧!特别希望你确实能够对完整的四步过程感到舒心——而不想使你在“游戏”的这个阶段只见树木而不见森林。

                                                  图2.2  零售营销初步方案

2.2.4  第四步:确定事实

设计过程的第四步同时也是最后一步,在于仔细确定哪些事实要在事实表中出现。粒度定义在这里再次成为考虑问题的支点。只是需要指出,事实对于粒度必须是真实的:这里的粒度就是POS事务的各个分列项。当考虑潜在的事实时,你可能会再次发现,对早先的粒度设想或者维度选取做出调整是非常必要的。POS系统收集的事实包括销售量(比如,鸡精面汤的罐数)、单价与销售额等,其中销售额等于销售量与单价的乘积。更为复杂的POS系统也提供产品标准出厂价。假定这个价格事实容易得到,而不需要以采取一些英雄行为作为代价进行获取,就应该将它放在事实表中。到现在为止,所设计的事实表就开始显得与图2.3显示的情形相似了。

销售量、销售额与成本价这三个事实在所有维度中都具有良好的可加性。完全可以自如地对事实表进行切割,并且这三个事实的每种合计值也总是有效的、正确的。

                                                 图2.3  零售营销方案的度量事实

可以通过从销售额或者营业额中减去成本花销而计算出毛利润。尽管毛利润是经过计算得到的值,但它在各个维度中同样具有极好的可加性——完全可以针对任何时期内在任意数量的商场中销售的任意产品组合,计算出其毛利润。维度建模人员不时在问,通过计算得到的事实是否应该物理地存放在数据库中?我们的意思是,应该将它物理地存放起来。在本实例中,毛利润的计算是直接进行的,但将它存储起来可以消除用户出错的可能性。用户因不当地表示毛利润所付出的代价,将远远超出增加少量存储而需要的花费。同时,将毛利润存储起来也能够确保所有的用户及其报表生成应用能够一致地引用它。既然毛利润能够通过事实表行中相邻的数据计算得到,有人可能觉得,应该在一种同表没有什么区别的视图中执行这种计算。如果所有用户都通过这样的视图去存取数据,并且没有用户使用特殊的存取工具绕开这个视图而进入物理事实表的话,这样的方法是很合乎情理的。想在节省存储空间的同时,最大限度地减少用户可能出现的错误,视图当然是一种合乎逻辑的途径,但问题是DBA一定要能够保证不会在这种视图下出现数据存取的例外情形。同样,有的机构想在查询工具中执行这类计算。不过,这只是在所有用户都使用共同的工具存取数据的情况下才有效(经验表明,这样的情形是很少出现的)。

毛利润率可以用毛利润除以营业额而计算出来。毛利润率是非加型事实,因为它不能依照任何维度进行求和运算。只要在做除法之前不忘记加入营业额和成本花销,就可以算出任何产品组、任意数量的商店或者任何日期的毛利润率。这也可以表示为如下的设计原则:

¯诸如百分比和比率这类毛利润率都是非加性的。分子与分母都应该存放在事实表中。可以用数据存取工具求出事实表任何数据形式的比率,只是要记住求取的是合计值的比率,而不是比率的求和。

单价也是非加型事实。试图在任何维度范围内对单价进行求和,都会导致出现一些毫无意义的甚至显得荒谬的数值结果。要针对一系列商店或者一个时间跨度分析某种产品的平均售价,就必须在用销售总量去除销售总额之前,将相关销售额与销售量加起来。虽然数据仓库市场方面的报表生成器或者查询工具都应该自动地正确完成这个功能,但是很遗憾,其中一部分工具仍旧不能很圆满地做到这一点。

在设计的早期阶段,经常对可能需要的最大表即最大事实表的行数做出估计是很有益处的。在所给出的实例研究中,这一般不过是找源系统的头头们谈一谈,从而弄清一个时间区段内将生成多少POS事务项目的问题。零售流量在一天天地剧烈波动着,因此,需要选定一个对事务活动进行了解的合理时间周期。作为选择,还可以通过用项目平均售价去除连锁店的年毛营业额,而估算出一年要加入到事实表的行数。假设每年的毛营业额为40亿美元,并假定每张顾客票据上各分列项的平均售价为2美元,那么就可以算出每年大约有20亿个事务分列项。工程师通常都要进行的这个典型评估,差点使我们惊讶得从扶手椅上跳起来去重新考虑是否将设计进行下去。但作为设计人员,总是应该不断地进行评估以确定计算是否合乎情理。

◣ 2.3  维度表属性

在对四步过程走了一遍以后,现在可以回到维度表上面来,并致力于用丰富的属性将它们填充起来。

2.3.1  日期维度

这里首先从日期维度入手进行介绍。日期维度是几乎每个数据中心都必须提供的一个维度,因为实际上,每个数据中心都是时间系列的。事实上,日期通常是数据库进行潜在分类排序的首选维度,这样做的目的是,使按时间间隔连续加载的数据能够顺次存放到磁盘上的空白存储区中。

需要提请阅读过《数据仓库工具箱》(The Data Warehouse Toolkit)第一版(1996Wiley出版)的读者注意的是,该书将这个维度称做时间维度。不是揪住那个更显得模棱两可的术语不放,作者在本书中用日期维度指称按日期进行粒度定义的维度表。这有助于对时期维度和每天的时间维度进行区分,相关内容将在本章后面讨论。

与其他多数维度不一样,日期维度表可以事先建立。这样的表可存放以日期表示的510年的历史数据行,同样,也可以将未来几年的数据行放入其中。即使针对10年的每一天都进行存储,也只不过要3650行,这是一个相当小的维度表。对于零售环境中日常的日期维度表来说,可以采用图2.4中所推荐的部分列表项。

日期维度表的每列由行所代表的特定日期进行定义。“星期”一列含有每天像“星期一”这样的名称内容,该列可用于创建比较“星期一”与“星期日”的业务的报表。日历表中“月”所在列的日编号从每个月的1开始取值,然后根据月份的情况取到282930或者31,这一列对于每月的同一天进行比较的情况很有好处。按照类似的方式,可给出一年中每月的编号(1,…,12)。纪元表示法采用一种称为恺撒日编号(即从某纪元开始连续对日期进行计数)来有效地给出日编号。当然也可以在表中给出“星期”与“月份”的绝对编号列。所有这些整数都支持跨年度跨月份的简单数据运算。在生成报表时,常常要给出像“一月”这样的月份名称。此外,为报表给定一个“年月”(YYYY-MM)列标题也是很有用途的。同样,报表中很可能需要季度编号(Q1,…,Q4)或者诸如2001-Q4这样的年季度编号列。如果财政年度与日历表在周期上不一致,则可以为财政年度给出类似的列。

在“节假日”指示列中给出“节假日”或者“非节假日”这样的内容。要记住,维度表属性是做报表标记之用的,因此,简单地在“节假日”指示列中给出“Y”或者“N”是没有多大作用。关于这个问题,考虑一下要对某产品的节假日与非节假日的销售情况进行比较的报表应用就可以弄清楚。显然,列中给出“节假日”或者“非节假日”这样富有意义的值比一个“Y”或者“N” 之类的编码,要有用得多。还要注意,不应该在报表生成器中执行将编码标记翻译成易理解的标记这种操作,而应该将翻译内容存放在数据库中,以便使各种用户不受其报表生成工具的限制而得到一致的翻译内容。

                                                    图2.4  零售营销方案的日期维度

对于取值为“平日”与“周末”的“星期”指示列,理应做类似处理。不用说,“星期六”与“星期日”要归入“周末”之列。当然,可以对多个日期表属性进行共同约束,从而容易地实现诸如针对平日假期与周末假期进行比较这样的应用。

“销售时令”列应设置为零售时节的名称,比如以美国的情形为例,这些名称可以是圣诞节、感恩节、复活节、情人节、独立日,或者标为“不是”。“重大事件”列与“销售时令”列情形类似,可以标记为诸如“超级饭碗周日”或者“劳动者罢工”这样的特殊外部事件。一般性的促销活动通常不放在日期表中处理,而以促销维度表的形式进行更加完整的描述。这样做的主要原因在于,促销事件并不是仅仅由日期来定义,而通常是由日期、产品与商店的组合形式进行定义的。

有些设计人员可能在此处停下来询问为什么需要一个明确的日期维度表的原因。他们说,如果事实表的日期关键字是日期类型的字段,则任何SQL查询可以直接针对事实表日期关键字进行约束,并使用一般的SQL日期语法对月份或者年度进行过滤而避免进行想像起来开销显得很大的连接操作。有多个原因可以用来说明这个推断是站不住脚的。首先,如果关系数据库不能处理与日期维度表的高效率连接,则确实会遇到很大的麻烦。问题是,绝大多数数据库优化机制在处理维度查询方面具有非常高的效率,从而没有必要像对待瘟疫一样地对连接操作避之而惟恐不及。还有,在性能方面,绝大多数数据库不对SQL日期运算进行索引,因而由SQL运算字段进行约束的查询不会用到索引。

从可用性角度讲,典型的业务用户并不精通SQL日期语法,因此他或者她不能去直接利用与日期数据类型相关的内在功效。SQL日期函数不支持通过诸如周末、节假日、财务盘点、时令或者重大事件与平日这样的属性而施加的过滤操作。假如说业务上存在比照这些非标准日期属性进行数据切割的需要,那么一个明确的日期维度表就显得很必要了。说到底,日历逻辑要归入维度表而不是应用代码中去。最后,建议你将日期关键字指定为整数类型而不是任何形式的日期数据。一个基于SQL的日期关键字在典型情况下是8字节的,因而事实表各行的每个日期关键字要浪费4个字节。本章后面将就这方面的内容进行更多的介绍。

2.5给出了取自部分日期维度表的几行数据。

¯数据仓库总需要一个明确的维度表。有许多日期属性不能由SQL函数提供支持,这包括财务盘点、时令、节假日与周末等。与企图在查询中给定这些非标准日历运算的做法不同,而更应该在一个日期维度表中去检索它们。

如果用户要存取可以针对一天的情况进行分析(例如,在下班高峰的傍晚区间或者商场的第三班时间段内)的事务时间,就应该通过与事实表相连接的一个单独日历维度表来处理它。日期与时间几乎是完全不相关的。如果将这两个维度组合在一起,日期维度表就会增大许多。于是,假如在同一个表(或者支架)中按分钟对时间进行处理的话,那么一个只有3 650行就可以对10年的数据进行处理的简洁维度,一下子将扩展到5 256 000行。比较起来,显然更应该创建一个3 650行的日期维度表和一个分开的按分钟为每天记录1 440行的维度表。

5章将讨论在单个方案中处理多个日期的情形,在第11章与第14章中还将讨论跨国际的日期与时间考虑因素。

日期关键字

日期

日期完整描述

星期

日历月

日历年

财政年月

节假日指示符

周日指示符

1

01/01/2002

200211

星期二

1

2002

F2002-01

节日

平日

2

01/02/2002

200212

星期三

1

2002

F2002-01

非节日

平日

3

01/03/2002

200213

星期四

1

2002

F2002-01

非节日

平日

4

01/04/2002

200214

星期五

1

2002

F2002-01

非节日

平日

5

01/05/2002

200215

星期六

1

2002

F2002-01

非节日

周日

6

01/06/2002

200216

星期日

1

2002

F2002-01

非节日

周日

7

01/07/2002

200217

星期一

1

2002

F2002-01

非节日

平日

8

01/08/2002

200218

星期二

1

2002

F2002-01

非节日

平日

                                                        图2.5  日期维度表的细节

2.3.2  产品维度

产品维度描述杂货店的每个SKU。虽然典型的连锁店可能存储60 000SKUs,但在考虑跨连锁店与涵盖不再销售的历史产品的不同商品规划方案时,产品维度可能至少需要150 000行乃至多达百万行。产品维度几乎总是起源于操作型产品主文件。大多数零售商在总部对其产品主文件进行管理,并经常不间断地将文件的一部分下载到各商店的POS系统中。总部负责为每个由货物包装商创建的新UPC定义合适的产品主记录(以及具有惟一性的SKU),同时还负责定义将SKUs分配给诸如面包类食品、肉食品与农产品这些项目的规则。在产品主文件发生改变的任何时候,都要从产品主文件抽取数据并存入产品维度表中。

产品主文件的一个重要作用,就是维护每个SKU的许多描述属性。商品体系就是一组重要的属性。通常,各类SKUs堆积形成商标,商标堆积形成分类,而分类则堆积形成部门,其中的每个关系都是多对一的。这个商品体系与其他属性的详细情况如图2.6给出的部分产品所示。

产品关键字

产品描述

商标描述

分类描述

部门描述

含脂量

1

低碱烤肉包

烧烤

面包

面包房

低脂

2

松脆全麦切片

松脆

面包

面包房

一般

3

松淡全麦切片

松脆

面包

面包房

低脂

4

脱脂小桂卷

松软

甜面包

面包房

无脂

5

2加仑装美食香料

冷裹品

冷冻点心

冷冻食品部

无脂

6

1品脱装黄油软奶桃

鲜类

冷冻点心

冷冻食品部

低脂

7

1/2加仑装巧克力美食

冷冻

冷冻点心

冷冻食品部

一般

8

1品脱装草莓冰淇淋

冰冻

冷冻点心

冷冻食品部

一般

9

冰淇淋三明治

冰冻

冷冻点心

冷冻食品部

一般

                                                     图2.6  产品维度表的细节

对于每个SKU而言,所有层次的商品体系都是经过良好定义的,并且像SKU描述之类的一些属性还具有惟一性。在这种情况下,SKU描述项至少有150 000个不同的取值。在另一个极端,可能出现部门属性只有50个不同取值的情况。于是平均下来,部门属性的每个惟一值会有3 000个重复项。不过没什么关系!没有必要考虑将这些重复值分成另一个规范化表而节省一点空间。须知,维度表的空间需求同事实表的空间考量相比显得多么不起眼。

产品维度表的许多属性并不是商品体系的组成部分。例如,包装类型属性就极可能有瓶装、袋装、盒装或者其他类型。任何部门的任何产品都可能取这些值中的一个。将这类属性的约束和商品体系属性方面的约束进行组合,具有非常积极的意义。作为例子,可以看一下袋装谷物分类中的所有SKUs。换个角度来考虑这个问题,可以对维度属性进行顺次浏览看它们是否属于商品体系,或者可以使用属性进行向上或者向下探查而看它们是否属于商品体系。另外,甚至可以在产品维度表中明确地包含一个以上的体系。

推荐用于零售杂货数据中心的部分产品维度看起来同图2.7中的情形类似。

                                              图2.7  零售营销方案的产品维度

一个合理的产品维度表可以拥有50或者更多的描述属性。每个属性都是约束和构造行标题的丰富来源。从这个意义上讲,如此费心劳神除了得到能够提供更多信息的行标题之外,别无所求。还是看看已经按部门针对销售额和销售量进行了汇总的简单报表吧: 

  部门描述        销售额       销售量

  面包店           $12 331      5 088

  冷冻食品部   $31 776      15 565

如果进行向下探查,实际上可以从产品维度将诸如商标这样的任何其他属性拖入紧接部门的下一级报表,并且能够自动探查到次一级的细节层次。在商品体系内,一个典型的向下探查结果与如下情形非常类似:

部门描述              商标描述        销售额              销售量

面包点                  炸油条            $3 009             1 138

面包点                  脆饼                $3 024             1 476

面包点                  软糕点            $6 298             2 474

冷冻食品部          雪糕                $5 321             2 640

冷冻食品部          鲜货                $10 476           5 234

冷冻食品部          冷狗                $7 328             3 092

冷冻食品部          冰糕                $2 184             1 437

冷冻食品部          速冻                $6 467             3 162

或者可以按脂肪含量属性向下探查,即使它不在商品堆积体系之列也是如此。

部门描述         脂肪含量        销售额               销售量

面包点             无脂                $6 29                 2 474

面包点             低脂                $5 027               2 086

面包点             一般                $1 006                  528

冷冻食品部     无脂                $5 321               2 640

冷冻食品部     低脂                $10 476             5 234

冷冻食品部     一般                $15 979             7 691

前面煞费苦心地给出探查例子的目的是为了得出作为设计原则来表述的观点。

¯在数据中心进行的向下探查操作不过是通过维度表添加一些行标题,而向上探查就是删除行标题。可以通过来自多个显式体系的属性而进行向上或者向下探查操作,也可以按非体系部分的属性进行同样的操作。

产品维度是几乎每个数据中心都拥有的两到三个基本维度之一。用尽可能多的描述属性对这个维度进行填充时,应该特别小心。一组丰富而完整的维度属性会转化为丰富而完整的用户数据分析能力。第4章将进一步研究产品维度,同时在那里还将讨论处理产品属性变化方面的内容。

2.3.3  商场维度

商场维度描述杂货连锁店的每个商场。与每个大型杂货店业务中基本上都能够得到的产品主文件不同,并不能保证存在可用的商场综合主文件。每当有新的或者变化的产品出现时,就需要将产品主文件下载到各个商场。不过,单个POS系统并不需要一个商场主文件。IT人员必须经常性地对来自总部多个操作源的商场维度必要分量进行装配。

在实例研究中,商场维度是基本的地理维度,每个商场可被看成一个位置。据此,可以将商场堆积成诸如美国的ZIP编码、县与州这样的任意地理属性。商场通常也堆积成商场的一些行政区或者地区。这两种不同的维度都很容易在商场维度中表示出来,因为地理体系与商场地区体系对每个商场行来说,都是经过良好定义的。

¯在维度表中表示多个体系是不常见的。在理想情况下,属性名与值在跨多个体系的范围内应该是惟一的。

推荐在杂货店业务方面使用如图2.8所示的商场维度表。

建筑平面图的类型、影印处理类型以及财务服务类型等都是描述特定商场的短文本描述内容。这些内容不应该是单个字符的编码,而应该是出现在下拉列表或者报表行标题中,显得意义明确,并经过标准化处理的1020个字符大小的描述内容。

描述销售面积的列应该是数字型的,并且在理论上是跨商场可相加的。虽然有人可能试图将它放到事实表中,不过很明显,它是商场的一个不变属性,并且作为报表约束或者行标题使用的情况比用做求和的可加分量要多得多。因为这些原因,可以确信,销售面积属于商场维度表。

                                           图2.8  零售营销方案的商场维度

首度开业日期与最新重修日期一般都是连接到日期维度表拷贝的关键字。这些日期维度拷贝通过VIEW结构在SQL中进行定义,并在语义上同基本日期维度相区别。VIEW定义的形式如下:

CREATE VIEW FIRST_OPEN_DATE (FIRST_OPEN_NUMBER, 

    FIRST_OPEN_MONTH, …)

    AS SELECT DAY_NUMBER, MONTH, 

    FROM DATE

这样,系统运作起来就好像存在一个称做FIRST_OPEN_DATE的日期维度表拷贝。针对这个新日期表的约束不会对基本日期维度表上的约束产生任何影响。第一个开放日期视图是对商场维度来说可以允许的一个支架。要注意的是,视图的所有列都重新进行了仔细的标记,以便使它们不与基本维度表的列互相混淆。第6章还会对支架做进一步的讨论。

2.3.4  促销维度

促销维度是方案中可能最令人感兴趣的维度。促销维度描述产品销售的促销情形。促销情形包括临时降价、廊端展销、报纸广告与优惠券等。因为这个维度用来描述被认定会使产品销售发生变化的因素而通常被叫做因果维度(与偶然维度相对)。

总部与商场的经理都对确定一个促销活动是否有效感兴趣。促销由下列一至多个因素进行评判:

o 促销产品的销售是否在促销区间出现增长。这叫做上扬(lift)。上扬只有当商场能够就什么是在没有促销的情况下促销产品可以达到的销售底线而取得一致意见时,才能进行评测。底线值可以从以前的销售历史,以及在某种场合下借助于复杂的数学模型而计算出来。

o 除去促销区间(过渡时间)内销售方面的增长以外,促销产品的销售是否就在促销进行之前或者随后表现出减少的情形。换句话说,销售人员实施过从正常定价产品到临时减价产品方面的销售转移吗?

o 是否发生促销产品的销售出现增长,而临近货架上的其他产品销售却呈现出相应的降低情况(发生同类相食)?

o 在考虑了促销之前、区间与其后的时间段因素(市场生长)的情况下,促销类别中所有产品的销售是否都经历了一个实际的总体增长?

o 促销是否赢利。在通常情况下,促销利润要按促销类别的利润增量与考虑了时间过渡、同类调剂以及销售底线的比值而计算出来。其中,底线应该考虑了包括临时降价、广告、展示和优惠券在内的促销花费等因素。

对销售产生潜在影响的因果情形,并不需要由POS系统进行直接的跟踪。这种事务系统要保持对降价与削价的跟踪。通常优惠券的使用也由该事务进行捕获,因为顾客在购买商品时要么出示优惠券,要么不这么做。广告与商场的展示可能需要与其他源头进行连结,各种可能的因果情形都是高度相关的。临时降价会通常与广告或者廊端展销联系在一起,优惠券通常也与广告相关联。因为这个原因,在促销维度为促销出现的每种组合都创建一行显得很有意义。在一年的进程中,可能出现1 000个广告,5 000次临时降价和1 000次廊端展销,但可能只有10 000个这三种手段的组合能够影响任何特定的产品。例如,在某给定维度中,大多数商场都会同时运作所有三种机制,而只有少数几个商场不进行廊端展销。在这种情况下,就需要两个单独的促销情形行,一个用于通常的降价并外加广告与展示,而一个用于降价并外加单纯的广告。推荐使用的一个促销维度表显示在图2.9中。

                                    图2.9  零售营销方案的促销维度

从单纯的逻辑角度看,通过将四个主要的因果维度机制(价格降低、广告、展示与优惠券)分开形成单独的维度,而不是将它们组合成一个维度,就能够记录维度方面非常相似的信息。究竟使用哪种方式,最后的选择权掌握在设计人员手中。赞成将四个维度揉合在一起,基于包括如下两个方面的考虑:

o 既然四个因果机制是高度相关的,那么组合起来的单个维度就不会比分开的任何一个大许多。

o 组合起来的单个维度能够高效地进行浏览,以弄清各种不同的价格降低、广告、展销与优惠券是如何在一起进行应用的。当然,这样的浏览仅仅显示了可能的组合。在维度表中所进行的浏览,并不能揭示促销对哪家商场和哪种产品产生了影响。这类信息放在事实表中。

倾向于将四个因果机制分开而形成不同维度表的想法是基于对如下两个因素的权衡而产生的:

o 在用户分开考虑这些机制时,分开的维度对业务群体来说更容易理解。这一点在业务需求调研区间就会显露出来。

o 独立维度的管理相对组合维度的管理,表现得更加直截了当。

记住,在数据仓库的信息内容方面,这两种选择之间并不存在什么不同。

在典型情况下,许多销售事务分列项目会涉及到并不在促销之列的产品。因此,需要在促销维度中包括以其惟一关键字来标识“不在促销之列”的一行,从而避免在事实表中出现空促销关键字的情况。如果在作为引用维度表的外关键字而进行定义的事实表列中放置一个空值,引用完整性就会遭到破坏。除了出现引用完整性方面的警告外,空关键字还是用户疑惑不解的根源,因为他们不能对空关键字进行连接。

¯必须避免在事实表中出现空关键字。在这方面显得比较合适的设计是在对应的维度表中包括一行来标识该维度对度量值不可用。

1.促销范围的非事实型事实表

不管对促销维度进行如何处理,所给出的零售营销维度方案还不能对一个重要问题做出回答:什么样的促销产品没有卖出去?销售事实表仅仅记录了实际卖出的SKUs,而不存在将没有卖出去的SKUs0事实进行表示的事实表行,因为这样做会造成事实表的过度膨胀。在关系数据库领域,需要另外的一个促销范围或者事件事实表来协助回答关于什么没有发生方面的问题。在该实例研究中,促销范围事实表的关键字是日期、产品、商场与维度。这显然看起来与刚设计的事实表显得很相似,不过,在粒度方面会存在非常大的不同。在使用促销维度事实表的情形下,不管产品卖出与否,都要在该事实表为每天(或者一周,因为许多零售促销会持续一周)中每个商场的每个促销产品创建一行。通过范围事实表可以看到由促销活动所定义的关键字之间的关系,而不必关心诸如实际产品销售这样的其他事情。之所以将它称之为非事实型事实表,是因为它不具有度量指标;它仅仅能够捕获所涉及的关键字之间的关系。为了确定什么产品属于促销之列却没有卖出,需要采取两个步骤。首先,要查询促销范围表以确定某天促销产品的范围,然后,根据POS销售事实表确定卖出了什么产品。针对先前问题的答案,取决于这两个列表之间所给出的不同内容。注意从第12章中获取关于非事实型事实表方面更为完整的内容,在那里将给出促销范围表的例子,并提供表现两表所具有的不同内容的SQL语句。如果工作场合是OLAP立方体环境,那么通常容易回答关于什么产品没有卖出之类的问题,因为这种立方体一般都含有非实际行为方面的明确信息块。

2.3.5  退化的事务编号维度

零售事实表包含每个分列项目行的POS事务编号。在传统的父-子关系型数据库中,POS事务编号是事务标题记录的关键字,这样的记录包含了诸如事务日期与商场标识符这样的在总体上对事务有效的所有信息。然而,在这里给出的维度模型中,已经将这个令人感兴趣的标题信息抽取出来而放到其他维度中了。不过,POS事务编号仍旧非常有用,因为它可以作为组关键字而将单个事务中售出的所有产品集中在一起。

尽管POS事务编号看起来像事实表中的一个维度关键字,但这里还是将从其他方面看极可能形成一个POS事务维度的所有描述性项目内容进行了剔除。既然所形成的维度为空,那么不妨将POS事务编号称做退化维度(Degenerate Dimension)(图2.10DD符号标识的部分)。诸如POS事务编号这样的固有操作型票据编号,应该自然而然地放在事实表中,而不用连接到维度表。退化维度在事实表粒度表示单个事务或者事务分列项目时是很常见的,因为它表示了父实体的惟一标识符。订单编号、发票编号与提货单编号等几乎总是以退化维度的形式在维度模型中出现。

                                                    图2.10  零售营销方案的查询

退化维度经常在事实表主关键字方面发挥着一个有机组成部分的角色。在该实例研究中,零售业营销事实表的主关键字由退化POS事务编号与产品关键字组成(假定POS系统可将POS购物架上某种产品的全部销售情况堆积成单个分列项)。通常,事实表的主关键字是表的外关键字的一个子集。一般情况下,并不需要用到事实表的每个外关键字就能保证事实表行具有惟一性。

¯诸如订单编号、发票编号与提货单编号这类操作型控制编号通常会引起空维度的出现,而在事实表中以退化维度(即维度关键字没有对应的维度表)的形式表示出来,其中,事实表的粒度就是文档本身或者文档中的一个分列项。

如果由于某种原因,一个或者多个属性在创建了所有其他维度以后被合法地略去了,并且看起来是属于当前标题实体范围内的属性,那么可以简单地创建一个具有常规连接关系的常规维度记录。不过,这就不需要再给出一个退化维度了。

◣ 2.4  零售方案的运用

针对设计出来的零售POS方案,现在来看看如何将它放到查询环境中去使用。业务用户很可能对更好地理解波士顿地区的商场在20021月份内,通过快餐类产品的促销而在每周取得的销售总额感兴趣。如图2.10所示,应该针对日期维度的月份与年度、商场维度的行政区以及产品维度中的分类施加查询约束条件。

如果查询工具对按周末日期与促销来分组的销售额进行求和,那么查询结果会与下面列出的情形看起来很相似。在这里,可以明显地看出维度模型与相关的查询结果之间的关系。高质量的维度属性是至关重要的,因为它们是查询约束条件与结果集合标签的来源之所在。

  日历周末日期              促销活动名称            销售量

200216               无促销                       22 647

2002113             无促销                         4 851

2002120           “超级饭碗”促销       7 248

2002127           “超级饭碗”促销     13 798

 

日历周末日期            “超级饭碗”销售量   “无促销”销售量

200216                                         0                       22 647

2002113                                       0                         4 851

2002120                               7 248                                 0

2002127                             13 798                                 0

如果使用一个功能更强的数据存取工具,可能以交叉表格的报表形式给出结果。这样的报表对业务用户具有比通过一条SQL语句查询得到的数据列,具有更大的吸引力。

◣ 2.5  零售方案的扩展

到此为止,第一个维度模型已经设计完毕,该是将注意力转向对设计进行扩展的时候了。假设零售商将要实施一项购物常客计划。现在,不是了解到某位未知名的购物者在他或者她的手推车中放了26种物品,而是能够明确地知道某特订购物者,也就是Julie Kimball,每周内会购买些什么。不妨设想一下,业务用户们对通过众多地理、人口、行为以及其他区分购物者的特征进行购物模式分析会多么感兴趣。

处理这种新的购物常客信息是相当直接的——创建一个购物常客维度表,并在事实表中增加另一个外关键字。既然不能要求购物者将他们过去的购物收据全部提交上来,而给出一个新的购物常客编号对过去的销售事务进行标记,那么应该使用一个与历史事实表行中的“购物常客计划之前”描述内容相对应的购物者关键字。同样,由于不是每个到杂货店购物的人都将拥有一个购物常客卡,因此还应该在购物者维度中包括一个“非确认购物常客”行。如同前面对促销维度所进行的讨论一样,一定要避免在事实表中出现空关键字。

与用购物常客维度对初始模型进行修饰一样,还可以增加如图2.11所示的一天中的时间和同事务相联系的职员方面的维度。任何在事实表度量值方面具有单一值的描述属性,都可以作为一个好的候选维度加入到已存在的维度中,或者自成一个维度。关于维度是否可以依附在事实表上,应该在已定义的粒度基础上做出二元的“是/不”的决定。如果还有疑问,就应该回过头去看看设计过程“第二步”的内容了。

初始方案之所以能够自然地扩展而适应这些新的维度,很大程度上是因为选择了在最佳粒度层面上对POS事务数据进行建模。适用于该粒度的维度的加入,并不改变已存在的维度关键字或者事实,而所有先前存在的应用也用不着拆除或者修改就能继续运行。如果最初定义的粒度是每天的零售营销情况(按天、商场、产品和促销进行求和的事务),而不是事务分列项细节层次的,那么将不能容易地插入购物常客、每日时间或者职员维度。过早的汇总或者聚集处理必然限制对维度的增补,因为添加的维度通常在一个更高的粒度上并不适用。

                                                         图2.11  零售营销修改方案

很显然,总有一些修改永远不能得到自然的处理。如果一个数据源变得不可用,并且又没有兼容的替代品,那么依赖于这个数据源的数据仓库应用就将停止运作。不过,维度模型可预见的对称性使其能够承受在源数据或者建模设想方面进行一些颇为重大的修改,而不会使存在的应用变得无效。下面将从最简单的内容开始,描述一些不能预见的修改类目。

新的维度属性。例如,当发现了产品方面的新文字描述内容时,这些属性就要作为新的列加入到维度中。所有现存的应用都不会在意这些新属性而继续发挥着作用。如果新属性只是在某个特定的时间点以后才可用,那么就应该在旧维度记录中填写“不可用”或者与其等价的内容。

新的维度。正如刚刚在图2.11中示例的那样,可以通过添加一个新的外关键字字段,并使用从新维度得来的主关键字值对它正确地进行填充,从而在已有的事实表中增加一个维度。

新的度量值事实。如果有新的度量值事实变得可用,则可以将它们自然地添加到事实表中。最简单的情形是,新的事实在与现存的事实同样的度量值事件和粒度上可用。在这种情况下,事实表应该被修改而增加新的列并填入相应的值。如果ALTER TABLE语句不能用,则必须定义另一个包括有添加的列与从第一个表中复制过来的行的表。如果新事实仅仅在向前的某个时间点可用,那么需要在旧事实行中设置一些空值。当新的度量值事实本来就是以不同的粒度出现时,这就产生一个更为复杂的情形。如果新的事实不能分配或者指派给事实表的初始粒度,就很有可能要将新的事实归入到自身的事实表中。在相同的事实表中混杂不同的粒度,几乎总会出错。

维度变得具有更多的粒度性。有时侯,很希望提高维度的粒度。在大多数情况下,初始维度属性可以被包含到一个新的具有更多粒度性的维度中,因为它们能按多对一的关系很好地堆积起来。具有更多粒度性的维度,经常意味着建立一个具有更多粒度性的事实表。于是,除了丢弃事实表并进行重建以外,很可能没有其他可供选择的办法。当然,所有现存的应用不会受到影响。

全新数据源的加入,会同时牵涉现存的维度和不能预见的新维度。新的数据源几乎总是具有自己的粒度和维度,因此,需要创建一个新的事实表。应该避免强行用新的度量值去适合一个存在一致性度量值要求的表的做法。现存应用将仍然能够发挥作用,因为现存事实和维度表并没有发生改变。

◣ 2.6  经受住安逸诱惑的考验

以第一个维度设计作为支撑,就可以直接去正视那些对具有规范化应用背景的建模人员,特别具有诱惑力的问题了。数据仓库团队应该有意识地打破一些传统的建模规则,因为这是将注意力集中到通过容易使用与良好性能来发挥业务价值,而不是放到事务处理效率上的需要。

2.6.1  维度的规范化处理(雪花处理)

具有重复文本值的平面退化维度表可能使一个规范化建模人员感到不舒服。还是回头看看实例研究中的产品维度表吧!150 000种产品形成50个独立的部门。受到规范化充分熏陶的建模人员不是要在产品维度表中冗余地存储20个字节的部门描述信息,而是想存储一个2字节的部门编码,然后为部门翻译内容单独创建一个新的部门维度。事实上,如果将初始设计中的所有描述符全部规范化成单个的维度表,他们或许感到更为舒服一些。他们争辩说,这样的设计节省空间,因为只需要在150 000行的维度表中存储含义模糊的编码而不是冗长的描述符。

此外,一些建模人员认为,对经过规范化设计的维度表维护起来容易一些。假如部门描述内容发生变化,他们需要做的只是更新一个实例值,而不是更新初始产品维度中的3 000个重复实例值。如何进行维护确实常常由规范化处理的规范来决定,但要记住,所有这一切在比较靠后的距数据加载到展示环节的维度方案发生之前尚有很长一段距离的转储环节。

维度表的规范化处理一般称做雪花处理(snowflaking)。这种处理将冗余属性从平面的退化维度表中去掉,并放到另一个规范化的维度表中去。图2.12给出了对初始方案部分地进行雪花处理的例子。该模式被完全雪花处理以后,就会形成一个完全的3NF实体关系图。将图2.12与较早设计的图2.10对照起来看,结果是令人吃惊的。虽然两个图指的是一回事,但维度表多出的内容(尽管在表示上进行了简化)显得过于庞大了。

即便雪花处理是对维度模型的合法扩展,但一般说来,仍然希望设计人员能够经受雪花处理的诱惑,转而注重易使用与高性能这两个基本的设计主旨。

多数经过雪花处理的表使数据展示变得更为复杂。用户不可避免地要去与这个复杂性较劲。记住,简明性是退化维度表要夺取的基本目标之一。

                                                图2.12  部分雪花的产品维度

同样,数据库优化器也要与雪花模式的复杂性进行较量。过多的表与连接通常都会转化成更慢的查询性能。连接说明的复杂性增加了优化器在次要方面开销过大以及选取低效算法策略的机会。

因使用雪花维度表而节省下来的少量磁盘空间是无关紧要的。用2字节编码取代有150 000行的产品维度表中那个20字节的部门描述,能够节省至多2.7 MB的空间(150 000×18字节),但事实表却可能有10 GB之大!维度表几乎总是比事实表的几何尺寸要小。为节省磁盘空间而投入精力去规范化对产品维度表,只不过是在浪费时间。

雪花处理降低了用户在维度中进行浏览的能力。浏览操作经常涉及到对一至多个维度属性进行约束,并查看在这些约束条件下其他属性的各种取值。浏览操作使用户能够了解维度属性值之间的关系。

显然,如果只是想得到分类描述的列表,雪花产品维度表确实具有良好的响应。不过,假如想看到分类中的所有商标,就需要对商标与分类维度进行遍历。如果还想继续对分类中的每个商标的包装类型进行列表显示的话,就不得不遍历更多的表。用来执行这些看起来很简单的查询SQL是非常复杂的,更何况还没有接触其他的维度或者事实表呢。

最后,雪花处理挫败了对位图索引的使用。位图索引对于诸如产品维度表的分类与部门列这样的低基数字段的索引,是很有用途的。它们极大地提高了查询或者针对单个字段进行约束的速度。所以说,雪花处理不可避免地会干扰设计人员使这种性能增益技术发挥作用的努力。

¯维度表应该在物理上保持平面的特点。规范化或者雪花维度表制约了跨属性的浏览操作,并禁止对位图索引的使用。通过规范化维度表节省下来的磁盘空间一般都少于整个设计结构所需磁盘空间总量的1%。因此,理应有意地牺牲这点维度表空间来换取高性能与易使用方面的优点。

当然,有时进行雪花处理也是允许的。比如,前面给出的关于商场维度方面的日期维度支架,就是一个很好的例子。这里面的许多相关属性被反复地使用,并在各个方面独自发挥着作用。设计人员一定要在雪花设计方面持保守态度,并且只在对其提出明确的要求时才使用。

2.6.2  维度使用过多

维度模型的事实表自然是高度规范化与紧凑的。没有什么办法针对事实表中那些在关键字方面显得特别复杂的多对多关系,进行更进一步的规范化处理,因为这些维度彼此是不相关的。每家商场每天都营业,因此,几乎每种产品迟早会在大多数或者每家商场中进行促销。

有趣的是,尽管对退化维度表感到不自在,一些设计人员却还偏向于对事实表进行退化处理。他们不是在事实表中给出单个产品的外关键字,而是为产品体系中诸如商标、子类目、类目与部门这样的每个经常用于分析的元素,都包括一个外关键字。同样,日期关键字也被很唐突地转化成一系列连接到分开的星期、月份、季度与年度维度表关键字之上。在还没来得及对它进行认识之前,紧凑的事实表已经被变成一个逐字逐句连接到众多维度表上的难于驾驭的怪物。作者很愿意将这些设计方案亲切地称为蜈蚣(centipedes),因为事实表看起来就像图2.13所显示的那样,有将近100只腿。很显然,像蜈蚣一样的设计已经滑入到维度使用过多的陷阱中了。

记住,即使采用紧凑格式,事实表仍然是维度设计中的庞然大物。设计出含有过多维度的事实表,会导致在事实表磁盘空间方面极大地增加对容量的要求。虽然可能愿意在维度表上使用额外的空间,但事实表对空间的消耗着实令人关注,因为它是尺寸上最大的表。没有什么办法为“蜈蚣”例子中由几个部分构成的大量关键字有效地建立索引。过多的连接对于可用性与查询性能来说,都是一个问题。

                                           图2.13  维度过多的蜈蚣状事实表

大多数业务处理都可以在事实表中用个数少于15的维度表示出来。如果设计中存在25个以上的维度,就应该想办法将相关维度组合成单个维度。诸如体系层次这样的完全相关的属性,以及与统计相关的合理属性,都应该成为同一维度的组成部分。当形成的单个新维度比分开的维度的笛卡尔积显著地小,那么意味着对维度合成方式的选取是适当的。

¯存在特别多的维度一般都预示了不同维度并不是完全独立的,而应该组合成单个维度这样的一个迹象。将体系的元素在事实表中表示成分开的维度,是维度建模方面的一种错误做法。

◣ 2.7  代理关键字

极力提倡设计人员在维度模型中使用代理关键字,而不要依赖操作型产品编码。代理关键字有许多其他的别名,如虚义关键字、整型关键字、强制关键字、指定关键字与合成关键字等。简单地说,代理关键字就是在填充维度时按需要而顺序分配的多个整数值。例如,为第一条产品记录分配一个值为1的产品代理关键字,第二条分配2等。代理关键字仅仅用于维度表到事实表的连接。

建模人员有时很不情愿放弃对自然关键字的使用,因为他们想在操作型编码的基础上对事实表进行操控而避免对维度表进行连接。不过要知道,维度表乃是接触事实的入口。比如说,操作型编码中,第5到第9之间的字符用于标识制造商,那么该制造商的名字应该作为维度表属性包括进来。大体说来,要避免在数据仓库关键字中包括带有技巧性的内容,因为主观上的任何设想最后可能变得无效。同样,查询和数据存取应用都不应该在关键字上存在内置的相关性,因为这种逻辑也容易变得无效。

¯数据仓库中维度和事实表之间的每个连接都应该用没有明确含义的整型代理关键字来建立。应该避免使用自然的操作型产品编码。没有一个数据仓库关键字应该是意义隐晦的,相反,只要看一下关键字就能直接知道它想表达的行方面的信息。

在初始阶段使用操作型编码可能使维度模型实现起来更快一些,但代理关键字最后会确定无疑地带来回报。有时候,这看起来很像是在给数据仓库注射流感药剂——一种疫苗,引入与管理代理关键字确实会让人感受到少许的艰辛,但最终的好处终归是主要的。

代理关键字的主要好处之一是,能够对数据仓库环境的操作型变化进行缓冲。代理关键字允许数据仓库团队维持对环境的控制,而不会受到产品编码生成、更新、删除、再生与重用等操作型规则的妨碍。在许多机构中,历史操作型编码(例如,非活动性账户编号或者过时的产品编码)会在废弃一个时期之后重新进行指定。如果账户编号要经过失效12个月之后才能再生的话,那么这个操作型系统将免不了要挨一顿揍,因为它们的业务规则不允许将数据挂起如此漫长的一段时间。换句话说,数据仓库要把数据保留多年。代理关键字给数据仓库提供,对这两种具有相同操作型账户编号的独立实例进行区分的机制。如果仅仅依赖于使用操作型编码,那么同样容易受到数据采集或者在合并情况下关键字重叠问题的困扰。代理关键字允许数据仓库团队对来自多个操作型源系统的数据进行合并,即使它们之间缺乏一致的源关键字也无所谓。

使用代理关键字还可以获得性能上的优势。代理关键字小到只有一个整数所占据的空间大小,却能确保充裕地容纳维度行以后可能需要的序号或者最大编号。操作型编码常常是以一个混合了字母与数字的大容量字符串的形式而存在的。较小的代理关键字转化成较小的事实表、较小的事实表索引以及每组输入输出操作中更多的事实表行。在典型情况下,一个4字节的整数足够处理绝大多数的维度情形。一个4字节的整数存储起来是一个单一整数,而不是四个十进制数字。它是32位长的,因而能处理大约20亿个正值232-1)或者总共40亿个正负值(从-232-1到232-1)。正如前面所说的一样,这几乎对于任何维度而言都是远远够用的。记住,如果存在一个10亿行数据的大型事实表,那么每个事实表行的每个字节都将转化成另外10亿字节的存储量。

正如较早所提到的那样,代理关键字用于记录那些诸如“不在促销之列”这样的可能没有操作型编码的维度情形。通过对数据仓库的关键字施加控制,就能够做到不管是否缺少操作型编码方案,总可以分配一个代理关键字将这类情况标识出来。

类似地可以发现,维度模型中还存在有待确定的日期。SQL日期是不可能取“日期待定”或者“日期不可用”这类值的。这就是提倡设计人员使用代理关键字作为日期关键字,而不是使用SQL日期数据类型的另一个原因(假如前面的原理说明还不足以令人信服的话)。

日期维度是其代理关键字应该以某种富有意义的连续次序进行分配的一种维度。换句话说,第一年11号的代理关键字值应该赋值为12号赋值为221号应赋值为32等。不希望设计人员将日历方面的技巧(例如,用YYYY-MM-DD表示日期)扩展到这些关键字上来,因为这样做可能促使人们绕开日期查找维度表。当然,这类小技巧形式的使用,还会让用户没有办法去表示“尚未发生”以及其他一些常见的日期情形。不是刚才还在说,事实表行应该是连续次序的吗?将代理日期关键字处理成日期序号,可以允许事实表在日期关键字基础上进行物理分区。在日期的基础上对一个大型事实表进行分区是非常有效的,因为这样做可以使过去的数据能够很得体地被删除掉,而新的数据也能够在不妨碍事实表其他部分的情况下进行加载与建立索引。

最后,代理关键字是支持处理维度表属性修改的一项基本技术。实际上,这是使用代理关键字最重要的原因之一。第4章将用整整一节通过渐变维度(slowly changing dimension)来介绍代理关键字在这方面的使用情况。

当然,在代理关键字的分配与管理方面确实需要投入一些精力,但这并不像许多人想像的那样令人生畏。同时,很有必要在转储环节建立与维护一个用于替代每个事实与维度表行的合适代理关键字的交叉引用表。第16章将给出管理与处理维度方案的代理关键字流程图。

在结束关键字主题的讨论之前,有必要阻止一下在维度方面使用合并或者复合关键字的做法。简单地将几个自然关键字粘贴在一起或者用时间标记将自然关键字组合起来,是不可能创建一个真正的代理关键字的。另外,还应该避免在维度与事实表之间给出多个并行的连接,即有时被称为双筒连接(double-barreled joins)的连接情形,因为它们会对性能产生不利的影响。

虽然在通常情况下,不会因为分配代理关键字而对维度进行退化处理,但还是应该对每种情形进行评估以确定是否需要一个代理关键字。如果事务控制编号在跨地域范围内不是惟一的或者需要进行重用时,则代理关键字就是必要的。例如,零售商POS系统就没有必要分配一个在跨商场范围内仍然具有惟一性的事务编号。一旦该系统达到其最大的编号值,就可以归零并重用以前的事务编号。再说,事务控制编号还可能是一个24字节大小的混合有字母和数字的大容量列项。在这种情况下,使用代理关键字也是有利的。从技术上讲,以这种方式建模的控制编号维度不再需要进行退化处理。

现在可以设想一下零售营销方案的第一个版本,如何表示数据库的逻辑与物理设计了。换句话说,关系型数据库仅仅包含5个实际的表:零售营销事实表与日期、产品、商场与促销维度表。每个维度表具有一个主关键字,而事实表除了具有一个退化维度事务编号之外,还具有由这四个外关键字组成的一个复合关键字。或许在这个设计上,最引人注目的将是事实表的简明性。如果四个关键字都是进行了紧凑处理的连续整数,那么仅仅需要为所有四个关键字保留14个字节这么小的存储空间(日期、产品与促销维度各用4个字节,而商场用2个字节)。同时,事务编号可能需要另外的8个字节。如果事实表4类事实中的任何一个都是4字节的整数,则仅仅需要再保留另外的16个字节。这就使事实表仅仅只有38个字节那么宽。即使有一个10亿行的事实表,它也不过占据大约38 GB的主数据空间。这种流线型(streamlined)的事实表行在维度设计中显得很典型。

如图2.11所示经过修饰的零售营销方案还具有另外三个维度。如果为每个购物者与职员分配4个字节,并且为每日时间(准确到分钟)分配2个字节,则事实表仅仅增加到48个字节。这样一来,10亿行的事实表也才占据48 GB的空间。

◣ 2.8  市场篮子分析

零售营销方案极其详细地表明了每家商场在什么情况下销售了什么样的产品。不过,该方案并不能让业务用户轻易地分析出哪些产品是在相同市场篮子中一起销售出去的。这种对一起售出的产品进行组合分析的概念,就是数据挖掘人员所熟知的相似成组(affinity grouping),不过,更为通俗的叫法是市场篮子分析(market basket analysis)。市场篮子分析向零售商们揭示关于如何进行各种商品项目组合销售方面的奥秘。如果冷冻面糊正餐搭配可乐产品进行销售的情况很好,那么这两种产品就应该就近摆放或者标出配套价格。市场篮子分析的概念很容易扩展到其他场合。在制造行业中,了解关于什么产品可以一起订购方面的信息非常有用,因为人们很可能希望推出成套产品的总体标价。

前面所给出的零售营销事实表不便用来进行市场篮子分析,因为它没有可用于在分列项事实行之间进行约束和分组的SQL。数据挖掘工具和一些OLAP产品可用来帮助用户进行市场篮子分析,不过,接下来还是描述一种不用这些工具而更加直接地进行市场篮子分析的方式。事先要提醒的是,这项技术相当先进,如果你现在并不做市场篮子分析,那么只要简单地浏览一下本节,从而对相关技术进行一般的了解就可以了。

2.14给出了一个从零售营销事务衍生出来的市场篮子事实表的例子。市场篮子事实表是某给定时间段内配套销售产品组的周期快照。这类事实包括将A产品与B产品、A产品销售总额及配套销售的计量单位与B产品销售总额及配套销售的计量单位等内容涵盖在内的篮子(顾客票据)总数。篮子数目是半加型事实。例如,如果市场篮子事实表的顾客票据含有面糊、软饮料与花生酱等分列项目,那么该单据会在面糊-软饮料组合事实行计数一次,在面糊-花生酱组合行计数一次等。不言而喻,必须非常小心以避免对多个产品的销售计数进行求和。

                                     图2.14  从购买事务移植的市场篮子事实表

读者可能注意到,市场篮子事实表有两个通用产品关键字(产品关键字AB)。这里创建的是一个包含了诸如各种产品、商标与类目这种实体的单一产品维度表,其中的实体处在体系的多个层次上。这个针对普通产品维度表专门给出的变体,包含了少量的非常具有一般性的属性,同时,为产品体系结构的各种层次指定了代理关键字,以免引起相互重叠。

从概念上讲,记录市场篮子的相关性是很简单的,但纯粹的产品组合编号将使分析变得复杂起来。假设产品集存在N种产品,并且用户企图建立一个表,将订单中遇到的每种产品关键字的可能搭配都包括进来,那么可以得到N2种产品组合(实际上是N×(N-1)才确切)。换句话说,如果总共存在10 000种产品,那么可以得到将近100 000 000个成对的组合。这样一来,当处理数目巨大的产品时,可能的组合数目将很快达到近乎荒谬的程度。假如在零售商场中销售100 000SKUs,可能的SKU组合竟达百亿之多!

解决市场篮子分析方面的实际问题的答案在于记住:首要目标是了解对产品配套销售富有实际意义的组合。对于市场篮子事实表来说,首先令人感兴趣的是具有大篮子(顾客票据)计数值的行。既然这些产品组合被频繁地引起注意,因此有理由更进一步地考察它们的情况。其次,用户还可能需要找出产品A和产品B的销售额或者计量单位之间处于某种合理平衡的情形。如果销售额或者计量单位严重失衡,那么所做的一切就只是找出了与其他无关紧要的产品进行搭配的那些销售很旺的产品,这对于制定主要的销售方法或者促销决策帮不了多大的忙。

为了避免市场篮子事实表中产品对出现组合爆炸,需要采用一个渐进的修剪算法。在这里,假定产品体系结构的顶层是类目,并由此开始讨论该算法的内容。首先,枚举出类目到类目的全部市场篮子组合。比如说,假设存在25个类目,那么这个步骤将生成625个市场篮子行。然后,通过仅仅将那些具有比较大的订购数目,并且将产品A与产品B(就是这里的类目)的销售额和计量单位保持适度平衡的行挑选出来,对得到的这个列表进行修剪以便进一步的分析。于是,通过实验就可以得到篮子计数阀值和平衡范围应该是多少。

接下来,下推到细节的第二个层次,并假定它是商标。从前面最后一步得到的修剪组合集开始,通过按类目(产品B)枚举出所有的商标(产品A)组合,可以探查到产品A。类似地,通过按商标(产品B)枚举出所有的商标(产品A)组合,可以探查一个产品体系层次而到达产品B。再一次对那些具有最高的篮子计数频率,并在销售额与计量单位上保持平衡的列表进行修剪而延伸到体系结构的下一个层次。

随着对体系结构的向下延伸,将生成篮子计数值越来越小的行。直到最后,无法找到具有合理阈值的更大相关篮子计数为止。一旦满足了分析人员的探查要求之后,就可以随时停下来了。这种自顶向下进行探查的方法的优点之一是,在每处找到的行都是最相关的并且是作用最大的。按渐进方式对列表进行的处理,可以引起用户对已有相关结果给出更多的关注。可以设想一下,使这个处理过程自动完成的情形——向下搜索产品体系结构,忽略过小的篮子计数值,并且总是争取在很高的篮子计数值情况下得到保持平衡的销售额和计量单位。该过程可以在产品对数达到某个希望的阈值,或者在用篮子计数、销售价格或者计量单位表达的总体情形达到某种较低的限度时终止。

针对这个方法可以做出的改变是,从一个特定的类目、商标乃至一个产品开始该处理过程。同样,这种思想也是首先用所有的类目与该特定产品进行组合,然后沿着体系结构向下探查。另外的一种做法是,着眼于给定时间段内某顾客购买的产品混合情况,而不管它们是否具有相同的篮子计数。在任何一种情形下,为了简化最终查询与分析方面的内容展示,与市场篮子分析相联系的许多艰苦工作,都丢给了转储环节的ETL过程。

◣ 2.9  小结

本章对维度模型设计方面的内容进行了首次展现。不管产业界实际上是如何处理的,作者极力推崇采用四步过程进行维度模型的设计。记住,与维度模型联系在一起的粒度知识同样是特别重要的。用原子型数据加载事实表能够获取最大的灵活性,因为这样做,可以从任何方面入手对数据进行汇总。只要事实表局限于聚集程度更高的信息,那么汇总的想法一旦证明是无效的,就撞到南墙上了。还要记住,用详尽而强壮的描述属性填充维度表也是至关重要的。

下一章仍然在零售业范围内讨论机构中进行另一种业务处理所需要的技能,以确保对前面的努力进行支撑,同时避免设计显得过于庞大。