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

MongoDB聚合优化:如何合并Unwind与条件匹配的两次查询?

单聚合管道实现需求方案

核心思路

通过单次$unwind展开items数组,利用$facet在同一管道内并行计算目标类别最高分商品和全局最高分商品,最后通过$cond判断分支逻辑,返回对应结果,彻底避免两次聚合的重复操作。

MongoDB原生聚合管道示例

db.products.aggregate([
  // 仅执行一次items数组展开,忽略空数组文档
  { $unwind: { path: "$items", preserveNullAndEmptyArrays: false } },
  // 标记当前item是否属于目标类别
  {
    $addFields: {
      "items.isTargetCategory": { $eq: ["$items.category", "YOUR_TARGET_CATEGORY"] }
    }
  },
  // 分面并行处理两组数据
  {
    $facet: {
      // 第一组:筛选目标类别商品,取最高分的第一个
      targetItems: [
        { $match: { "items.isTargetCategory": true } },
        { $sort: { "items.score": -1 } },
        { $limit: 1 },
        { $replaceRoot: { newRoot: "$items" } }
      ],
      // 第二组:取全局所有商品中最高分的第一个
      allItems: [
        { $sort: { "items.score": -1 } },
        { $limit: 1 },
        { $replaceRoot: { newRoot: "$items" } }
      ]
    }
  },
  // 根据目标类别是否有结果,选择返回对应数据
  {
    $project: {
      result: {
        $cond: {
          if: { $gt: [{ $size: "$targetItems" }, 0] },
          then: { $arrayElemAt: ["$targetItems", 0] },
          else: { $arrayElemAt: ["$allItems", 0] }
        }
      }
    }
  },
  // 将结果作为根文档返回
  { $replaceRoot: { newRoot: "$result" } }
])

Spring Data MongoDB 实现代码

import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.domain.Sort;

// 替换为你的目标类别参数
String targetCategory = "electronics";

// 构建聚合管道
Aggregation aggregation = Aggregation.newAggregation(
    // 展开items数组,跳过无items的文档
    Aggregation.unwind("items", false),
    // 添加目标类别标记字段
    Aggregation.addFields()
        .addFieldWithValue("items.isTargetCategory", 
            ComparisonOperators.Eq.valueOf("items.category").equalToValue(targetCategory))
        .build(),
    // 分面聚合:同时计算两组结果
    Aggregation.facet()
        // 目标类别组:筛选→排序→取首项→替换根
        .and(
            Aggregation.match(Criteria.where("items.isTargetCategory").is(true)),
            Aggregation.sort(Sort.Direction.DESC, "items.score"),
            Aggregation.limit(1),
            Aggregation.replaceRoot("items")
        ).as("targetItems")
        // 全局组:排序→取首项→替换根
        .and(
            Aggregation.sort(Sort.Direction.DESC, "items.score"),
            Aggregation.limit(1),
            Aggregation.replaceRoot("items")
        ).as("allItems")
        .build(),
    // 分支判断:选择目标组或全局组的结果
    Aggregation.project()
        .and(
            ConditionalOperators.when(
                ComparisonOperators.Gt.valueOf(ArrayOperators.Size.lengthOfArray("targetItems"))
                    .greaterThanValue(0)
            ).then(
                ArrayOperators.ArrayElemAt.arrayOf("targetItems").elementAt(0)
            ).otherwise(
                ArrayOperators.ArrayElemAt.arrayOf("allItems").elementAt(0)
            )
        ).as("result"),
    // 最终返回结果文档
    Aggregation.replaceRoot("result")
);

// 执行聚合,替换为你的实体类
AggregationResults<Item> results = mongoTemplate.aggregate(aggregation, "products", Item.class);
Item highestScoreItem = results.getUniqueMappedResult();

关键优势

  • 仅执行一次$unwind操作,避免两次聚合的重复计算开销
  • 利用$facet并行处理两组数据,性能优于两次独立查询
  • 通过$cond实现分支逻辑,完全在MongoDB端完成计算,减少应用层与数据库的交互次数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 19:51:02