You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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"}
        }
    }
])

关键说明

  1. 动态分组适配:如果要按小时/周/月分组,只需修改$group阶段_id里的时间字段:
    • 按小时:添加hour: {$hour: "$dateObj"}
    • 按周:用week: {$week: "$dateObj"}替代day字段
    • 按月:去掉day字段,保留year和month
  2. 无数据时段补全:如果需要补全无上报数据的时段(比如某天完全没数据),可以先生成目标时间范围内的所有时段序列,再用$lookup和聚合结果做左连接,具体逻辑可根据业务对数据完整性的要求调整。
  3. 版本兼容:$setWindowFields是MongoDB 5.0+支持的语法,若使用更低版本,可以用$group+$push将所有时段数据存入数组,再通过$reduce遍历数组计算差值,代码复杂度会稍高。

内容的提问来源于stack exchange,提问作者Harsh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 21:10:37