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

MongoDB三层嵌套数组聚合分组:合并tunerDetails至对应session

解决MongoDB三层嵌套数组的聚合分组问题

我完全懂你的困扰:直接三层unwind后分组要么丢失上层的linearTvChannels信息,要么会产生大量重复的session条目。下面是一套完整的聚合方案,既能精准筛选出signalLocked=0的tunerDetails,又能把它们合并到所属的session数组中,还能完整保留linearTvChannels的所有信息:

完整聚合管道示例

假设你的集合名为tv_data,文档结构大致如下:

{
  "_id": ObjectId("xxx"),
  "otherTopField": "some_value",
  "linearTvChannels": [
    {
      "channelId": "ch_001",
      "channelName": "体育频道",
      "sessions": [
        {
          "sessionId": "ses_001",
          "startTime": ISODate("2024-05-01T18:00:00Z"),
          "tunerDetails": [
            { "tunerId": "tun_001", "signalLocked": 0, "signalStrength": -50 },
            { "tunerId": "tun_002", "signalLocked": 1, "signalStrength": -30 }
          ]
        }
      ]
    }
  ]
}

对应的聚合管道如下:

db.tv_data.aggregate([
  // 1. 展开最外层的linearTvChannels数组,保留顶层文档标识
  {
    $unwind: "$linearTvChannels"
  },
  // 2. 展开当前频道下的sessions数组,保留频道和会话信息
  {
    $unwind: "$linearTvChannels.sessions"
  },
  // 3. 展开当前会话下的tunerDetails数组,准备筛选
  {
    $unwind: "$linearTvChannels.sessions.tunerDetails"
  },
  // 4. 筛选出signalLocked=0的tunerDetails条目
  {
    $match: {
      "linearTvChannels.sessions.tunerDetails.signalLocked": 0
    }
  },
  // 5. 按「原文档ID+频道ID+会话ID」分组,合并同一会话的目标tunerDetails
  {
    $group: {
      _id: {
        docId: "$_id",
        channelId: "$linearTvChannels.channelId",
        sessionId: "$linearTvChannels.sessions.sessionId"
      },
      // 保留频道的其他字段(按需添加)
      channelName: { $first: "$linearTvChannels.channelName" },
      // 保留会话的其他字段(按需添加)
      startTime: { $first: "$linearTvChannels.sessions.startTime" },
      // 收集符合条件的tunerDetails到数组
      tunerDetails: { $push: "$linearTvChannels.sessions.tunerDetails" },
      // 保留顶层文档的其他字段
      otherTopField: { $first: "$otherTopField" }
    }
  },
  // 6. 按「原文档ID+频道ID」分组,合并同一频道下的会话
  {
    $group: {
      _id: {
        docId: "$_id.docId",
        channelId: "$_id.channelId"
      },
      channelName: { $first: "$channelName" },
      otherTopField: { $first: "$otherTopField" },
      // 收集当前频道下的所有有效会话
      sessions: {
        $push: {
          sessionId: "$_id.sessionId",
          startTime: "$startTime",
          tunerDetails: "$tunerDetails"
        }
      }
    }
  },
  // 7. 还原顶层文档结构,合并处理后的频道数组
  {
    $group: {
      _id: "$_id.docId",
      otherTopField: { $first: "$otherTopField" },
      linearTvChannels: {
        $push: {
          channelId: "$_id.channelId",
          channelName: "$channelName",
          sessions: "$sessions"
        }
      }
    }
  }
])

关键思路说明

  • 分层分组,逐层还原:从最底层的tunerDetails开始,先按「文档+频道+会话」的唯一标识分组,合并同一会话下的目标条目;再按「文档+频道」分组,合并同一频道下的会话;最后按原文档ID分组,还原顶层结构,全程不会丢失任何上层信息。
  • 保留非数组字段:在每个$group阶段用$first保留频道名称、会话时间这类非数组字段,确保这些信息不会被分组操作覆盖。
  • 精准过滤无效数据:在展开所有数组后用$match筛选目标条目,避免无效数据进入后续分组流程。

可选优化:用数组操作替代多层unwind

如果你的MongoDB版本在4.2以上,可以直接用$map和$filter在数组内完成筛选,避免多次unwind的性能开销:

db.tv_data.aggregate([
  {
    $addFields: {
      linearTvChannels: {
        $filter: {
          input: {
            $map: {
              input: "$linearTvChannels",
              as: "channel",
              in: {
                // 保留频道所有原有字段
                ..."$$channel",
                sessions: {
                  $filter: {
                    input: {
                      $map: {
                        input: "$$channel.sessions",
                        as: "session",
                        in: {
                          // 保留会话所有原有字段
                          ..."$$session",
                          // 筛选signalLocked=0的tunerDetails
                          tunerDetails: {
                            $filter: {
                              input: "$$session.tunerDetails",
                              cond: { $eq: ["$$this.signalLocked", 0] }
                            }
                          }
                        }
                      }
                    },
                    // 过滤掉没有有效tunerDetails的会话
                    cond: { $gt: [{ $size: "$$this.tunerDetails" }, 0] }
                  }
                }
              }
            }
          },
          // 过滤掉没有有效会话的频道
          cond: { $gt: [{ $size: "$$this.sessions" }, 0] }
        }
      }
    }
  }
])

这种方式不需要展开数组,直接在原结构内完成筛选和过滤,性能更优,代码也更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 18:05:18