对于变化的维度属性,类型1的处理方式是数据架构师可用的最简单且最具破坏性的策略。当源系统中维度属性发生变化时,此方法会用新值覆盖维度表中对应的属性。不保留任何历史数据。记录只反映最新状态,这相当于声称旧值从未存在过。这种方法主要用于更正数据错误(例如拼写错误),或管理对业务分析而言历史准确性不重要的属性。虽然它易于实施且存储效率高,但在部署前,您必须了解它在分析报告中产生的特定副作用。更新机制当ETL管道检测到源记录发生变化时,系统使用自然键定位维度表中对应的行。然后更新已更改的特定列。代理键保持不变,保持与事实表的引用完整性。设想一个场景,维度表中定义了一个客户“Acme Corp”。该客户最初被分配到“东部”销售区域。如果销售组织重组并将Acme Corp移至“西部”区域,类型1更新会简单地将该客户行中的字符串“East”更改为“West”。下图说明了类型1更新期间维度行的状态转换。digraph G { rankdir=LR; node [shape=plaintext, fontname="Sans-Serif", fontsize=10]; edge [fontname="Sans-Serif", fontsize=9, color="#868e96"]; subgraph cluster_0 { label = "t=0时的状态"; fontcolor = "#868e96"; color = "#dee2e6"; style = rounded; tbl_before [label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="10"> <tr><td bgcolor="#dee2e6"><b>surrogate_key</b></td><td bgcolor="#dee2e6"><b>customer_id</b></td><td bgcolor="#dee2e6"><b>territory</b></td></tr> <tr><td>101</td><td>C500</td><td bgcolor="#a5d8ff">East</td></tr> </table>>]; } subgraph cluster_1 { label = "传入的变更"; fontcolor = "#868e96"; color = "#dee2e6"; style = rounded; update_op [label="更新: C500\n新区域: 西部", shape=box, style=filled, fillcolor="#ffe066", color="#f59f00"]; } subgraph cluster_2 { label = "t=1时的状态"; fontcolor = "#868e96"; color = "#dee2e6"; style = rounded; tbl_after [label=<<table border="0" cellborder="1" cellspacing="0" cellpadding="10"> <tr><td bgcolor="#dee2e6"><b>surrogate_key</b></td><td bgcolor="#dee2e6"><b>customer_id</b></td><td bgcolor="#dee2e6"><b>territory</b></td></tr> <tr><td>101</td><td>C500</td><td bgcolor="#ffc9c9">West</td></tr> </table>>]; } tbl_before -> update_op [color="#adb5bd"]; update_op -> tbl_after [label="覆盖", color="#fa5252", penwidth=2]; }类型1更新机制中,原始值永久丢失并被新值替代。实现逻辑从工程角度看,类型1通常通过SQL中的MERGE语句或UPDATE命令来实现。其逻辑是将传入的暂存数据与现有目标维度进行比较。如果我们将维度属性集表示为 $A = {a_1, a_2, ..., a_n}$,将传入的新状态表示为 $A'$,则操作确保对于任何行 $r$:$$r[a_i] \leftarrow A'[a_i]$$这种等式赋值无论行何时创建都会发生。在Snowflake或BigQuery等现代云数据仓库中,标准的MERGE模式可以高效地处理这种情况:MERGE INTO dim_customer AS target USING stg_customer_updates AS source ON target.customer_id = source.customer_id WHEN MATCHED AND target.territory != source.territory THEN UPDATE SET target.territory = source.territory;请注意,我们只在值不同时才进行更新。这可以避免不必要的写入操作,因为在依赖微分区的列式存储中,这些操作会产生开销。对历史报告的影响SCD 类型1的主要权衡是历史数据的改写。由于属性是原地修改的,任何链接到该维度行的事实表记录都会立即与新值关联。即使对于几年前发生的交易也是如此。以上述示例为例:2022年,Acme Corp(东部)购买了价值1,000美元的商品。2022年运行的报告显示“东部”区域收入为1,000美元。2023年,Acme Corp迁至“西部”。类型1更新执行。2023年运行的关于2022年业绩的报告现在显示“西部”区域收入为1,000美元。这种现象违反了报告不可变原则。基于维度属性的聚合会随时间发生变化。如果 $R$ 是特定区域 $T$ 的总收入,计算方式如下:$$R_T = \sum_{i=1}^{n} (p_i \times q_i) \text{ 其中 } Territory(d_i) = T$$当 $Territory(d_i)$ 通过类型1更改时,项 $(p_i \times q_i)$ 将从旧区域的总和转移到新区域。历史期间的总收入数字每次生成报告时都会变化。适用场景尽管会破坏历史数据,SCD 类型1仍然是特定属性的正确设计选择。您应该在以下情况下采用此模式:数据修正: 变化是为了更正错误(例如将拼写错误的姓名从“Jonh”更正为“John”)。我们希望历史数据反映“John”,因为“Jonh”从未真实存在过。不相关的历史数据: 业务不需要跟踪特定列的历史数据。例如,如果客户更改了电子邮件地址,市场部门可能只关注当前有效的电子邮件地址以确保联系得上。他们不需要根据旧的电子邮件域名分析销售业绩。性能优化: 维度表非常庞大(数百万行)且变化迅速,但这些变化在分析上并不重要。为每次微小更新维护历史数据(类型2)会使表膨胀并降低查询性能。通过明确选择类型1,您优先考虑当前数据的准确性和存储效率,而不是历史数据的保留。在下一节中,我们将介绍类型2,类型2通过对行进行版本管理而非覆盖来解决历史一致性问题。