Mongoose聚合中$filter未移除匹配元素的问题排查
问题:Mongoose聚合无法移除用户notifications数组中指定类型的通知记录
我用以下Mongoose聚合查询,想把用户的privacyStatus更新为指定值,同时移除notifications数组(ObjectId类型)中所有type为follow_request的通知记录:
User.aggregate([ { $match: { _id: new mongoose.Types.ObjectId(req.user._id) }, }, { $lookup: { from: "Notification", localField: "notifications", foreignField: "_id", as: "notifications", }, }, { $set: { privacyStatus: value, notifications: { $filter: { input: "$notifications", cond: { $ne: ["$$this.type", "follow_request"], }, }, }, }, }, { $merge: { into: "users", }, }, ]).then((result) => { res.send("Done") })
数据库里privacyStatus能成功更新,但即使存在符合过滤条件的通知,notifications数组完全没变化。
模型定义
User模型:
{ privacyStatus: {type: Boolean}, notifications: [{type: mongoose.Schema.Types.ObjectId, ref: "Notification"}] }
Notification模型:
{ type: {type: String, default: "follow_request"} }
示例文档
用户文档:
{ _id: ObjectId('66324110da2f0f7175ca1949'), username: "sam", notifications: [ ObjectId('663c1c0bd0sa68fae7cf6ad2') ] }
通知文档:
{ _id: ObjectId('663c1c0bd0sa68fae7cf6ad2'), type: "follow_request" }
问题原因
核心问题是类型不匹配:
- 你用
$lookup把原先是ObjectId数组的notifications,转换成了完整的Notification文档数组; - 但User模型里
notifications字段的类型是ObjectId数组,直接把过滤后的文档数组用$merge存回去时,MongoDB会因为类型不兼容,忽略这个字段的更新操作。
解决方案
方案1:修正聚合查询,将过滤后的文档映射回ObjectId数组
在$set阶段,过滤完文档后,用$map提取每个文档的_id,转换成ObjectId数组再更新:
User.aggregate([ { $match: { _id: new mongoose.Types.ObjectId(req.user._id) }, }, { $lookup: { from: "Notification", localField: "notifications", foreignField: "_id", as: "notifications", }, }, { $set: { privacyStatus: value, // 先过滤不符合条件的通知,再映射回ObjectId数组 notifications: { $map: { input: { $filter: { input: "$notifications", cond: { $ne: ["$$this.type", "follow_request"] }, }, }, in: "$$this._id" } }, }, }, { $merge: { into: "users", on: "_id", // 指定匹配主键,确保更新正确文档 whenMatched: "merge" // 存在则合并更新字段 }, }, ]).then((result) => { res.send("Done") })
方案2:更高效的两步更新(推荐)
没必要用聚合,直接分两步操作:先查询所有type为follow_request的通知ID,再用$pull从用户的notifications数组中移除这些ID,同时更新privacyStatus。这种方式性能更好,避免了聚合拉取大量关联数据:
async function updateUserSettings() { // 第一步:获取所有需要移除的通知ID const followRequestIds = await Notification.find({ type: "follow_request" }).distinct("_id"); // 第二步:更新用户的隐私状态并移除指定通知 await User.updateOne( { _id: req.user._id }, { $set: { privacyStatus: value }, $pull: { notifications: { $in: followRequestIds } } } ); res.send("Done"); } updateUserSettings();
内容的提问来源于stack exchange,提问作者Max adam
相关产品推荐
相关产品推荐

