聚合管道
$match/$group/$lookup。
30 分钟
聚合管道
这一课你将能做到
- 用
$match / $group / $sort / $project / $limit写出多阶段聚合管道,完成「分组统计」类报表需求 - 用
$unwind展开数组、用$lookup做集合关联,并说明各自的使用边界 - 用 explain 观察聚合执行,知道管道顺序对性能的影响(先过滤后分组)
为什么要学
find 只能回答「哪些文档符合条件」,回答不了「每个用户的通过率是多少」「每节课的平均尝试次数」「本月活跃用户 Top10」——这类问题需要分组、统计、关联,这就是聚合管道(Aggregation Pipeline)的地盘。
人话:find 是「筛」,聚合是「流水线」——文档像流水线上的产品,经过一道道工序(stage),每道工序改变形状,最后一道出来的就是报表。会写 SQL 的 GROUP BY,就能类比学会聚合管道。
先建立图像
SQL: SELECT lessonSlug, AVG(score) FROM submissions
WHERE status='accepted' GROUP BY lessonSlug ORDER BY avg DESC LIMIT 5
聚合管道:
[ 全量文档 ]
│ $match { status: "accepted" } ← WHERE
▼
[ 过滤后文档 ]
│ $group { _id: "$lessonSlug", avg: {$avg: "$score"} } ← GROUP BY
▼
[ 分组结果 ]
│ $sort { avg: -1 } ← ORDER BY
▼
│ $limit 5 ← LIMIT
▼
[ 最终报表 ]
核心心法:管道越靠前放 $match 和 $limit,后面 stage 处理的数据越少。
跟着做
准备数据(本站提交记录 + 课时两个集合):
use codeforge
db.submissions.drop()
db.submissions.insertMany([
{ userId: 1024, lessonSlug: "mdbrm-intro", status: "accepted", score: 100, attempts: 1, submittedAt: new Date("2026-10-01") },
{ userId: 1024, lessonSlug: "mdbrm-types", status: "failed", score: 40, attempts: 2, submittedAt: new Date("2026-10-02") },
{ userId: 1024, lessonSlug: "mdbrm-types", status: "accepted", score: 88, attempts: 1, submittedAt: new Date("2026-10-03") },
{ userId: 2048, lessonSlug: "mdbrm-intro", status: "accepted", score: 60, attempts: 3, submittedAt: new Date("2026-10-04") },
{ userId: 2048, lessonSlug: "mdbrm-types", status: "accepted", score: 92, attempts: 2, submittedAt: new Date("2026-10-05") },
{ userId: 4096, lessonSlug: "mdbrm-intro", status: "failed", score: 30, attempts: 5, submittedAt: new Date("2026-10-06") }
])
db.lessons.drop()
db.lessons.insertMany([
{ slug: "mdbrm-intro", title: "MongoDB 概览", difficulty: 1 },
{ slug: "mdbrm-types", title: "BSON 与类型", difficulty: 2 }
])
步骤 1:$match + $group —— 每个用户的通过数
db.submissions.aggregate([
{ $match: { status: "accepted" } },
{ $group: {
_id: "$userId", // 分组键
passCount: { $sum: 1 }, // 每组计数
avgScore: { $avg: "$score" }, // 平均分
bestScore: { $max: "$score" }
}},
{ $sort: { passCount: -1 } }
])