MongoDB聚合查询:筛选Product集合中不在ActivityUser的user_id数据
问题:找出Product集合中user_id存在但ActivityUser集合中不存在的文档
集合数据
Product集合
[ {_id: "6483987fe5b1ba0028320e89", user_id: "6483987fe5b1ba0028320555" }, {_id: "6483987fe5b1ba0028320e89", user_id: "6483987fe5b1ba0028320888" } ]
ActivityUser集合
[ {_id: "6483987fe5b1ba0028320e89", user_id: "6483987fe5b1ba0028320777", activeTime: "", module: "module" }, {_id: "6483987fe5b1ba0028320e89", user_id: "6483987fe5b1ba0028320901", activeTime: "", module: "module" } ]
期望结果
[{_id: "6483987fe5b1ba0028320e89", user_id: "6483987fe5b1ba0028320555" }]
问题代码(返回空结果)
const aggregate = [ { $group: { _id: '$user_id', }, }, { $lookup: { from: 'ActivityUser', localField: 'user_id', foreignField: 'user_id', 'as': "activity" } }, { $match: { "activity.user_id": { $exists: false } } } ]; const data = await ProductModel.aggregate(aggregate)
问题分析与解决方法
你的聚合代码有两个核心问题:
- $group阶段丢失原始文档数据:仅按
user_id分组但未保留_id等字段,后续无法还原目标文档结构。 - 匹配条件错误:当
$lookup无匹配结果时,activity是空数组,而非不存在user_id字段,用"activity.user_id": {$exists: false}无法命中这种场景。
正确实现方式
方式一:聚合管道一步完成
const aggregate = [ // 关联ActivityUser,获取匹配记录 { $lookup: { from: 'ActivityUser', localField: 'user_id', foreignField: 'user_id', as: 'activity' } }, // 筛选出无匹配的文档(activity为空数组) { $match: { 'activity': { $size: 0 } } }, // 可选:移除activity字段,还原原始文档结构 { $project: { activity: 0 } } ]; const data = await ProductModel.aggregate(aggregate);
方式二:分步查询(性能更优)
如果集合数据量较大,这种方法效率更高:
// 先获取ActivityUser中所有已存在的user_id const existingUserIds = await ActivityUserModel.distinct('user_id'); // 直接筛选Product中不在该列表的文档 const data = await ProductModel.find({ user_id: { $nin: existingUserIds } });
说明
- 方式一适合需要在聚合流程中做后续处理的场景,一步完成所有逻辑。
- 方式二代码更简洁,利用
distinct获取已存在的user_id列表后,用$nin筛选,在ActivityUser有完善索引时性能更佳。
内容的提问来源于stack exchange,提问作者Amos Evange
相关产品推荐
相关产品推荐

