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();
关键修改点:
- 移除了导致过滤的第二个
$match阶段,确保所有符合条件的Chat都能被返回,不管有没有未读消息。 - 将原来的
messages关联替换为带内部pipeline的$lookup,直接在关联时就过滤出接收者为当前Admin且未读的消息,后续直接取数组长度就是未读数量,比先关联所有消息再过滤更高效。 - 若Admin和User是独立集合,需确认Message的
reciever字段是否能匹配Admin的_id,若不能则需调整Schema(比如用refPath支持多集合关联)或业务逻辑。
内容的提问来源于stack exchange,提问作者Akbarjon Abdukarimov
相关产品推荐
相关产品推荐

