Mongoose聚合查询按关联字段过滤返回空数组问题排查
Mongoose聚合查询返回空数组的原因及解决方案
问题场景
我编写了两个Mongoose聚合函数,第二个函数从真实存在的通知中提取出lounge_id,但将该ID传入第一个函数时,始终返回空数组。相关代码与Model定义如下:
第一个函数(获取指定 lounge 的通知)
const getNotificationsForLounge = async (lounge_id) => { try { const notifications = await Notification.aggregate([ { $match: { "lounge_join_request.lounge": lounge_id, lounge_join_request: { $ne: null }, }, }, ]); console.log("🚀 ~ notifications:", notifications); return notifications; } catch (error) { console.log("🚀 ~ error:", error); } };
第二个函数(获取随机带 lounge 加入请求的通知并调用第一个函数)
const getNotificationWithRandomLoungeJoinRequest = async () => { try { const notification = await Notification.aggregate([ { $match: { category: "LOUNGE_JOIN_REQUEST", lounge_join_request: { $ne: null }, }, }, { $sample: { size: 1 } }, { $lookup: { from: "lounge_join_requests", localField: "lounge_join_request", foreignField: "_id", as: "lounge_join_request", }, }, { $project: { _id: 0, lounge_id: "$lounge_join_request.lounge" } }, ]); const lounge_id = notification[0].lounge_id[0]; console.log( "🚀 ~ file: loungeServices.js:663 ~ getNotificationWithRandomLoungeJoinRequest ~ lounge_id:", lounge_id ); return await getNotificationsForLounge(lounge_id); } catch (error) { console.log("🚀 ~ error:", error); } };
相关Model定义
Notification.js
const NotificationSchema = new Schema({ lounge_join_request: { type: Schema.Types.ObjectId, ref: "lounge_join_requests", default: null, }, }); module.exports = Notification = mongoose.model( "notification", NotificationSchema );
LoungeJoinRequest.js
const LoungeJoinRequestSchema = new Schema({ lounge: { type: Schema.Types.ObjectId, ref: "lounge", required: true, }, }); module.exports = LoungeJoinRequest = mongoose.model( "lounge_join_requests", LoungeJoinRequestSchema );
Lounge.js
const LoungeSchema = new Schema( { name: { type: String, }, } ); module.exports = Channel = mongoose.model("lounge", LoungeSchema);
问题原因
核心错误在于第一个函数的查询逻辑:
Notification集合中的lounge_join_request字段仅存储**lounge_join_requests集合的ObjectId引用**,并非包含lounge字段的嵌套文档。你直接用"lounge_join_request.lounge"作为匹配条件,相当于在Notification文档中查找一个不存在的嵌套字段,自然返回空数组。- 第二个函数能拿到正确的
lounge_id,是因为它通过$lookup关联了lounge_join_requests集合,获取到完整的请求文档后才提取出lounge字段,但第一个函数没有做任何关联查询。
解决方案
提供两种可行的修正方案:
方案一:聚合查询+关联查询
const getNotificationsForLounge = async (lounge_id) => { try { const notifications = await Notification.aggregate([ // 先过滤掉没有关联请求的通知 { $match: { lounge_join_request: { $ne: null } } }, // 关联lounge_join_requests集合获取完整请求数据 { $lookup: { from: "lounge_join_requests", localField: "lounge_join_request", foreignField: "_id", as: "lounge_join_request" } }, // 过滤出关联请求中lounge匹配目标ID的记录 { $match: { "lounge_join_request.lounge": lounge_id } }, // 将关联的数组转为单个对象(一对一关联场景) { $unwind: "$lounge_join_request" } ]); console.log("🚀 ~ notifications:", notifications); return notifications; } catch (error) { console.log("🚀 ~ error:", error); } };
方案二:Mongoose populate+find查询(更简洁)
const getNotificationsForLounge = async (lounge_id) => { try { const notifications = await Notification.find({ lounge_join_request: { $ne: null } }) .populate({ path: "lounge_join_request", // 仅关联匹配目标lounge_id的请求 match: { lounge: lounge_id } }) // 过滤掉关联后请求为空的记录(即不匹配的通知) .then(docs => docs.filter(doc => doc.lounge_join_request !== null)); console.log("🚀 ~ notifications:", notifications); return notifications; } catch (error) { console.log("🚀 ~ error:", error); } };
补充说明
- 方案一适合复杂查询场景,纯聚合逻辑性能更优;方案二利用Mongoose的ORM特性,代码更简洁易读。
- 第二个函数中
notification[0].lounge_id[0]的写法是正确的,因为$lookup返回的是数组,若想简化可在聚合阶段添加$unwind操作,避免多层数组取值。
内容的提问来源于stack exchange,提问作者AG_HIHI
相关产品推荐
相关产品推荐

