Docs 菜单
Docs 主页
/
MongoDB Manual
/ / /

$group(聚合)

在此页面上

  • 定义
  • 兼容性
  • 语法
  • Considerations
  • 示例
  • 其他资源
$group

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

组键通常是一个字段或一组字段。组键也可以是表达式的结果。使用 $group 管道阶段中的 _id 字段来设置组键。有关用法示例,请参阅下文。

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

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

注意

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

可以使用 $group 查找托管在以下环境中的部署:

  • MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务

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

  • MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本

$group 阶段具有以下原型形式:

{
$group:
{
_id: <expression>, // Group key
<field1>: { <accumulator1> : <expression1> },
...
}
}
字段
说明

_id

必需。 _id表达式指定群组键。如果指定的_id 值为空值或任何其他常量值,$group 阶段将返回聚合所有输入文档值的单个文档。请参阅按空值分组示例。

field

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

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

<accumulator> 操作符必须是以下累加器操作符之一:

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中可用,也可作为表达式使用。

返回中位数(第 50 百分位数)的近似标量值。

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 阶段的管道 sortsgroups 仅使用 $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" }

注意

当您使用$group 检索分分片的集合中的不同值时,如果操作结果为DISTINCT_SCAN ,则结果可能包含孤立文档。

The only semantically correct 管道 that is impacted is effectively a logical equivalent of a command, where there isdistinct a$group stage at or near the beginning of the 管道 and the$group is not preceded by a stage$sort

示例,以下形式的 $group 操作可能会产生 DISTINCT_SCAN

{ $group : { _id : "$<field>" } }

有关检索非重复值行为的更多信息,请参阅非重复命令行为。

要查看操作是否会产生DISTINCT_SCAN ,请检查操作的解释结果。

下面的聚合操作按 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 组,计算集合中所有文档的总销售额、平均数量和计数。

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 向输出添加一个字段,其中包含每位作者的图书总份数。

注意

生成的文档不得超过 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 操作符的广泛示例。

后退

$graphLookup