MongoDB聚合:如何将日期数组转换为带日期计数的键值对对象
修正后的MongoDB聚合管道实现方案
- 原投影阶段存在字段引用错误:原代码取值的
$session.date是$group阶段生成的未做会话去重的原始数组,和reduceSessions阶段生成的去重后会话数组(字段名为sessions)逻辑脱节,是sessions日期数组出现重复值的根源。 - 新增日期计数转换逻辑:基于去重后的会话数组,将纯日期数组转换为「日期为键、出现次数为值」的对象结构,所有计算仅在单条分组结果上完成,无需额外全量unwind数据集,性能损耗极低。
具体调整步骤
- 替换原有
projection阶段的字段计算逻辑,保留原有统计字段的同时,用聚合内置操作实现日期计数:
const projection = { $project: { _id: 0, episode: "$_id", plays: { $size: "$sessions" }, dropoff: { $avg: "$sessions.averageOfSession" }, // 替换原sessions的取值逻辑,实现日期计数对象 sessions: { $reduce: { input: "$sessions.date", // 引用去重后的会话数组的日期值 initialValue: {}, in: { $mergeObjects: [ "$$value", { $arrayToObject: [[ { k: "$$this", v: { $add: [ // 动态读取该日期已有的计数,不存在则从0开始累加 { $ifNull: [ { $getField: { field: "$$this", input: "$$value" } }, 0 ] }, 1 ] } } ]] } ] } } }, events: "$count" } }
- 修正原有排序阶段的字段引用:因为
projection阶段已经将原分组用的_id字段置为0,原排序逻辑引用_id会无法拿到有效值,改为按episode字段排序:
// 原排序逻辑 // { $sort : { _id : 1 } } // 修正后 { $sort: { episode: 1 } }
调整完成后,聚合返回的结构就会完全匹配目标要求:
{ "episode": 5, "plays": 4, "dropoff": 3737.25, "sessions": { "2020-11-15": 3, "2020-11-16": 1 }, "events": 4 }
可选优化
原reduceSessions阶段的会话去重逻辑存在边界问题:当初始累加值为空数组时,"$$value._id"取值为null,会导致$in判断异常,可以替换为更稳定的去重写法,避免边缘case下的结果错误:
const reduceSessions = { $addFields: { sessions: { $filter: { input: "$session", as: "item", cond: { $eq: [ { $indexOfArray: ["$session._id", "$$item._id"] }, // 仅保留每个session id第一次出现的位置 { $indexOfArray: ["$session._id", "$$item._id"] } ] } } } } }
内容的提问来源于stack exchange,提问作者Mauro Zurlo
相关产品推荐
相关产品推荐

