$group(聚合)
定义
兼容性
您可以使用 $group
对以下环境中的部署进行操作
MongoDB Atlas:MongoDB 在云中的完全托管服务
MongoDB 企业版:基于订阅、自我管理的 MongoDB 版本
MongoDB 社区版:源代码可用、免费使用并可自我管理的 MongoDB 版本
语法
$group
阶段的以下原型形式
{ $group: { _id: <expression>, // Group key <field1>: { <accumulator1> : <expression1> }, ... } }
字段 | 描述 |
---|---|
_id | |
field | 可选。 使用 累加器运算符。 计算得出 |
注意事项
性能
$group
是一个阻塞阶段,它会导致管道等待所有输入数据被检索到阻塞阶段后再处理数据。阻塞阶段可能会降低性能,因为它减少了具有多个阶段的管道的并行处理。阻塞阶段还可能因大型数据集而使用大量内存。
累加器操作符
必须使用以下累加器操作符之一:
已更改在版本5.0.
名称 | 描述 |
---|---|
返回用户定义累加器函数的结果。 | |
为每个组返回一个包含唯一表达式值的数组。数组元素的顺序是未定义的。 已更改在版本5.0: 在 | |
返回数值的平均值。忽略非数值值。 已更改在版本5.0: 在 | |
返回组中第一个文档的 表达式 的结果。 已更改在版本5.0: 在 | |
返回组内最后一个文档的 已更改在版本5.0: 在 | |
返回每个组的最高表达式值。 已更改在版本5.0: 在 | |
返回通过组合每个组的输入文档创建的文档。 | |
返回每个组的最低表达式值。 已更改在版本5.0: 在 | |
返回每个组中文档的表达式值数组。 已更改在版本5.0: 在 | |
返回输入值的总体标准差。 已更改在版本5.0: 在 | |
返回输入值的样本标准差。 已更改在版本5.0: 在 | |
返回数值的总和。忽略非数值。 已更改在版本5.0: 在 | |
$group
和内存限制
如果 $group
阶段超过 100 兆字节 RAM,MongoDB 会将数据写入临时文件。然而,如果 allowDiskUse 选项设置为 false
,则 $group
返回错误。更多信息,请参阅 聚合管道限制。
$group
性能优化
本节介绍了优化以提高 $group
的性能。有可以手动进行的优化和 MongoDB 内部进行的优化。
返回每个组的第一或最后文档的优化
如果一个管道同时对同一个字段进行排序
和分组
,并且$group
阶段只使用$first
或$last
累加器运算符,建议在分组字段上添加一个与排序顺序匹配的索引。在某些情况下,$group
阶段可以使用索引快速找到每个组的第一个或最后一个文档。
示例
如果名为foo
的集合包含一个索引{ x: 1, y: 1 }
,以下管道可以使用该索引来找到每个组的第一个文档
db.foo.aggregate([ { $sort:{ x : 1, y : 1 } }, { $group: { _id: { x : "$x" }, y: { $first : "$y" } } } ])
基于插槽的查询执行引擎
从版本5.2开始,MongoDB使用基于插槽的执行查询引擎来执行$group
阶段,如果
$group
是管道的第一个阶段。管道中所有前面的阶段也可以由基于插槽的执行引擎.
更多信息,请参阅$group
优化。
示例
统计集合中的文档数量
在 mongosh
中,创建一个名为 sales
的示例集合,包含以下文档
db.sales.insertMany([ { "_id" : 1, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("2"), "date" : ISODate("2014-03-01T08:00:00Z") }, { "_id" : 2, "item" : "jkl", "price" : Decimal128("20"), "quantity" : Int32("1"), "date" : ISODate("2014-03-01T09:00:00Z") }, { "_id" : 3, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32( "10"), "date" : ISODate("2014-03-15T09:00:00Z") }, { "_id" : 4, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") }, { "_id" : 5, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") }, { "_id" : 6, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") }, { "_id" : 7, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("10") , "date" : ISODate("2015-09-10T08:43:00Z") }, { "_id" : 8, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") }, ])
以下聚合操作使用 $group
阶段来统计 sales
集合中的文档数量
db.sales.aggregate( [ { $group: { _id: null, count: { $count: { } } } } ] )
该操作返回以下结果
{ "_id" : null, "count" : 8 }
此聚合操作等同于以下 SQL 语句
SELECT COUNT(*) AS count FROM sales
检索不同值
以下聚合操作使用 $group
阶段从 sales
集合中检索不同的项目值
db.sales.aggregate( [ { $group : { _id : "$item" } } ] )
该操作返回以下结果
{ "_id" : "abc" } { "_id" : "jkl" } { "_id" : "def" } { "_id" : "xyz" }
按项目分组条件
以下聚合操作按 item
字段对文档进行分组,计算每个项目的总销售额,并只返回总销售额大于或等于 100 的项目
db.sales.aggregate( [ // First Stage { $group : { _id : "$item", totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } } } }, // Second Stage { $match: { "totalSaleAmount": { $gte: 100 } } } ] )
- 第一阶段
- 在
$group
阶段,通过item
将文档分组以检索不同的项目值。此阶段为每个项目返回totalSaleAmount
。 - 第二阶段
$match
阶段筛选结果文档,只返回totalSaleAmount
大于或等于100的项目。
该操作返回以下结果
{ "_id" : "abc", "totalSaleAmount" : Decimal128("170") } { "_id" : "xyz", "totalSaleAmount" : Decimal128("150") } { "_id" : "def", "totalSaleAmount" : Decimal128("112.5") }
此聚合操作等同于以下 SQL 语句
SELECT item, Sum(( price * quantity )) AS totalSaleAmount FROM sales GROUP BY item HAVING totalSaleAmount >= 100
计算数量、总和和平均值
在 mongosh
中,创建一个名为 sales
的示例集合,包含以下文档
db.sales.insertMany([ { "_id" : 1, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("2"), "date" : ISODate("2014-03-01T08:00:00Z") }, { "_id" : 2, "item" : "jkl", "price" : Decimal128("20"), "quantity" : Int32("1"), "date" : ISODate("2014-03-01T09:00:00Z") }, { "_id" : 3, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32( "10"), "date" : ISODate("2014-03-15T09:00:00Z") }, { "_id" : 4, "item" : "xyz", "price" : Decimal128("5"), "quantity" : Int32("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") }, { "_id" : 5, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") }, { "_id" : 6, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") }, { "_id" : 7, "item" : "def", "price" : Decimal128("7.5"), "quantity": Int32("10") , "date" : ISODate("2015-09-10T08:43:00Z") }, { "_id" : 8, "item" : "abc", "price" : Decimal128("10"), "quantity" : Int32("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") }, ])
按年度天数分组
以下管道计算了2014年每天的销售额、平均销售数量和销售计数。
db.sales.aggregate([ // First Stage { $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } } }, // Second Stage { $group : { _id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }, count: { $sum: 1 } } }, // Third Stage { $sort : { totalSaleAmount: -1 } } ])
- 第一阶段
$match
阶段筛选文档,只将2014年的文档传递到下一阶段。- 第二阶段
$group
阶段按日期对文档进行分组,并计算每个组中文档的总销售额、平均数量和总计数。- 第三阶段
$sort
阶段按每个组的总销售额降序排序结果。
该操作返回以下结果
{ "_id" : "2014-04-04", "totalSaleAmount" : Decimal128("200"), "averageQuantity" : 15, "count" : 2 } { "_id" : "2014-03-15", "totalSaleAmount" : Decimal128("50"), "averageQuantity" : 10, "count" : 1 } { "_id" : "2014-03-01", "totalSaleAmount" : Decimal128("40"), "averageQuantity" : 1.5, "count" : 2 }
此聚合操作等同于以下 SQL 语句
SELECT date, Sum(( price * quantity )) AS totalSaleAmount, Avg(quantity) AS averageQuantity, Count(*) AS Count FROM sales WHERE date >= '01/01/2014' AND date < '01/01/2015' GROUP BY date ORDER BY totalSaleAmount DESC
按 null
分组
以下聚合操作指定了一个 _id
分组为 null
,计算集合中所有文档的总销售额、平均数量和计数。
db.sales.aggregate([ { $group : { _id : null, totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }, count: { $sum: 1 } } } ])
该操作返回以下结果
{ "_id" : null, "totalSaleAmount" : Decimal128("452.5"), "averageQuantity" : 7.875, "count" : 8 }
此聚合操作等同于以下 SQL 语句
SELECT Sum(price * quantity) AS totalSaleAmount, Avg(quantity) AS averageQuantity, Count(*) AS Count FROM sales
交叉数据
在 mongosh
中,创建一个名为 books
的示例集合,包含以下文档
db.books.insertMany([ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }, { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ])
按 author
对 title
进行分组
以下聚合操作将 books
集合中的数据交叉,以按作者分组标题。
db.books.aggregate([ { $group : { _id : "$author", books: { $push: "$title" } } } ])
操作返回以下文档
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] } { "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
按作者分组文档 author
以下聚合操作按 author
分组文档
db.books.aggregate([ // First Stage { $group : { _id : "$author", books: { $push: "$$ROOT" } } }, // Second Stage { $addFields: { totalCopies : { $sum: "$books.copies" } } } ])
- 第一阶段
$group
使用$$ROOT
系统变量按作者分组整个文档。此阶段将以下文档传递到下一阶段{ "_id" : "Homer", "books" : [ { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ] }, { "_id" : "Dante", "books" : [ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 } ] } - 第二阶段
$addFields
在输出中添加一个字段,包含每位作者书籍的总副本数。注意
生成的文档不得超过 BSON 文档大小 限制的 16 兆字节。
操作返回以下文档
{ "_id" : "Homer", "books" : [ { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ], "totalCopies" : 20 } { "_id" : "Dante", "books" : [ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 } ], "totalCopies" : 5 }
其他资源
《使用邮编数据集进行聚合》教程提供了一个关于 $group
操作符在常见用例中的详细示例。