数据管理和数据仓库 中的维度包含有诸如地理位置、客户或产品等实体的相对静态数据。Slowly Changing Dimensions (SCDs) 捕获的数据变化缓慢而不可预测,且不按固定的周期。[1]
某些方案可能导致参照完整性 问题。
假设,数据库包含销售记录表,该表通过外键链接到其他维度,其中一个维度包含公司销售人员的数据,销售人员工作地点列存储其所在的办事处,销售人员有时会从一个区域办事处转移到另一个办事处。出于生成历史销售报告目的,有必要记录销售人员早先所在的区域办事处,若该销售人员现在被分配到其他的区域办事处。
可使用SCD Type0~6管理方法处理这些问题,Type 6 SCD有时也被称为混合SCD。
内容
- Type 0: 保留原始值
- Type 1: 覆盖原始值
- Type 2: 新增一行
- Type 3: 新增属性
- Type 4: 新增历史表
- Type 6
- Type 2 / Type 6 fact implementation
7.1 Type 2 代理键及Type 3属性
7.2 Pure Type 6 实现
7.3 同时存在代理键和自然键- 组合types
- 另见
- 注意事项
- 参考资料
Type 0: 保留原始值
Type 0维度属性被指定"持久"或“原始”的属性值后将永远不会改变,例如出生日期、原始信用评分,Type 0适用于大多数日期维度属性[2]
Type 1: 覆盖原始值
用新数据覆盖旧数据,因此不跟踪历史数据。
供应商表的示例:
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State |
---|---|---|---|
123 | ABC | Acme Supply Co | CA |
上面的示例中,Supplier_Code是自然键,Supplier_Key是代理键。从技术上讲,代理键不是必需的,该行可使用自然键(Supplier_Code)做唯一标示,但是,请尽量使用整数而不是字符以优化join性能,除非字符键中的字节数小于整数键中的字节数。
如果供应商将总部迁至伊利诺伊州,则记录将被覆盖:
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State |
---|---|---|---|
123 | ABC | Acme Supply Co | IL |
Type 1的缺点是数据仓库中没有历史记录,优点是易于维护。
如果按州汇总计算得到汇总表,需要在更改Supplier_State时重新汇总计算。[1]
Type 2: 新增一行
此方法通过使用不同的代理键 和/或不同的版本号为维度表中给定自然键创建多条记录以跟踪历史数据,每条插入都保留所有的历史记录。
例如,如果供应商迁移到伊利诺伊州,记录版本号将按顺序递增:
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State | Version. |
---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | 0 |
124 | ABC | Acme Supply Co | IL | 1 |
另外一个方法是增加effective date
列.
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State | Start_Date | End_Date |
---|---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | 01-Jan-2000 | 21-Dec-2004 |
124 | ABC | Acme Supply Co | IL | 22-Dec-2004 | NULL |
第二行中的null
End_Date表示当前行记录“生效”,在某些情况下,可使用诸如9999-12-31作为结束日期,以便该字段被索引,因此查询时不需要使用空值代替。
第三种方法使用effective date
和current
标志。
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State | Effective_Date | Current_Flag |
---|---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | 01-Jan-2000 | N |
124 | ABC | Acme Supply Co | IL | 22-Dec-2004 | Y |
Current_Flag = 'Y' 标示当前行记录“生效”
引用特定代理键(Supplier_Key)的事务将永久绑定到SCD表代理键行所定义的时间片上。按州维度的汇总表能持续反映历史状态,即供应商在交易时所处的状态,不需要更新操作。通过自然键引用实体,必须删除唯一约束,使DBMS的参照完整性 变得不可能。
如果对维度的内容进行了追溯更改,或维度添加了新属性(例如Sales_Rep列),该属性有效日期与已定义的有效日期字段不同,则可能导致现有事务需要更新以反映新情况。 这是一个昂贵的数据库操作,因此如果维度模型可能会发生变化,则Type 2 SCD不是一个好的选择。[1]
Type 3: 新增属性
此方法使用单独的列来跟踪更改历史并保留有限的历史记录,Type 3保留的历史数据受限于用于存储历史数据的列数。Type 1和Type 2中的原始表结构相同,但Type 3添加了新列。在以下示例中,向表中添加了一个附加列以记录供应商的原始状态 - 仅存储前一次的修改历史记录。
Supplier_Key | Supplier_Code | Supplier_Name | Original_Supplier_State | Effective_Date | Current_Supplier_State |
---|---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | 22-Dec-2004 | IL |
此记录包含最原始状态列(Original_Supplier_State)和当前状态列 - 如果供应商所在州再次改变,则无法跟踪中间的修改值。
此类型的一个变种是创建Previous_Supplier_State列而不是Original_Supplier_State列,用于跟踪最近的修改历史修改记录.[1]
Type 4: 新增历史表
Type 4使用“历史表”,其中一个表保存当前数据,另一个表用于记录部分或所有更新操作,销售表(Fact table)引用两个代理键(当前数据表+历史表)以提升查询性能。
对于上面的示例,原始表名称为Supplier,历史表为Supplier_History。
Supplier
Supplier_key | Supplier_Code | Supplier_Name | Supplier_State |
---|---|---|---|
124 | ABC | Acme&Johnson Supply Co | IL |
Supplier_History
Supplier_key | Supplier_Code | Supplier_Name | Supplier_State | CREATE_DATE |
---|---|---|---|---|
123 | ABC | Acme&Johnson Supply Co | CA | 14-June-2003 |
124 | ABC | Acme&Johnson Supply Co | IL | 22-Dec-2004 |
此方法类似于数据库审计表和 change data capture 技术。
Type 6
Type 6方法结合了Type1,2和3(1 + 2 + 3 = 6),对该术语起源的一个可能的解释是,它是由Ralph Kimball 在与Kalido的Stephen Pace谈话时提出的[citation needed], Ralph Kimball在数据仓库工具包[1]中将此方法称为“Unpredictable Changes with Single-Version Overlay”。[1]
下表Supplier及一条供应商示例记录:
Supplier_Key | Row_Key | Supplier_Code | Supplier_Name | Current_State | Historical_State | Start_Date | End_Date | Current_Flag |
---|---|---|---|---|---|---|---|---|
123 | 1 | ABC | Acme Supply Co | CA | CA | 01-Jan-2000 | 31-Dec-9999 | Y |
Current_State和Historical_State是相同的, 可选的Current_Flag属性标示当前行为供应商的当前或最新记录。
当Acme Supply Company搬到伊利诺伊州时,我们会添加一条新记录,就像在Type 2中一样处理,但是包含一个行键以确保每行都有一个唯一键:
Supplier_Key | Row_Key | Supplier_Code | Supplier_Name | Current_State | Historical_State | Start_Date | End_Date | Current_Flag |
---|---|---|---|---|---|---|---|---|
123 | 1 | ABC | Acme Supply Co | CA | CA | 01-Jan-2000 | 21-Dec-2004 | N |
123 | 2 | ABC | Acme Supply Co | IL | CA | 22-Dec-2004 | 31-Dec-9999 | Y |
我们用新信息覆盖第一条记录(Row_Key = 1)中的Current_Flag信息,如在Type 1中那样处理。我们创建一行新记录来跟踪更改,如在Type 2中一样处理。我们将历史存储在第二个State列(Historical_State)中,如Type 3中一样处理。
例如,如果供应商要再次重新定位,我们会向Supplier维度添加另一条记录,我们将覆盖Current_State列的内容:
Supplier_Key | Row_Key | Supplier_Code | Supplier_Name | Current_State | Historical_State | Start_Date | End_Date | Current_Flag |
---|---|---|---|---|---|---|---|---|
123 | 1 | ABC | Acme Supply Co | CA | CA | 01-Jan-2000 | 21-Dec-2004 | N |
123 | 2 | ABC | Acme Supply Co | IL | CA | 22-Dec-2004 | 03-Feb-2008 | N |
123 | 3 | ABC | Acme Supply Co | NY | IL | 04-Feb-2008 | 31-Dec-9999 | Y |
Type 2 / type 6 fact implementation
Type 2 代理键及Type 3属性
在许多Type 2和Type 6 SCD的实现中,当事实数据(如销售数据)加载到数据仓库[1],维度中的代理键(即维度主键)被放入事实表(如销售表)中以代替自然键 (如supply_code)。根据事实记录的生效日期以及维度表中的Start_Date和End_Date选择维度代理键,这可让事实数据很容易地join到相应的生效日期内正确的dimension数据。
以下是使用Type 6 Hybrid方法创建的Supplier表:
Supplier_Key | Supplier_Code | Supplier_Name | Current_State | Historical_State | Start_Date | End_Date | Current_Flag |
---|---|---|---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | CA | 01-Jan-2000 | 21-Dec-2004 | N |
124 | ABC | Acme Supply Co | IL | CA | 22-Dec-2004 | 03-Feb-2008 | N |
125 | ABC | Acme Supply Co | NY | IL | 04-Feb-2008 | 31-Dec-9999 | Y |
Delivery表包含正确的Supplier_Key后,可以使用该键轻松join到Supplier表。以下SQL为每个事实记录检索当前供应商状态以及供应商在交货时所处的状态:
SELECT
delivery.delivery_cost,
supplier.supplier_name,
supplier.historical_state,
supplier.current_state
FROM delivery
INNER JOIN supplier
ON delivery.supplier_key = supplier.supplier_key
Pure Type 6 实现
如果维度可能会发生变化,那么Type 2通过每个时间片代理键来关联可能出现问题。[1]
Type 6实现不使用时间片内维度的代理健,而使用每个主数据项的代理键(例如,每个唯一供应商具有唯一代理键)。这避免了主数据中的任何变化对现有交易数据产生影响。
在查询事务时,它还允许更多选项。
以下是使用纯Type 6方法的Supplier表:
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State | Start_Date | End_Date |
---|---|---|---|---|---|
456 | ABC | Acme Supply Co | CA | 01-Jan-2000 | 21-Dec-2004 |
456 | ABC | Acme Supply Co | IL | 22-Dec-2004 | 03-Feb-2008 |
456 | ABC | Acme Supply Co | NY | 04-Feb-2008 | 31-Dec-9999 |
以下示例显示了如何扩展查询以确保为每个事务检索到单个供应商记录。
SELECT
supplier.supplier_code,
supplier.supplier_state
FROM supplier
INNER JOIN delivery
ON supplier.supplier_key = delivery.supplier_key
AND delivery.delivery_date BETWEEN supplier.start_date AND supplier.end_date
生效日期(Delivery_Date)为2001年8月9日的事实记录,将join到Supplier_Code为ABC的维度,其Supplier_State为“CA”,生效日期为2007年10月11日的事实记录,也将链接到Supplier_Code为ABC的维度,但Supplier_State为“IL”。
虽然更复杂,但这种方法有许多优点,包括:
- 现在可以通过DBMS实现参照完整性,但是不能通过Supplier_Code作为表Product外键,将每个产品都绑定在特定时间片上。
- 如果事实上有多个日期(例如,Order Date, Delivery Date, Invoice Payment Date),可以选择使用哪个日期用于关联查询。
- 您可以通过更改日期过滤器逻辑来做“如同现在”,“如同在交易时间”或“如同在某个时间点”的查询。
- 如果维度表中有更改,无需对Fact表做更新(例如,添加额外的回溯列而更改了时间片,或者如果在维度表上的日期中出现错误,可以轻松更正它们)。
- 您可以在维度表中引入bi-temporal日期。
- 可以将事实join维度表的多个版本,以便在同一查询中报告不同生效日期的相同信息。
以下示例显示如何使用特定日期,例如“2012-01-01 00:00:00”(可能是当前日期时间)。
SELECT
supplier.supplier_code,
supplier.supplier_state
FROM supplier
INNER JOIN delivery
ON supplier.supplier_key = delivery.supplier_key
AND '2012-01-01 00:00:00' BETWEEN supplier.start_date AND supplier.end_date
同时存在代理键和自然键
另一种实现是将代理键和自然键放入事实表中。[3] 这允许用户根据以下内容选择适当的维度记录:
- 事实记录的Primary生效日期(上图),
- 最近或最新的信息,
- 与事实记录相关的任何其他日期。
即使使用Type 2而不是Type 6,此方法也可以更灵活地join到维度。
这是我们使用Type 2方法可能创建的Supplier表:
Supplier_Key | Supplier_Code | Supplier_Name | Supplier_State | Start_Date | End_Date | Current_Flag |
---|---|---|---|---|---|---|
123 | ABC | Acme Supply Co | CA | 01-Jan-2000 | 21-Dec-2004 | N |
124 | ABC | Acme Supply Co | IL | 22-Dec-2004 | 03-Feb-2008 | N |
125 | ABC | Acme Supply Co | NY | 04-Feb-2008 | 31-Dec-9999 | Y |
以下SQL检索每个事实记录最新的Supplier_Name和Supplier_State:
SELECT
delivery.delivery_cost,
supplier.supplier_name,
supplier.supplier_state
FROM delivery
INNER JOIN supplier
ON delivery.supplier_code = supplier.supplier_code
WHERE supplier.current_flag = 'Y'
如果事实记录中有多个日期,可以使用其他日期而不是Primary生效日期将事实join到维度,例如,Delivery表可能有Delivery_Date作为Primary生效日期,但也可能有与记录关联的Order_Date。
以下SQL使用Order_Date检索每条记录正确的Supplier_Name和Supplier_State:
SELECT
delivery.delivery_cost,
supplier.supplier_name,
supplier.supplier_state
FROM delivery
INNER JOIN supplier
ON delivery.supplier_code = supplier.supplier_code
AND delivery.order_date BETWEEN supplier.start_date AND supplier.end_date
一些警告:
- DBMS的参照完整性是不实现的,因为没有唯一的key来关联。
- 如果使用代理键建立维度关联,则实体绑定到特定时间片上。
- 如果join查询不正确,则可能会返回重复的行和/或得到错误的答案。
- 日期比较可能性能不佳。
- 某些商业智能工具无法很好地处理复杂的join
- 需要仔细设计维度表的ETL处理进程,以确保每条不同的引用数据项没有时间段没有重叠。
组合types
不同的SCD Type可以应用于表的不同列,例如,我们可以将Type 1应用于Supplier_Name列,将Type 2应用于同一个表的Supplier_State列。
另见
- Change data capture
- Temporal database
- Log trigger
- Entity–attribute–value model - Vertical
- Multitenancy
注意事项
- Kimball, Ralph; Ross, Margy. The Data Warehouse Toolkit: The Complete Guide to Dimensional Modeling.
- Jump up^ http://www.kimballgroup.com/2013/02/design-tip-152-slowly-changing-dimension-types-0-4-5-6-7/
- Jump up^ Ross, Margy; Kimball, Ralph (March 1, 2005). "Slowly Changing Dimensions Are Not Always as Easy as 1, 2, 3". Intelligent Enterprise.