分享至

简介

MongoDB 是一个基于文档的 NoSQL 数据库,其中数据存储在由 JSON 文档组成的集合中。与任何数据库一样,MongoDB 也有用户可以用来访问数据的语言。在 MongoDB 的情况下,这种语言被称为 MongoDB 查询语言,简称为 MQL。无论是 MQL 还是 SQL,数据库查询都可能从简单开始,但随着数据库的扩展,更复杂的查询就会出现。

MongoDB 聚合框架 是一种以一种将更复杂的查询分解为顺序操作的方式查询 MongoDB 文档的方法。它将复杂逻辑分解为顺序操作。在本指南中,我们将介绍 MongoDB 聚合框架,讨论常见的聚合阶段,最后将提供一个简单的聚合管道示例。

MongoDB 聚合框架是如何工作的?

MongoDB 聚合框架的目的是设计一个包含多个阶段的管道来处理文档。您从集合的数据开始,在管道的每个阶段之后,您都更接近最终结果,最终结果将是所需的文档。

每个阶段对文档执行一个操作。可以执行多个操作。例如,一个阶段可以过滤、分组,甚至计算数据的值。在每个阶段之后,输出的文档将传递到下一个阶段,依此类推,直到没有剩余的阶段。

使用聚合框架,可以实现几个目标。我们将深入研究实际操作语法的具体示例,但在理论上,书店小说部门的分析师可以设置一个框架,根据流派或作者对购买次数进行分组,以便为销售场所提供信息。他们能够通过添加阶段来迭代他们的查询,直到数据正是他们想要的。无论什么团队,都可以从数据中获得洞察,而这些洞察在使用聚合管道的组合时更容易发现。

哪些是最常见的 MongoDB 聚合操作?

截至本文撰写之时,MongoDB 框架中大约有 38 个聚合阶段可用。在本指南中,我们不会深入研究所有阶段,但您可以在 官方 MongoDB 文档 中查看整个列表。我们将花一些时间重点介绍一些将在示例管道中使用到的阶段。

  • $project:重新排列流中的每个文档,例如通过添加新字段或删除现有字段。对于每个输入文档,输出一个文档。
  • $match:筛选文档流,只允许匹配的文档以未修改的形式传递到下一个管道阶段。 $match 使用标准的 MongoDB 查询。对于每个输入文档,要么输出一个文档(匹配),要么输出零个文档(不匹配)。
  • $group:根据指定的标识符表达式对输入文档进行分组,并在指定的情况下,将累加器表达式应用于每个组。使用所有输入文档并为每个不同的组输出一个文档。输出文档仅包含标识符字段,以及在指定的情况下,累积的字段。
  • $sort:根据指定的排序键对文档流进行重新排序。只有顺序发生变化;文档保持未修改。对于每个输入文档,输出一个文档。
  • $skip:跳过前 n 个文档,其中 n 是指定的跳过数字,并将剩余的文档以未修改的形式传递到管道。对于每个输入文档,要么输出零个文档(对于前 n 个文档),要么输出一个文档(如果在 前 n 个文档之后)。
  • $limit:将前 n 个文档以未修改的形式传递到管道,其中 n 是指定的限制。对于每个输入文档,要么输出一个文档(对于前 n 个文档),要么输出零个文档(在 前 n 个文档之后)。
  • $unwind:从输入文档中解构数组字段,为每个元素输出一个文档。每个输出文档都用元素值替换数组。对于每个输入文档,输出 n 个文档,其中 n 是数组元素的个数,对于空数组,可以为零个文档。

聚合管道实战

为了用实际例子来展示聚合,我们将使用一个虚构的书店来设置一个管道。我们将从一些库存订单数据开始,创建一个管道,将这些原始数据转换为输出哪些作者有多个订单以及订购了多少本他们的书。

首先,我们将一些示例订单文档插入到集合 bookOrders 中。

db.bookOrders.insertMany ( [
{ _id: 0, first_name: "Fyodor", last_name: "Dostoyevsky", book_title: 'Demons', genre: 'Fiction', quantity: 10, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 1, first_name: "Fyodor", last_name: "Dostoyevsky", book_title: 'Brothers Karamosov', genre: 'Fiction', quantity: 25, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 2, first_name: "Jacques", last_name: "Derrida", book_title: 'The Politics of Friendship', genre: 'Fiction', quantity: 5, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 3, first_name: "Charles", last_name: "Dickens", book_title: 'Tale of Two Cities', genre: 'Fiction', quantity: 6, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 4, first_name: "James", last_name: "Joyce", book_title: 'Ulysses', genre: 'Fiction', quantity: 30, date: ISODate( "2021-03-13T11:19:30Z" ) },
{ _id: 5, first_name: "Henry David", last_name: "Thoreau", book_title: 'Walden', genre: 'Nonfiction', quantity: 15, date: ISODate( "2021-03-13T11:19:30Z" ) },
{ _id: 6, first_name: "Virginia", last_name: "Woolf", book_title: "A Room of One's Own", genre: 'Nonfiction',
quantity: 18, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 7, first_name: "Virginia", last_name: "Woolf", book_title: "Mr's Dalloway", genre: 'Fiction', quantity: 14, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 8, first_name: "Zadie", last_name: "Smith", book_title: 'White Teeth', genre: 'Fiction', quantity: 8, date: ISODate( "2022-10-21T11:19:30Z" ) },
{ _id: 9, first_name: "Charles", last_name: "Dickens", book_title: 'The Old Curiousity Shop', genre: 'Fiction', quantity: 6, date: ISODate( "2022-10-21T11:19:30Z" ) }
] )

现在我们的集合中有一些示例文档,我们可以开始我们的查询了。聚合管道使用 db.<collection-name>.aggregate() 方法运行。我们的目标是设计一个查询,返回一个列表,其中包含订购了最多小说总量的作者。下面是一个示例聚合查询,其中描述了每个阶段。

db.bookOrders.aggregate ( [
// Stage 1: The $match operator scans the collection for documents
matching the specified condition to pass to the next stage.
{
$match:
{
genre: "Fiction"
}
},
// Stage 2: The $project operator specifies which fields
in the matched documents should pass onto the next stage.
{
$project:
{
last_name : 1,
quantity : 1
}
},
// Stage 3: The $group operator groups the documents by the specified expression
and outputs a document for each unique grouping. The _id field specifies the distinct key to group by.
{
$group:
{
_id: "$last_name",
totalQuantity: { $sum: "$quantity" } }
},
// Stage 4: The $sort operator specifies the field(s) to sort by and the order.
-1 specifies a descending order and 1 specifies ascending order.
{
$sort:
{ totalQuantity: -1 }
}
] )

运行聚合查询后,我们将获得以下输出

[
{ _id: 'Dostoyevsky', totalQuantity: 35 },
{ _id: 'Joyce', totalQuantity: 30 },
{ _id: 'Woolf', totalQuantity: 14 },
{ _id: 'Dickens', totalQuantity: 12 },
{ _id: 'Smith', totalQuantity: 8 },
{ _id: 'Derrida', totalQuantity: 5 }
]

这个例子故意很简单,但它演示了聚合管道如何简化一些查询的复杂性。达到预期输出的每一步都清晰地分解并细化为一个明确的阶段。

根据集合和文档数据结构,在构建聚合管道时需要考虑优化。此外,此框架可能不适用于所有复杂的逻辑。它是依赖于具体情况的。

在示例的前两个阶段可以发现一个小的优化。通常,$match 运算符用于启动大多数管道,并且是最佳实践。但是,如果您的集合充满了非常大的文档,那么建议改为使用 $project 运算符。从 $project 开始会限制传递到下一阶段的字段数量,并在管道中更早地减少一些不必要的负载。

结论

在这篇文章中,我们介绍了 MongoDB 的聚合框架。我们讨论了它是什么以及它如何成为简化复杂逻辑和冗长查询的工具。聚合管道的阶段将逻辑分解为易于理解和操作的块。

聚合管道简化了数据访问,了解它的工作原理非常重要。MongoDB 的聚合框架可以做的事情远远超过我们在书店示例中演示的,我们希望这个介绍能让你踏上进一步探索的道路。

关于作者
Alex Emerich

Alex Emerich

Alex 是一个典型的观鸟爱好者,喜欢嘻哈音乐和阅读,也喜欢写关于数据库的文章。他现在住在柏林,他经常漫无目的地走在城市里,就像利奥波德·布鲁姆一样。