
本文介绍如何使用 mongodb 聚合管道(特别是 `$map` 与 `$size`)将存储的二维数组(如多选题各选项的用户 id 列表)高效转换为一维长度数组,适用于动态选项数量的问卷场景。
在构建在线测验系统时,常采用嵌套数组结构来记录用户投票结果:外层数组 results 的每个元素是一个子数组,代表某一道题中某一选项被选中的用户 ID 列表。例如:
{ "results": [["10938381", "10938382"], [], ["10938383", "10938384", "10938385"], ["10938386"]] }目标是将其聚合为各选项被选择次数的统计数组:
{ "results": [2, 0, 3, 1] }该需求的关键在于:不依赖固定长度(选项数动态为 2–5),且需在服务端完成计算(避免将原始大数组传至前端再处理)。
✅ 推荐方案:使用 $map + $size
MongoDB 原生聚合操作符 $map 可对数组每个元素执行表达式,配合 $size 即可逐项获取子数组长度。完整 Mongoose 聚合链如下:
YourModel.aggregate([
{
$project: {
results: {
$map: {
input: "$results",
in: { $size: "$$this" }
}
}
}
}
]);- input: "$results":指定遍历源字段(即二维数组);
- in: { $size: "$$this" }:对每个子数组($$this 表示当前迭代项)计算其元素个数;
- 输出结果自动保持原顺序与长度,天然支持任意长度的 results 数组(2、3、4 或 5 个子数组均可)。
⚠️ 注意事项
- $size 在 MongoDB 中仅适用于数组类型;若 results 字段可能缺失或非数组(如 null/undefined/字符串),建议前置校验:
{ $project: { results: { $ifNull: ["$results", []] } } } - 若需同时返回题目其他字段(如 _id、questionText),可在 $project 阶段显式保留:
{ $project: { _id: 1, questionText: 1, results: { $map: { /* ... */ } } } } - 此聚合无需索引优化(纯内存计算),但若后续需按某选项热度排序(如 {$sort: {"results.2": -1}}),注意 MongoDB 不支持对动态索引字段高效排序——此时建议预计算并存为独立字段(如 choiceCounts: [2,0,3,1])。
✅ 总结
使用 $map 遍历二维数组并结合 $size 获取子数组长度,是 MongoDB 中简洁、通用且性能良好的解决方案。它完全满足动态选项数、服务端实时聚合、语义清晰等核心诉求,推荐作为测验类应用中结果统计的标准实践。










