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

MongoDB聚合:如何按action为start拆分数组生成新数组

如何在MongoDB聚合管道中按"start"动作拆分actions数组为多个session

嘿,我明白你要做的事了——把聚合后得到的actions数组,以每个action: "start"为起点拆分成独立的session字段,对吧?咱们直接把这个逻辑加到你现有的聚合管道里就行,我给你一步步拆解:

先理清楚现有管道的输出

你的现有管道最后会输出每个文档的_id、count和按时间排序好的actions数组,这正是我们需要的基础数据,接下来就对这个数组做拆分处理。

完整的聚合管道(含新增阶段)

把下面这些阶段加到你现有管道的最后那个$match阶段之后就行:

db.collection.aggregate( [
  // 你的现有管道阶段
  { "$match" : { "action" : { "$exists" : true }, "domain" : "domain.com" } },
  { "$unwind" : "$action" },
  { "$sort" : { "action.datetime" : 1.0 } },
  { "$group" : { "_id" : "$id", "count" : { "$sum" : 1.0 }, "actions" : { "$addToSet" : { "id" : "$id", "action" : "$action.action", "datetime" : "$action.datetime" } } } },
  { "$unwind" : "$actions" },
  { "$sort" : { "actions.datetime" : 1.0 } },
  { "$group" : { "_id" : "$actions.id", "count" : { "$sum" : 1.0 }, "actions" : { "$push" : { "action" : "$actions.action", "datetime" : "$actions.datetime" } } } },
  { "$match" : { "count" : { "$gt" : 1.0 } } },

  // 新增的拆分逻辑阶段
  // 1. 给每个action标记所属的session编号
  {
    $set: {
      sessionActions: {
        $reduce: {
          input: "$actions",
          initialValue: { currentSession: 0, items: [] },
          in: {
            currentSession: {
              $cond: [
                { $eq: ["$$this.action", "start"] },
                { $add: ["$$value.currentSession", 1] },
                "$$value.currentSession"
              ]
            },
            items: {
              $concatArrays: [
                "$$value.items",
                [
                  {
                    $mergeObjects: [
                      "$$this",
                      { 
                        sessionId: { 
                          $cond: [
                            { $eq: ["$$this.action", "start"] }, 
                            { $add: ["$$value.currentSession", 1] }, 
                            "$$value.currentSession"
                          ] 
                        }
                      }
                    ]
                  }
                ]
              ]
            }
          }
        }
      }
    }
  },
  // 2. 展开带session标记的数组,方便后续分组
  { $unwind: "$sessionActions.items" },
  // 3. 按文档ID和sessionID分组,收集每个session的所有action
  {
    $group: {
      _id: {
        docId: "$_id",
        sessionId: "$sessionActions.items.sessionId"
      },
      sessionActions: { $push: "$sessionActions.items" }
    }
  },
  // 4. 清理数据,只保留action和datetime字段
  {
    $project: {
      _id: 0,
      docId: "$_id.docId",
      sessionId: "$_id.sessionId",
      actions: {
        $map: {
          input: "$sessionActions",
          as: "item",
          in: { action: "$$item.action", datetime: "$$item.datetime" }
        }
      }
    }
  },
  // 5. 把每个session转换成键值对,准备转成顶层字段
  {
    $group: {
      _id: "$docId",
      sessions: {
        $push: {
          k: { $concat: ["session", { $toString: "$sessionId" }] },
          v: "$actions"
        }
      }
    }
  },
  // 6. 最终把session字段合并到文档顶层,得到你想要的格式
  {
    $replaceRoot: {
      newRoot: {
        $mergeObjects: [
          { _id: "$_id" },
          { $arrayToObject: "$sessions" }
        ]
      }
    }
  }
], { "allowDiskUse" : true } );

每个新增阶段的作用

  • $set + $reduce:遍历actions数组,遇到action: "start"就把session编号加1,给每个action打上对应的sessionId标记,这样我们就能区分哪些action属于同一个session。
  • $unwind:把带标记的数组展开成单个文档,方便按session分组。
  • $group(第二次):把同一个session里的action收集到一起,形成每个session的数组。
  • $project:清理掉多余的标记字段,只保留你需要的action和datetime。
  • $group(第三次):把每个session的数组转换成{k: "sessionX", v: [...actions...]}的格式,为转成顶层字段做准备。
  • $replaceRoot:把session键值对转换成文档的顶层字段,同时保留原文档的_id,就得到了你想要的最终格式。

注意事项

  • 如果你不想用session1、session2这种命名,可以修改$concat里的字符串,比如改成"userSession_"之类的。
  • 如果你的MongoDB版本比较新(4.4+),这个逻辑完全没问题;如果是旧版本,可能需要调整一些语法,但核心思路是一样的——先标记session,再分组转换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:39:36