文档视界 最新最全的文档下载
当前位置:文档视界 › 数据仓库ETl工具箱6

数据仓库ETl工具箱6

数据仓库ETl工具箱6
数据仓库ETl工具箱6

提交事实表

事实表装有企业的度量数据。事实表与度量的关系非常简单。如果存在一个度量,则它可以被模型化为事实表的行。如果事实表的行存在,则它就是一个度量。那么什么是度量呢?一个关于度量通用的定义是:通过工具或比例等级可以测量观察的数量值。

在维度建模时,我们有意识地围绕企业的数字度量创建我们的数据库。事实表包含度量,维表包含关于度量的上下文。这种关于事物的简单视图被一次又一次的证明是最终用户直观理解我们的数据仓库的方式。这也是我们为什么通过维度模型打包和提交数据仓库内容的原因。

流程检查

规划与设计:需求/现状 -> 架构 -> 实现 -> 测试/发布

数据流:抽取 -> 清洗 -> 规格化 -> 提交

第5章描述了如何创建数据仓库的维表。也许从维表开始介绍会觉得很奇怪,因为度量以及事实表才是最终用户真正想要看的内容,但是维表是事实表数据的入口,事实只有通过维度解释才会变得有意义。由于第5章详细完整地描述了维,因此本章的内容多少会变得简单一些。

事实表基本结构

每一个事实表通过表的粒度来定义。事实表的粒度是事件度量的定义。设计者必须至始至终按照度量如何在现实世界中理解来规定事实表的粒度。例如,图6.1中的事实表的粒度为指定的零售发票上的单个货品。我们并不从定义这些字段的粒度开始,而是将粒度表示成为维度的外键和事实表的某些字段。粒度定义必须按照现实的,物理的度量意义来定义,然后才考虑维度和事实表中的其他字段等其他因素。

所有的事实表包含了一组关联到维表的外键,而这些维表提供了事实表度量的上下文。大多数的事实表还包括了一个或者多个数值型的度量字段,我们称之为事实(Fact)。请看图6.1。某些事实表中还包还了一个或者多个特殊的近似维度字段,他们是在第5章中介绍的退化维度(Degenerate Dimensions)。退化维度存在于事实表,但是他们不是外键,不关联任何真正的维表。我们在图6.1中使用符号DD来标识退化维度。

图6.1最细粒度的销售交易事实表

在现实实践中,事实表一般都至少包含3个维度,而且绝大多数包含更多的维度。由于过去的20年中,随着数据仓库以及相应的软、硬件技术的成熟,事实表技术有了很大的提高,可以存储越来越多的最细粒度上量测值。越来越少的度量,越来越多的维度。在最初的零售销售数据仓库中,数据集仅仅是粗粒度的聚合值。这些早期的零售数据仓库通常只有3到4个维度(产品、市场、时间和促销)。现在,我们搜集销售交易的原子级别的零售数据。一个单独的销售交易中很轻易就包含了图6.1中的10个维度(日历日;产品;商店级别的现金帐簿;客户;员工;商店经理;价格范围;促销折扣;交易类型和付款方式)。更多的时候我们还要在目前的基础上增加新的维度:比如商店人口统计学指标,市场竞争事件和天气!

事实上,每个事实表都包含一组由表中的字段定义的主键。在图6.1中,事实表一个可能的主键是由两个退化维度:发票号(Ticket number)和行号(LineNumber)组成的联合键。这两个字段唯一的定义了出纳登记簿cashier register上的单个商品的度量事件。另外一个可能的等价主键是时间戳和出纳登记簿cashier register的组合。

如果在设计的时候没有給于足够的注意,那么就可能违反事实表上主键的假设:可能在同一个时段两个同样的度量事件会发生,但是数据仓库团队却没有意识到这一点。显然,每个事实表应该拥有一个主键,即使仅仅是出于管理的需要也应该在事实表设立主键。如果没有主键完整性,那么事实表中有可能存在两个或者更多一样的记录,而且没有办法按照独立的量测事件来区分他们。但是只要ETL团队保证单独的数据装载合理地表示唯一的量测事

件,通过在装载时为数据增加唯一的序号就可以在事实表中唯一标识记录。尽管唯一的序号和业务没有关联,而且不应该发布给最终用户,但是它在管理上保证了一个单独和可能的量测的发生。如果ETL团队不能够保证单独加载表示的是合法的独立的量测事件,那么在数据加载的时候数据上必须已经正确的定义了主键。

前面的例子表明需要所有的ETL作业可以在发布或者发生错误时有再次运行的能力,以保证不会错误的更新目标数据库。在SQL语法中,更新不变化的值通常是安全的,但是更新增量的值是危险的。如果主键已经强制定义,那么插入是安全的,因为插入重复的值会触发错误。如果限制是基于简单字段值,那么通常来说删除也是安全的。

确保参照完整性

在维度模型中,参照完整性意味着事实表中的每个字段使用的是合法的外键。换句话说,没有事实表记录包含了被破坏的或者未知的外键参照。

在维度模型中可能有两种情况会导致违反参照完整性:

1. 加载包含了错误外键的事实表记录

2. 删除了维表记录,而其主键在事实表中被使用。

如果没有注意参照完整性,那么就会非常容易破坏它。笔者曾经研究了很多没有严格保证参照完整性的事实表;每个案例中都能够发现严重的冲突。一个事实表记录违反参照完整性(记录中包含一个或者多个外键)不仅仅是讨厌,更严重的是这非常危险。推想一下,一条合理的记录正确的记录了量测事件,但是不正确的存储在数据库中。任何使用了坏维度的事实表查询将不能包含该记录,因为按照定义,维表和该事实表记录的关联不会发生。但是在动态聚合中忽略该坏维度的查询结果却包含了这条记录。

在图6.2中,我们显示了在ETL过程中可以保证参照完整性的三个主要位置。

图6.2强制参考完整性选择

1 在将事实数据加载到最终表之前,仔细的审核和准备数据,在删除任何维表记录之前,仔细地审核

2 在每次事实表插入操作和每次维表删除操作动作发生时,使用数据库自己的参照完整性检查

3 加载后对事实表进行扫描检查,以发现和更正有问题的外键,解决参照完整性冲突。

从实践角度来讲,第一个选择更加可行。在数据加载到事实表中最后一步就是查找事实表记录中的自然外键,然后将他们替换成为维表中的代理键。这个过程将在下一节代理键环节中仔细地介绍。这个过程的核心是一个特殊的查找表,它包含了每一个外来的自然键所使用的代理键的值。如果这张表被正确的维护,那么事实表中的记录将满足参照完整性。同样在维表记录被删除的时候也需要尝试联结事实表和维表,只有在查询返回null的时候才能够删除该记录。

第二种选择利用数据库的参照完整性来检查的方法虽然很自然,但是在批量的加载成千上万条甚至几百万条记录的时候效率太低。当然这只是一个软件技术的问题,如Reb Brick 数据库系统(现在IBM在销售)设计为在任何时候都维护参照完整性,它可以在1个小时内向事实表加载100万条记录,同时检查所有维度的参照完整性。

第三种在数据库更新完成后检查参照完整性的方法在理论上可以发现所有的冲突,但是花费的代价可能惊人的高,对参照完整性的检查需要使用:

select f.product_key

from fact_table f

where f.product_key not in (select p.product_key from

product_dimension p)

在拥有几百万行产品维度数据,以及数亿行事实表纪录的环境中,这是一个荒谬的查询。当然可以将查询仅仅限于当天加载的数据,但这需要假设时间维度的外键是正确的为前提。但是使用第一种选择时,这种方法可以作为一种额外的检查。

代理键管道

在建立事实表时,最终的ETL步骤是将新数据记录的自然键转化成正确的、当期的代理键。在这节,我们假设所有的记录已经加载到事实表中。换句话说,我们需要使用为每个维度实体(如客户或者产品)使用当前的代理键。我们将在这章的最后处理迟到的事实记录。

我们理论上可以通过在每个维表中获取最新的记录来为自然键获得当前的代理键,这在逻辑上是正确的,但是很慢。替代方法是为每一个维度维护一个专门的代理键查找表。这张表在新的维度实体创建的时候或者记录在发生类型2缓慢变化维度2的更新的时候被更新。我们在第5章讨论图5.16的时候介绍了这种表。

维表在插入或者缓慢变化维度2更新发生之后,在处理事实表之前,这个维表必须被完全的更新。在更新事实表之前的维表更新过程是维护维表和事实表参照一致性的一般过程。反向的过程在删除记录的时候发生,首先我们删除不需要的事实表记录,然后删除不再联结到事实表的维度记录。

不必因为事实表不再参照该记录而删除维表记录。即使事实表中没有引用该维

度实体,维度实体也可能需要存在或者保存在维表中。

当我们更新维表的时候,不仅仅要更新所有的维表记录,还要更新存储当期的数据仓库键和自然键关联关系的代理键查找表。

图6.3代理键管道

我们处理新增的事实表记录的过程非常容易理解。请看图6.3。我们从新增事实表记录中获得每个自然键键值,然后替换成对应的当期代理键。注意我们的提法:替换。我们在事实表记录中并不保存自然键。如果你关心具体的自然键值,你可以关联维表记录得到。

如果你有4到12个自然键,每个新增的事实表记录需要4到12个单独的查找表来获得正确的代理键。首先,我们建立多线程的应用来流化所有的输入数据记录,使其通过图6.3中的所有步骤。这里的多线程,指的是记录#1在完成连续的键查找和替换的同时,#2在后面进行相应的处理,我们并不在第一个查找步骤中处理所有的新增记录,然后将整个文件交给下一步。在经过所有必要的处理过程后才将所有的记录写到磁盘意味着更高的效率。在处理过程中应该尽量充分使用内存。

如果可能,所有需要的查找表应该常驻内存,以便每条事实表记录在处理它的自然键的时候随机访问。这是将查找表从原始数据仓库维表中分离的一个原因。假设对于某个维度,我们用上百万记录的查找表,如果自然键有20字节而代理键为4字节,我们需要24MB的内存来保存查找表。在使用4-8GB内存的机器作为数据准备机data-staging的环境中,我们可以非常容易在内存中加载所有的查找表。

我们前面的讨论的架构是如何设计发挥最大性能。如果你仅仅是每天加载几万条数据,而且装载窗口很大,那么你不需要强调这种级别的性能。你可以定义事实表和维表的星型联结,将自然键转化成代理键,用SQL处理整个过程。这种情况下,如果新增的数据不能匹配维度(参照完整性失败)你还可以对某些维度定义外联结。

可以在这种环境中使用程序工具awk,因为他支持在内存中使用自然键作为下标建立Array来完成自然键到代理键的翻译。这样,如果你定义Translate_Dimension_A[natural_key] = surrogate_key,那么处理每一条事实表记录就非常简单:print Translate_Dimension_A($1), Translate_Dimension_B($2) 。

在一些重要的大事实表中,你可能会遇到不寻常的monster维度,例如居民客户,可能拥有上亿条记录。如果我们只有一个这样的维度,那么我们仍然可以定义一个快速传递pipelined代理键系统,虽然这样巨大的维表在查找的时候会大量的读取磁盘。秘密在于按照维度的自然键对使用的事实表和查找表进行排序。这样,代理键替换变成了在两个文件之间一次性的按照顺序的合并。这个过程相当的快,尽管没有在内存中处理。如果你有两个这样的monster查找表在你的处理流程中pipeline中,而且不能在内存中处理,那么对于非排序的维度Key键上的随机访问所带来的I/O是难以承受的。

由于在源系统中有可能没有参照完整性检查,这样对事实表记录进行代理键处理的时候要考虑处理一些无效的自然键。这种情况下,我们建议创建一个新的代理键,增加一条维表记录,标示为 Unknown。如果无法对数据最终更正,那么每个无效的自然键都应该给一个唯一的新的代理键。如果你的业务本身要求这些无效的自然键必须保持并且不被处理,那么在所有受到影响的维度中都只需要有一个单个的唯一的代理键(缺省的Unknown记录)。

使用维表而不是查找表

上一节介绍的查找表对于解决那些处理得事实表数据记录都是同阶段的,换句话说,都是当前的非常有效。如果有相当数量的事实记录延迟到达,那么查找表就不再适用,这时候必须使用维表作为正确的代理键的源。当然,这种假设的前提是维表的设计要遵循第5章的建议。

避免使用独立的查找表还可以简化事实表加载之前的ETL管理工作,因为这种情况下不需要同步维表和查找表。

某些ETL工具套件通过查找事实表记录中的自然键创建高速、内存缓存,然后通过实时查询维表来获得相应的代理键。这种工作方式如果可行就可以避免使用查找表。可能的缺陷是在访问大维表时动态创建缓存的代价会增加。如果这个动态查找过程绑定事实记录中的自然键和时间戳,那么就可以查询那些搜索代理键的历史值,这种方法可以很高效的处理那些迟到的事实表记录。对于这个问题,您应该询问具体的ETL厂商。

基础粒度

由于事实表中存储了企业中所有的数值度量,可以猜想那会有很多的事实表类型。但是事实上,事实表可以归入三种基础类型。我们强烈推荐在每次设计的时候坚持使用这三种简单的类型。设计者通过混合使用这些简单类型构造更复杂的结构的时候,实际上是将避免发生严重错误的巨大的负担推给了最终用户的查询工具和应用。换句话说规则是每个事实表应

该有且只有一个粒度。

这三种事实表类型是:交易粒度,周期快照和聚合快照。我们将在下面讨论这三种粒度。交易粒度事实表

交易粒度表示的是在特定时间、空间点上的一次瞬间的测量。典型的例子是零售交易。当产品通过扫描器时,扫描器就会发出蜂鸣声(只有扫描器发声),那么一个记录就被记录下来。交易粒度记录只有度量事件发生的时候才被记录。这样,交易粒度事实表既可以为空的也可包含成百亿的记录。

我们讨论过交易粒度事实表的原子粒度可以包含很多的维度。你可以参照图表6.1展示的零售扫描事件。

像一个零售商店环境中可能只有一个可以度量的事务类型。而其他的环境,如保险流程,在数据流中可能混杂了多种的交易类型。这种情形下,数字度量字段通常标识为Amount,同时需要一个交易维度来解释是什么交易。参照图表6.4,在交易粒度表中的数值度量一定是参照某个度量事件,不会有时间跨度,或者是其他的时间。换句话说,事实必须属于同一个粒度。

图6.4保险标准的交易粒度事实表

交易粒度事实表是三种类型事实表中最大和最详细的。由于单个的交易往往带有时间戳,交易粒度的事实表通常用于复杂和分析。例如,在保单处理环境中,需要交易粒度事实表来描述复杂的保单处理的交易过程,分析不同类型的交易处理的时间。在其他两种类型事实表中不存储这些信息。但是不是所有的周期快照和聚合快照都可以从交易粒度的事实表通过常规的聚合得到。在保险环境中,保费业务系统能够在每月为每个产品生成一种有利可图的保险费措施。这个度量值必须存进每月的周期快照中,而非交易粒度事务表。计算保费的商业规则非常的复杂,数据仓库不可能利用那些低粒度的交易来进行很高效的计算。

交易的时间可能为分,秒或微秒,这些时间需要通过日历组件以及相应的自然日历维度的外键系统来模式化,在事实表中按照第5章图5.5介绍的使用SQL中完整的日期时间数据类型存储。

由于交易粒度事实表具有不可预测的数据分布,前端应用不能在查询中假定任何主键集。这个问题当客户维度需要和人口统计学行为维度进行匹配的时候显得更为突出。如果限定过于的狭窄(例如某个日历日),查询可能不会有任何的结果,和人口统计学条件匹配的

客户被忽略了。考虑到这个问题的数据库架构师通过指定一个事实无关的覆盖表,该表中存储了所有意义的键的组合,以便确保应用能够使客户和人口统计学指标匹配。本章后面将讨论事实无关事实表。我们马上会在后续的章节中介绍周期快照事实表,他很好的解决了数据稀疏问题,但在主键上数据是稠密的。

在理想的环境中,同代的交易级别事实表记录是通过一个固定的时间间隔大批量的抽取到数据仓库中的。大多数情况下,大的事实表在典型的DBMS环境下应该按照时间分区。这样就允许DBA可以将时间最近的分区上删除索引,提高批量导入到该分区的速度。在数据加载完成后,需要恢复索引。如果分区可以被重命名和交换,那么在数据加载的时候,可以在数据加载的时候将事实表短暂的离线。这是一个复杂的问题,同索引策略和物理数据存储的不同有密切关系。

图6.5银行中帐户检查的周期快照

可能要在事实表上存在一个不依赖于分区逻辑的不可删除的索引。另外一些并行处理数据库技术物理上分发数据,这样最新的数据可能不存储在一个物理分区上。

当进入的交易数据是按照数据流的方式,而不是离散的基于文件的装载方式,我们将进入实时数据仓库的领域,关于实时数据仓库将在第11章介绍。

周期快照事实表

周期快照事实表表现的是一个时间段,或者规律性的重复。这类表非常适合跟踪长期的过程,例如银行账户和其他形式的财务报表。最常用的财务上的周期快照事实表通常有一个月粒度。在周期快照事实表中的数据必须符合该粒度(就是说,他们必须量测的是同一个时间段中的活动)。在图6.5中,我们显示了一个银行支票账户的按照月的周期快照。这个设计很显然的特点就是大量的事实。任何账户的数值度量都是基于时段且有意义的。由于这个原因,在其整个生命周期内,周期快照事实表的良好修改应该是增加该粒度上更多的事实。对于良好修改的介绍见后续章节。

在周期快照事实表中的日期维度是周期。所以,对于月度周期快照的日期维度是日历月的维度。我们在第五章对生成这样的聚合日期维度进行了讨论。

一个有趣的问题是在周期快照记录中如何处理非时间维度的代理键。由于周期快照只有在该周期过去以后才能够产生,最常见的非时间维的代理键的选择是在对应周期内的最后的值。例如,在图6.5中账户和机构维度的代理键应该是周期期末的值,尽管在该周期内账户和机构的描述可能改变。这些中间代理键在月度周期快照中并不出现。

周期快照事实表可以完全知道数据的分布,在图6.5中的账户活动事实表每月每账户有一个一条记录。只要账户还在活跃,应用就可以假定每个查询中涉及不同的维度。

发布给最终用户应用使用的事实表可以预测数据分布,但是原始的源表却不能。你需要将周期快照事实表的主要维度和原始的源表进行外连接来确保你为每种合理的键组合生成了记录,即使其中一些在当前的加载过程中还没有出现。

周期快照事实表和那些交易粒度表有着同样的加载特性。当数据加载到数据仓库过程中,在每个周期性加载过程中所有的记录将按照最近的时间分区进行集合分布。

然而,对于周期快照事实表的维护策略有两点细微的不同。传统的策略是当周期过去后,一次性的加载所有的记录。进一步的,周期快照维护了一个当前紧迫滚动周期。图6.5银行事实表可以有36个固定的时间周期,表示了过去3年的活动,另外还有一个特殊的第37个月,其记录在当前周期的每天晚上进行增量更新。如果最后一天的数据按照通常的方式正确的加载,那么第37个月的统计数据就是正确的。如果最后的周期快照不同于最后一天的加载,那么这个策略就不再吸引人了,因为behind-the-scenes后台总帐的调整在月末结账过程没有在常规的数据下载中得到反映。

当紧迫滚动周期整天不断地通过数据流的方式更新,而不是通过周期性文件加载,我们将进入实时数据仓库的领域,关于实时数据仓库将在第十一章介绍。

由于用于期末度量计算的业务规则非常复杂,创建一个持续更新周期快照可能很困难,甚至是不可能的。例如,对于保险公司,保费计算是在期末通过业务系统计算的,而且这些计算仅仅能够在期末计算。数据仓库不能很容易得在期中算出保费;另外,业务规则的复杂程度也超过了普通ETL转换逻辑处理的范畴。

聚合快照事实表

聚合快照事实表用于描述那些有明确开始和结束的过程,例如合同履行,保单受理以及

常见的工作流。聚合快照不适合长期连续的处理,如跟踪银行账户或者描述连续的生产制造过程,如造纸。

聚合快照事实表的粒度是一个实体从其创建到当前状态的完整的历史。图6.6显示了一个聚合快照事实表,它的粒度是发货发票上的行项目。

聚合快照事实表有几个特殊的属性。最明显的是图6.6中大量的日历日期外键。所有的聚合快照事实表都拥有一组日期,按照表的标准模式来创建。对于图6.6中发货发票行项目的标准模式为:订单日期(order date)、请求发货日期(requested ship date)、实际发货日期(actual ship date)、交付日期(delivery date)、上次付款日期(last payment date)、退货日期(return date)和结算日期(settlement date)。我们可以假设在发货发票创建的时候会创建一条记录。创建时,仅仅有订单日期和请求发货日期是已知的。这条记录插入了事实表,并且对两个已知的日期使用了外键。剩余的外键都是不可用的的,他们的代理键必须指向日历日维表中的对应Not Applicable的特殊的记录。随着时间过去,事件发生,原始的记录被修改,对应到其他日期的外键被修改指到了实际的日期。上次付款日期随着付款发生被更改了多次。

图6.6粒度为发票行级的累积快照事实表

对于正常的订单,退货和结算日期可能永远也不会修改。

在聚合快照事实表中的记录当事件发生的时候会被重写。注意在Oracle中,单条记录的大小依赖于其内容。所以Oracle中聚合快照事实表的大小总是在增长的。这将影响到磁盘块的使用。当由于有大量的改变造成大量块碎片的时候,卸载和重新加载记录是非常有必要的,这可以提升性能。另外的方式是根据两个维度,如日期和当前状态(Open/Closed)进行表分区。使用当前状态进行初始分区,当条目变为闭合状态Closed的时候,将其转移

到另外的分区。

一个聚合快照事实表是表现有确定开始和结束的处理过程的非常高效的,吸引人的方法。如果过程场景中有越多地过程可以由在事实表中时间定义,那么最终用户应用就越简单。如果最终用户经常需要了解复杂和不正常的状态,例如货物损坏或者发货地址错误,那么最合适的还是交易粒度事实表,它可以查看发货过程中的所有事件。

准备装载事实表

在本节中,我们将介绍如何高效的处理数据加载并克服常见的效率障碍。如果不能采用正确的方法,那么数据加载对于ETL开发者将是一个恶梦。接下来的3节中将概述你将面对的障碍。

管理索引

索引对于查询来说可以提升性能,但是在数据加载的时候起到的却是相反作用。如果不能很好的处理,那些使用了大量索引的表将导致你的处理变得非常缓慢。在开始加载数据以前,在前置任务中需要删除所有的索引,然后在后置任务中重建所有的索引。如果在加载过程中包含数据更新(Update)操作,需要将那些要执行更新的记录和其他仅仅需要简单插入的记录分开,单独处理。简单的说,请按照下列的步骤来避免索引导致的ETL处理瓶颈:

1. 从要插入的记录中分离那些需要执行更新操作的记录

2. 删除那些更新操作不需要的索引

3. 加载那些需要更新的记录

4. 删除剩余的索引

5. 通过批量加载方式执行数据插入

6. 重建所有的索引。

管理分区

为了管理或者提高查询性能,使用分区技术将数据库表(及他们的索引)在物理上分为一些小的表。分区技术的最终优势是,当一个查询要从10年的数据中获得一个月的数据的时候,无需扫描所有的数据,而是直接从包含该月数据的分区中获得数据。表分区可以极大的提高对大事实表的查询的性能。表分区对于最终用户是透明的。只有DBA和ETL团队才会关心分区。

最常用的事实表分区策略是按照日期字段来分区。原因是日期维度是预加载和静态的,我们可以明确的知道代理键是什么样的。通常我们都可以发现设计者为了分区的目的在事实表中加了时间戳字段,但是除非在用户查询的时候包含了时间戳的限定,否则优化器并不会使用分区。由于用户通常都会使用基于日期维度的限定,这就需要使用关联到日期维度的事实表主键进行分区,这样才可以让优化器利用分区。

对于按照时间间隔进行分区的表中经常使用的是年、季度、月。对于那些异常巨大的事实表可以使用周,甚至是日进行分区。通常数据仓库的设计者需要和DBA团队共同决定每个表的分区策略,需要通知ETL团队哪些分区表需要ETL维护。

如果DBA团队在分区管理上不占主导地位的话,那么ETL过程必须管理他们。如果你

的加载频率是月度,分区维护将很简单。当你的加载频率和分区不同,或者分区不是基于时间的,那么这个处理将会变得具有挑战性。

假设你的事实表按照年进行分区,并且前3年的数据由DBA团队创建。在2004年12月31日以后,当你试图加载数据的时候,在Orcale中,你将收到如下的错误:ORA-14400: inserted partition key is beyond highest legal partition key

这时候,ETL处理有两种选择:

通知DBA团队,等他们手工创建好下一个分区,然后继续

动态创建下一个分区,以备数据加载的需要

一旦可以识别进入数据的代理键,ETL过程可以通过将最新的日期键和最新数据分区表中最大的值进行比较,判断新进的数据是否适合数据库中已有分区。

select max(date_key) from 'STAGE_FACT_TABLE'

同下面的值进行比较

select high_value from all_tab_partitions

where table_name = 'FACT_TABLE'

and partition_position = (select max(partition_position)

from all_tab_partitions where table_name = 'FACT_TABLE')

如果进入的数据是已定义的分区的下一年的数据,那么ETL过程可以使用过程前脚本创建下一个分区。

ALTER TABLE fact_table

ADD PARTITION year_2005 V ALUES LESS THAN (1828)

--1828 is the surrogate key for January 1, 2005.

我们讨论的维护过程可以通过写存储过程,在每次加载前供ETL过程调用。存储过程中可以产生所需的ALTER TABLE语句,根据进入数据的年份插入需要的1月1日的代理键。

覆盖回滚日志

根据设计,任何关系型数据库管理系统都支持处理事务中错误处理。通过在日志中记录每一个事务,系统可以从未提交的事务错误中恢复。当错误发生的时候,数据库访问日志,撤销任何尚未提交的事务。提交(Commit)事务意味着你或者你的应用明确的通知数据库事务中每个处理都已经完成,事务应该永久的写入磁盘。

回滚日志又叫做redo重做日志,这对交易系统(OLTP)来说是异常重要的。但是在数据仓库环境中,所有的事务都是由ETL过程管理的,回滚日志仅仅是为了获得优化的加载性能增加的额外的特性。数据仓库不需要回滚日志的原因包括:

所有的数据通过受ETL系统管理的进程输入

数据批量的加载

如果加载过程失败,数据可以很容易的重新加载

每种数据库管理系统都有不同的日志功能,采用不同的方式处理回滚。

装载数据

完成一个新表的首次加载过有一些需要解决的问题。最大的挑战是一次加载极大量的数据。

单独处理数据插入。很多ETL工具(以及一些数据库)提供update else insert功能。

这个功能非常方便,且有着非常简单的数据流程逻辑,但是性能非常的低。ETL

过程对已经存在的数据的更新逻辑中包括区分那些已经存储在事实表中记录和新

数据。当处理大量的数据的任何时候,你想到的是数据批量加载到数据仓库。但是

不幸的是,很多批量导入工具不支持更改已经存在的记录。通过分离需要更新的记

录,你可以先处理更新,然后再执行批量的导入,这样获得最佳的加载性能。

利用批量加载工具。使用批量加载工具,而非使用SQL语句加载大量数据可以降低数据库负载,并极大的提高加载效率。

并行的加载。在加载大量数据的时候,将数据物理上分成不同的逻辑段。如果加载5年的数据,你可以做5个数据文件,每个文件中包含一年的数据。一些ETL工

具允许你根据数据范围进行数据分区。一旦数据被分成均等的部分,运行ETL过

程并行的加载所有的分段。

最小化物理更新。在表中更新记录操作需要耗用DBMS很多资源,最大的原因是数据库要生成回滚日志。要最小化对回滚日志的操作,可以采用批量的加载数据。

如何处理那些需要更新的数据呢?很多情况下,最佳的方式是删除要更新的记录,

然后批量的加载所有的数据。由于要做更新的数据和总的数据量的比率会极大的影

响优化方式的选择,因此需要一些反复测试来判断针对具体情况的最终加载策略。

在数据库外进行聚合。在数据库之外进行排序,合并和聚合要比在DBMS内使用SQL语句,使用COUNT和SUM函数,GROUP BY 和 ORGER BY 关键字高效

的多。ETL过程需要将巨大数量的数据进行排序、合并放在进入关系型数据库准

备区之前完成。很多ETL工具提供这些功能,但是专门的工具在操作系统级别执

行排序/合并意味着为处理大数据集进行额外的投资。

ETL过程应该最小化那些通过数据库批量加载所完成的更新和插入操作。如果需要大量的更新,请考虑通过批量加载工具截断和重新加载整个事实表。当更新量很小的时候,考虑分离那些需要更新的记录,对其单独的处理。

增量装载

增量加载用于周期性的加载,目的是同步数据仓库和相应的源系统。增量加载可以以任意的时间间隔甚至连续(实时的)进行。在写本文的时候,常用的加载的时间间隔为每天,但是这不是一个确定的或者最佳的加载时间间隔。用户通常倾向于按日加载,因为他们通常将日粒度的静态数据存储在数据仓库中,而避免加载瞬间变动的数据,因为数据不断的变化,将导致同一天内的报表结果不一致。

ETL常规的数据加载实际上是将历史数据初始加载到数据仓库中处理的特例。一个有用的建议是保持历史加载和增量加载处理过程的一致性。ETL团队必须将抽取处理的开始日期和结束日期可参数化,这样ETL程序既可以很灵活的加载小的增量部分,也可以一次加载全部的历史数据。

插入事实

当创建新的事实记录,你需要尽快地获得数据。如果利用数据库批量加载工具,那么事实表可能对于SQL INSERT语句来说太大了。使用SQL INSERT语句会导致产生一些数据库日志,这些日志的目的是错误恢复,但是对于数据仓库环境他们完全是多余的。如果加载程序失败了,ETL工具能够从错误中恢复,并从错误点开始处理剩下的部分,而不需要使用数据库的日志。

在主流的ETL工具中,错误恢复是一个普遍的功能,不同的厂商处理错误、错误恢复的方式不同。请确认ETL厂商如何解释他们的错误恢复机制,并选择需要手工干涉最少的产品。在ETL POC中,请测试工具的错误恢复能力。

更新和纠正事实

我们已经在很多地方讨论了对数据仓库数据的更新,尤其是事实数据。大家都认可一点,除了缓慢变化维度,其他维度必须准确地反映出对应数据源中的数据。但是,对于在数据仓库中的事实数据得更新,仍然有几个问题需要讨论。

最大的争论是数据仓库必须反映在业务系统的所作的所有变化,这种观点仅仅是理论上的,现实中很多数据仓库不是这样的。毕竟数据仓库目的是支持对业务的分析,而不是针对数据的来源系统。数据仓库要正确地反映业务活动,就必须要准确地描述实际的事实。无论如何讨论,数据条目错误都不是一个业务事件(除非你建立一个数据集市专门来分析数据条目的精确性)。

记录下与正确记录发生矛盾的不必要的记录可能会导致矛盾的结果,会干扰分析结果。考虑下列的例子:一个公司发售了1000个苏打水容器,在源系统中记录为12盎司听装。在数据发布到数据仓库时发现了一个错误,应该为20盎司瓶装。发现问题后,源系统立即进行了更新。业务上从来就没有卖过12盎司听装,但是在执行销售分析的时候,业务上不需要知道发生过错误,相反的,保留错误的数据可能导致将结果错误的解释为12盎司听装。在数据仓库中通过3种基本的办法来更正数据错误:

消除事实

更新事实

删除和重新加载事实

所有三种策略都产生了实际的效果――1000瓶20盎司每瓶苏打的销售。

消除事实

消除错误继承是创建一个错误记录的完整备份,但是度量是原始度量乘以-1。这样在反向事实表中负的度量就可以将原始的错误记录影响剔除。

使用消除事实而不是使用其他方法更正事实有很多原因。最主要的是审计的需要,如果需要对获取数据错误进行分析,那么消除错误将是一个好的建议。但是,当捕获实际的错误对于业务非常重要的时候,交易系统中应该有自己的数据条目审计能力。

使用消除事实,而不是更新或者删除的另外一个原因是考虑数据量和ETL效率。当事实表中有几千万记录的时候,必须考虑搜索和更新已有记录所带来的ETL性能的降低。ETL

团队有责任为优化性能提出自己的方案。你不可以基于技术标准来改变业务规则。如果业务上要求清除错误,而不是消除他们,那么你就有责任满足这个需求。本章讨论了一些可以保证你的过程是最优化的选择。

更新事实

在事实表中更新事实可以理解为是一种加强处理。在大多数的数据库管理系统中,为了做回滚保护,一个UPDATE操作会自动的在数据库日志中进行记录。而数据库记录日志将极大的降低加载的效率。最佳的更新事实表的方法是通过批量加载工具REFRESH表。如果你不得不使用SQL来UPDATE事实表,需要确保那些能够唯一标识行的列都进行了索引,另外的索引需要被删除。不必要的索引将极大地降低更新操作的性能。

删除事实

大部分人认为删除错误记录是更正事实表中错误数据的最佳方法。这样做的缺点是操作后报表将和以前的报表结果不一致。如果你采用这种方式,需要有办法更新原来的报表来保持一致性。大多数情况下,如果当前的版本能够正确地反映实际情况,那么更改数据也不是一件坏事情。

理论上,从事实表中删除事实在数据仓库中是被禁止的。但在实际中,在大多数数据仓库中删除事实是比较普遍的。如果你的业务上需要删除,那么可以采用两种方式: 物理删除。在大多数情况下,人们不希望看到源交易系统中不存在的数据。当需要物理删除的时候,你需要按照业务规则,删除不想要的数据。

逻辑删除。逻辑删除事实表记录是一种安全删除。一个逻辑删除需要利用一个称为Deleted的额外的字段,通常为Bit或者Boolean数据类型,作为事实表中的标记字

段表示该字段是否已删除。对于逻辑删除方法需要特别注意的一点,是需要在每个

查询中都使用限定来过滤到那些已经被逻辑删除的记录。

物理删除事实

物理删除意味着数据已经从数据仓库中永久的删除。在执行物理删除的之前,必须提醒用户一旦删除后,数据将不能再被访问。

用户通常有一个误解,一旦数据进入数据仓库,就永久的存在。所以当用户说他们从来没有(Never)理由查看(See)删除的数据,需要对这两个词进行澄清,确认客户知道自己说的正确的表达了自己的意思,且用了正确的词汇。

从来不(Never)。用户会很自然的按照当前的需求出发考虑问题,因为这是基于当前使用的数据。从来没有接触过数据仓库的用户不会习惯从历史的角度分析问题。

有一句格言,你不能想象你不曾有的东西。在绝大多数情况下,当用户说从来不的

时候,实际上是说很少(rarely)。请确认你的用户明确的知道物理删除是永久的删

除记录。

查看(See)。当用户说查看,更多的是指在报表中的数据展现。因为用户通常对未加工的数据没有概念。所有的数据的发布都是通过一些发布方法来完成,例如商务

智能工具或者报表,他们可以自动的过滤不需要的数据。最佳的方法是和负责数据

展现的团队确认这些需求,如果没有这样的团队,那么确保你的用户理解物理删除

是从数据仓库中永久的删除记录。

一旦永久物理删除得到确认,下一步的问题就是选择什么样的策略来查找和删除不需要的事实。最简单的解决删除的方式是截断和重新加载事实表。但是截断和重新加载仅仅对较小的数据仓库适用。如果你有一个包含许多事实表的数据仓库,每个表中包含上百万或者上亿条的记录,那么不建议使用多个增量加载来截断和删除整个数据仓库。

如果在源系统中没有包含审计表来捕捉删除的记录,那么你必须在集结区中存储每次抽取的数据,然后和下一次数据加载进行比较来获得任意遗失的数据。

如果源系统中包含审计表,那么你将是幸运的。如果删除或者修改数据非常重要,或者需要在将来进行跟踪,那么交易系统中常常有审计表。如果在源系统中没有审计表,另外发现删除事实的方法就是比较源数据和集结区中包含最近一次加载数据的数据表,这意味着每天(或者每个ETL间隔),你必须在集结区中保留抽取数据的拷贝。

在ETL处理中,在上一次的抽取和本次抽取加载到准备区后,在两张表中执行SQL MINUS。

Insert into deleted_rows nologging

select * from prior_extract

MINUS

select * from current_extract

MINUS查询的结果为那些在源系统中被删除但是已经加载到数据仓库中的记录。在处理完成后,你可以删除prior_extract表,将current_extract表命名为 prior_extract,最后创建新的current_extract表。

逻辑删除事实

当物理删除被禁止或者需要被分析,你可以逻辑删除记录,但是物理上保留。逻辑删除需要利用一个称为Deleted的额外的字段,通常为Bit或者Boolean数据类型,作为事实表中的标记字段表示该字段是否已删除。需要特别注意对于逻辑删除方法,需要在每个查询中都使用限定来过滤到那些已经被逻辑删除的记录。

无事实的事实表

每个事实表的粒度是一个事件量测。在某些情况下,事件可以发生,但是没有具体的测量值。例如一个事实表用来记录交通事故事件。每个事件的发生是无可质疑的,维度设计是强制性且非常直接的。如图6.7所示。但是当维度装载后却有可能不存在事实。就像本例一样,事件跟踪经常会产生无事实(factless)的情况。

实际上,图6.7中的设计有一些非常有趣的特性。复杂的事故可能包含多个当事人,原告方和证人。他们通过关联表将当事人组、原告组和证人组连接起来。这种设计可以表示从最简单的单方交通事故到复杂的多车连环相撞事故。在这个例子中,随着时间的发展,当事故发生的时候,事故当事人、原告和证人被加入组中。

图6.7表达车与事故事件的无事实事实表

这种应用的ETL逻辑是用于判断新增的记录表示的是一个新的事故还是已有的。一个事故的主自然键需要在首次事故报告的时候被分配。通过事故当事人、原告和证人的重复记录可以用于发现理赔欺诈。

关于无事实事实表的另外的例子是覆盖,经典的例子是某天在某个商店促销的产品列表。这个表有四个外键,但是没有事实,如图6.8所示。这个表用于与典型的销售表关联,用于回答促销中销售了哪些产品?什么没有发生,这种查询的问题在数据仓库工具集(第二版 251-253页)中有专门的介绍。由于每个商店的价格系统中有特价的记录,那么对于商店建立促销产品的ETL数据是很容易的。但是对于其他的促销活动,如特殊的展览或者媒体广告则需要相应的反馈数据,这些数据并非来自价格系统。利用展览在零售业的数据源处理上是个难题,因为通常在制造企业中这些数据源表示为进行展览的成本,最终,需要公正的第三方走访每个商店来获得准确的数据。

图6.8无事实的覆盖表

用类型2历史数据补充类型1事实表

一些环境主要使用类型1事实表,例如,一个完整的客户购买历史通常通过类型1的客户维度来显示客户最新的档案。在单纯的类型1的环境中,客户的历史描述不可用。在这种情况下,客户维表和完全的类型2的维表相比较小而且较简单,在类型1的维度中,自然键和维度的主键有1对1的关系。

但是在很多类型1的环境中,对于客户历史的访问需要进行分析。这种情况下可以采用3种方法:

1、在旁边同时维护完整类型的类型2的维度。这样做的好处就是可以维护主要的类型1维度的干净和简单。通过查询类型2的维表来查找某个时段内有效的旧的用户档案,使用同样的时间范围限定来访问事实表。这种方法对于那些显示即时性动作的事实表能够很好工

作,(immediate actions),即时性动作如零售销售,这样的事实表中客户就是量测事件。但是有些事实表记录了延时动作,如有争议消费发生后几个月后发生的退款。在这个案例中,客户档案所处的时间段没有和事实表的事件逻辑上交迭。另外的案例:当产品在到期日售出,并在一个月后产品资料已经改变后发生退货也会造成时间同步上的问题。这是另外一个延时动作事实表的例子。如果你的事实表表示的是延时的动作,那么不能采用这种方法。

2、按照完整的类型2维度创建主维度。缺点是,和类型1的维度相比更大更复杂。通过在所有查询中使用嵌入对维度的SELECT语句仅仅能获取当前客户维度记录的自然键,可以模拟类型1维度的效果;用这些自然键获取所有的用于最后和事实表关联的历史维度记录。

3、对于主要的维度使用完整的类型2的维表,同时在事实表代理键旁边存储对应的自然键。缺点是维表比类型1的维表大,且更复杂。但是如果最终用户应用关心的最新的客户记录,可以用自然键来和事实表进行关联,从而获得整个历史。这消除了方法2中的嵌入式查询。

优化更正

维度模型的一个最重要的优点是可以在不影响最终用户查询或者应用的前提下,对最终发布的框架进行一系列大的修改。我们把这称之为优化更正。这是维度模型世界和规范化模型世界最根本的区别,在后者这些修改将导致应用停止工作,因为物理框架被改变了。

对于维度框架有四种类型的优化更改:

1、在已经存在的事实表中增加同一粒度的事实

2、在已经存在的事实表中增加同一粒度的维度

3、在已经存在的维度中增加属性

4、增加已经存在的事实表和维表的粒度大小

图6.9图示了这四种类型的更改。前三种需要DBA对事实表和维表执行ALTER TABLE 操作。应该优先考虑对存在的表的ALTER TABLE操作,而不是删除、重新定义事实表或者维表,最后再加载数据。

图6.9四种类型的优化更改

前三类更改带来一个问题,如何处理那些在更改事实、维度或者属性之前的旧的历史表。显然,如果有对应的旧的历史数据可用是最简单的。但大多数情况下,在维度模型中增加事实、维度或者属性的原因是他们在当前变成可用的。当这些改变仅仅是当前以及以前时候,我们可以按照下列的方式处理:

1、增加了一个事实。新增的事实的历史值存储为null,当时间跨越事实增加点的时候,Null值可以被正确的计算。计数和求平均的结果是正确的。

2、增加了一个维度。对于之前事实表中的记录,新增加的维度的外键必须指向维表中的非应用记录。

3、增加了一个维度属性。在类型1的维度中,不需要做任何的修改,新的实体在所有的维表记录中可用。在类型2的维度中,在增加实体之前的所有的记录对应的实体为null。当记录的时间跨度包含增加实体时间点的时候,会有一点麻烦,但是也有办法将实体增加到这些记录。

第四种类型的优化优化更改–增加维度模型的粒度和前三种相比则要复杂的多。假设我们在记录如图6.1中展示的单独的零售销售记录。假设我们已经选择使用店铺维度来汇总地区销售,而不是使用现金收银机维度。在两种情况下,事实表的记录数是完全一样的,原因是事实表的粒度是单个的零售销售(在销售小票上的某行记录)。唯一的区别在于在基础粒度上使用现金收银机的视图还是店铺视图作为地区维度。但是由于现金收银机和它所属的店铺之间有确定的多对一关系,因此店铺实体是两种方式间共同的选择。如果这个维度称为地区,当在从店铺-地区改变成现金收银机-地区视角的时候,原来应用中的SQL不需要修改。

在不改变应用的前提下也有可能增加事实表的粒度。例如,周的数据可以改为日粒度数据。日期维度可以从周变为日,原来所用基于周的限定和分组规则仍然有效。

事实表中多个度量单位

流程检查

规划与设计:需求/现状 -> 架构 -> 实现 -> 测试/发布

数据流:抽取 -> 清洗 -> 规格化 -> 提交

某些情况下,价值链中包含了在系统生产过程中的多个业务过程监控,或者在不同的检查点使用不同的度量,这样会导致数量表达的不一致。每个人都同意这一点,虽然数字是正确的,但是价值链中不同的人会希望按照不同的单位来查看。例如,制造业主管希望按照车皮或者集装箱来考察整个生产流程,而店铺主管需要按照运输包装、零售包装、销售单位或者购买单位(弹片的口香糖)查看数据。类似的,相同数量的产品可能有几种不同的计价,可能需要按照供货价格、列表价格、初始报价、最终价格来显示。这种需求,最终会导致对于每一条事实记录可能有多个基础数量。

考虑一下下面的情况,我们有10个基础的事实数量,5种单位,4种作价方式。一种错误的做法是事实表中建立13个数量事实,然后由用户或者应用开发者在维表中查找正确的转换因子,尤其是当用户利用事实表,不使用关联得到的不同时间点,然后用时间去查询产品维表的时候。另外将所有的事实组合按照不同的单位存储在主事实表中也是一种错误的做法,那样每条记录需要10*5+10*4=90个事实列!正确的折衷办法是建立有10个数量事实、4个单位转换因子和5个作价因子的底层事实表。这里我们仅仅需要4个转换因子,而不是5个,因为基础事实已经按照其中的一种单位表示了,其他更大或者更小的单位仅仅需要通过乘法或者除法就可以得到。我们的物理设计现在为10+4+4=18个事实,如图6.10。

当需要增加一个新的产品记录来反映这些因子(尤其是成本和价格)的微小变化的时候,在事实表中如此的封装事实可以降低对产品维度的压力。从变化的角度来看,这些因子更像事实,而非维度实体。

图6.10包含10个事实、5种单位、4个定价模式的物理事实表设计我们现在可以将这个事实表通过一个或者多个视图发布给用户。最复杂的视图可能包含

相关文档