面试题答案
一键面试假设students
集合中的文档结构大致如下:
{
"name": "张三",
"age": 22,
"scores": {
"math": 85,
"english": 90,
"chinese": 78
}
}
聚合管道操作语句如下:
db.students.aggregate([
// 阶段1:将成绩字段展开为单个文档
{
$unwind: {
path: "$scores",
includeArrayIndex: "course"
}
},
// 阶段2:计算每个学生每个课程的年龄段
{
$addFields: {
ageGroup: {
$subtract: [
{ $subtract: ["$age", { $mod: ["$age", 5] }] },
0
]
}
}
},
// 阶段3:按年龄段和课程分组,计算平均成绩
{
$group: {
_id: { ageGroup: "$ageGroup", course: "$course" },
averageScore: { $avg: "$scores" }
}
},
// 阶段4:按年龄段和平均成绩降序排列
{
$sort: {
"_id.ageGroup": 1,
"averageScore": -1
}
},
// 阶段5:每个年龄段内保留平均成绩最高的课程
{
$group: {
_id: "$_id.ageGroup",
course: { $first: "$_id.course" },
averageScore: { $first: "$averageScore" }
}
},
// 阶段6:按年龄段升序排列最终结果
{
$sort: {
"_id": 1
}
},
// 阶段7:格式化输出结果
{
$project: {
ageGroup: "$_id",
course: 1,
averageScore: 1,
_id: 0
}
}
]);
性能优化策略
- 尽早过滤和投影:在聚合操作开始阶段,尽量减少数据量。虽然本题中未涉及明确的过滤,但在实际场景中,如果有条件可以尽早通过
$match
进行过滤。 - 合理使用
$unwind
:在需要将数组展开时,$unwind
操作是必要的,但要注意其性能影响。在本题中,展开成绩数组以方便后续计算平均成绩。 - 减少中间数据量:通过
$group
和$sort
等操作,在每个阶段尽量减少传递到下一个阶段的数据量。例如,在计算完平均成绩后,通过$sort
和再次$group
来保留每个年龄段平均成绩最高的课程,避免不必要的数据传递。 - 利用索引:如果在
age
字段上创建索引,可以加速按年龄段分组和排序的操作。例如,可以使用db.students.createIndex({ age: 1 })
来创建索引。