
在处理时间序列数据时,一个常见的需求是计算某个关键指标在不同时间点之间的变化量,即增量或差值。例如,在物联网(iot)应用中,我们可能需要监控设备每小时的能耗增量,或者在金融领域计算股票价格的日涨跌幅。这类分析通常需要比较相邻时间点的数据,并在此基础上进行计算。
本文将以一个具体的场景为例:给定一系列包含设备编码(code)、时间戳(timestamp)和能量值(energy)的文档,我们需要计算每个设备每小时的能量增量,即当前小时开始时的能量值减去前一个小时开始时的能量值。
假设我们有以下格式的MongoDB文档集合:
[
{
"_id": 1,
"timestamp": "2023-05-15T10:00:00Z",
"code": "abc",
"energy": 2333
},
{
"_id": 2,
"timestamp": "2023-05-15T10:10:00Z",
"code": "abc",
"energy": 2340
},
// ... 其他相同 code 的文档 ...
{
"_id": 6,
"timestamp": "2023-05-15T11:00:00Z",
"code": "abc",
"energy": 2370
},
{
"_id": 7,
"timestamp": "2023-05-15T10:00:00Z",
"code": "def",
"energy": 3455
},
// ... 其他不同 code 的文档 ...
{
"_id": 12,
"timestamp": "2023-05-15T11:00:00Z",
"code": "def",
"energy": 3500
}
]我们的目标是计算类似以下格式的输出:
[
{
"timestamp": "2023-05-15T11:00:00Z",
"code": "abc",
"energy": 37
}, // 2370 (11:00) - 2333 (10:00) = 37
{
"timestamp": "2023-05-15T11:00:00Z",
"code": "def",
"energy": 45
} // 3500 (11:00) - 3455 (10:00) = 45
]为了实现上述目标,我们将构建一个多阶段的MongoDB聚合管道。核心思想是:首先对数据进行排序,然后按设备编码和小时进行分组,获取每个小时的第一个能量值。接着,利用窗口函数($setWindowFields)在每个设备编码的分区内,获取当前小时和前一个小时的能量值,最后计算它们的差值。
在进行任何时间序列分析之前,确保数据按时间戳升序排列至关重要。这为后续的$group和$setWindowFields操作奠定了基础,保证了“第一个”和“前一个”的准确性。
{ $sort: { timestamp: 1 } }此阶段的目的是为每个code和每个小时找到其对应的第一个能量值。
{
$group: {
_id: {
code: "$code",
hour: { $dateTrunc: { date: "$timestamp", unit: "hour" } }
},
firstEnergy: { $first: "$energy" }
}
}经过此阶段,文档将变为 { _id: { code: "abc", hour: ISODate("...") }, firstEnergy: 2333 } 这样的形式。
这是实现“前一个”值计算的关键阶段。$setWindowFields允许我们在一个分区内定义一个窗口,并对窗口内的数据执行聚合操作。
{
$setWindowFields: {
partitionBy: "$_id.code",
sortBy: { "_id.hour": 1 },
output: {
prevEnergy: {
$push: "$firstEnergy",
window: { documents: [-1, 0] }
}
}
}
}在$setWindowFields阶段之后,第一个小时(每个code的第一个小时)的prevEnergy数组将只包含一个元素(当前小时的firstEnergy),因为它没有前一个小时的数据。为了只保留有有效差值的文档,我们使用$match来过滤掉这些不完整的数组。
{ $match: { "prevEnergy.1": { $exists: true } } }最后,我们使用$project阶段来计算实际的能量差值,并格式化输出文档,使其符合预期。
{
$project: {
_id: 0, // 排除 _id 字段
timestamp: "$_id.hour",
code: "$_id.code",
energy: {
$subtract: [
{ $last: "$prevEnergy" }, // 当前小时的能量值
{ $first: "$prevEnergy" } // 前一个小时的能量值
]
}
}
}将上述所有阶段组合起来,完整的MongoDB聚合管道如下:
db.collection.aggregate([
// 1. 确保数据按时间戳升序排列
{ $sort: { timestamp: 1 } },
// 2. 按 code 和小时分组,获取每小时的第一个能量值
{
$group: {
_id: {
code: "$code",
hour: { $dateTrunc: { date: "$timestamp", unit: "hour" } }
},
firstEnergy: { $first: "$energy" }
}
},
// 3. 使用窗口函数获取当前和前一个小时的能量值,按 code 分区
{
$setWindowFields: {
partitionBy: "$_id.code",
sortBy: { "_id.hour": 1 },
output: {
prevEnergy: {
$push: "$firstEnergy",
window: { documents: [-1, 0] }
}
}
}
},
// 4. 过滤掉没有前一个小时数据的文档
{ $match: { "prevEnergy.1": { $exists: true } } },
// 5. 计算能量差值并格式化输出
{
$project: {
_id: 0,
timestamp: "$_id.hour",
code: "$_id.code",
energy: {
$subtract: [
{ $last: "$prevEnergy" },
{ $first: "$prevEnergy" }
]
}
}
}
])通过本教程,我们深入探讨了如何利用MongoDB强大的聚合管道功能,特别是$dateTrunc和$setWindowFields,来高效地计算时间序列数据中特定字段的增量或差值。这种方法不仅适用于能量数据,还可以广泛应用于各种需要分析时间序列变化的场景,例如用户行为分析、系统性能监控等。掌握这些聚合技巧,将极大地提升您在MongoDB中处理复杂数据分析任务的能力。
以上就是MongoDB聚合管道:计算时间序列数据中字段的增量与差值的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号