合并MongoDB populate返回的嵌套数组并去重的实现方案
MongoDB嵌套访客数组合并去重方案
需求说明
需要将MongoDB populate操作返回的嵌套访客数组合并为一维数组,同时按照id去除重复项,支持MongoDB原生聚合或纯JS两种实现方式。
原始嵌套数组结构
"visitors": [ [ { "name": "matan", "id": "61793e6a0e08cdcaf213c0b1" }, { "name": "shani", "id": "61793e910e08cdcaf213c0b5" } ], [ { "name": "david", "id": "6179869cb4944c6b19b05a23" }, { "name": "orit", "id": "617986e535fdf4942ef659bd" } ], [ { "name": "david", "id": "6179869cb4944c6b19b05a23" }, { "name": "orit", "id": "617986e535fdf4942ef659bd" } ] ]
预期输出结构
"visitors": [ { "name": "matan", "id": "61793e6a0e08cdcaf213c0b1" }, { "name": "shani", "id": "61793e910e08cdcaf213c0b5" }, { "name": "david", "id": "6179869cb4944c6b19b05a23" }, { "name": "orit", "id": "617986e535fdf4942ef659bd" } ]
业务背景
实际业务为查询单个太阳系下的所有访客,数据关联层级为 solars > planets > visitors,三个集合的Schema定义如下:
const solarsModel = new Schema({ planets: [ { type: Schema.Types.ObjectId ,ref:'planet'} ], starName: { type: String, required: true, default: "" } }) const planetModel = new Schema({ planetName: { type: String, required: true, default: "" }, system:{type: Schema.Types.ObjectId, ref: 'solar'}, visitors: [{ type: Schema.Types.ObjectId , ref: 'visitor'}] }) const visitorModel = new Schema({ visitorName:{ type: String, required: true, default: "" }, homePlanet: {type: Schema.Types.ObjectId, ref:"planet" }, visitedPlanets: [{ type: Schema.Types.ObjectId, ref:"planet" }] })
此前使用多层populate查询得到嵌套数组结果,代码如下:
const response = await solarModel .findById({ _id: data.id }) .select({ starName: 1, _id: 0 }) .populate({ path: "planets", select: { visitors: 1, _id: 0 }, populate: { path: "visitors", select: "visitorName", }, }) .exec();
实现方案
方案1:纯JS直接处理返回结果
如果不需要修改查询逻辑,可直接对populate返回的嵌套数组做降维去重处理:
// visitors为populate返回的嵌套数组 const uniqueVisitors = [...new Map( visitors.flat().map(item => [item.id, item]) ).values()];
逻辑说明:先用flat()将二维数组降为一维,再通过Map按访客id去重,最后转为数组即可得到结果。
方案2:MongoDB聚合查询实现
直接在数据库层面通过聚合管道完成关联、合并、去重操作,性能更优:
exports.findVisitorSystemHandler = async (data) => { const systemName = await solarModel.findById({ _id: data.id }); const response = await planetModel.aggregate([ // 匹配当前太阳系下的所有行星 { $match: { system: makeObjectId(data.id) } }, // 关联访客表拉取访客信息 { $lookup: { from: "visitors", localField: "visitors", foreignField: "_id", as: "solarVisitors", }, }, // 过滤不需要返回的访客字段 { $project: { solarVisitors: { visitedPlanets: 0, homePlanet: 0, __v: 0, }, }, }, // 拆分数组得到单个访客对象 { $unwind: "$solarVisitors" }, // 分组去重,合并所有访客 { $group: { _id: null, system: { $addToSet: systemName.starName }, solarVisitors: { $addToSet: { id: "$solarVisitors._id", name: "$solarVisitors.visitorName", }, }, }, }, // 拆解星系名称数组 { $unwind: "$system" }, // 过滤不需要返回的_id字段 { $project: { _id: 0, }, }, ]); return response; };
内容的提问来源于stack exchange,提问作者stephan bazbaz
相关产品推荐
相关产品推荐

