$group(聚合)
定义
兼容性
可以使用 $group
查找托管在以下环境中的部署:
MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本
语法
$group
阶段具有以下原型形式:
{ $group: { _id: <expression>, // Group key <field1>: { <accumulator1> : <expression1> }, ... } }
字段 | 说明 |
---|---|
_id | |
field | 可选。使用累加器操作符进行计算。 |
Considerations
性能
$group
是一个阻塞阶段,这会导致管道在处理数据前等待为阻塞阶段检索所有输入数据。 阻塞阶段可能会降低性能,因为它会减少具有多个阶段的管道的并行处理。 对于大型数据集,阻塞阶段还可能使用大量内存。
累加器操作符
<accumulator>
操作符必须是以下累加器操作符之一:
5.0 版本中的更改。
名称 | 说明 |
---|---|
返回用户定义的累加器函数的结果。 | |
返回每个群组的唯一表达式值数组。未定义数组元素的排序。 5.0 版中的更改:可在 | |
返回数值的平均值。忽略非数字值。 5.0 版中的更改:可在 | |
返回群组中第一个文档的表达式结果。 5.0 版中的更改:可在 | |
返回群组内前 5.2 版新增功能:可在 | |
返回群组中最后一份文档的表达式结果。 5.0 版中的更改:可在 | |
返回群组内后 5.2 版新增功能:可在 | |
返回每个群组的最大表达式值。 5.0 版中的更改:可在 | |
返回通过组合每个组的输入文档创建的文档。 | |
返回每个群组的最小表达式值。 5.0 版中的更改:可在 | |
返回每组中文档的大量表达式值。 5.0 版中的更改:可在 | |
返回输入值的总体标准偏差。 5.0 版中的更改:可在 | |
返回输入值的样本标准偏差。 5.0 版中的更改:可在 | |
返回数值的总和。忽略非数字值。 5.0 版中的更改:可在 | |
$group
和内存限制
如果 $group
阶段超过 100 兆字节 RAM,MongoDB 会将数据写入临时文件。但是,如果将 allowDiskUse 选项设置为 false
,$group
将返回错误。有关更多信息,请参阅聚合管道限制。
$group
性能优化
本节将介绍为提高 $group
性能而进行的优化。有些优化可以手动执行,有些优化由 MongoDB 在内部执行。
优化以返回每个群组的第一份或最后一份文档
如果同一字段和 $group
阶段的管道 sorts
和 groups
仅使用 $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
Retrieve Distinct Values
以下聚合操作使用 $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
下面的聚合操作指定了 null
的 _id
组,计算集合中所有文档的总销售额、平均数量和计数。
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
Pivot Data
在 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 } ])
将 按 进行分组title
author
以下聚合操作将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
向输出添加一个字段,其中包含每位作者的图书总份数。注意
生成的文档不得超过 16 MB 的 BSON 文档大小限制。
该操作将返回以下文档:
{ "_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
操作符的广泛示例。