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

MongoDB聚合管道实现多文档数组元素计数及分组回退问题

问题:统计多名玩家特殊单位总数的MongoDB聚合实现

我正在开发一个统计多名玩家特殊单位总数的程序。MongoDB中的文档结构简化后如下(rosterUnits数组长度为0至7,共7种特殊单位):

[
  {
    _id: ObjectId(...),
    member: {
      rosterUnits: [ "Unit1", "Unit2", "Unit3", "Unit4"]
    }
  },
  {
    _id: ObjectId(...),
    member: {
      rosterUnits: [ "Unit1", "Unit3"]
    }
  },
  ...
]

期望得到的结果如下:

{
  _id: ...,
  result: [
    {
      name: "Unit1",
      count: 2
    },
    {
      name: "Unit2",
      count: 1
    },
    {
      name: "Unit3",
      count: 2
    },
    ...
    {
      name: "Unit7",
      count: 0
    }
  ]
}

如何通过aggregate pipeline实现该需求?


补充说明(2023年2月7日)

实际文档结构复杂,现有聚合查询已完成公会匹配、成员拆分、关联玩家数据、统计GP等操作:

db.getCollection('guilds').aggregate([
    { $match: { 'profile.id': 'jrl9Q-_CRDGdMyNjTQH1rQ' } },
    //{ $match: { 'profile.id': { $in : ['jrl9Q-_CRDGdMyNjTQH1rQ', 'Tv_j9nhRTgufvH7C7oUYAA']} } },
    { $project: { member: 1, profile: 1 } },
    { $unwind: "$member" },
    {
        $lookup: {
            from: "players",
            localField: "member.playerId",
            foreignField: "playerId",
            pipeline: [
                {
                    $project: {
                        profileStat: 1,
                        rosterUnit: {
                            $let: {
                                vars: { gls: ["JABBATHEHUTT:SEVEN_STAR", "JEDIMASTERKENOBI:SEVEN_STAR", "GRANDMASTERLUKE:SEVEN_STAR", "LORDVADER:SEVEN_STAR", "GLREY:SEVEN_STAR", "SITHPALPATINE:SEVEN_STAR", "SUPREMELEADERKYLOREN:SEVEN_STAR"], },
                                in: {
                                    $reduce: {
                                        input: "$rosterUnit",
                                        initialValue: [],
                                        in: {
                                            $cond: {
                                                if: { $gt: [{ $indexOfArray: ["$$gls", "$$this.definitionId"] }, -1] },
                                                then: { $concatArrays: ["$$value", [{ definitionId: "$$this.definitionId", count: 1 }]] },
                                                else: { $concatArrays: ["$$value", []] }
                                            }
                                        },
                                    }
                                }
                            }
                        }
                    }
                }
            ],
            as: "member"
        }
    },
    {
        $addFields: {
            member: { $arrayElemAt: ["$member", 0] },
            gpStats: {
                $let: {
                    vars: { member: { $arrayElemAt: ["$member", 0] } },
                    in: {
                        $reduce: {
                            input: "$$member.profileStat",
                            initialValue: {},
                            in: {
                                characterGp: {
                                    $arrayElemAt: [
                                        "$$member.profileStat.value",
                                        {
                                            $indexOfArray: [
                                                "$$member.profileStat.nameKey",
                                                "STAT_CHARACTER_GALACTIC_POWER_ACQUIRED_NAME"
                                            ]
                                        }
                                    ]
                                },
                                shipGp: {
                                    $arrayElemAt: [
                                        "$$member.profileStat.value",
                                        {
                                            $indexOfArray: [
                                                "$$member.profileStat.nameKey",
                                                "STAT_SHIP_GALACTIC_POWER_ACQUIRED_NAME"
                                            ]
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    {
        $group: {
            _id: "$profile.id",
            guildName: { $first: "$profile.name" },
            memberCount: { $first: "$profile.memberCount" },
            guildGp: { $first: "$profile.guildGalacticPower" },
            totalGp: { $sum: { $sum: [{ $toInt: "$gpStats.characterGp" }, { $toInt: "$gpStats.shipGp" }] } },
            avgTotalGp: { $avg: { $sum: [{ $toInt: "$gpStats.characterGp" }, { $toInt: "$gpStats.shipGp" }] } },
            characterGp: { $sum: { $toInt: "$gpStats.characterGp" } },
            shipGp: { $sum: { $toInt: "$gpStats.shipGp" } },

        }
    }

])

我希望在$group阶段新增上述特殊单位计数的字段,若对member.rosterUnit执行$unwind后,如何回到原有的分组结构?


解决方案

你可以通过两次分组实现需求:先按「公会ID+单位ID」分组统计数量并累加GP数据,再按「公会ID」分组聚合最终结果,同时补全count为0的单位。以下是修改后的完整聚合管道:

db.getCollection('guilds').aggregate([
    { $match: { 'profile.id': 'jrl9Q-_CRDGdMyNjTQH1rQ' } },
    { $project: { member: 1, profile: 1 } },
    { $unwind: "$member" },
    {
        $lookup: {
            from: "players",
            localField: "member.playerId",
            foreignField: "playerId",
            pipeline: [
                {
                    $project: {
                        profileStat: 1,
                        rosterUnit: {
                            $let: {
                                vars: { gls: ["JABBATHEHUTT:SEVEN_STAR", "JEDIMASTERKENOBI:SEVEN_STAR", "GRANDMASTERLUKE:SEVEN_STAR", "LORDVADER:SEVEN_STAR", "GLREY:SEVEN_STAR", "SITHPALPATINE:SEVEN_STAR", "SUPREMELEADERKYLOREN:SEVEN_STAR"] },
                                in: {
                                    $reduce: {
                                        input: "$rosterUnit",
                                        initialValue: [],
                                        in: {
                                            $cond: {
                                                if: { $gt: [{ $indexOfArray: ["$$gls", "$$this.definitionId"] }, -1] },
                                                then: { $concatArrays: ["$$value", [{ definitionId: "$$this.definitionId", count: 1 }]] },
                                                else: "$$value"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            ],
            as: "member"
        }
    },
    {
        $addFields: {
            member: { $arrayElemAt: ["$member", 0] },
            gpStats: {
                $let: {
                    vars: { member: { $arrayElemAt: ["$member", 0] } },
                    in: {
                        characterGp: {
                            $arrayElemAt: [
                                "$$member.profileStat.value",
                                { $indexOfArray: ["$$member.profileStat.nameKey", "STAT_CHARACTER_GALACTIC_POWER_ACQUIRED_NAME"] }
                            ]
                        },
                        shipGp: {
                            $arrayElemAt: [
                                "$$member.profileStat.value",
                                { $indexOfArray: ["$$member.profileStat.nameKey", "STAT_SHIP_GALACTIC_POWER_ACQUIRED_NAME"] }
                            ]
                        }
                    }
                }
            },
            // 预先计算当前玩家的GP数据,避免后续重复计算
            playerTotalGp: { $sum: [{ $toInt: "$gpStats.characterGp" }, { $toInt: "$gpStats.shipGp" }] },
            playerCharacterGp: { $toInt: "$gpStats.characterGp" },
            playerShipGp: { $toInt: "$gpStats.shipGp" }
        }
    },
    // 展开玩家的特殊单位数组,保留无特殊单位的玩家(防止数据丢失)
    { $unwind: { path: "$member.rosterUnit", preserveNullAndEmptyArrays: true } },
    // 第一次分组:按公会+单位统计数量,同时累加GP数据
    {
        $group: {
            _id: {
                guildId: "$profile.id",
                unitId: { $ifNull: ["$member.rosterUnit.definitionId", "UNKNOWN"] }
            },
            count: { $sum: { $ifNull: ["$member.rosterUnit.count", 0] } },
            guildName: { $first: "$profile.name" },
            memberCount: { $first: "$profile.memberCount" },
            guildGp: { $first: "$profile.guildGalacticPower" },
            totalGp: { $sum: "$playerTotalGp" },
            avgTotalGp: { $avg: "$playerTotalGp" },
            characterGp: { $sum: "$playerCharacterGp" },
            shipGp: { $sum: "$playerShipGp" }
        }
    },
    // 第二次分组:按公会聚合,整理单位统计结果
    {
        $group: {
            _id: "$_id.guildId",
            guildName: { $first: "$guildName" },
            memberCount: { $first: "$memberCount" },
            guildGp: { $first: "$guildGp" },
            totalGp: { $first: "$totalGp" },
            avgTotalGp: { $first: "$avgTotalGp" },
            characterGp: { $first: "$characterGp" },
            shipGp: { $first: "$shipGp" },
            // 收集已统计的单位数据
            unitCounts: {
                $push: {
                    name: "$_id.unitId",
                    count: "$count"
                }
            }
        }
    },
    // 补全所有7种特殊单位,包括count为0的
    {
        $addFields: {
            result: {
                $map: {
                    input: ["JABBATHEHUTT:SEVEN_STAR", "JEDIMASTERKENOBI:SEVEN_STAR", "GRANDMASTERLUKE:SEVEN_STAR", "LORDVADER:SEVEN_STAR", "GLREY:SEVEN_STAR", "SITHPALPATINE:SEVEN_STAR", "SUPREMELEADERKYLOREN:SEVEN_STAR"],
                    as: "unit",
                    in: {
                        name: "$$unit",
                        count: {
                            $let: {
                                vars: { matched: { $first: { $filter: { input: "$unitCounts", cond: { $eq: ["$$this.name", "$$unit"] } } } } },
                                in: { $ifNull: ["$$matched.count", 0] }
                            }
                        }
                    }
                }
            }
        }
    },
    // 可选:移除临时字段unitCounts
    { $project: { unitCounts: 0 } }
])

关键步骤说明

  1. 预先计算玩家GP:在$addFields阶段新增playerTotalGp等字段,提前计算每个玩家的GP数据,减少后续重复计算。
  2. 安全展开单位数组:使用$unwind并设置preserveNullAndEmptyArrays: true,确保没有特殊单位的玩家数据不会被过滤,这类玩家的单位count会被计为0。
  3. 第一次分组(公会+单位):同时完成单位数量统计和GP数据累加,保证同一公会的GP统计准确。
  4. 第二次分组(按公会):将单位统计结果收集为数组,同时保留原有的公会信息和GP统计字段,回到你需要的分组结构。
  5. 补全count为0的单位:通过$map遍历所有7种特殊单位,用$filter匹配已统计的数量,未匹配到的设为0,生成符合需求的result数组。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 19:50:20