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;
关键说明
- UnwindOperation:必须先展开
data数组,否则无法单独处理每个key-value子对象 - 第一次分组:将同一个原始文档中的日期和总额聚合到一起,确保每个原文档对应一条包含核心数据的记录
- 第二次分组:基于提取出的日期进行分组,完成同日期的总额汇总
- 结果转换:Spring聚合返回的结果需要手动转为你需要的键值对Map格式
内容的提问来源于stack exchange,提问作者Héctor Iglesias
相关产品推荐
相关产品推荐

