如何在Mongoose中修正聚合后的对象数组投影格式?
问题:移除聚合查询结果中history的嵌套对象,保留数组格式
我有一个记录卡车与司机的数据库,需要追踪司机驾驶卡车的时间以及驾驶过的所有卡车记录,为此定义了Mongoose Schema:
const driverHistorySchema = mongoose.Schema({ driver: { type: Schema.Types.ObjectId, ref: "Employee", required: true, }, history: [{ transport: { type: Schema.Types.ObjectId, ref: "Transport", required: true, }, time: { type: Date, required: true } }] })
针对Transport表编写了聚合查询,想要关联司机的驾驶记录,但返回结果里的history字段嵌套了一层对象,不是预期的数组格式:
原查询代码:
const transports = await Transport.aggregate([ { $lookup: { from: "driverhistories", let: { "driverId": "$driver" }, pipeline: [ { $match: { $expr: { $eq: ["$driver", "$$driverId"] } } }, { $replaceRoot: { newRoot: { history: "$history" } } }, ], as: "history" } }, { "$unwind": { "path": "$history", "preserveNullAndEmptyArrays": true } }, ]);
当前返回结果:
{ "_id": "xxxx", .... "history": { "history": [ { "transport": "xxxx", "time": "2023-11-02T09:19:14.654Z", "_id": "xxxx" }, { "transport": "xxxx", "time": "2023-11-03T11:23:50.956Z", "_id": "xxxx" } ] } }
期望结果:
{ "_id": "xxxx", .... "history": [ { "transport": "xxxx", "time": "2023-11-02T09:19:14.654Z", "_id": "xxxx" }, { "transport": "xxxx", "time": "2023-11-03T11:23:50.956Z", "_id": "xxxx" } ] }
尝试过几种投影方式都没能解决嵌套问题,比如:
{ $project: { _id: 0, history: "$history" } }{ $project: { _id: 0, transport: "$history.transport", time: "$history.time" } }
解决方案
问题出在原查询的$replaceRoot步骤,它把driverHistory文档包装成了{ history: [...] }的对象,导致$lookup后得到的是包含该对象的数组,再经过$unwind就形成了嵌套结构。下面提供两种可行的修改方案:
方案一:针对单条driverHistory记录的场景(每个司机对应一条驾驶历史)
const transports = await Transport.aggregate([ { $lookup: { from: "driverhistories", let: { driverId: "$driver" }, pipeline: [ { $match: { $expr: { $eq: ["$driver", "$$driverId"] } } }, { $project: { _id: 0, history: 1 } } // 仅保留history数组字段 ], as: "tempHistory" // 用临时字段存储lookup结果 } }, // 提取临时字段中第一个元素的history数组,赋值给目标history字段 { $addFields: { history: { $arrayElemAt: ["$tempHistory.history", 0] } } }, // 移除临时字段 { $project: { tempHistory: 0 } }, // 处理无匹配记录的情况,将history设为空数组 { $addFields: { history: { $ifNull: ["$history", []] } } } ]);
方案二:兼容多条driverHistory记录的场景(合并所有匹配的驾驶记录)
如果存在一个司机对应多条driverHistory文档的情况,可以用$reduce合并所有history数组:
const transports = await Transport.aggregate([ { $lookup: { from: "driverhistories", let: { driverId: "$driver" }, pipeline: [ { $match: { $expr: { $eq: ["$driver", "$$driverId"] } } }, { $project: { _id: 0, history: 1 } } ], as: "tempHistory" } }, // 合并所有匹配到的history数组 { $addFields: { history: { $reduce: { input: "$tempHistory", initialValue: [], in: { $concatArrays: ["$$value", "$$this.history"] } } } } }, { $project: { tempHistory: 0 } } ]);
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

