$group 是什么
$group 是聚合管道里的分组统计阶段:_id 定义分组键,其余字段用累加器($sum、$avg、$min、$max、$push、$addToSet、$first、$last)计算指标。分组键可以是一整个字段、字段组合对象,或 null 表示把全部文档视作一组。它对应 SQL 的 GROUP BY,但结果字段名由你自由命名。
基本信息
| 语法 | { $group: { _id: <表达式>, 字段: { <累加器>: <表达式> } } } |
|---|---|
| 单字段分组 | _id: "$sku" |
| 组合键 | _id: { sku: "$sku", day: "$day" } |
| 全集合一组 | _id: null |
| 内存上限 | 每组结果受 100MB 限制,超出需 allowDiskUse |
| 位置 | 通常紧跟在 $match 之后 |
典型使用场景
- 按商品统计销量与订单数,做销量排行
- 按用户统计下单次数与累计金额
- 按天/月分桶统计趋势(配合 $dateToString)
- 去重计数:用 $addToSet 收集后统计集合大小
- 取每组首尾记录:先 $sort 再用 $first / $last
- 多维度交叉统计:分组键用对象组合多个字段
分组统计示例
分组统计示例
db.orders.aggregate([
{ $match: { status: "paid" } }, # 先过滤缩小数据量
{ $group: { _id: "$sku", totalQty: { $sum: "$qty" }, avgQty: { $avg: "$qty" }, orders: { $sum: 1 }, users: { $addToSet: "$user" } } },
{ $project: { sku: "$_id", totalQty: 1, orders: 1, userCount: { $size: "$users" }, _id: 0 } },
{ $sort: { totalQty: -1 } }
])常用累加器
| 累加器 | 作用 | 注意点 |
|---|---|---|
| $sum | 求和或计数 | 非数值会被忽略 |
| $avg | 求平均 | 依赖数值字段 |
| $min / $max | 取极值 | 可与 $sort 配合取首尾 |
| $push | 收集为数组 | 可能撑爆 16MB |
| $addToSet | 去重收集 | 元素多时内存占用高 |
| $first / $last | 取首尾值 | 需先排序才有确定语义 |
$group 的输出只有 _id 和累加器字段,需要保留原始字段名时用 $project 重命名;分组键用对象时 _id 也会是对象。
$push 会把每组所有值都收进数组,分组基数大时可能超过单文档 16MB 上限并直接失败,改用 $addToSet 或先聚合降维。
先 $match 再 $group 与先 $group 再 $match 结果相同但代价差很多:前者只对候选数据分组,后者要对全量文档分组后再过滤。
分组统计实践要点
- 分组键尽量选低基数字段,高基数分组会产出海量小桶
- 先在 $match 里缩小范围再进入 $group,能明显降低内存压力
- 需要保留原始字段时用 $first / $min 等累加器,不能直接输出裸列
- 时间分桶用 $dateToString 或 $dateTrunc,注意时区口径
- 结果要落库时在管道末尾用 $merge,可增量写入报表集合
- 结果规模不确定时先用 $limit 试跑,避免一次跑爆内存