$unwind + $count 不能直接统计所有数组元素个数,因为 $unwind 默认丢弃空数组、null 或缺失字段的文档,导致漏计;正确做法是用 $size 配合 $ifNull 和 $sum 求和。

为什么 $unwind + $count 不能直接统计所有数组元素个数
很多人试过先 $unwind 再跟 $count,结果发现返回的是“展开后文档总数”,而不是“原数组元素总个数”——因为如果某条文档的数组字段为空、null 或缺失,$unwind 默认会丢弃该文档,导致漏计数。
更隐蔽的问题是:当数组嵌套多层(比如 comments.replies),或需跳过空数组但保留空值占位时,单纯 $unwind 不够用。
-
$unwind默认行为是preserveNullAndEmptyArrays: false,空数组/缺失字段直接被过滤 - 若集合中有 100 条文档,其中 5 条的
tags是空数组[],那$unwind后只剩 95 条,$count返回 95,而非真实元素总数 - 想统计“所有数组元素个数之和”,本质是聚合阶段求和,不是数文档数
正确做法:用 $size 配合 $sum 求和
最稳的方式不是展开,而是直接取每个数组长度,再累加。只要字段存在且是数组,$size 就能返回其元素个数;对非数组或缺失字段,配合 $ifNull 可安全兜底。
示例:统计集合 posts 中所有 tags 数组的元素总数:
[
{
"$group": {
"_id": null,
"totalTagsCount": { "$sum": { "$size": { "$ifNull": ["$tags", []] } } }
}
}
]
-
$ifNull: ["$tags", []]把null或缺失字段转为空数组,避免$size报错 -
$size对空数组返回0,对非数组字段(如字符串)会报错,所以确保字段预期为数组 - 不需要
$unwind,性能更好,尤其在大集合上避免爆炸式展开
必须用 $unwind 的场景:要按数组元素做分组或去重统计
如果目标不是“总个数”,而是“有多少个不重复的标签”,或者“每个标签出现几次”,那就绕不开 $unwind —— 但得显式开启保留空值选项。
示例:统计所有非空 tags 元素的去重总数(即有多少个唯一 tag):
[
{
"$unwind": {
"path": "$tags",
"preserveNullAndEmptyArrays": true
}
},
{
"$match": { "tags": { "$ne": null } }
},
{
"$group": {
"_id": "$tags",
"count": { "$sum": 1 }
}
},
{
"$count": "uniqueTagCount"
}
]
-
preserveNullAndEmptyArrays: true让空数组也生成一条{ tags: null }文档 -
$match紧跟其后过滤掉这些null,只留真实元素 - 最后用
$count统计去重后的文档数,才是唯一 tag 总数
容易忽略的边界情况
真实业务数据里,数组字段经常不规整。这几个点不处理,统计结果就偏小:
- 字段类型混杂:有的文档
tags是数组,有的是字符串,$size会失败;应先用$isArray判断:{ "$size": { "$cond": [{ "$isArray": "$tags" }, "$tags", []] } } - 深层嵌套数组:如
comments[].replies[],需先$map提取每条评论的replies长度,再$sum - 超大数组触发内存限制:单个文档中数组超过 16MB 或元素超 1000 万时,
$unwind会失败,而$size不受影响
多数时候,你要的只是“所有数组元素加起来一共多少个”,$size + $sum 就够了;只有真要逐个摊开分析时,才值得承担 $unwind 的开销和复杂度。


















