MongoDB按字段分组聚合:实现Facebook式通知分组查询
实现MongoDB通知集合的Facebook式分组聚合(按
_parent字段分组) 嘿,这个需求我之前做过类似的!要实现Facebook那种「还有50人点赞了你的帖子」的通知分组效果,核心就是用MongoDB的聚合框架,按_parent字段(搭配通知类型区分)做分组聚合,同时统计数量、整理展示文本。下面一步步给你拆解实现思路和代码:
先明确集合结构(假设)
首先假设你的notifications集合文档大概是这样的:
{ "_id": ObjectId("664d2f1a1b3c4d5e6f7a8b9c"), "_parent": ObjectId("664d2f1a1b3c4d5e6f7a8b9d"), // 关联的帖子/评论ID "type": "like", // 通知类型:like/comment/follow等 "actor": ObjectId("664d2f1a1b3c4d5e6f7a8b9e"), // 触发通知的用户ID "recipient": ObjectId("664d2f1a1b3c4d5e6f7a8b9f"), // 接收通知的用户ID "createdAt": ISODate("2024-05-20T10:00:00Z") }
核心聚合管道实现
下面是完整的聚合查询,我会逐个阶段解释:
db.notifications.aggregate([ // 1. 过滤:只处理当前用户的通知,缩小数据集 { $match: { recipient: ObjectId("当前用户的ID") // 可选:加上时间范围过滤,比如只查最近30天的通知 // createdAt: { $gte: ISODate("2024-04-20T00:00:00Z") } } }, // 2. 分组:按_parent和type聚合,这是实现分组的核心 { $group: { _id: { parentId: "$_parent", type: "$type" // 同一帖子的点赞/评论要分开分组,不能混在一起 }, totalCount: { $sum: 1 }, // 该分组下的通知总数 // 收集触发通知的用户ID,后续用来生成用户名列表 recentActors: { $push: "$actor" }, latestTime: { $max: "$createdAt" } // 取分组内最新的时间,用于排序 } }, // 可选:关联用户集合,把actorID转成用户名(如果需要在聚合层处理) { $lookup: { from: "users", localField: "recentActors", foreignField: "_id", as: "actorDetails" } }, // 3. 格式化:生成友好的通知文本,适配不同数量的情况 { $project: { _id: 0, parentId: "$_id.parentId", type: "$_id.type", totalCount: 1, latestTime: 1, notificationText: { $cond: { // 如果总数超过3,显示「A、B还有N人XX了你的内容」 if: { $gt: ["$totalCount", 3] }, then: { $concat: [ "$actorDetails.0.username", "、", "$actorDetails.1.username", "还有", { $toString: { $subtract: ["$totalCount", 2] } }, "人", // 根据通知类型生成对应文本 { $switch: { branches: [ { case: { $eq: ["$_id.type", "like"] }, then: "点赞了你的帖子" }, { case: { $eq: ["$_id.type", "comment"] }, then: "评论了你的帖子" }, { case: { $eq: ["$_id.type", "follow"] }, then: "关注了你" } ], default: "互动了你的内容" } } ] }, // 总数<=3的话,直接列出所有用户 else: { $concat: [ { $join: ["$actorDetails.username", "、"] }, { $switch: { branches: [ { case: { $eq: ["$_id.type", "like"] }, then: "点赞了你的帖子" }, { case: { $eq: ["$_id.type", "comment"] }, then: "评论了你的帖子" }, { case: { $eq: ["$_id.type", "follow"] }, then: "关注了你" } ], default: "互动了你的内容" } } ] } } } } }, // 4. 排序:按最新的通知时间倒序,让用户先看到最新的分组 { $sort: { latestTime: -1 } } ])
关键阶段解释
- $match:先过滤出当前用户的通知,这一步非常重要,能大幅减少后续聚合处理的数据量,提升效率。如果通知量很大,建议加上时间范围过滤(比如只查最近30天)。
- $group:这是分组的核心,通过
_id里的parentId(即你的_parent字段)和type来区分不同的分组,确保同一帖子的点赞和评论不会被合并到一起。同时统计总数、收集触发者ID、记录最新时间。 - $lookup(可选):如果需要在聚合层直接把用户ID转换成用户名,可以用这个阶段关联用户集合。如果你的应用层有用户缓存,也可以在拿到结果后再转换,这样更灵活。
- $project:把聚合后的结果格式化成前端需要的样子,用
$cond判断数量,生成不同的提示文本,用$switch适配不同的通知类型。 - $sort:按最新的时间排序,确保用户看到的是最新的分组通知。
进阶优化建议
- 如果通知量极大,可以考虑预聚合:定时跑这个聚合查询,把结果存在一个专门的
user_notifications_summary集合里,前端直接查询这个集合,提升响应速度。 - 在
$group阶段用$slice限制收集的actor数量,比如recentActors: { $slice: [{ $push: "$actor" }, 2] },只保留前2个用户,减少数据传输量。 - 给
recipient、_parent、createdAt字段建立索引,提升$match阶段的查询速度。
内容的提问来源于stack exchange,提问作者Fadi Quader
相关产品推荐
相关产品推荐

