MongoDB Aggregate 核心阶段($match/$project/$group/$sort)及分页详解
你想了解 MongoDB 聚合管道中 $match、$project、$group、$sort 的作用,以及聚合分页的实现方式,下面逐一拆解说明:
一、聚合管道(Aggregate)核心阶段详解
MongoDB 的 aggregate() 是基于管道思想的数据分析工具,每个管道阶段(如 $match、$project)会接收上一阶段的输出作为输入,处理后传递给下一阶段,最终得到目标结果。
1. $match:数据筛选(过滤)
作用
类似于普通查询的 find() 条件,用于在聚合管道初期过滤不需要的文档,只保留满足条件的文档进入后续管道阶段。
核心特点
- 尽可能放在管道最前面:减少后续阶段处理的数据量,提升聚合效率(可利用索引优化查询)
- 支持与
find()相同的查询操作符($eq、$gt、$in、$regex等)
示例
筛选出 age 大于 20 且 gender 为 "male" 的用户文档:
db.users.aggregate([
{
$match: {
age: { $gt: 20 },
gender: "male"
}
}
])2. $project:字段投影(筛选/重命名/计算)
作用
控制文档的字段展示形式,支持三种核心操作:
- 保留需要的字段、排除不需要的字段(类似
find()的第二个参数) - 重命名字段
- 生成新的计算字段(基于原有字段做运算、拼接等)
核心特点
- 用
1表示保留字段,0表示排除字段(_id字段默认保留,需手动指定_id: 0排除) - 支持通过
$引用原有字段,进行算术运算、字符串拼接等
示例
保留 name 字段,排除 _id,重命名 age 为 userAge,新增 fullName 字段(拼接 firstName 和 lastName):
db.users.aggregate([
{
$project: {
_id: 0,
name: 1,
userAge: "$age", // 重命名
fullName: { $concat: ["$firstName", " ", "$lastName"] } // 新增计算字段
}
}
])3. $group:数据分组聚合
作用
按照指定字段对文档进行分组,并对每组数据进行聚合计算(如统计数量、求和、平均值等),类似 SQL 中的 GROUP BY。
核心特点
- 必须指定
_id字段:用于定义分组依据(_id: "$gender"按性别分组,_id: null表示全局分组(所有文档为一组)) - 支持多种聚合操作符:
$sum(求和)、$avg(平均值)、$count(计数)、$max/$min(最大/最小值)等
示例
按 gender 字段分组,统计每组用户数量(userCount)和平均年龄(avgAge):
db.users.aggregate([
{
$group: {
_id: "$gender", // 分组依据:性别
userCount: { $count: {} }, // 计数(等价于 $sum: 1)
avgAge: { $avg: "$age" } // 计算平均年龄
}
}
])4. $sort:结果排序
作用
对当前管道阶段的文档进行排序,支持升序或降序排列,类似 SQL 中的 ORDER BY。
核心特点
- 用
1表示升序,-1表示降序 - 可按单个字段或多个字段排序(多字段排序时,按配置顺序优先级递减)
- 若在
$skip/$limit(分页阶段)前使用,可实现有序分页
示例
先按 age 降序排序,再按 name 升序排序:
db.users.aggregate([
{
$sort: {
age: -1, // 年龄降序
name: 1 // 姓名升序
}
}
])二、MongoDB 聚合分页实现
聚合分页依赖两个专用管道阶段:$skip(跳过指定数量文档)和 $limit(限制返回文档数量),通常与 $sort 配合使用,保证分页结果的一致性。
1. 分页核心阶段说明
| 管道阶段 | 作用 | 示例 |
|---|---|---|
$skip | 跳过前面 N 条文档,用于指定分页的起始位置 | $skip: 20(跳过前 20 条,对应第 3 页,每页 10 条) |
$limit | 限制返回的文档数量,用于指定每页显示条数 | $limit: 10(每页显示 10 条) |
2. 分页完整流程(含排序)
核心逻辑
$match(筛选)→ $sort(排序,保证分页有序)→ $skip(跳过前面页数的文档)→ $limit(获取当前页文档)
公式
- 跳过文档数 =
(当前页码 - 1) * 每页显示条数 - 示例:第 2 页,每页 10 条 → 跳过
(2-1)*10=10条
完整示例
查询 gender 为 "female" 的用户,按 age 降序排序,实现第 2 页、每页 10 条的分页:
// 分页参数
const pageNum = 2; // 当前页码
const pageSize = 10; // 每页条数
const skipCount = (pageNum - 1) * pageSize; // 跳过的文档数
db.users.aggregate([
// 1. 筛选数据(尽可能放在最前面)
{ $match: { gender: "female" } },
// 2. 排序(保证分页结果一致,必须在 skip/limit 前)
{ $sort: { age: -1 } },
// 3. 跳过前面的文档
{ $skip: skipCount },
// 4. 限制当前页返回条数
{ $limit: pageSize },
// 可选:投影需要的字段
{ $project: { _id: 0, name: 1, age: 1, gender: 1 } }
])3. 分页总数查询(可选,用于计算总页数)
若需要显示总页数(如“共 5 页”),需额外执行一次聚合,用 $count 统计符合条件的总文档数:
// 查询总条数
db.users.aggregate([
{ $match: { gender: "female" } }, // 与分页查询的筛选条件一致
{ $count: "total" } // 统计总文档数,返回 { total: 45 } 格式
])
// 总页数 = Math.ceil(总条数 / 每页条数) → Math.ceil(45 / 10) = 5 页三、注意事项
- 管道顺序影响性能:
$match尽量放在最前,$sort放在$skip/$limit前,避免无效排序/分页 - 大数据量分页:
$skip在数据量极大时(如跳过 10 万条以上)性能较差,可优化为“基于最后一条文档的字段值分页”(如按_id或时间戳) - 索引优化:为
$match和$sort的字段建立索引,大幅提升聚合效率
总结
$match:筛选文档,减少后续处理量;$project:控制字段展示/生成计算字段$group:按字段分组并做聚合统计;$sort:对文档进行升序/降序排列- 聚合分页:核心是
$skip(跳过前 N 条)+$limit(每页条数),配合$sort保证有序,总条数用$count查询 - 分页公式:跳过数 =
(当前页码-1) * 每页条数,管道顺序建议:$match→$sort→$skip→$limit