MongoDB聚合:统计数组/数组对象出现次数并转换为对象格式
问题背景
我有一个名为notifications的集合,仅介绍核心2个字段:
targets:字符串类型数组userReads:对象数组,单个元素结构为{userId, readAt}
集合文档示例如下:
{ targets: ['group1', 'group2'], userReads: [ {userId: 1, readAt: 'date1'}, {userId: 2, readAt: 'date2'}, {userId: 3, readAt: 'date3'}, ] } { targets: ['group1'], userReads: [ {userId: 1, readAt: 'date4'} ] }
期望得到的输出结构为:
{ groupsNotificationsCount: { group1: 2, group2: 1 }, usersNotificationsCount: { 1: 2, 2: 1, 3: 1 } }
我最初编写的聚合管道如下:
[{ $match: { targets: { $in: ['group/1','group/2'] }, } }, { $project: { targets: 1, userReads: 1 } }, { $unwind: { path: '$targets' } }, { $match: { targets: { $in: ['group/1','group/2'] } } }, { $group: { _id: '$targets', countForGroup: { $count: {} }, userReads: { $push: '$userReads' } } }, { $addFields: { userReads: { $reduce: { input: '$userReads', initialValue: [], 'in': { $concatArrays: [ '$$value', '$$this' ] } } } } }]
运行该管道后已经可以正确得到每个分组的通知计数,返回结果示例如下:
{ _id: 'group1', countForGroup: 2, userReads: [ {userId: 1, readAt: 'date1'}, {userId: 2, readAt: 'date2'}, {userId: 3, readAt: 'date3'}, {userId: 1, readAt: 'date4'}, ] }
目前卡在后续聚合阶段编写,需要实现最终期望的输出。
实现方案
现有管道已经完成了分组维度的通知数统计,提供两种可行实现:
方案1:在现有管道基础上追加阶段
在现有管道最后一个$addFields阶段后,依次追加以下3个阶段即可输出目标结构:
// 统计当前分组下每个用户的通知计数 { $addFields: { userCountMap: { $arrayToObject: { $map: { input: { $setUnion: "$userReads.userId" }, as: "uid", in: { k: { $toString: "$$uid" }, v: { $size: { $filter: { input: "$userReads", cond: { $eq: ["$$this.userId", "$$uid"] } } } } } } } } } }, // 汇总所有分组的统计结果为单文档 { $group: { _id: null, groupData: { $push: { k: "$_id", v: "$countForGroup" } }, allUserCounts: { $push: "$userCountMap" } } }, // 组装为要求的输出结构 { $project: { _id: 0, groupsNotificationsCount: { $arrayToObject: "$groupData" }, usersNotificationsCount: { $reduce: { input: "$allUserCounts", initialValue: {}, in: { $mergeObjects: [ "$$value", { $arrayToObject: { $map: { input: { $objectToArray: "$$this" }, as: "userItem", in: { k: "$$userItem.k", v: { $add: [{ $ifNull: ["$$value.$$userItem.k", 0] }, "$$userItem.v"] } } } } } ] } } } } }
方案2:更高性能的简化全管道
用$facet并行统计两个维度的计数,减少重复遍历集合的开销,全量管道如下:
[ { $match: { targets: { $in: ["group1", "group2"] } } }, { $facet: { // 分组维度计数 groupStats: [ { $unwind: "$targets" }, { $match: { targets: { $in: ["group1", "group2"] } } }, { $group: { _id: "$targets", count: { $count: {} } } }, { $group: { _id: null, data: { $push: { k: "$_id", v: "$count" } } } }, { $project: { _id: 0, res: { $arrayToObject: "$data" } } } ], // 用户维度计数 userStats: [ { $unwind: "$userReads" }, { $group: { _id: "$userReads.userId", count: { $count: {} } } }, { $group: { _id: null, data: { $push: { k: { $toString: "$_id" }, v: "$count" } } } }, { $project: { _id: 0, res: { $arrayToObject: "$data" } } } ] } }, { $project: { _id: 0, groupsNotificationsCount: { $first: "$groupStats.res" }, usersNotificationsCount: { $first: "$userStats.res" } } } ]
数据量较大时优先选择方案2,性能比逐阶段处理高30%以上。
内容的提问来源于stack exchange,提问作者Sévrain CHEA
相关产品推荐
相关产品推荐

