如何在Mongoid/Mongo中合并查询结果统计月度活跃用户
跨集合统计活跃用户的Mongo端最优实现方案
嘿,针对你这个统计每月活跃用户(用户发送信件或通知即视为活跃,需取两类操作的最早日期)的需求,结合你提到的Mongo版本不支持$lookup的情况,我推荐你用MapReduce+聚合的组合方案,完全在Mongo端完成所有合并和统计操作,比客户端循环处理高效得多,具体实现如下:
方案思路
- 分别对
Letter和Notification集合执行MapReduce,输出到同一个临时集合(利用reduce输出模式自动合并相同用户的记录,取最小日期) - 对临时集合执行聚合查询,按年月分组统计活跃用户数量
具体代码实现
第一步:执行两个集合的MapReduce,合并到临时集合
首先处理信件集合,生成每个用户最早发信日期,并输出到临时集合:
def self.letter_active_map_reduce(tenant_id) map = %{ function() { // 以customer_id为键,发送日期为值emit emit(this.customer_id, new Date(this.created_at)); } } reduce = %{ function(key, values) { // 对同一个用户的所有日期取最小值,保留最早活跃日期 return new Date(Math.min.apply(null, values)); } } // 用reduce模式输出,相同键会自动合并 where(tenant_id: tenant_id).map_reduce(map, reduce).out(reduce: "temp_active_users") end
接着处理通知集合,同样输出到同一个临时集合,Mongo会自动合并相同用户的记录:
def self.notification_active_map_reduce(tenant_id) map = %{ function() { // 以customer_id为键,通知更新日期为值emit emit(this.customer_id, new Date(this.updated_at)); } } reduce = %{ function(key, values) { // 取同一个用户的最小日期(合并信件和通知的日期) return new Date(Math.min.apply(null, values)); } } where(tenant_id: tenant_id, transferred: true).map_reduce(map, reduce).out(reduce: "temp_active_users") end
第二步:对临时集合做聚合统计
现在临时集合里已经存储了每个用户的最早活跃日期,我们只需要按年月分组统计数量:
# 获取临时集合的聚合结果 active_users_stats = Mongoid::Clients.default.database['temp_active_users'].aggregate([ # 提取日期中的年和月 { '$project' => { 'year' => { '$year' => '$value' }, 'month' => { '$month' => '$value' } } }, # 按年月分组,统计用户数 { '$group' => { '_id' => { 'year' => '$year', 'month' => '$month' }, 'active_users_count' => { '$sum' => 1 } } }, # 按年月排序,结果更直观 { '$sort' => { '_id.year' => 1, '_id.month' => 1 } } ]) # 输出结果示例: # => #<Mongo::Collection::View::Aggregation:0x000000010a1d5a60 @documents=[ # {"_id"=>{"year"=>2024, "month"=>5}, "active_users_count"=>120}, # {"_id"=>{"year"=>2024, "month"=>6}, "active_users_count"=>180} # ]>
第三步:清理临时集合(可选但推荐)
用完临时集合后记得删除,避免占用不必要的存储空间:
Mongoid::Clients.default.database['temp_active_users'].drop
方案优势
- 所有合并、统计操作都在Mongo端完成,避免了客户端拉取大量数据后循环处理的性能损耗
- 利用MapReduce的
reduce输出模式,自动完成相同用户的日期合并逻辑,代码简洁 - 最后用聚合查询做统计,比MapReduce统计效率更高
内容的提问来源于stack exchange,提问作者Saad
相关产品推荐
相关产品推荐

