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

MongoDB按日期分组求和聚合(Map结构)问题求助

问题分析与解决方案

你的核心问题在于文档结构是嵌套数组的键值对形式,直接通过普通的分组和条件判断无法正确提取invoice-date和invoice-total的值:

  • 未展开data数组,MongoDB无法直接定位到数组中指定key对应的value
  • 分组字段invoice-date根本不存在于顶层文档,导致分组后的_id为null
  • 求和时的条件判断针对的是整个数组是否包含key=invoice-total,而非提取该子对象的value

正确的聚合逻辑(MongoDB原生管道)

先看原生聚合语句,理解核心步骤:

db.documents.aggregate([
  // 1. 展开data数组,将每个子对象转为单独文档
  { $unwind: "$data" },
  // 2. 按原文档_id分组,聚合出当前文档的日期和总额
  {
    $group: {
      _id: "$_id",
      invoiceDate: { $first: { $cond: [{ $eq: ["$data.key", "invoice-date"] }, "$data.value", "$$REMOVE"] } },
      invoiceTotal: { $first: { $cond: [{ $eq: ["$data.key", "invoice-total"] }, "$data.value", "$$REMOVE"] } }
    }
  },
  // 3. 按日期分组,汇总总额
  {
    $group: {
      _id: "$invoiceDate",
      total: { $sum: "$invoiceTotal" }
    }
  },
  // 4. 调整输出格式(可选)
  {
    $project: {
      _id: 0,
      invoiceDate: "$_id",
      total: 1
    }
  }
])

Spring Data MongoDB 对应实现代码

将上述逻辑转为Spring 3.1.4的代码:

import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import java.util.HashMap;
import java.util.Map;

// 1. 展开data数组,拆分每个key-value子对象
UnwindOperation unwindStage = Aggregation.unwind("data");

// 2. 按原文档ID分组,提取当前文档的日期和总额
GroupOperation firstGroupStage = Aggregation.group("_id")
        .first(ConditionalOperators.when(Criteria.where("data.key").is("invoice-date"))
                .then("data.value")
                .otherwiseExclude())
        .as("invoiceDate")
        .first(ConditionalOperators.when(Criteria.where("data.key").is("invoice-total"))
                .then("data.value")
                .otherwiseExclude())
        .as("invoiceTotal");

// 3. 按日期分组,汇总所有同日期的发票总额
GroupOperation secondGroupStage = Aggregation.group("invoiceDate")
        .sum("invoiceTotal")
        .as("total");

// 4. 调整输出字段结构
ProjectionOperation projectionStage = Aggregation.project()
        .andExpression("_id").as("invoice-date")
        .andInclude("total")
        .andExclude("_id");

// 构建完整聚合管道
Aggregation aggregation = Aggregation.newAggregation(
        unwindStage,
        firstGroupStage,
        secondGroupStage,
        projectionStage
);

// 执行聚合并转换为目标Map格式
AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, "documents", Map.class);
Map<String, Double> finalResult = new HashMap<>();
for (Map<String, Object> item : results.getMappedResults()) {
    String date = item.get("invoice-date").toString();
    Double total = ((Number) item.get("total")).doubleValue();
    finalResult.put(date, total);
}

return finalResult;

关键说明

  1. UnwindOperation:必须先展开data数组,否则无法单独处理每个key-value子对象
  2. 第一次分组:将同一个原始文档中的日期和总额聚合到一起,确保每个原文档对应一条包含核心数据的记录
  3. 第二次分组:基于提取出的日期进行分组,完成同日期的总额汇总
  4. 结果转换:Spring聚合返回的结果需要手动转为你需要的键值对Map格式

内容的提问来源于stack exchange,提问作者Héctor Iglesias

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 17:57:11