简介
MongoDB 是一个基于文档的 NoSQL 数据库,数据以集合的形式组织,每个集合由 JSON 文档组成。与任何数据库一样,MongoDB 有一种用户可以用来访问数据的语言。在 MongoDB 的情况下,这种语言是 MongoDB 查询语言,简称 MQL。无论是 MQL 还是 SQL,数据库查询都可以从简单开始,但随着数据库的扩展,会出现更复杂的查询。
MongoDB 聚合框架是一种从 MongoDB 查询文档的方法,它将这些更复杂的查询分解开来。它将复杂逻辑分解为顺序操作。在本指南中,我们将介绍 MongoDB 聚合框架,讨论常见的聚合阶段,并以一个简单的聚合管道示例结束。
如果您正在使用 MongoDB,请查看 Prisma 的 MongoDB 连接器!您可以使用 Prisma Client 自信地管理生产 MongoDB 数据库。
要开始使用 MongoDB 和 Prisma,请查看我们的 从头开始指南 或如何添加到现有项目。
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 documentsmatching the specified condition to pass to the next stage.{$match:{genre: "Fiction"}},// Stage 2: The $project operator specifies which fieldsin 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 expressionand 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 的聚合框架可以做比我们在书店示例中展示的更多的事情,我们希望这篇介绍能让您进一步探索。
如果您正在使用 MongoDB,请查看 Prisma 的 MongoDB 连接器!您可以使用 Prisma Client 自信地管理生产 MongoDB 数据库。
要开始使用 MongoDB 和 Prisma,请查看我们的 从头开始指南 或如何添加到现有项目。
