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

MongoDB聚合中$match失效:如何统计未读消息数

问题描述

我有Chat、Admin、User、Message四类实体:Chat关联User和Admin,Message关联Chat且具备viewed布尔属性。我想要获取指定Admin的Chat数据,同时关联User信息并统计未读消息数量。

原聚合代码如下:

Chat.aggregate([
  {
    $match: {
      admin: new Types.ObjectId(admin.id),
    },
  },
  {
    $lookup: {
      from: "admins",
      localField: "admin",
      foreignField: "_id",
      as: "admin",
    },
  },
  { $unwind: "$admin" },

  {
    $lookup: {
      from: "users",
      localField: "user",
      foreignField: "_id",
      as: "user",
    },
  },
  { $unwind: "$user" },

  {
    $lookup: {
      from: "messages",
      localField: "_id",
      foreignField: "chat",

      as: "messages",
    },
  },
 //following match statement doesn't work
  // {
  //   $match: {
  //     "messages.viewed": false,
  //     //"messages.reciever":new Types.ObjectId(admin.id),
  //   },
  // },

  {
    $project: {
      id: "$_id",

      "admin.id": "$admin._id",
      "admin.email": "$admin.email",
      "admin.vendorId": "$admin.vendorId",

      "user.fullName": 1,
      "user.id": "$user._id",
      "user.phoneNumber": 1,
      "unreadMsgs": {
        $size: {
          $filter: {
            input: "$messages",
            as: "message",
            cond: { $eq: ["$$message.viewed", false] },
          },
        },
      },
    },
  },
]).exec();

代码中被注释的第二个$match语句无法正常工作:当存在未读消息时代码可正常返回,但无未读消息时会返回空数组;取消该$match后可正常获取数据,但我希望在不执行额外过滤操作的前提下,统计未读消息数,请问该如何实现?

相关Schema如下:

Chat Schema

const chatSchema = new Schema(
  {
    user: { type: Schema.Types.ObjectId, ref: User },
    admin: { type: Schema.Types.ObjectId, ref: Admin },
    product: { type: Schema.Types.ObjectId, ref: Product },

  },
  {
    toJSON: {
      transform(doc, ret) {
        ret.id = ret._id;
        delete ret._id;
      },
    },
  }
);

Message Schema

const messageSchema = new Schema(
  {
    sender: { type: Schema.Types.ObjectId, required: true, ref: User },
    reciever: { type: Schema.Types.ObjectId, required: true, ref: User },
    chat: { type: Schema.Types.ObjectId, required: true, ref: Chat },
    message: String,
    file: String,
    viewed:{type:Boolean, default:false}
  },
  {
    toJSON: {
      transform(doc, ret) {
        ret.id = ret._id;
        delete ret._id;
      },
    },
    timestamps: true,
  }
);

User和Admin Schemas

const adminSchema = new Schema(
  {
    email: String,
    password: String,
    vendorId: { type: Schema.Types.ObjectId, ref: "Vendor" },
    online:{type:Boolean, default:false},
    super: { type: Boolean, default: false },
  },
  {
    toJSON: {
      transform(doc, ret) {
        ret.id = ret._id;
        delete ret._id;
      },
    },
  }
);

const userSchema = new Schema(
  {
    fullName: String,
    password: String,
    phoneNumber: Number,
    avatar: String,
    gender: String,
    birthdate: Date,
    online:{type:Boolean, default:false},
    basket:[{type:Schema.Types.ObjectId, ref:Product}]
  },
  {
    toJSON: {
      transform(doc, ret) {
        ret.id = ret._id;
        delete ret._id;
      },
    },
  }
);
解决方案

问题出在你添加的第二个$match阶段:当某个Chat下没有未读消息时,"messages.viewed": false这个条件会直接过滤掉该Chat文档,导致返回空数组。其实你不需要这个额外的$match,因为你已经在$project里用$filter统计未读消息数了,而且要确保即使没有未读消息,Chat数据依然保留。

另外,注意你的Message Schema里reciever字段关联的是User,若未读消息特指用户发给当前Admin的消息,统计时需要加上接收者匹配条件,避免统计错误。

修改后的聚合代码如下:

Chat.aggregate([
  {
    $match: {
      admin: new Types.ObjectId(admin.id),
    },
  },
  // 关联Admin信息
  {
    $lookup: {
      from: "admins",
      localField: "admin",
      foreignField: "_id",
      as: "admin",
    },
  },
  { $unwind: "$admin" },

  // 关联User信息
  {
    $lookup: {
      from: "users",
      localField: "user",
      foreignField: "_id",
      as: "user",
    },
  },
  { $unwind: "$user" },

  // 关联消息时直接过滤出当前Admin的未读消息,提升效率
  {
    $lookup: {
      from: "messages",
      let: { chatId: "$_id", adminId: new Types.ObjectId(admin.id) },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                { $eq: ["$chat", "$$chatId"] },
                { $eq: ["$reciever", "$$adminId"] },
                { $eq: ["$viewed", false] }
              ]
            }
          }
        }
      ],
      as: "unreadMessages"
    },
  },

  // 投影字段并统计未读消息数
  {
    $project: {
      id: "$_id",
      "admin.id": "$admin._id",
      "admin.email": "$admin.email",
      "admin.vendorId": "$admin.vendorId",
      "user.fullName": 1,
      "user.id": "$user._id",
      "user.phoneNumber": 1,
      unreadMsgs: { $size: "$unreadMessages" }
    },
  },
]).exec();

关键修改点:

  1. 移除了导致过滤的第二个$match阶段,确保所有符合条件的Chat都能被返回,不管有没有未读消息。
  2. 将原来的messages关联替换为带内部pipeline的$lookup,直接在关联时就过滤出接收者为当前Admin且未读的消息,后续直接取数组长度就是未读数量,比先关联所有消息再过滤更高效。
  3. 若Admin和User是独立集合,需确认Message的reciever字段是否能匹配Admin的_id,若不能则需调整Schema(比如用refPath支持多集合关联)或业务逻辑。

内容的提问来源于stack exchange,提问作者Akbarjon Abdukarimov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:50:20