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
相关产品推荐
相关产品推荐

