分享到

简介

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 是典型的观鸟、热爱嘻哈的爱书之人,也喜欢撰写关于数据库的文章。他目前居住在柏林,在那里可以看到他像利奥波德·布卢姆一样漫无目的地走在城市中。