在上一篇mongodb聚合操作之Aggregation Pipeline中详细介绍了什么是mongodb聚合操作中的Aggregation Pipeline以及参数细节。本篇将开始介绍Aggregation聚合操作中的group分组操作,相当于mysql的group by聚合。
1. 简介
说明:
按照指定的_id表达式对输入文档进行分组,并对每个不同的分组输出一个文档。每个输出文档的_id字段包含惟一的group by值。输出文档还可以包含包含某些累加器表达式值的计算字段,不对其输出文档排序
语法:
{
$group:
{
_id:
...
}
}
参数讲解:
_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选项来处理大型数据集的操作。