文档菜单
文档首页
/
MongoDB 手册
/ / /

$group(聚合)

本页内容

  • 定义
  • 兼容性
  • 语法
  • 注意事项
  • 示例
  • 附加资源
$group

$group 阶段根据“分组键”将文档分组。输出为每个唯一分组键一个文档。

分组键通常是字段,或字段组。分组键也可以是表达式的结果。在 $group 管道阶段使用 _id 字段设置分组键。以下为使用示例.

$group 阶段输出中,_id 字段设置为该文档的分组键。

输出文档还可以包含使用 累加器表达式。 设置的附加字段。

注意

$group 不对其输出文档进行排序。

您可以使用 $group 对以下环境中的部署进行操作

  • MongoDB Atlas:MongoDB 在云中的完全托管服务

  • MongoDB 企业版:基于订阅、自我管理的 MongoDB 版本

  • MongoDB 社区版:源代码可用、免费使用并可自我管理的 MongoDB 版本

$group 阶段的以下原型形式

{
$group:
{
_id: <expression>, // Group key
<field1>: { <accumulator1> : <expression1> },
...
}
}
字段
描述
_id
必需。 _id 表达式指定分组键。如果您指定了 _id 为 null 或任何其他常量值,则 $group 阶段将返回一个文档,该文档聚合所有输入文档中的值。请参阅按 Null 分组的示例。
field
可选。 使用 累加器运算符。 计算得出

_id累加器运算符 可以接受任何有效的 expression。有关表达式更多信息,请参阅 表达式运算符。

$group 是一个阻塞阶段,它会导致管道等待所有输入数据被检索到阻塞阶段后再处理数据。阻塞阶段可能会降低性能,因为它减少了具有多个阶段的管道的并行处理。阻塞阶段还可能因大型数据集而使用大量内存。

必须使用以下累加器操作符之一:

已更改在版本5.0.

名称
描述
返回用户定义累加器函数的结果。

为每个组返回一个包含唯一表达式值的数组。数组元素的顺序是未定义的。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回数值的平均值。忽略非数值值。

已更改在版本5.0: $setWindowFields 阶段中可用。

根据指定的排序顺序返回组内的最低元素。

新增在版本5.2.

$group$setWindowFields 阶段中可用。

根据指定的排序顺序,返回组内最低 n 字段的聚合。

新增在版本5.2.

$group$setWindowFields 阶段中可用。

返回组中的文档数量。

$count 管道阶段不同。

新增在版本5.0: $group$setWindowFields 阶段中可用。

返回组中第一个文档的 表达式 的结果。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回组内前n个元素的聚合。只有当文档处于定义的顺序时才有意义。与$firstN数组运算符不同。

新增在版本5.2: $group表达式$setWindowFields阶段中可用。

返回组内最后一个文档的表达式结果。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回组内最后n个元素的聚合。只有当文档处于定义的顺序时才有意义。与$lastN数组运算符不同。

新增在版本5.2: $group表达式$setWindowFields阶段中可用。

返回每个组的最高表达式值。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回组内n个最大值元素的聚合。与$maxN数组运算符不同。

新增在版本5.2.

$group$setWindowFields和作为表达式中可用。

返回中位数(50th 百分位数)的近似值,作为标量值。

新增在版本7.0.

此运算符作为累加器在这些阶段可用

它还可用作聚合表达式

返回通过组合每个组的输入文档创建的文档。

返回每个组的最低表达式值。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回组内n个最小值元素的聚合。与$minN数组运算符不同。

新增在版本5.2.

$group$setWindowFields和作为表达式中可用。

返回与指定的百分位数值对应的标量值数组。

新增在版本7.0.

此运算符作为累加器在这些阶段可用

它还可用作聚合表达式

返回每个组中文档的表达式值数组。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回输入值的总体标准差。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回输入值的样本标准差。

已更改在版本5.0: $setWindowFields 阶段中可用。

返回数值的总和。忽略非数值。

已更改在版本5.0: $setWindowFields 阶段中可用。

根据指定的排序顺序返回组内的最高元素。

新增在版本5.2.

$group$setWindowFields 阶段中可用。

根据指定的排序顺序返回组内顶部n个字段的聚合。

新增在版本5.2.

$group$setWindowFields 阶段中可用。

如果 $group 阶段超过 100 兆字节 RAM,MongoDB 会将数据写入临时文件。然而,如果 allowDiskUse 选项设置为 false,则 $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

提示

另请参阅

以下聚合操作指定了一个 _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 }
])

以下聚合操作将 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 分组文档

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 操作符在常见用例中的详细示例。

返回

$graphLookup