You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mongoid/Mongo中合并查询结果统计月度活跃用户

跨集合统计活跃用户的Mongo端最优实现方案

嘿,针对你这个统计每月活跃用户(用户发送信件或通知即视为活跃,需取两类操作的最早日期)的需求,结合你提到的Mongo版本不支持$lookup的情况,我推荐你用MapReduce+聚合的组合方案,完全在Mongo端完成所有合并和统计操作,比客户端循环处理高效得多,具体实现如下:

方案思路

  1. 分别对Letter和Notification集合执行MapReduce,输出到同一个临时集合(利用reduce输出模式自动合并相同用户的记录,取最小日期)
  2. 对临时集合执行聚合查询,按年月分组统计活跃用户数量

具体代码实现

第一步:执行两个集合的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:07:54