如何最好地处理缓慢变化维度 (SCD2) 中的历史数据变化
Posted
技术标签:
【中文标题】如何最好地处理缓慢变化维度 (SCD2) 中的历史数据变化【英文标题】:How to best handle historical data changes in a Slowly Changing Dimension (SCD2) 【发布时间】:2016-03-10 05:06:51 【问题描述】:表格:
我工作的公司有一个渐变维度(员工数据),该维度已使用 Kimball 方法进行存储。包含此数据的维度表有一个主键(int identity employee_key
,在其他表中用作代理)、一个自然键(employee_id
)、有效日期范围(valid_date
和invalid_date
)和各种 SCD1和 SCD2 数据元素随时间跟踪。这是一个简化的示例:
employee_key | employee_id | valid_date | invalid_date | employee_name | employee_role
1 | 1001 | 1/1/2015 | 6/1/2015 | Bob | DBA
2 | 1001 | 6/2/2015 | NULL | Bob | Developer
3 | 1002 | 1/1/2015 | NULL | Jill | DBA
在上面的例子中,employee_key
是主键(代理),employee_id
是自然键。其他值应该是不言自明的。该表反映了:
-
Bob 是一名 DBA,从 2015 年 1 月 1 日开始,到 2015 年 6 月 1 日结束。
Bob 从 2015 年 6 月 2 日开始担任开发人员,目前担任该职位。
Jill 从 2015 年 1 月 1 日开始担任 DBA,目前担任该职位。
现在,我们还有大量引用该维度的事实表。一个这样的事实表包含员工记录的所有时间,并且精确到一天。我们并不真正关心这些表的结构,只是它们使用代理键链接到我们的员工维度,并且它们通常包含很多行(在 10M-200M 之间)。下面是一个包含时间记录的事实表示例:
calendar_dt | employee_id | employee_key | time_code | hours
1/1/2015 | 1001 | 1 | 1234 | 2.25
1/1/2015 | 1001 | 1 | 21 | 3.50
1/2/2015 | 1001 | 1 | 21 | 8.00
...
6/1/2015 | 1001 | 1 | 21 | 4.00
通过代理键 employee_key
链接到员工维度有一个重要的业务目的 - 它可以实现准确的历史报告,而无需使用 BETWEEN
运算符进行昂贵的连接。例如,我们可以说 Bob 在 2015 年 6 月 1 日记录的时间归因于他的 DBA 角色,而 Bob 在 2015 年 6 月 2 日记录的时间归因于他的开发者角色。
据我所知,这是一个有点标准的 Kimball 实现。
问题:
这个实现不能很好地处理数据的更正。假设在我们之前的示例中,HR 告诉我们 Bob 在 2015 年 5 月 1 日到 2015 年 6 月 1 日的有效日期范围内调任分析师角色,但他们未能将其输入系统。这给我们带来了一个主要问题:我们需要将employee_key = 1
所在的行拆分为具有不同有效/无效日期的两行。此外,我们需要找到所有现在错误地引用employee_key = 1
的地方并更新它们。以下是问题:
-
我们需要对大量巨大的表运行昂贵的更新操作。我们无法在每次需要进行更正时都这样做。
维度行拆分需要手动完成,使表格面临数据输入错误或有效/无效日期范围重叠的风险。
拆分一行违反了一条重要规则:主键是不可变的,一旦分配就永远不会改变。
解决方案:
我可以想出很多方法来解决这个问题,但没有一个是优雅的:
-
只需忍受更新代理键数据的噩梦。也许会强制按正常计划进行更正,从而减少我们需要运行此更新的次数。
将员工维度表转换为每员工每天一行的表。这样做的好处是允许在
employee_id
和calendar_dt
上进行自然键连接。它还使此键不可变,并允许识别适当的代理键值,而无需在维度表中查找它。不管维度表如何变化,事实表总是会引用正确的行。这具有将我们的 100,000 行维度表转换为 20M 行的主要缺点。
还有哪些其他解决方案?我不可能是唯一遇到这个问题的人...帮帮我!
注意事项:
我们假设数据永远不需要时间元素(粒度始终为天级别)。 我们假设employee_id
的值永远不会改变(是的,我知道这是一个危险的假设)。
【问题讨论】:
【参考方案1】:您的 DW 有一个要求:“可以随时追溯更改员工信息”,因此您的 DW 设计必须适应这一点。
可能有很多方法可以解决这个问题,但我想到的最简单的一种(并且在类似情况下对我有用的一种)是引入一个新的类型 2 集成表:master_employee_time。此表将维护原始时间记录数据的版本化历史记录,仅包含业务密钥。
employee_time_key | employee_id | valid_date | invalid_date | time date | time code | hours
1 | 1001 | 1/1/2015 | NULL | 1/1/2015 | 1234 | 2.25
2 | 1001 | 1/1/2015 | NULL | 1/1/2015 | 21 | 3.5
3 | 1001 | 1/2/2015 | NULL | 1/2/2015 | 21 | 8
注意:此表可能需要特殊的更新条件,具体取决于您可以访问多少时间表数据,可能会将 type2 更改限制为过去一年中的 time_dates,然后根据需要手动处理任何较旧的更新。
一旦你有了这个表,你就可以通过将 master_employee 和 master_employee_time 与类似的东西结合起来,在每次加载时重新创建当前的事实表
insert into fact_employee_time
select
t.calendar_dt, e.employee_id, e.employee_key, t.time_code, t.hours
from
master_employee_time t
inner join master_employee e on t.employee_id = e.employee_id
where
--limit to 'current' time recordings
t.invalid_date is null
and
--get the employee record active for the time recordings day
e.valid_date <= t.time_date
and
(e.invalid_date is null OR e.invalid_date >t.time_date)
[评论] 这也使您能够更新时间记录,同时为那些要求“我必须能够更改历史数据”的人的不可避免的“为什么我的一月份数字发生变化”的要求保留可审计的历史记录。 . [/评论]
【讨论】:
我真的对你的回答感到困惑。您说我们需要引入类型 2 集成表吗?这是一个维度表还是事实表?您的声明说该表将维护原始时间记录和业务密钥的版本化历史,但下表有“时间日期”、“时间代码”,这真的让我很困惑!您能否简化您的答案,以便我理解。原谅我的无知。以上是关于如何最好地处理缓慢变化维度 (SCD2) 中的历史数据变化的主要内容,如果未能解决你的问题,请参考以下文章