第9章 MongoDB聚合管道实战(一):$project操作符的应用与技巧

1,744 阅读3分钟

在上一篇文章【MongoDB性能提升--索引详解与实战案例】中,通过上述索引的设置与应用,我们已经看到了查询性能的显著改善。然而,在处理更加复杂的数据分析需求时,仅仅依靠索引是不够的。MongoDB的聚合框架提供了强大的工具来处理数据的汇总、过滤等操作,使我们能够在数据库层面完成数据分析工作。下文中,我们将探讨如何利用MongoDB的聚合管道来实现这样的高级功能。

知识回顾

MongoDB 的聚合管道提供了一系列强大的操作符,用于处理和分析数据。以下是一些常用的聚合阶段及其介绍:

1. $match

  • 功能:过滤文档以指定条件。

  • 使用场景:在数据流经过管道时,选择符合条件的文档进行后续处理。

  • 示例

    { $match: { status: "active" } }
    

2. $group

  • 功能:将多个文档分组,并对每个组进行聚合操作(例如求和、计数等)。

  • 使用场景:进行汇总计算,如计算总销售额、平均值等。

  • 示例

    {
      $group: {
        _id: "$customer_id",
        totalSales: { $sum: "$amount" }
      }
    }
    

3. $sort

  • 功能:对文档进行排序。

  • 使用场景:根据特定字段的值进行升序或降序排序。

  • 示例

    { $sort: { totalSales: -1 } } // 降序排序
    

4. $project

  • 功能:选择、重命名或计算字段。

  • 使用场景:输出所需的字段,进行字段的格式化或计算。

  • 示例

    {
      $project: {
        stu_name: 1,
        averageScore: { $avg: "$scores" }
      }
    }
    

5. $limit

  • 功能:限制返回的文档数量。

  • 使用场景:控制输出结果集的大小。

  • 示例

    { $limit: 5 }
    

6. $skip

  • 功能:跳过指定数量的文档。

  • 使用场景:常用于分页,跳过前面的文档。

  • 示例

    { $skip: 10 }
    

7. $unwind

  • 功能:将数组字段拆分为多条文档,每个文档只包含数组中的一个元素。

  • 使用场景:处理文档中包含数组字段的情况,便于进一步分析。

  • 示例

    { $unwind: "$items" }
    

8. $addFields

  • 功能:向文档添加新字段或更新现有字段。

  • 使用场景:在管道中动态计算和添加字段。

  • 示例

    {
      $addFields: {
        totalPrice: { $multiply: ["$price", "$quantity"] }
      }
    }
    

9. $replaceRoot

  • 功能:替换输入文档为指定文档。

  • 使用场景:当需要将嵌套文档提升为根文档时使用。

  • 示例

    {
      $replaceRoot: { newRoot: "$details" }
    }
    

10. $facet

  • 功能:进行多管道并行处理,可以在同一集合上进行不同的聚合查询。

  • 使用场景:在一个聚合操作中获得多个不同的结果。

  • 示例

    {
      $facet: {
        totalSales: [{ $group: { _id: null, total: { $sum: "$amount" } }}],
        averageSales: [{ $group: { _id: null, average: { $avg: "$amount" } }}]
      }
    }
    

11. $lookup

  • 功能:进行集合间的连接操作(类似 SQL 的 JOIN)。

  • 使用场景:将来自其他集合的数据合并到当前文档。

  • 示例

    {
      $lookup: {
        from: "customers",
        localField: "customer_id",
        foreignField: "_id",
        as: "customerInfo"
      }
    }
    

任务描述

使用 MongoDB 的聚合管道计算每位学生的平均成绩,并找出表现最好的学生。利用聚合管道中的 $project$sort 和 $limit 等操作符,可以方便地进行数据分析与处理,满足具体的业务需求。 假设我们有一个 student 集合,记录了学生的姓名、学号和他们在不同科目的成绩。集合中的文档结构如下:

{
  "_id": ObjectId("..."),
  "stu_no": "S0001",
  "stu_name": "张小峰",
  "scores": {
    "math": 85,
    "english": 92,
    "science": 78
  }
}

我们希望计算每位学生的平均成绩,并根据平均成绩找出前两名表现最好的学生。

任务准备

首先,我们需要插入一些示例数据:

db.students.insertMany([
  {
    stu_no: "S0001",
    stu_name: "张小峰",
    scores: { math: 85, english: 92, science: 78 }
  },
  {
    stu_no: "S0002",
    stu_name: "李小平",
    scores: { math: 78, english: 80, science: 88 }
  },
  {
    stu_no: "S0003",
    stu_name: "刘知民",
    scores: { math: 95, english: 90, science: 85 }
  },
  {
    stu_no: "S0004",
    stu_name: "黄海涛",
    scores: { math: 70, english: 75, science: 80 }
  },
  {
    stu_no: "S0005",
    stu_name: "李小苒",
    scores: { math: 60, english: 70, science: 65 }
  }
]);

任务实施

聚合管道实现

使用以下聚合管道计算学生的平均成绩,并找出前两名的表现最佳学生:

db.students.aggregate([
  {
    $project: {
      stu_no: 1,
      stu_name: 1,
      averageScore: {
        $avg: [
          "$scores.math",
          "$scores.english",
          "$scores.science"
        ]
      }
    }
  },
  {
    $sort: { averageScore: -1 } // 按平均成绩降序排序
  },
  {
    $limit: 2 // 只返回前两名
  }
]);

聚合管道分解

  1. $project

    • stu_no: 1 和 stu_name: 1:保留学生的学号和姓名。
    • averageScore:计算每位学生的平均成绩。$avg 操作符用于计算分数的平均值。
  2. $sort

    • { averageScore: -1 }:按照计算出的平均成绩进行降序排序。
  3. $limit

    • 限制结果只返回前两名表现最佳的学生。

执行结果

执行上述聚合管道后,您将获得类似如下的结果:

[  { "stu_no": "S0003", "stu_name": "刘知民", "averageScore": 90 },  { "stu_no": "S0001", "stu_name": "张小峰", "averageScore": 85 }]

这表明学生 刘知民 的平均成绩最高,达到了 90 分,其次是学生 张小峰,平均成绩为 85 分。

实验实训

小任务1:提取特定字段

编写一个聚合查询,从students集合中提取所有学生的姓名和他们的数学成绩。

预期输出:

学生姓名 (stu_name) 数学成绩 (math_score) MongoDB聚合查询:

db.students.aggregate([
  {
    $project: {
      stu_name: 1,
      math_score: "$scores.math"
    }
  }
]);

小任务2:重命名字段

编写一个聚合查询,将所有学生的成绩字段名从scores下的科目名(如"math")改为统一的格式,如math_score。

预期输出:

学生姓名 (stu_name) 数学成绩 (math_score) 英语成绩 (english_score) 科学成绩 (science_score) MongoDB聚合查询:

db.students.aggregate([
  {
    $project: {
      stu_name: 1,
      math_score: "$scores.math",
      english_score: "$scores.english",
      science_score: "$scores.science"
    }
  }
]);

小任务3:计算总分

编写一个聚合查询,计算每个学生的总分(即数学、英语和科学三门科目的成绩之和)。

预期输出:

学生姓名 (stu_name) 总分 (total_score) MongoDB聚合查询:

db.students.aggregate([
  {
    $project: {
      stu_name: 1,
      total_score: {
        $add: ["$scores.math", "$scores.english", "$scores.science"]
      }
    }
  }
]);

小任务4:计算各科目成绩占比

编写一个聚合查询,计算每个学生在各个科目中的成绩占总分的比例(百分比形式)。

预期输出:

学生姓名 (stu_name) 数学成绩占比 (math_percentage) 英语成绩占比 (english_percentage) 科学成绩占比 (science_percentage) MongoDB聚合查询:

db.students.aggregate([
  {
    $project: {
      stu_name: 1,
      total_score: {
        $add: ["$scores.math", "$scores.english", "$scores.science"]
      },
      math_percentage: {
        $divide: ["$scores.math", { $add: ["$scores.math", "$scores.english", "$scores.science"] }]
      },
      english_percentage: {
        $divide: ["$scores.english", { $add: ["$scores.math", "$scores.english", "$scores.science"] }]
      },
      science_percentage: {
        $divide: ["$scores.science", { $add: ["$scores.math", "$scores.english", "$scores.science"] }]
      }
    }
  },
  {
    $project: {
      stu_name: 1,
      math_percentage: { $multiply: ["$math_percentage", 100] },
      english_percentage: { $multiply: ["$english_percentage", 100] },
      science_percentage: { $multiply: ["$science_percentage", 100] }
    }
  }
]);

小任务5:筛选出总分超过250分的学生

编写一个聚合查询,筛选出所有三门科目总分超过250分的学生。

预期输出:

学生姓名 (stu_name) 总分 (total_score) MongoDB聚合查询:

db.students.aggregate([
  {
    $project: {
      stu_name: 1,
      total_score: {
        $add: ["$scores.math", "$scores.english", "$scores.science"]
      }
    }
  },
  {
    $match: {
      total_score: { $gt: 250 }
    }
  }
]);

通过这些小任务,您可以更好地理解如何使用$project阶段来选择、重命名、计算字段,并根据需要进行数据筛选。这些技能对于处理复杂的数据分析任务至关重要。

总结

通过以上案例,我们展示了如何使用 MongoDB 的聚合管道计算每位学生的平均成绩,并找出表现最好的学生。利用聚合管道中的 $project$sort 和 $limit 等操作符,可以方便地进行数据分析与处理,满足具体的业务需求。这种灵活性使得 MongoDB 成为处理复杂数据分析任务的理想选择。下一篇我们继续讲解聚合管理$group应用。