如何在MongoDB中计算当日与前日activeEnergy最大值的差值
文档示例结构
{ "metadata": { "dat": "jkjcsvbdskjcbdskjcbdac", "meterId": "kahcvajc" }, "activeEnergy": 1111, "actualtime": 1689827191000 }
问题描述
以上是MongoDB集合ts_events的文档示例,目前仅关注activeEnergy字段。现有聚合代码可根据前端参数动态按小时、日、周、月分组统计,但由于数据并非每秒上报,用时段内max-min计算会遗漏无数据时段的统计。理想的计算逻辑是当前时段max值减去前一时段max值(比如当日max减前日max),但不知道怎么调整分组逻辑实现这个需求,求解决方案。
现有聚合代码
db.ts_events.aggregate([ { $project: { "y": {"$year": {$toDate: "$actualtime"}}, "m": {"$month": {$toDate: "$actualtime"}}, "d": {"$dayOfMonth": {$toDate: "$actualtime"}}, "h": {"$hour": {$toDate: "$actualtime"}}, "activeEnergy": 1, "metadata.meterId": 1, "activePower": 1, "actualtime": 1, "powerFactor": 1, "metadata.dat": 1 } }, { $match: { "metadata.dat": "62f0f3459731692a5eab5ad6/south0tpbit/tamilnadu5dvs8w/chennaidzc2yd/kknagarj4ffzo", "actualtime": { $gte: 1656613800000, $lte: 1659292199999 }, // "metadata.device":"ObjectId(62f0f9b5f757672222282d9)" 如何用ObjectId匹配? "metadata.meterId": "911615402222257_2" } }, { $group: { _id: { date: { year: "$y", month: "$m", day: "$d" // hour: "$h", }, meter: "$metadata.meterId" }, maxValue: {$max: "$activeEnergy"}, minValue: {$min: "$activeEnergy"}, averageActivePowerOfDay: {$avg: "$activePower"}, averagePowerFactorOfDay: {$avg: "$powerFactor"} } }, { $addFields: { differnce: {$subtract: ["$maxValue", "$minValue"]} } }, { $group: { _id: null, res: {$push: "$$ROOT"}, differnceSum: {$sum: "$differnce"}, averageActivePowerOverThePeriod: {$avg: "$averageActivePowerOfDay"}, averagePowerFactorOverThePeriod: {$avg: "$averagePowerFactorOfDay"} } } ])
解决方案
要实现当前时段max减前一时段max的逻辑,核心是先按时段分组得到每个时段的max值,再通过排序+窗口函数获取前一时段的max值,最后计算差值。具体步骤和调整后的代码如下:
调整后聚合代码(以按日分组为例)
db.ts_events.aggregate([ // 1. 先过滤目标数据 { $match: { "metadata.dat": "62f0f3459731692a5eab5ad6/south0tpbit/tamilnadu5dvs8w/chennaidzc2yd/kknagarj4ffzo", "actualtime": {$gte: 1656613800000, $lte: 1659292199999}, "metadata.meterId": "911615402222257_2" } }, // 2. 转换时间格式并提取必要字段 { $project: { "dateObj": {$toDate: "$actualtime"}, "activeEnergy": 1, "metadata.meterId": 1, "activePower": 1, "powerFactor": 1 } }, // 3. 按时段分组,获取每个时段的max和平均值 { $group: { _id: { meter: "$metadata.meterId", year: {$year: "$dateObj"}, month: {$month: "$dateObj"}, day: {$dayOfMonth: "$dateObj"} // 如需按小时分组,添加 hour: {$hour: "$dateObj"} }, maxValue: {$max: "$activeEnergy"}, averageActivePower: {$avg: "$activePower"}, averagePowerFactor: {$avg: "$powerFactor"}, periodStart: {$min: "$dateObj"} // 保留时段起始时间用于排序 } }, // 4. 按时间顺序排序,确保后续窗口函数能正确取到前一时段数据 { $sort: {"periodStart": 1} }, // 5. 用窗口函数获取前一时段的maxValue { $setWindowFields: { partitionBy: "$_id.meter", // 按电表独立计算 sortBy: {"periodStart": 1}, output: { prevMaxValue: { $lag: "$maxValue", outputMissing: 0 // 第一个时段无前置值,设为0,可根据业务调整为null } } } }, // 6. 计算当前时段与前一时段的差值 { $addFields: { periodDifference: {$subtract: ["$maxValue", "$prevMaxValue"]} } }, // 7. 汇总全周期统计结果 { $group: { _id: null, periodDetails: {$push: { period: "$_id", maxEnergy: "$maxValue", prevMaxEnergy: "$prevMaxValue", energyConsumed: "$periodDifference", avgActivePower: "$averageActivePower", avgPowerFactor: "$averagePowerFactor" }}, totalEnergyConsumed: {$sum: "$periodDifference"}, avgActivePowerOverPeriod: {$avg: "$averageActivePower"}, avgPowerFactorOverPeriod: {$avg: "$averagePowerFactor"} } } ])
关键说明
- 动态分组适配:如果要按小时/周/月分组,只需修改
$group阶段_id里的时间字段:- 按小时:添加
hour: {$hour: "$dateObj"} - 按周:用
week: {$week: "$dateObj"}替代day字段 - 按月:去掉day字段,保留year和month
- 按小时:添加
- 无数据时段补全:如果需要补全无上报数据的时段(比如某天完全没数据),可以先生成目标时间范围内的所有时段序列,再用
$lookup和聚合结果做左连接,具体逻辑可根据业务对数据完整性的要求调整。 - 版本兼容:
$setWindowFields是MongoDB 5.0+支持的语法,若使用更低版本,可以用$group+$push将所有时段数据存入数组,再通过$reduce遍历数组计算差值,代码复杂度会稍高。
内容的提问来源于stack exchange,提问作者Harsh
相关产品推荐
相关产品推荐

