Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs 菜单
Docs 主页
/ /

$ 群组 (聚合阶段)

$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 版中的更改:可在 $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 阶段可以使用索引来快速找到每组的第一个文档。

例子

如果 movies集合包含索引{ year: 1, title: 1 },则以下管道可以使用该索引来查找群组的第一个文档:

db.movies.aggregate([
{
$sort: { year: 1, title: 1 }
},
{
$group: {
_id: { year: "$year" },
title: { $first: "$title" }
}
}
])

注意

从版本 7.0.17 开始,对于补丁版本的 7.0,默认不再启用基于插槽的查询执行引擎。如果您希望查询使用基于插槽的查询执行引擎,请升级到版本 8.0,默认下处于启用状态。

从版本 5.2 开始,如果满足以下任一条件,MongoDB 将使用基于插槽的执行查询引擎来执行 $group 阶段:

  • $group 是管道中的第一个阶段。

  • 管道中的所有先前阶段也可以由基于槽位的执行引擎执行。

有关更多信息,请参阅 $group 优化

本页上的示例使用 sample_mflix示例数据集 中的数据。有关如何将此数据集加载到自管理MongoDB 部署中的详细信息,请参阅加载示例数据集。如果对示例数据库进行了任何修改,则可能需要删除并重新创建数据库才能运行本页上的示例。

以下聚合操作使用 $group 阶段来计算 movies 集合中的文档数量:

db.movies.aggregate([
{
$group: {
_id: null,
count: { $count: {} }
}
}
])
[
{
_id: null,
count: 21349
}
]

这个聚合操作相当于以下 SQL 语句:

SELECT COUNT(*) AS count FROM movies

提示

以下聚合操作使用$group 阶段从rated movies集合中检索不同的 值:

db.movies.aggregate( [ { $group : { _id : "$rated" } } ] )
[
{
_id: 'TV-PG'
},
{
_id: 'PG'
},
{
_id: 'TV-14'
},
{
_id: 'OPEN'
},
{
_id: 'Not Rated'
},
{
_id: 'GP'
},
{
_id: 'TV-Y7'
},
{
_id: 'G'
},
{
_id: 'PG-13'
},
{
_id: null
},
{
_id: 'M'
},
{
_id: 'R'
},
{
_id: 'TV-MA'
},
{
_id: 'APPROVED'
},
{
_id: 'PASSED'
},
{
_id: 'Approved'
},
{
_id: 'AO'
},
{
_id: 'TV-G'
}
]

注意

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

受影响的唯一语义正确的管道实际上是 命令的逻辑等效项,其中在管道的开头或附近有一个distinct 阶段,并且$group $group前面没有$sort 阶段。

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

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

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

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

以下聚合操作按 rated字段对文档进行分组,计算每个评级的总运行时间,并仅返回总运行时间大于或等于 100000 的评级:

db.movies.aggregate(
[
// First Stage
{
$group: {
_id: "$rated",
totalRuntime: { $sum: "$runtime" }
}
},
// Second Stage
{
$match: { "totalRuntime": { $gte: 100000 } }
}
]
)
[
{
_id: 'PG-13',
totalRuntime: 250843
},
{
_id: 'R',
totalRuntime: 582318
},
{
_id: null,
totalRuntime: 967127
},
{
_id: 'PG',
totalRuntime: 191204
}
]
第一个阶段:
$group阶段按rated 对文档进行分组,以检索不同的评级值。此阶段返回每个评级群组的totalRuntime
第二个阶段:
$match阶段筛选生成的文档,仅返回totalRuntime 大于或等于100000 的评分。

这个聚合操作相当于以下 SQL 语句:

SELECT rated,
Sum(runtime) AS totalRuntime
FROM movies
GROUP BY rated
HAVING totalRuntime >= 100000

提示

以下管道计算 1910之前每年的总运行时间、平均运行时间和电影数量:

db.movies.aggregate([
{ $match: { "year": { $lt: 1910 } } },
{
$group: {
_id: "$year",
totalRuntime: { $sum: "$runtime" },
averageRuntime: { $avg: "$runtime" },
count: { $sum: 1 }
}
},
{ $sort: { totalRuntime: -1 } }
])
[
{ _id: 1909, totalRuntime: 14, averageRuntime: 14, count: 1 },
{ _id: 1903, totalRuntime: 11, averageRuntime: 11, count: 1 },
{ _id: 1896, totalRuntime: 2, averageRuntime: 1, count: 2 }
]
第一个阶段:
$match1910阶段会筛选文档,仅将 之前发布的电影传递到下一阶段。
第二个阶段:
$group阶段按年份对文档进行分组,并计算群组文档的总运行时间、平均运行时间和总数。
第三个阶段:
$sort阶段按群组的总运行时间对结果进行降序排序。

这个聚合操作相当于以下 SQL 语句:

SELECT year,
Sum(runtime) AS totalRuntime,
Avg(runtime) AS averageRuntime,
Count(*) AS count
FROM movies
WHERE year < 1910
GROUP BY year
ORDER BY totalRuntime DESC

提示

以下聚合操作指定 null_id群组,计算集合中所有文档的总运行时间、平均运行时间和计数。

db.movies.aggregate([
{
$group: {
_id: null,
totalRuntime: { $sum: "$runtime" },
averageRuntime: { $avg: "$runtime" },
count: { $sum: 1 }
}
}
])
[
{
_id: null,
totalRuntime: 2167458,
averageRuntime: 103.65652797704448,
count: 21349
}
]

这个聚合操作相当于以下 SQL 语句:

SELECT Sum(runtime) AS totalRuntime,
Avg(runtime) AS averageRuntime,
Count(*) AS count
FROM movies

提示

以下聚合操作对 movies集合中的数据进行透视,以按年份对书名进行群组:

db.movies.aggregate([
{ $match: { year: { $lt: 1910 } } },
{ $group: { _id: "$year", titles: { $push: "$title" } } },
{ $sort: { _id: 1 } }
])
[
{ _id: 1896, titles: [ 'The Kiss', 'The Kiss' ] },
{ _id: 1903, titles: [ 'The Great Train Robbery' ] },
{ _id: 1909, titles: [ 'A Corner in Wheat' ] }
]

以下聚合操作按年份对文档进行分组:

db.movies.aggregate([
{ $match: { year: { $lt: 1910 } } },
{ $group: { _id: "$year", movies: { $push: "$$ROOT" } } },
{
$addFields: {
totalRuntime: { $sum: "$movies.runtime" }
}
},
{ $sort: { _id: 1 } }
])
[
{ _id: 1896, movies: '...', totalRuntime: 2 },
{ _id: 1903, movies: '...', totalRuntime: 11 },
{ _id: 1909, movies: '...', totalRuntime: 14 }
]
第一个阶段:
$match 筛选文档,仅将 1910之前发布的电影传递到下一阶段。
第二个阶段:
$group使用$$ROOT 系统变量按年份对整个文档进行群组。
第三个阶段:

$addFields 在输出中添加一个字段,其中包含每年的电影总放映时间。

注意

生成的文档不得超过 16 MiB 的 BSON 文档大小限制。

第四阶段:
$sort_id按 升序对生成的文档进行排序。

本页上的C#示例使用Atlas示例数据集中的 sample_mflix数据库。要学习;了解如何创建免费的MongoDB Atlas 群集并加载示例数据集,请参阅MongoDB .NET/ C#驱动程序文档中的入门

以下 Movie 类对 sample_mflix.movies 集合中的文档进行建模:

public class Movie
{
public ObjectId Id { get; set; }
public int Runtime { get; set; }
public string Title { get; set; }
public string Rated { get; set; }
public List<string> Genres { get; set; }
public string Plot { get; set; }
public ImdbData Imdb { get; set; }
public int Year { get; set; }
public int Index { get; set; }
public string[] Comments { get; set; }
[BsonElement("lastupdated")]
public DateTime LastUpdated { get; set; }
}

注意

用于 Pascal Case 的 ConventionPack

此页面上的 C# 类在其属性名称中使用 Pascal 命名法,而 MongoDB 集合中的字段名称则使用 camel 命名法。为了解决这种差异,可以在应用程序启动时使用以下代码注册一个 ConventionPack

var camelCaseConvention = new ConventionPack { new CamelCaseElementNameConvention() };
ConventionRegistry.Register("CamelCase", camelCaseConvention, type => true);

要使用MongoDB .NET/C# 驱动程序将 $group 阶段添加到聚合管道,请对 PipelineDefinition 对象调用 Group() 方法。

以下示例创建了一个管道阶段,该阶段按文档的 Rated字段的值对文档进行分组。每个群组的评分显示在每个输出文档中名为 Rating 的字段中。每个输出文档还包含名为 TotalRuntimeMedianRuntimeNinetiethPercentileRuntime 的字段,它们存储群组中电影的总运行时间值、中位数和第 90 个百分位数运行时间值。

var pipeline = new EmptyPipelineDefinition<Movie>()
.Group(
id: m => m.Rated,
group: g => new
{
Rating = g.Key,
TotalRuntime = g.Sum(m => m.Runtime),
MedianRuntime = g.Select(m => m.Runtime).Median(),
NinetiethPercentileRuntime = g.Select(m => m.Runtime).Percentile(new[] { 0.9 })
}
);

本页上的 Node.js 示例使用 Atlas 示例数据集中的 sample_mflix数据库。要学习如何创建免费的MongoDB Atlas 集群并加载示例数据集,请参阅MongoDB Node.js驱动程序文档中的入门

要使用MongoDB Node.js驱动程序将 $group 阶段添加到聚合管道,请在管道对象中使用 $group操作符。

以下示例创建了一个管道阶段,该阶段按文档的 rated字段的值对文档进行分组。每个输出文档都包含一个 rating字段,用于存储每个群组的评分。每个输出文档还包含一个名为 totalRuntime 的字段,用于存储该群组中所有电影的总运行时间。然后,该示例运行聚合管道:

const pipeline = [
{
$group: {
_id: "$rated",
rating: { $first: "$rated" },
totalRuntime: { $sum: "$runtime" }
}
}
];
const cursor = collection.aggregate(pipeline);
return cursor;

Group and Total Data 教程提供了常见使用案例中 $group 操作符的广泛示例。

要学习;了解有关相关管道阶段的更多信息,请参阅 $addFields 指南。

后退

$graphLookup

获得技能徽章

免费掌握“数据转换基础”!

了解详情

在此页面上