You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:40:27