如何在MongoDB中查询子数组并保留成员role字段?
MongoDB聚合查询:保留members数组中的role字段
问题背景
现有groups和users两个集合,数据如下:
groups集合数据
db.groups.insertMany([ { title: 'Hi', members: [ { userId: ObjectId('62589515f239750e7e44b958'), role: 'Admin' }, { userId: ObjectId('655f634c632d0c23b9d455de'), role: 'Member' } ] } ])
users集合数据
db.users.insertMany([ { _id: ObjectId('62589515f239750e7e44b958'), name: 'A', }, { _id: ObjectId('655f634c632d0c23b9d455de'), name: 'B', } ])
需要通过聚合操作得到如下格式的结果:
{ title: 'Hi', members: [ { user: { _id: ObjectId('62589515f239750e7e44b958'), name: 'A', }, role: 'Admin' }, { user: { _id: ObjectId('655f634c632d0c23b9d455de'), name: 'B', }, role: 'Member' }, ] }
解决方案
提供两种可行的聚合管道实现方案,按需选择:
方案一:拆分-关联-重组数组
db.groups.aggregate([ // 拆分members数组,保留每个元素的role和userId { $unwind: "$members" }, // 关联users集合,匹配用户信息 { $lookup: { from: "users", localField: "members.userId", foreignField: "_id", as: "memberUser" } }, // 将关联到的用户数据转为单个对象,同时保留role字段 { $addFields: { "members.user": { $arrayElemAt: ["$memberUser", 0] }, "members.role": "$members.role" } }, // 清理临时字段 { $unset: "memberUser" }, // 按title分组,重组members数组 { $group: { _id: "$title", title: { $first: "$title" }, members: { $push: "$members" } } }, // 移除分组生成的_id字段(可选) { $unset: "_id" } ])
步骤说明:
$unwind:把members数组拆分为单个文档,每个文档包含原分组信息和一个带userId、role的成员项。$lookup:根据members.userId关联users集合,匹配结果存入memberUser数组。$addFields:将memberUser数组的唯一元素赋值给members.user,同时显式保留原role字段。$unset:删除临时生成的memberUser字段。$group:按title分组,把拆分后的成员项重新组合成数组。$unset:可选操作,移除分组产生的_id,让结果结构更贴合需求。
方案二:直接关联数组内元素(MongoDB 3.6+支持)
db.groups.aggregate([ { $lookup: { from: "users", let: { members: "$members" }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$members.userId"] } } }, { $project: { name: 1, userId: "$_id" } } ], as: "usersList" } }, { $addFields: { members: { $map: { input: "$members", as: "m", in: { role: "$$m.role", user: { $arrayElemAt: [ { $filter: { input: "$usersList", as: "u", cond: { $eq: ["$$u.userId", "$$m.userId"] } } }, 0 ] } } } } } }, { $unset: "usersList" } ])
步骤说明:
$lookup:通过let传递当前文档的members数组,子管道匹配所有_id在members.userId中的用户,并投影出所需字段。$addFields+$map:遍历原members数组,对每个成员项,用$filter从usersList中匹配对应用户,将其作为user字段,同时保留原role。$unset:删除临时的usersList字段。
两种方案均可得到预期结果,方案二无需拆分数组,在大数据量场景下性能更优。
内容的提问来源于stack exchange,提问作者Hoai Thuong
相关产品推荐
相关产品推荐

