如何在MongoDB中批量筛选指定列表内近7天未登录的用户?
问题
我正在NestJS结合MongoDB的环境中开发一个API,目的是给近7天未登录系统的注册用户发送提醒邮件。
每次用户登录时,我会在session集合中创建如下结构的文档(用户单日可拥有多个session):
{ user_email: "dev@gmail.com", _created: "2019-05-13T13:09:39.000+00:00" }
我有一个需要验证的目标邮箱列表,示例如下:
['dev@gmail.com', 'test@fakemail.com', 'hahew66382@kaimdr.com', 'hoheray334@oncebar.com']
有没有办法不用逐个遍历用户,就能判断这些邮箱对应的用户在近7天内是否有session记录?我考虑过用聚合查询,再对比结果中未出现的邮箱,但对聚合查询经验不足,不确定思路是否正确。以下是我的尝试:
[ { '$match': { 'user_email': { '$in': ['dev@gmail.com', 'test@fakemail.com', 'hahew66382@kaimdr.com', 'hoheray334@oncebar.com'] }, '_created': { '$gte': new Date('Thu, 10 Nov 2022 00:00:00 GMT') // 7 days ago since today is 2022-11-17 } } }, { '$sort': { '_created': -1 } }, { '$group': { '_id': '$user_email', 'last_session': { '$first': '$_created' } } } ]
解决方案
你的思路完全可行,通过聚合查询筛选近7天有活跃session的用户,再从目标列表中排除这些用户,就能得到需要发邮件的对象。下面给你两种优化方案:
方案一:客户端完成差集计算
先通过聚合查询获取近7天有登录记录的用户邮箱,再在代码层面过滤目标列表:
// 动态计算7天前的日期,避免硬编码 const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); // 聚合查询获取活跃用户邮箱 const activeUsers = await sessionCollection.aggregate([ { $match: { user_email: { $in: targetEmailList }, _created: { $gte: sevenDaysAgo } } }, { $group: { _id: '$user_email' } // 按邮箱去重,只保留唯一邮箱 }, { $project: { email: '$_id', _id: 0 } // 重命名字段方便后续处理 } ]).toArray(); // 提取活跃邮箱数组 const activeEmails = activeUsers.map(item => item.email); // 过滤出近7天未登录的用户邮箱 const inactiveUsers = targetEmailList.filter(email => !activeEmails.includes(email));
方案二:完全用MongoDB聚合完成逻辑
如果不想在客户端处理数组,可以直接用MongoDB的$setDifference操作符在聚合管道内计算差集:
const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const result = await sessionCollection.aggregate([ // 筛选近7天内目标用户的session记录 { $match: { user_email: { $in: targetEmailList }, _created: { $gte: sevenDaysAgo } } }, // 按邮箱分组去重 { $group: { _id: '$user_email' } }, // 将活跃邮箱合并为一个数组 { $group: { _id: null, activeEmails: { $push: '$_id' } } }, // 计算目标列表与活跃列表的差集,得到非活跃用户 { $project: { inactiveUsers: { $setDifference: [targetEmailList, '$activeEmails'] }, _id: 0 } } ]).toArray(); // 最终结果就是需要发邮件的用户邮箱数组 const inactiveUsers = result[0]?.inactiveUsers || [];
额外优化建议
- 你的原始聚合查询已经覆盖核心逻辑,如果需要保留用户最后登录时间,可以保留
$sort和last_session字段;如果只需要判断活跃状态,去掉$sort和last_session能提升查询效率。 - 给
session集合创建联合索引,大幅提升查询速度:// 创建user_email和_created的联合索引 await sessionCollection.createIndex({ user_email: 1, _created: -1 });
内容的提问来源于stack exchange,提问作者fvilar
相关产品推荐
相关产品推荐

