简介
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,请查看我们的从零开始指南或如何添加到现有项目。