缓变维度 (SCD) 是一个框架,用于管理和跟踪数据仓库中维度数据随时间的变化。此框架将维度称为“缓慢变化”,因为它假设数据 SCD 涵盖低频变化,但没有任何明显的时间模式。当数据仓库的要求涵盖基于数据历史状态追踪和重现输出的功能时,请使用 SCD。
SCD 的一个常见使用案例是报告。示例,在财务报告系统中,您需要解释上个月生成的报告中的聚合值与数据仓库中当前版本的报告中的聚合值之间的差异。
SQL中 SCD 的不同实现称为“类型”。类型 0 和 1 是最基本的类型,仅分别追踪数据的原始状态或数据的当前状态。类型 2 是最常用的实施,它创建三个新字段:validFrom
、validTo
以及最新数据设立的可选标志(通常称为 isValid
或 isEffective
)。
SCD 类型
SCD 类型 | 说明 |
---|---|
类型 0 | 只能保持原始状态,不能更改数据。 |
类型 1 | 只保留更新后的状态,不能存储历史记录。 |
类型 2 | 将历史记录保留在新文档中。 |
类型 3 | 在同一文档的新字段中保留历史记录。 |
类型 4 | 将历史记录保存在单独的集合中。 |
类型 6 | 类型 2 和类型 3 的组合。 |
MongoDB中的 SCD
您可以将 SCD框架应用MongoDB ,就像应用关系数据库一样。缓慢变化维度的概念适用于针对特定使用案例选择和优化的数据模型中的每个文档。
例子
考虑一个名为 prices
的集合,其中存储了一设立商品的价格。您需要追踪商品价格随时间的变化,以便能够进程商品的退货,因为退款必须与购买时商品的价格一致。集合中的每个文档都有一个 item
和 price
字段:
db.prices.insertMany( [ { 'item': 'shorts', 'price': 10 }, { 'item': 't-shirt', 'price': 2 }, { 'item': 'pants', 'price': 5 }, ] )
假设一条裤子的价格从 5
变更为 7
。要追踪此价格变化,请假定 SCD 类型 2 的必要数据字段的默认值。validFrom
的默认值为 01.01.1900
,validTo
的默认值为 01.01.9999
,isValid
的默认值为 true
。要使用 'item': 'pants'
更改对象中的 price
字段,请插入一个新文档来表示裤子的当前状态,并将先前有效的文档更新为不再有效:
let now = new Date(); db.prices.updateOne( { 'item': 'pants', "$or": [ { "isValid": false }, { "isValid": null } ] }, { "$set": { "validFrom": new Date("1900-01-01"), "validTo": now, "isValid": false } } ); db.prices.insertOne( { 'item': 'pants', 'price': 7, "validFrom": now, "validTo": new Date("9999-01-01"), "isValid": true } );
为避免破坏有效性链,请确保上述两个数据库操作在同一时间戳发生。根据应用程序的要求,您可以将上述两个命令包装到一个ACID 事务中,以确保MongoDB始终一起应用这两个更改。有关更多信息,请参阅 事务。
以下操作演示如何查询包含 pants
项目的文档的最新 price
:
db.prices.find( { 'item': 'pants', 'isValid': true } );
要查询包含 pants
项的文档在特定时间点的 price
,请使用以下操作:
let time = new Date("2022-11-16T13:00:00"); db.prices.find( { 'item': 'pants', 'validFrom': { '$lte': time }, 'validTo': { '$gt': time } } );
跟踪少数字段的变更
如果只需追踪文档中几个字段随时间的更改,则可以通过将字段的历史记录作为大量嵌入第一个文档中来使用 SCD 类型 3。
示例,以下聚合管道将文档中表示 pants
的 price
更新为 7
,并将 price
的前一个值以及前一个 price
失效的时间戳存储在一个大量priceHistory
:
db.prices.aggregate( [ { $match: { 'item': 'pants' } }, { $addFields: { price: 7, priceHistory: { $concatArrays: [ { $ifNull: [ '$priceHistory', [] ] }, [ { price: "$price", time: now } ] ] } } }, { $merge: { into: "prices", on: "_id", whenMatched: "merge", whenNotMatched: "fail" } } ] )
Outlook Data Federation
上述示例着眼于严格而准确地表示文档字段更改。有时,您对显示历史数据的要求可能不那么严格。示例,您可能有一个应用程序在大多数时候只需要访问权限数据的当前状态,但您必须对数据的完整历史记录运行一些分析查询。
在这种情况下,您可以将当前版本的数据存储在一个集合中,并将历史更改存储在另一个集合中。然后,您可以使用 MongoDB Atlas联合数据库 功能从活动MongoDB 集群中删除历史集合,而在完全托管版本中则使用 Online 存档。
其他使用案例
虽然缓慢变化的维度有助于数据仓库,但您也可以在事件驱动的应用程序中使用 SCD框架。如果不同类型的类别中有罕见事件,则按类别查找最新事件的成本很高,因为此进程可能需要对数据进行分组或排序才能查找当前状态。
对于偶发事件,您可以修改数据模型,添加一个字段来存储下一个事件的时间以及每个文档的事件时间。新的日期字段可确保您在对特定点执行搜索时,可以轻松高效地检索要搜索的相应事件。