MongoDB中如何分组嵌套文档数组且不影响父文档
在MongoDB中对嵌入式文档数组分组并保留父文档其他内容
数据库结构
db={ "users": [ { "firstName": "David", "lastName": "Mueller", "messages": [ { "text": "hello", "type": "PERSONAL" }, { "text": "test", "type": "DIRECT" } ] }, { "firstName": "Mia", "lastName": "Davidson", "messages": [ { "text": "hello world", "type": "DIRECT" }, { "text": ":-)", "type": "PERSONAL" }, { "text": "hi there", "type": "DIRECT" } ] } ] }
期望结果
[ { "firstName": "David", "lastName": "Mueller", "messages": [ { "_id": "PERSONAL", "count": 1 }, { "_id": "DIRECT", "count": 1 } ] }, { "firstName": "Mia", "lastName": "Davidson", "messages": [ { "_id": "PERSONAL", "count": 1 }, { "_id": "DIRECT", "count": 2 } ] } ]
解决方案
针对嵌入式文档数组的分组需求,提供两种通用实现方式,适配不同场景:
方法一:基于$reduce的数组内分组(无需拆分数组,性能更优)
该方法直接在原文档内完成数组分组统计,避免$unwind拆分数组的开销,适合数据量较大的场景。
db.users.aggregate([ // 第一步:用$reduce统计每个type的数量,生成键值对对象 { $addFields: { messages: { $reduce: { input: "$messages", initialValue: {}, in: { $mergeObjects: [ "$$value", { "$$this.type": { $add: [ { $ifNull: ["$$value.$$this.type", 0] }, 1 ] } } ] } } } } }, // 第二步:将键值对对象转换为期望的数组格式 { $addFields: { messages: { $map: { input: { $objectToArray: "$messages" }, as: "item", in: { _id: "$$item.k", count: "$$item.v" } } } } } ])
扩展适配(支持min、sum等聚合函数)
如果需要统计其他指标(如字段最小值、总和),只需调整$reduce内的逻辑。例如统计每个type对应的text长度最小值:
{ $addFields: { messages: { $reduce: { input: "$messages", initialValue: {}, in: { $mergeObjects: [ "$$value", { "$$this.type": { minLength: { $min: [ { $ifNull: ["$$value.$$this.type.minLength", 9999] }, { $strLenCP: "$$this.text" } ] }, count: { $add: [{ $ifNull: ["$$value.$$this.type.count", 0] }, 1] } } ] } } } } } }
方法二:基于$unwind+$group的拆分重组(逻辑更直观)
该方法先拆分数组为单个文档,分组统计后再合并回原文档结构,逻辑易懂,适合复杂分组需求。
db.users.aggregate([ // 拆分数组,每个message成为独立文档 { $unwind: "$messages" }, // 按用户唯一标识+消息类型分组,统计数量 { $group: { _id: { userId: "$_id", firstName: "$firstName", lastName: "$lastName", type: "$messages.type" }, count: { $sum: 1 } } }, // 按用户合并统计结果,生成messages数组 { $group: { _id: { userId: "$_id.userId", firstName: "$_id.firstName", lastName: "$_id.lastName" }, messages: { $push: { _id: "$_id.type", count: "$count" } } } }, // 重塑文档结构,还原原字段 { $project: { _id: "$_id.userId", firstName: "$_id.firstName", lastName: "$_id.lastName", messages: 1 } } ])
内容的提问来源于stack exchange,提问作者LDK
相关产品推荐
相关产品推荐

