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

MongoDB聚合按年月分组并返回无数据月份的技术咨询

解决MongoDB聚合按年月分组时返回无数据月份的问题

这个需求在报表统计场景里太常见了——要展示时间范围内的所有年月,哪怕某个月份没有匹配的评分数据也要返回空值。核心思路是先生成目标时间范围内的完整年月序列,再和实际聚合结果做左连接,这样就能把缺失的月份补全了。

完整聚合管道示例

假设你的集合名为ratings,时间范围是2018-01-01到2018-03-01,下面是完整的聚合管道:

const startDate = ISODate("2018-01-01");
const endDate = ISODate("2018-03-01");

db.ratings.aggregate([
  // 第一步:生成时间范围内的所有年月文档(作为基础维度)
  {
    $documents: [{}] // 生成一个空文档,用来承载后续的年月序列
  },
  {
    $addFields: {
      // 将起止日期转成YYYYMM格式的数字,生成连续的年月范围
      startYearMonth: { $toInt: { $dateToString: { format: "%Y%m", date: startDate } } },
      endYearMonth: { $toInt: { $dateToString: { format: "%Y%m", date: endDate } } }
    }
  },
  {
    $addFields: {
      monthsRange: { $range: ["$startYearMonth", "$endYearMonth" + 1, 1] }
    }
  },
  { $unwind: "$monthsRange" },
  {
    $project: {
      year: { $toInt: { $substr: [{ $toString: "$monthsRange" }, 0, 4] } },
      month: { $toInt: { $substr: [{ $toString: "$monthsRange" }, 4, 2] } }
    }
  },
  // 第二步:左连接实际的评分聚合数据
  {
    $lookup: {
      from: "ratings",
      let: { targetYear: "$year", targetMonth: "$month" },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                { $eq: [{ $year: "$date" }, "$$targetYear"] },
                { $eq: [{ $month: "$date" }, "$$targetMonth"] },
                { $exists: ["$rating", true] },
                { $gte: ["$date", startDate] },
                { $lte: ["$date", endDate] }
              ]
            }
          }
        },
        // 根据你的需求统计评分,比如求平均值、总和或者计数
        { $group: { _id: null, avgRating: { $avg: "$rating" }, recordCount: { $sum: 1 } } }
      ],
      as: "ratingData"
    }
  },
  // 第三步:处理空数据的月份,将缺失值转为null或0
  {
    $project: {
      _id: 0,
      year: 1,
      month: 1,
      avgRating: { $ifNull: [{ $arrayElemAt: ["$ratingData.avgRating", 0] }, null] },
      recordCount: { $ifNull: [{ $arrayElemAt: ["$ratingData.recordCount", 0] }, 0] }
    }
  },
  // 可选:按年月排序
  { $sort: { year: 1, month: 1 } }
])

关键步骤解释

  1. 生成完整年月序列:

    • 用$documents生成一个空起始文档,然后通过$dateToString将起止日期转为YYYYMM格式的数字。
    • 用$range生成连续的年月数字数组,再通过$unwind展开成单个年月文档,最后拆分出year和month字段。
  2. 左连接实际数据:

    • 通过$lookup的管道参数,关联原集合中对应年月的评分数据,同时保留你的$match条件(评分存在、日期在范围内)。
    • 在lookup的管道内完成分组统计,这样每个年月只会返回一条统计结果。
  3. 补全缺失值:

    • 用$arrayElemAt提取lookup返回的统计数据(因为每个年月最多只有一条结果),再用$ifNull将空数据转为你需要的默认值(比如null或0)。

注意事项

  • 如果你的MongoDB版本低于5.0,$documents算子不可用,可以用$unionWith结合一个包含空文档的集合来替代,或者手动构造年月数组。
  • 如果时间跨度很大(比如几年),生成$range时要确保YYYYMM的数字连续性是正确的(比如201812之后是201901,这时候$range的步长1就不适用了,需要用日期增量的方式生成序列,比如用$dateAdd循环生成每个月的第一天)。

内容的提问来源于stack exchange,提问作者Victor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:08:39