$group 与累加器
本教程共 50 篇 · 第 30 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
30. $group 与累加器
本节目标:学会用 $group 按字段分组,并用 $sum/$avg/$max/$min/$first/$last/$push 等累加器在每组内做统计,得到「每个用户的订单数、总额、均值」这类报表。
分组统计是聚合最核心的能力。$group 把文档按某个「分组键」归拢成若干组,再对每组用累加器(Accumulator)汇总。我们先用订单数据看效果。
db.orders.insertMany([
{ _id: 1001, user_id: 1, status: "completed", total: 657 },
{ _id: 1002, user_id: 2, status: "completed", total: 1099 },
{ _id: 1003, user_id: 1, status: "shipped", total: 597 },
{ _id: 1004, user_id: 3, status: "pending", total: 528 },
{ _id: 1005, user_id: 2, status: "cancelled", total: 398 }
])
_id:分组键
$group 必须有一个 _id 字段,它的值决定「怎么分组」。写 "$_id字段" 就按那个字段分组;写 null 表示所有文档归为一组(全局统计)。
// 按 user_id 分组,统计每人的订单数
db.orders.aggregate([
{
$group: {
_id: "$user_id",
orderCount: { $sum: 1 }
}
}
])
结果会是 user_id=1 两组?不——用户 1 有两条(1001、1003),所以 _id:1 的 orderCount 是 2;用户 2 是 2;用户 3 是 1。
Note
$sum: 1表示「每组每来一条就加 1」,等价于计数。$sum: "$total"则是把 total 字段累加起来。
累加器一览
| 累加器 | 作用 | 示例 |
|---|---|---|
$sum | 求和 | { $sum: "$total" } |
$avg | 平均值 | { $avg: "$total" } |
$max | 组内最大 | { $max: "$total" } |
$min | 组内最小 | { $min: "$total" } |
$first | 组内首条 | { $first: "$status" } |
$last | 组内末条 | { $last: "$status" } |
$push | 收集成数组 | { $push: "$_id" } |
一个 $group 用多个累加器
可以在同一个 $group 里同时算多个指标,效率比多次分组高:
// 每人的订单数、总金额、平均金额、最大单
db.orders.aggregate([
{
$group: {
_id: "$user_id",
orderCount: { $sum: 1 },
totalAmount: { $sum: "$total" },
avgAmount: { $avg: "$total" },
maxAmount: { $max: "$total" }
}
}
])
Tip
$first/$last的结果依赖文档进入$group的顺序。如果顺序重要,记得在$group之前先用$sort排好。
$push:把组内值收进数组
$push 把每组的某个字段收集成一个数组,适合「列出某人所有订单号」:
// 每人对应的订单 _id 列表
db.orders.aggregate([
{
$group: {
_id: "$user_id",
orderIds: { $push: "$_id" }
}
}
])
全局统计:_id 写 null
不想分组、只想算全集的总和与均值,把 _id 设成 null:
// 所有订单的总额和均值
db.orders.aggregate([
{
$group: {
_id: null,
allTotal: { $sum: "$total" },
allAvg: { $avg: "$total" }
}
}
])
组合 $match 再 $group
统计「已完成订单」的各用户总额,先过滤再分组:
db.orders.aggregate([
{ $match: { status: "completed" } },
{
$group: {
_id: "$user_id",
completedAmount: { $sum: "$total" }
}
}
])
Warning
$group默认会把整个集合读进来分组,数据量大时很吃内存。MongoDB 对管道有 100MB 内存限制,超了会报错。要么前置$match缩小数据,要么加allowDiskUse: true允许落盘。
分组统计是报表的基石。下一章看聚合里的排序、分页和计数阶段。