在上一篇mongodb聚合操作之Aggregation Pipeline中详细介绍了什么是mongodb聚合操作中的Aggregation Pipeline以及参数细节。本篇将开始介绍Aggregation聚合操作中的group分组操作,相当于mysql的group by聚合。
1.?简介
说明:
按照指定的_id表达式对输入文档进行分组,并对每个不同的分组输出一个文档。每个输出文档的_id字段包含惟一的group by值。输出文档还可以包含包含某些累加器表达式值的计算字段,不对其输出文档排序
语法:
{
??$group:
????{
??????_id: <expression>, // Group By Expression
??????<field1>: { <accumulator1> : <expression1> },
??????...
????}
?}
参数讲解:
_id:必须的。如果您将_id值指定为null或任何其他常数值,则$group阶段将计算所有输入文档作为一个整体的累计值。参见Null分组示例。_id和累加器操作符可以接受任何有效的表达式
field:可选的。使用累加器操作符计算。
操作符必须是以下累加器操作符之一:
$addToSet:返回每个组的惟一表达式值数组。数组元素的顺序未定义
$avg:返回数值的平均值。忽略了非数字值。
$first:为每个组从第一个文档返回一个值。只有在文档按已定义的顺序排列时才定义顺序。
$last:从最后一个文档中为每个组返回一个值。只有在文档按已定义的顺序排列时才定义顺序。
$max:返回每个组的最大表达式值。
$min:返回每个组的最小表达式值。
$push:返回每个组的表达式值数组。
$sum:返回数值的和。忽略了非数字值。
$mergeObjects:返回通过组合每个组的输入文档创建的文档。
$stdDevPop:返回输入值的总体标准差。
$stdDevSamp:返回输入值的样本标准差。
2.?示例
初始化数据:
db.groupExample.insertMany([
??{ "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
??{ "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
??{ "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
??{ "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : ?NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
??{ "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
??{ "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
??{ "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
??{ "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
])
2.1.?计算文档总数
示例:下面的聚合操作使用$group阶段来计算groupExample集合中的文档数量:
db.groupExample.aggregate( [
??{
????$group: {
???????_id: null,
???????count: { $sum: 1 }
????}
??}
] )
结果:8
2.2.?对某一字段进行分组
示例:对item字段进行分组
db.groupExample.aggregate( [ { $group : { _id : "$item" } } ] )
结果:
{
????"_id" : "xyz"
}
{
????"_id" : "jkl"
}
{
????"_id" : "def"
}
{
????"_id" : "abc"
}
2.3.?对某一字段进行分组后having
示例:以下聚合操作按item字段对文档进行分组,计算每个项目的总销售额,并只返回总销售额大于或等于100的项目:
db.groupExample.aggregate(
??[
?{
??????$group :
????????{
??????????_id : "$item",
??????????totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
????????}
?????},
?????{
???????$match: { "totalSaleAmount": { $gte: 100 } }
?????}
???]
?)
以上操作相当于mysql中:SELECT item,
???Sum(( price * quantity )) AS totalSaleAmount
FROM ??groupExample
GROUP ?BY item
HAVING totalSaleAmount >= 100
结果:
{ "_id" : "abc", "totalSaleAmount" : NumberDecimal("170") }
{ "_id" : "xyz", "totalSaleAmount" : NumberDecimal("150") }
{ "_id" : "def", "totalSaleAmount" : NumberDecimal("112.5") }
2.4.?计算计数、总和和平均值
示例:计算2014年的总销售额、平均销售额、每天的销售额并排序:
db.groupExample.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 }
??}
?])
以上操作相当于mysql中:
SELECT date,
???????Sum(( price * quantity )) AS totalSaleAmount,
???????Avg(quantity) ????????????AS averageQuantity,
???????Count(*) ?????????????????AS Count
FROM ??groupExample
GROUP ?BY Date(date)
ORDER ?BY totalSaleAmount DESC
结果:
{ "_id" : "2014-04-04", "totalSaleAmount" : NumberDecimal("200"), "averageQuantity" : 15, "count" : 2 }
{ "_id" : "2014-03-15", "totalSaleAmount" : NumberDecimal("50"), "averageQuantity" : 10, "count" : 1 }
{ "_id" : "2014-03-01", "totalSaleAmount" : NumberDecimal("40"), "averageQuantity" : 1.5, "count" : 2 }
2.5.?分组ID字段是null,计算总数
示例:下面的聚合操作将组_id指定为null,用于计算总销售额、平均数量和集合中所有文档的计数。
db.groupExample.aggregate([
??{
????$group : {
???????_id : null,
???????totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
???????averageQuantity: { $avg: "$quantity" },
???????count: { $sum: 1 }
????}
??}
?])
以上操作相当于mysql中:
SELECT Sum(price * quantity) AS totalSaleAmount,
???????Avg(quantity) ????????AS averageQuantity,
???????Count(*) ?????????????AS Count
FROM ??groupExample
结果:
{
??"_id" : null,
??"totalSaleAmount" : NumberDecimal("452.5"),
??"averageQuantity" : 7.875,
??"count" : 8
}
2.6.?对某一个字段进行分组,并查询出分组下数据加到数组中
初始化数据:
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 }
])
示例:
db.books.aggregate([
???{ $group : { _id : "$author", books: { $push: "$title" } } }
?])
以上操作相当于mysql中:SELECT author,GROUP_CONCAT(title) as books FROM books GROUP BY author
结果:
{
????"_id" : "Homer",
????"books" : [
????????"The Odyssey",
????????"Iliad"
????]
}
{
????"_id" : "Dante",
????"books" : [
????????"The Banquet",
????????"Divine Comedy",
????????"Eclogues"
????]
}
2.7.?使用$$ROOT系统变量对整个文档进行分组
初始化数据:
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 }
])
示例:
db.books.aggregate([
???// First Stage
???{
?????$group : { _id : "$author", books: { $push: "$$ROOT" } }
???},
???// Second Stage
???{
?????$addFields:
???????{
?????????totalCopies : { $sum: "$books.copies" }
???????}
???}
?])
结果:
{
????"_id" : "Homer",
????"books" : [
????????{
????????????"_id" : 7000.0,
????????????"title" : "The Odyssey",
????????????"author" : "Homer",
????????????"copies" : 10.0
????????},
????????{
????????????"_id" : 7020.0,
????????????"title" : "Iliad",
????????????"author" : "Homer",
????????????"copies" : 10.0
????????}
????],
????"totalCopies" : 20.0
}
{
????"_id" : "Dante",
????"books" : [
????????{
????????????"_id" : 8751.0,
????????????"title" : "The Banquet",
????????????"author" : "Dante",
????????????"copies" : 2.0
????????},
????????{
????????????"_id" : 8752.0,
????????????"title" : "Divine Comedy",
????????????"author" : "Dante",
????????????"copies" : 1.0
????????},
????????{
????????????"_id" : 8645.0,
????????????"title" : "Eclogues",
????????????"author" : "Dante",
????????????"copies" : 2.0
????????}
????],
????"totalCopies" : 5.0
}
3.?注意事项
注意内存说明:
$group阶段的RAM有100mb字节的限制。默认情况下,如果阶段超过这个限制,$group将返回一个错误。要允许处理大型数据集,请将allowDiskUse选项设置为true。此标志允许$group操作写入临时文件。有关更多信息,请参见db.collection.aggregate()方法和aggregate命令。
版本2.6中的变化:MongoDB为$group阶段引入了100mb的RAM限制,并引入allowDiskUse选项来处理大型数据集的操作。