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

MongoDB聚合管道:按type原地分组嵌套数组procedures

MongoDB聚合管道实现嵌套数组按type原地分组

需求回顾

将每个包含airport、country和procedures数组的文档,转换为procedures字段变为以type为键、对应元素数组为值的对象,且保留原文档所有字段,不混合不同文档的数据。

输入输出示例

输入文档

{
  "_id": ObjectId("60d21b4667d0d8992e610c85"),
  "airport": "JFK",
  "country": "USA",
  "procedures": [
    { "type": "departure", "details": "D1" },
    { "type": "arrival", "details": "A1" },
    { "type": "departure", "details": "D2" }
  ]
}

输出文档

{
  "_id": ObjectId("60d21b4667d0d8992e610c85"),
  "airport": "JFK",
  "country": "USA",
  "procedures": {
    "departure": [
      { "type": "departure", "details": "D1" },
      { "type": "departure", "details": "D2" }
    ],
    "arrival": [
      { "type": "arrival", "details": "A1" }
    ]
  }
}

方案一:基于$unwind+$group的通用实现

适合处理大型数组,兼容性好:

db.collection.aggregate([
  // 拆分procedures数组,保留空数组/无该字段的文档
  { $unwind: { path: "$procedures", preserveNullAndEmptyArrays: true } },
  // 按原文档ID、基础字段及procedure类型分组,收集同类型元素
  {
    $group: {
      _id: {
        docId: "$_id",
        airport: "$airport",
        country: "$country",
        type: "$procedures.type"
      },
      procedureList: { $push: "$procedures" }
    }
  },
  // 回到原文档维度,整理为键值对数组
  {
    $group: {
      _id: "$_id.docId",
      airport: { $first: "$_id.airport" },
      country: { $first: "$_id.country" },
      proceduresKV: {
        $push: { k: "$_id.type", v: "$procedureList" }
      }
    }
  },
  // 将键值对数组转为对象,替换原procedures字段
  {
    $addFields: {
      procedures: { $arrayToObject: "$proceduresKV" },
      proceduresKV: "$$REMOVE" // 清理临时字段
    }
  }
])

各阶段说明

  1. $unwind:将每个文档的procedures数组拆分为独立文档,确保后续能按type分组,preserveNullAndEmptyArrays避免丢失无procedures的文档。
  2. 第一个$group:通过docId(原文档_id)确保分组范围限定在单个文档内,同时按type收集同类型的procedure元素。
  3. 第二个$group:回归原文档维度,将同文档内的各类型分组结果整理为{k: type, v: 元素数组}的格式,方便转成对象。
  4. $addFields:用$arrayToObject将键值对数组转为目标对象,替换原procedures字段并清理临时数据。

方案二:基于$reduce的高效实现

无需拆分数组,性能更优,适合中小型数组:

db.collection.aggregate([
  {
    $addFields: {
      procedures: {
        $arrayToObject: {
          $reduce: {
            input: "$procedures",
            initialValue: [],
            in: {
              $let: {
                vars: {
                  // 检查当前type是否已在结果数组中存在
                  typeIndex: { $indexOfArray: ["$$value.k", "$$this.type"] }
                },
                in: {
                  $cond: {
                    if: { $ne: ["$$typeIndex", -1] },
                    // 已存在则追加元素到对应数组
                    then: {
                      $concatArrays: [
                        { $slice: ["$$value", 0, "$$typeIndex"] },
                        [{
                          k: "$$this.type",
                          v: { $concatArrays: ["$$value.$$typeIndex.v", ["$$this"]] }
                        }],
                        { $slice: ["$$value", { $add: ["$$typeIndex", 1] }] }
                      ]
                    },
                    // 不存在则新增键值对
                    else: { $concatArrays: ["$$value", [{ k: "$$this.type", v: ["$$this"] }]] }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
])

说明

用$reduce遍历procedures数组,维护一个键值对数组:

  • 对每个元素,检查其type是否已在数组中存在
  • 存在则将当前元素追加到对应类型的数组中
  • 不存在则新增一个{k: type, v: [当前元素]}的键值对
  • 最后用$arrayToObject将数组转为目标对象

错误原因分析

你之前的代码丢失airport结构且混合数据,是因为$group阶段没有以**原文档的唯一标识(如_id)**作为分组键,导致跨文档的type被合并,同时没有保留原文档的基础字段。上述方案通过绑定原文档ID,确保每个文档独立处理,完全避免了这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:24:53