MongoDB聚合中生成15分钟时间块计算均值的问题求助
MongoDB聚合管道实现15分钟时间块均值计算问题
需求:构建聚合管道,计算values集合中特定变量的15分钟均值,要求生成从00:00开始、按15分钟递增的时间块(支持当前日期或指定日期)。
集合结构及示例数据
Variables集合
{ "_id": ObjectId("65a64adb2cc01aa7c8474028"), "name": "Variable Name", "deviceId": ObjectId("65a64adb2cc01aa7c8474458") // 设备引用ID }
Values集合
{ "_id": ObjectId(), "value": 23.45, "time": ISODate("2024-01-17T11:09:35.681Z"), "metaField": {"varId": ObjectId("65a64adb2cc01aa7c8474028")} // 变量引用ID }
目标输出格式
{ "Date": "2024-01-17", "Segment": "00:00-00:15", "Block": 1, "Average": 23.45, "VarId": ObjectId("65a64adb2cc01aa7c8474028") }
当前聚合查询
[ { "$match": { "metaField.varId": ObjectId('65a64adb2cc01aa7c8474028'), "time": { "$gte": new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate()), "$lt": new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 1) } } }, { "$addFields": { "bucket": { "$floor": { "$divide": [{ "$subtract": [{ "$minute": "$time" }, 1] }, 15] } } } }, { "$group": { "_id": { "bucket": "$bucket" }, "count": { "$sum": 1 }, "sumValue": { "$sum": "$value" } } }, { "$project": { "_id": 0, "segment": { "$concat": [ { "$substr": [{ "$multiply": ["$_id.bucket", 15] }, 0, -1] }, ":00-", { "$substr": [{ "$add": [{ "$multiply": ["$_id.bucket", 15] }, 15] }, 0, -1] } ] }, "block": { "$add": ["$_id.bucket", 1] }, "average": { "$divide": ["$sumValue", "$count"] }, "varId": "$metaField.varId" } } ]
遇到的问题
时间块未按预期递增,结果与目标输出不符——核心原因是原逻辑仅基于分钟计算bucket,未考虑小时因素,导致跨小时的15分钟块会重复bucket编号,且时间格式化未补前导零,输出格式不规范。
优化后的聚合管道
[ { "$match": { "metaField.varId": ObjectId('65a64adb2cc01aa7c8474028'), "time": { "$gte": ISODate("2024-01-17T00:00:00Z"), // 指定日期,或保留原动态生成逻辑 "$lt": ISODate("2024-01-18T00:00:00Z") } } }, { "$addFields": { // 提取日期字符串,用于输出 "dateStr": { "$dateToString": { "format": "%Y-%m-%d", "date": "$time" } }, // 计算当天的总分钟数(小时*60 + 分钟),再除以15得到唯一的bucket编号 "totalMinutes": { "$add": [{ "$multiply": [{ "$hour": "$time" }, 60] }, { "$minute": "$time" }] }, "bucket": { "$floor": { "$divide": ["$totalMinutes", 15] } } } }, { "$group": { "_id": { "bucket": "$bucket", "varId": "$metaField.varId", "date": "$dateStr" }, "count": { "$sum": 1 }, "sumValue": { "$sum": "$value" } } }, { "$project": { "_id": 0, "Date": "$_id.date", "VarId": "$_id.varId", "Block": { "$add": ["$_id.bucket", 1] }, // 计算时间块起始的小时和分钟,补前导零 "startHour": { "$toString": { "$floor": { "$divide": [{ "$multiply": ["$_id.bucket", 15] }, 60] } } }, "startMinute": { "$toString": { "$mod": [{ "$multiply": ["$_id.bucket", 15] }, 60] } }, "endHour": { "$toString": { "$floor": { "$divide": [{ "$add": [{ "$multiply": ["$_id.bucket", 15] }, 15] }, 60] } } }, "endMinute": { "$toString": { "$mod": [{ "$add": [{ "$multiply": ["$_id.bucket", 15] }, 15] }, 60] } }, "Average": { "$divide": ["$sumValue", "$count"] } } }, { "$addFields": { // 格式化Segment字段,补前导零确保两位格式 "Segment": { "$concat": [ { "$cond": [{ "$lt": [{ "$strLenCP": "$startHour" }, 2] }, "0", "" ] }, "$startHour", ":", { "$cond": [{ "$lt": [{ "$strLenCP": "$startMinute" }, 2] }, "0", "" ] }, "$startMinute", "-", { "$cond": [{ "$lt": [{ "$strLenCP": "$endHour" }, 2] }, "0", "" ] }, "$endHour", ":", { "$cond": [{ "$lt": [{ "$strLenCP": "$endMinute" }, 2] }, "0", "" ] }, "$endMinute" ] } } }, { // 移除中间计算字段,保留目标输出字段 "$project": { "Date": 1, "Segment": 1, "Block": 1, "Average": 1, "VarId": 1 } }, { // 按Block升序排列,确保时间块顺序正确 "$sort": { "Block": 1 } } ]
关键改进点
- Bucket计算逻辑修正:基于当天总分钟数计算bucket,确保每个15分钟块(从00:00开始)对应唯一的bucket编号,避免跨小时重复。
- 时间格式化补零:通过
$cond和$strLenCP判断小时/分钟的字符串长度,补前导零,保证输出格式为HH:MM-HH:MM。 - 保留必要字段:分组时包含日期和变量ID,确保输出符合目标格式;最后按Block升序排列,保证结果顺序正确。
- 灵活日期范围:
$match阶段可替换为动态生成当前日期的逻辑(保留原代码的new Date()写法),也可指定固定日期。
内容的提问来源于stack exchange,提问作者anonymous_33008899
相关产品推荐
相关产品推荐

