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

MongoDB中如何分组嵌套文档数组且不影响父文档

在MongoDB中对嵌入式文档数组分组并保留父文档其他内容

数据库结构

db={
  "users": [
    {
      "firstName": "David",
      "lastName": "Mueller",
      "messages": [
        {
          "text": "hello",
          "type": "PERSONAL"
        },
        {
          "text": "test",
          "type": "DIRECT"
        }
      ]
    },
    {
      "firstName": "Mia",
      "lastName": "Davidson",
      "messages": [
        {
          "text": "hello world",
          "type": "DIRECT"
        },
        {
          "text": ":-)",
          "type": "PERSONAL"
        },
        {
          "text": "hi there",
          "type": "DIRECT"
        }
      ]
    }
  ]
}

期望结果

[
  {
    "firstName": "David",
    "lastName": "Mueller",
    "messages": [
      {
        "_id": "PERSONAL",
        "count": 1
      },
      {
        "_id": "DIRECT",
        "count": 1
      }
    ]
  },
  {
    "firstName": "Mia",
    "lastName": "Davidson",
    "messages": [
      {
        "_id": "PERSONAL",
        "count": 1
      },
      {
        "_id": "DIRECT",
        "count": 2
      }
    ]
  }
]

解决方案

针对嵌入式文档数组的分组需求,提供两种通用实现方式,适配不同场景:


方法一:基于$reduce的数组内分组(无需拆分数组,性能更优)

该方法直接在原文档内完成数组分组统计,避免$unwind拆分数组的开销,适合数据量较大的场景。

db.users.aggregate([
  // 第一步:用$reduce统计每个type的数量,生成键值对对象
  {
    $addFields: {
      messages: {
        $reduce: {
          input: "$messages",
          initialValue: {},
          in: {
            $mergeObjects: [
              "$$value",
              {
                "$$this.type": {
                  $add: [
                    { $ifNull: ["$$value.$$this.type", 0] },
                    1
                  ]
                }
              }
            ]
          }
        }
      }
    }
  },
  // 第二步:将键值对对象转换为期望的数组格式
  {
    $addFields: {
      messages: {
        $map: {
          input: { $objectToArray: "$messages" },
          as: "item",
          in: {
            _id: "$$item.k",
            count: "$$item.v"
          }
        }
      }
    }
  }
])

扩展适配(支持min、sum等聚合函数)

如果需要统计其他指标(如字段最小值、总和),只需调整$reduce内的逻辑。例如统计每个type对应的text长度最小值:

{
  $addFields: {
    messages: {
      $reduce: {
        input: "$messages",
        initialValue: {},
        in: {
          $mergeObjects: [
            "$$value",
            {
              "$$this.type": {
                minLength: {
                  $min: [
                    { $ifNull: ["$$value.$$this.type.minLength", 9999] },
                    { $strLenCP: "$$this.text" }
                  ]
                },
                count: { $add: [{ $ifNull: ["$$value.$$this.type.count", 0] }, 1] }
              }
            ]
          }
        }
      }
    }
  }
}

方法二:基于$unwind+$group的拆分重组(逻辑更直观)

该方法先拆分数组为单个文档,分组统计后再合并回原文档结构,逻辑易懂,适合复杂分组需求。

db.users.aggregate([
  // 拆分数组,每个message成为独立文档
  { $unwind: "$messages" },
  // 按用户唯一标识+消息类型分组,统计数量
  {
    $group: {
      _id: {
        userId: "$_id",
        firstName: "$firstName",
        lastName: "$lastName",
        type: "$messages.type"
      },
      count: { $sum: 1 }
    }
  },
  // 按用户合并统计结果,生成messages数组
  {
    $group: {
      _id: {
        userId: "$_id.userId",
        firstName: "$_id.firstName",
        lastName: "$_id.lastName"
      },
      messages: {
        $push: {
          _id: "$_id.type",
          count: "$count"
        }
      }
    }
  },
  // 重塑文档结构,还原原字段
  {
    $project: {
      _id: "$_id.userId",
      firstName: "$_id.firstName",
      lastName: "$_id.lastName",
      messages: 1
    }
  }
])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:10:55