MongoDB 数据分组

MongoDBBeginner
立即练习

介绍

在本实验中,你将学习 MongoDB 中数据聚合的基础知识。你将重点学习如何使用聚合管道来分组文档、对分组数据执行计算,然后过滤、排序和重塑结果。这些操作对于 MongoDB 中的数据分析和报告至关重要。在本实验结束时,你将能够熟练使用 $group 操作符以及其他关键的聚合阶段,从你的数据中提取有意义的见解。

按字段分组文档

数据聚合的第一步通常是根据共同字段对文档进行分组。在此步骤中,你将连接到 MongoDB shell,创建一个新的数据库和集合,然后使用 $group 操作符按类别对文档进行分组。

首先,在终端中运行以下命令打开 MongoDB shell:

mongosh

进入 shell 后,你将看到 test> 提示符。让我们切换到一个名为 salesdb 的新数据库并插入一些示例产品数据。当你首次向数据库和集合插入数据时,MongoDB 会自动创建它们。

将以下命令复制并粘贴到 mongosh shell 中:

use salesdb
db.products.insertMany([
  { category: "Electronics", brand: "Apple", price: 1200 },
  { category: "Electronics", brand: "Samsung", price: 800 },
  { category: "Electronics", brand: "Sony", price: 950 },
  { category: "Apparel", brand: "Nike", price: 150 },
  { category: "Apparel", brand: "Adidas", price: 120 },
  { category: "Books", brand: "Penguin", price: 25 },
  { category: "Books", brand: "Penguin", price: 35 }
]);

现在你有了数据,可以执行聚合操作了。以下命令按 category 字段对文档进行分组,并使用 $sum 累加器计算每个类别的总价。

db.products.aggregate([
  {
    $group: {
      _id: "$category",
      totalPrice: { $sum: "$price" }
    }
  }
]);

示例输出:

[
  { "_id": "Books", "totalPrice": 60 },
  { "_id": "Apparel", "totalPrice": 270 },
  { "_id": "Electronics", "totalPrice": 2950 }
]

让我们分解一下这个聚合阶段:

  • db.products.aggregate([...]): 这是执行聚合的方法。它接受一个由阶段组成的数组,形成一个管道。
  • $group: 这是对输入文档进行分组的阶段操作符。
  • _id: "$category": 这个表达式指定了分组的键。在这里,我们按 category 字段的值进行分组。$ 前缀表示字段路径。
  • totalPrice: { $sum: "$price" }: 这是一个累加器。它在输出文档中定义了一个名为 totalPrice 的新字段。$sum 操作符计算组内所有文档的 price 字段的总和。

使用多个累加器

$group 阶段可以同时计算多个聚合。你可以计算平均值、查找最小值或最大值,以及计算每个组中的项目数。此步骤演示了如何在单个 $group 阶段中使用多个累加器。

你仍然应该在 mongosh shell 中,使用 salesdb 数据库。

让我们编写一个更复杂的聚合,它计算每个类别的总价、平均价和产品数量。

db.products.aggregate([
  {
    $group: {
      _id: "$category",
      totalPrice: { $sum: "$price" },
      averagePrice: { $avg: "$price" },
      productCount: { $sum: 1 }
    }
  }
]);

示例输出:

[
  {
    "_id": "Books",
    "totalPrice": 60,
    "averagePrice": 30,
    "productCount": 2
  },
  {
    "_id": "Apparel",
    "totalPrice": 270,
    "averagePrice": 135,
    "productCount": 2
  },
  {
    "_id": "Electronics",
    "totalPrice": 2950,
    "averagePrice": 983.3333333333334,
    "productCount": 3
  }
]

这里是我们使用的新累加器:

  • averagePrice: { $avg: "$price" }: $avg 操作符计算组内所有文档的 price 字段的平均值。
  • productCount: { $sum: 1 }: 这是在组中计算文档数量的常用方法。对于每个文档,它将 1 加到总和中,从而有效地计算了文档数量。

过滤分组数据

在分组数据后,你通常需要根据计算出的值来过滤这些组。例如,你可能只想查看总销售额超过特定金额的类别。$match 阶段用于此目的。它可以放在 $group 阶段之后,以过滤分组后的文档。

让我们找出产品总价大于 500 的类别。

db.products.aggregate([
  {
    $group: {
      _id: "$category",
      totalPrice: { $sum: "$price" }
    }
  },
  {
    $match: {
      totalPrice: { $gt: 500 }
    }
  }
]);

示例输出:

[{ "_id": "Electronics", "totalPrice": 2950 }]

在此管道中:

  1. $group 阶段首先计算每个类别的 totalPrice
  2. 来自 $group 阶段的输出文档随后传递给 $match 阶段。
  3. $match 阶段过滤这些文档,只保留 totalPrice 字段大于 ($gt) 500 的文档。

这展示了聚合管道的强大之处,一个阶段的输出成为下一个阶段的输入。

对分组数据进行排序

在你获得分组和过滤后的数据后,最后一步通常是对其进行排序。$sort 阶段允许你根据一个或多个字段,以升序或降序排列文档。

让我们按类别对产品进行分组,计算总价,然后按 totalPrice 降序(从高到低)对结果进行排序。

db.products.aggregate([
  {
    $group: {
      _id: "$category",
      totalPrice: { $sum: "$price" }
    }
  },
  {
    $sort: {
      totalPrice: -1
    }
  }
]);

示例输出:

[
  { "_id": "Electronics", "totalPrice": 2950 },
  { "_id": "Apparel", "totalPrice": 270 },
  { "_id": "Books", "totalPrice": 60 }
]

$sort 阶段接受一个文档,该文档指定要排序的字段和排序顺序:

  • totalPrice: -1: 这将按 totalPrice 字段对文档进行排序。值 -1 指定降序。要按升序排序,应使用 1

你也可以按多个字段进行排序。例如,$sort: { category: 1, totalPrice: -1 } 将首先按类别名称字母顺序排序,然后在类别名称相同的情况下按总价降序排序。

使用 $project 重塑输出

有时 $group 阶段的输出格式并非你所需要的。例如,分组键默认命名为 _id$project 阶段允许你通过添加、删除或重命名字段来重塑输出文档。

让我们构建一个管道,按类别分组,按总价排序,然后重塑输出,为类别提供一个更具描述性的字段名。

db.products.aggregate([
  {
    $group: {
      _id: "$category",
      totalPrice: { $sum: "$price" }
    }
  },
  {
    $sort: {
      totalPrice: -1
    }
  },
  {
    $project: {
      _id: 0,
      category: "$_id",
      total: "$totalPrice"
    }
  }
]);

示例输出:

[
  { "category": "Electronics", "total": 2950 },
  { "category": "Apparel", "total": 270 },
  { "category": "Books", "total": 60 }
]

$project 阶段的工作方式如下:

  • _id: 0: 这将从输出中排除 _id 字段。默认情况下,_id 始终包含,除非被显式排除。
  • category: "$_id": 这将创建一个名为 category 的新字段,并将其值赋为现有 _id 字段的值。
  • total: "$totalPrice": 这将创建一个名为 total 的新字段,并将其值赋为 totalPrice 字段的值。

使用 $project 是格式化聚合管道最终输出以供应用程序或报告使用的强大方法。

总结

在本实验中,你学习了如何使用 MongoDB 聚合管道来分组和分析数据。你首先使用 $group 操作符对文档进行分组并计算总和。然后,你通过使用 $avg$sum: 1 等多个累加器来执行更复杂的计算,从而扩展了这一能力。你还学会了如何将聚合阶段链接在一起,使用 $match 过滤分组结果,使用 $sort 对其进行排序,以及使用 $project 将最终输出重塑为清晰、易读的格式。这些是任何使用 MongoDB 的开发者或数据分析师的基础技能。