使用聚合框架可以对集合中的文档进行变换和组合。可以用多个构件创建一个管道(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})
每个操作符都会接受一连串的文档,对这些文档做一些操作,最后将转换后的文档作为结果传递给下一个操作符(最后一个操作符是将结果返回给客户端)
不同的管道操作符可以按任意组合任意顺序一起使用,而且可以被重复多次使用。
$match用于对文档进行筛选,可以使用所有常规的查询操作符 。
//查询性别为男的用户
{$match:{"sex":"男"}}
//查询工资为10000以上的员工
{$match:{"saler":{$gt:10000}}}
应该尽可能将“$match”放在管道的前面:
使用”$project”可以从文档中提取字段(只返回想要的字段),可以重命名字段.
//指定只返回author字段(默认都包含“_id”字段,必须显式声明"_id":0,才不包含"_id"字段)
db.articles.aggregate({$project:{"author":1}})
//将每个用户文档的"_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”的索引。所以尽量在修改字段名称前使用索引。
group可以将文档根据特定字段的不同值进行分组,将分组的字段传递给”group”函数的”_id”字段
//按课程分组
{$group:{"_id":"course"}}
//按成绩分组
{$group:{"_id":"grade"}}
$group可以使用的操作符:
1: sum:value:对于每个文档,将value与计算结果相加2: s u m : v a l u e : 对 于 每 个 文 档 , 将 v a l u e 与 计 算 结 果 相 加 2 : avg:value :返回每个分组的平均值
3: max:expr:返回分组内的最大值4: m a x : e x p r : 返 回 分 组 内 的 最 大 值 4 : min:expr :返回分组内的最小值
5: first:expr:返回分组的第一个值,忽略其他的值,一般只有排序后,明确知道数据顺序的时候,这个操作才有意义6: f i r s t : e x p r : 返 回 分 组 的 第 一 个 值 , 忽 略 其 他 的 值 , 一 般 只 有 排 序 后 , 明 确 知 道 数 据 顺 序 的 时 候 , 这 个 操 作 才 有 意 义 6 : last:expr :与上面一个相反,返回分组的最后一个值
7: addToSet:expr:如果当前数组中不包含expr,那就将它加入到数组中8: a d d T o S e t : e x p r : 如 果 当 前 数 组 中 不 包 含 e x p r , 那 就 将 它 加 入 到 数 组 中 8 : push:expr:把expr加入到数组中
$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"}})
$sort可以根据一个或多个字段排序
$limit会接受一个数字n,返回结果集中的前n个文档
$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 }