聚合框架
使用聚合框架可以对集合中的文档进行变换和组合。可以用多个构件创建一个管道(pipeline,类似一个流),用于对一连串的文档进行处理。这些构件包括:
筛选(filtering)
投射(projecting)
分组(grouping)
排序(sorting)
限制(limiting)
跳过(skipping)
聚合的结果必须要限制在16M之内(MongoDB支持的最大的响应大小)。
要将每个操作传给aggreagte()函数。
db.集合.aggregate(构件1,构件2…)
//有一个保存着杂志文章的集合,找出发表文章最多的那个作者
db.articles.aggregate({$project:{"author":1}},${$group:{"_id":"$author","count":{"$sum":1}}},{$sort:{"count":-1}},{$limit:5})
每个操作符都会接受一连串的文档,对这些文档做一些操作,最后将转换后的文档作为结果传递给下一个操作符(最后一个操作符是将结果返回给客户端)
不同的管道操作符可以按任意组合任意顺序一起使用,而且可以被重复多次使用。
管道操作符
1、$match
$match用于对文档进行筛选,可以使用所有常规的查询操作符 。
//查询性别为男的用户
{$match:{"sex":"男"}}
//查询工资为10000以上的员工
{$match:{"saler":{$gt:10000}}}
应该尽可能将“$match”放在管道的前面:
- 一是可以快速将不需要的文档过滤掉,减少管道的工作量
- 二是在投射和分组之前执行“$match”,查询可以使用索引
2、$project
使用”$project”可以从文档中提取字段(只返回想要的字段),可以重命名字段.
1、从文档中选择想要的字段,可以指定包含或不包含一个字段
//指定只返回author字段(默认都包含“_id”字段,必须显式声明"_id":0,才不包含"_id"字段)
db.articles.aggregate({$project:{"author":1}})
2、将字段重命名
//将每个用户文档的"_id"再返回结果中重命名为"userId"
db.users.aggregate({$project:{"userId":"$_id","_id":0}})
{ "userId" : ObjectId("5b3c7f2020306147a4f53537") }
//必须明确指定将"_id"排除,否则会出现下面这种结果
{ "_id":ObjectId("5b3c7f2020306147a4f53537"), "userId" : ObjectId("5b3c7f2020306147a4f53537") }
//”$fieldname“语法是为了在聚合框架中引用fieldname字段的值,例如 ”$age“会被替换为"age"字段的内容,上面的"$_id"会被替换为进入管道的问个文档的"_id"字段的值
在字段进行重命名时,MongoDB不会记录字段的历史名称,即在”id”字段上有索引,重命名成”userId”后,在之后的操作中不能使用“_id”的索引。所以尽量在修改字段名称前使用索引。
3、$group
group可以将文档根据特定字段的不同值进行分组,将分组的字段传递给”group”函数的”_id”字段
//按课程分组
{$group:{"_id":"course"}}
//按成绩分组
{$group:{"_id":"grade"}}
$group可以使用的操作符:
1:
avg:value :返回每个分组的平均值
3:
min:expr :返回分组内的最小值
5:
last:expr :与上面一个相反,返回分组的最后一个值
7:
push:expr:把expr加入到数组中
3、$unwind
$unwind可以将数组中的每一个值拆分为单独的文档。
//一篇有多条评论的文章,可以使用$unwind将每条评论拆分为一个独立的文档
db.blog.findOne()
{
"_id" : ObjectId("5b3dceb8eb560553b80bf190"),
"content" : "...",
"author" : "wang",
"comments" : [
{
"comment" : "good post",
"author" : "John",
"votes" : 0
},
{
"comment" : "i thought it was too short",
"author" : "Claire",
"votes" : 3
},
{
"comment" : "free watch",
"author" : "Alice",
"votes" : -1
}
]
}
db.blog.aggregate({$unwind:"$comments"})
{
"_id" : ObjectId("5b3dceb8eb560553b80bf190"),
"content" : "...",
"author" : "wang",
"comments" : {
"comment" : "good post",
"author" : "John",
"votes" : 0
}
}
{
"_id" : ObjectId("5b3dceb8eb560553b80bf190"),
"content" : "...",
"author" : "wang",
"comments" : {
"comment" : "i thought it was too short",
"author" : "Claire",
"votes" : 3
}
}
{
"_id" : ObjectId("5b3dceb8eb560553b80bf190"),
"content" : "...",
"author" : "wang",
"comments" : {
"comment" : "free watch",
"author" : "Alice",
"votes" : -1
}
}
//查询特定子文档
//只想要作者为“Alice”的评论,不想要其他的信息
db.blog.aggregate({$project:{"_id":0,"comments":"$comments"}},{$unwind:"$comments"},{$match:{"comments.author":"Alice"}})
4、$sort
$sort可以根据一个或多个字段排序
5、$limit
$limit会接受一个数字n,返回结果集中的前n个文档
6、$skip
$skip接受一个数字n,丢掉结果集中的前n个文档,将剩余文档返回结果集。
示例:
//准备示例数据
for(var i=0;i<100;i++){
for(var j=0;j<4;j++){
db.scores.insert({"studentId":"s"+i,"course":"课程"+j,"score":Math.random()*100});
}
}
//找出考80分以上的课程门数最多的5个学生
步骤:
1、找出所有考了80分以上的学生,不区分课程
{$match:{"score":{$gte:80}}}
2、将每个学生的名字投影出来
{$project:{"studentId":1}}
3、对学生的名字分组,某个学生的名字出现一次,给他加1
{$group:{"_id":"$studentId","count":{$sum:1}}}
4、对结果集按照count进行降序排序
{$sort:{"count":-1}}
5、返回前面的5条数据
{$limit:5}
最终语句
db.scores.aggregate({$match:{"score":{$gte:80}}},
{$project:{studentId:1}},
{$group:{"_id":"$studentId","count":{$sum:1}}},
{$sort:{"count":-1}},{$limit:5}
)
result:
{ "_id" : "s17", "count" : 3 }
{ "_id" : "s42", "count" : 3 }
{ "_id" : "s0", "count" : 3 }
{ "_id" : "s4", "count" : 3 }
{ "_id" : "s7", "count" : 3 }