定义
兼容性
可以使用 $group 查找托管在以下环境中的部署:
MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本
语法
$group 阶段具有以下原型形式:
{ $group: { _id: <expression>, // Group key <field1>: { <accumulator1> : <expression1> }, ... } }
字段 | 说明 |
|---|---|
| 必需。 |
|
Considerations
性能
$group 是一个阻塞阶段,这会导致管道在处理数据前等待为阻塞阶段检索所有输入数据。阻塞阶段可能会降低性能,因为它会减少具有多个阶段的管道的并行处理。对于大型数据集,阻塞阶段还可能使用大量内存。
累加器操作符
<accumulator> 操作符必须是以下累加器操作符之一:
名称 | 说明 |
|---|---|
返回用户定义的累加器函数的结果。 | |
返回每个群组的唯一表达式值数组。未定义数组元素的排序。 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 阶段可以使用索引来快速找到每组的第一个文档。
例子
如果 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
Retrieve Distinct Values
以下聚合操作使用$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
提示
db.collection.countDocuments(),使用$sum表达式包装$group聚合阶段。
分组方式: null
以下聚合操作指定 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
提示
db.collection.countDocuments(),使用$sum表达式包装$group聚合阶段。
Pivot Data
按年份对标题进行分组
以下聚合操作对 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 } ]
本页上的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; } [] 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 的字段中。每个输出文档还包含名为 TotalRuntime、MedianRuntime 和 NinetiethPercentileRuntime 的字段,它们存储群组中电影的总运行时间值、中位数和第 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 指南。