MongoDB如何基于过滤聚合结果筛选近一周通话数少于2次的用户
问题说明
现有存储用户手机通话信息的集合,文档结构示例如下:
{ "_id": "1", "name": "John", "calls": [ "2022-07-01T01:05:00.000+00:00", "2022-07-08T02:14:00.000+00:00", "2022-07-08T05:27:00.000+00:00", "2022-07-09T03:46:00.000+00:00" ] }, { "_id": "2", "name": "George", "calls": [ "2022-06-28T01:05:00.000+00:00", "2022-06-29T02:14:00.000+00:00", "2022-06-29T05:27:00.000+00:00", "2022-07-02T03:46:00.000+00:00" ] }
注:原示例中数组内写索引键、重复
_id属于笔误,上述结构已修正为MongoDB合法的文档格式。
需求为 查询过去一周内通话次数少于2次的所有人员,逻辑拆分如下:
- 筛选出近一周内产生的所有通话记录
- 对筛选出的通话记录聚合计数
- 返回计数结果小于2的对应人员文档
目前已在投影阶段实现通话记录过滤与计数逻辑,代码如下:
projection: { callsNumber: { $size: { $filter: { input: "$calls", cond: { $gte: ["$$this", ISODate("2022-07-03T00:00:00.000+00:00")] } } } } }
存在的问题:无法在过滤条件中引用投影生成的字段,也不知道如何将投影逻辑转换为可直接筛选文档的过滤条件。
实现方案
核心问题原因:普通find查询的执行顺序是先匹配筛选文档,再执行投影返回指定字段,因此投影阶段生成的计算字段无法被前置的筛选逻辑引用。有两种成熟实现方式:
方案1:find + $expr 单查询实现
MongoDB 3.6及以上版本支持$expr操作符,允许在查询条件中直接使用聚合表达式,不需要单独定义投影,直接把通话次数计算逻辑放到筛选条件中即可:
// 动态计算一周前的时间,不需要硬编码日期 const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) db.collection.find({ $expr: { $lt: [ { $size: { $filter: { input: "$calls", cond: { $gte: ["$$this", oneWeekAgo] } } } }, 2 ] } })
方案2:聚合管道分阶段实现
如果逻辑更复杂需要拆分步骤,可使用聚合管道,管道阶段按书写顺序执行,可以先计算通话次数字段,再基于该字段做筛选:
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) db.collection.aggregate([ // 第一步:新增字段计算近一周通话次数 { $addFields: { callsNumber: { $size: { $filter: { input: "$calls", cond: { $gte: ["$$this", oneWeekAgo] } } } } } }, // 第二步:筛选通话次数小于2的文档 { $match: { callsNumber: { $lt: 2 } } }, // 可选:如果不需要返回callsNumber字段,可加投影阶段去掉 { $project: { callsNumber: 0 } } ])
结果验证
以给出的测试数据、硬编码截止时间为2022-07-03T00:00:00Z为例:
- John近一周通话共3次(7月8日2次、7月9日1次),不符合条件不会被返回
- George近一周通话共1次(7月2日1次),符合条件会被返回
内容的提问来源于stack exchange,提问作者Ale
相关产品推荐
相关产品推荐

