聚合
# 旧版内容
# COUNT
count可以返回集合中文档数量
db.test.find({}).count()
2
2
# distinct
distinct用来找出给定键的所有不同的值。使用时必须指定集合和键,这里我们返回了所有的名字
db.runCommand({"distinct":"test","key":"name"})
/* 1 */
{
"values" : [
"小游",
"张三"
],
"ok" : 1.0
}
2
3
4
5
6
7
8
9
# group
group做的聚合稍复杂一些。先选定分组所依据的键,而后 MongoDB就会将集合依据选定键值的不同分成若干组。然后可以通过聚合每一组内的文档,产生一个结果。文档如果你熟悉SQL,那么这个 group和SQL中的 GROUP BY差不多
# 聚合框架
使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以用多个构件创建一个管道( pipeline),用于对一连串的文档进行处理。这些构件包括筛选( filtering)、投射( projecting)、分组( grouping)、排序( sorting)、限制( limiting) 和跳过( skipping)。比如我们进行下面这个操作
- 将每个文章文档中的作者投射出来。
- 将作者按照名字排序,统计每个名字出现的次数。
- 将作者按照名字出现次数降序排列。
- 将返回结果限制为前5个。
这个操作比较复杂。。我这里就直接贴书上的例子吧
# 管道操作符
毎个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符(对于最后一个管道操作符,是将结果返回给客户端)。
# $match 操作符
match用于对文档集合进行筛选,之后就可以在筛选得到的文档子集上做聚合。
注意,按照书上的直接加{}在ROBO里面无法使用,所以这里使用了[],后面都按照这个来,不在提醒
db.test.aggregate([{
"$match":{"name":"小游"}
}])
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"like" : [
"写代码",
"看番"
]
}
2
3
4
5
6
7
8
9
10
11
12
13
# $project
相对于“普通”的查询而言,管道中的投射操作更加强大。使用"$ project"可以从子文档中提取字段,可以重命名字段,还可以在这些字段上进行一些有意思的操作。比如下面这个我们只提取名字
db.test.aggregate([{
"$project":{"name":1,"_id":0}
}])
/* 1 */
{
"name" : "小游"
}
/* 2 */
{
"name" : "张三"
}
2
3
4
5
6
7
8
9
10
11
12
我们可以对字段进行重命名(这里的$name指的是数据库里面的name字段)
db.test.aggregate([{
"$project":{"nickname":"$name","_id":0}
}])
/* 1 */
{
"nickname" : "小游"
}
/* 2 */
{
"nickname" : "张三"
}
2
3
4
5
6
7
8
9
10
11
12
# 管道表达式
默认我们使用的就是管道表达式
# 数学表达式
算数表达式可以对数值进行操作,我们的原始数据如下
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : [
"写代码",
"看番"
]
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : []
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
下面我们把 age 和 count 这两个数组相乘
db.test.aggregate([{
"$project":{"total":{"$multiply":["$age","$count"]}}
}])
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"total" : 200
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"total" : 500
}
2
3
4
5
6
7
8
9
10
11
12
13
14
表达式可以进行任意的嵌套,这里就贴书上的一个复杂的例子
算数表达式里面有下面这几种操作符
# 日期表达式
# 字符串表达式
这个可以对字符串进行各种操作
db.test.aggregate([{
"$project":{"name":{"$concat":["$name","666"]}}
}])
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游666"
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三666"
}
2
3
4
5
6
7
8
9
10
11
12
13
# 逻辑表达式
逻辑表达式可以控制语句
比如下面我们这里来简单判断一下大小
db.test.aggregate([{
"$project":{"name":{"$cond":[{"$eq":["$age",20]},"20岁","不是20岁"]},"age":1}
}])
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"age" : 20,
"name" : "20岁"
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"age" : 100,
"name" : "不是20岁"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# $group
$group操作可以将文档依据特定字段的不同值进行分组。我们的原始数据如下
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : [
"写代码",
"看番"
]
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : []
}
/* 3 */
{
"_id" : ObjectId("609f957aae177b370c33b0cb"),
"name" : "小游",
"age" : 66
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
下面我们使用group来对name进行分组操作
db.test.aggregate([{
"$group":{"_id":"$name"}
}])
/* 1 */
{
"_id" : "小游"
}
/* 2 */
{
"_id" : "张三"
}
2
3
4
5
6
7
8
9
10
11
12
# 分组操作符
这些分组操作符允许对每个分组进行计算,得到相应的结果。7.1节介绍过"$sum"分组操作符的作用:分组中每出现一个文档,它就对计算结果加1,这样便可以得到每个分组中的文档数量。
# 算数操作符
包括sum和average,我们先用 $sum
来统计字段的和
db.test.aggregate([{
"$group":{"_id":"$name","count":{"$sum":"$age"}}
}])
/* 1 */
{
"_id" : "张三",
"count" : 100
}
/* 2 */
{
"_id" : "小游",
"count" : 86
}
2
3
4
5
6
7
8
9
10
11
12
13
14
下面我们来统计平均值
db.test.aggregate([{
"$group":{"_id":"$name","count":{"$avg":"$age"}}
}])
/* 1 */
{
"_id" : "小游",
"count" : 43.0
}
/* 2 */
{
"_id" : "张三",
"count" : 100.0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 极值操作符
极值操作符可以用于获取数据集合中的边缘值,比如求集合中的最大值或者最小值之类的
比如我们获取这些集合中的最大值
db.test.aggregate([{
"$group":{"_id":"$name","count":{"$max":"$age"}}
}])
/* 1 */
{
"_id" : "张三",
"count" : 100
}
/* 2 */
{
"_id" : "小游",
"count" : 66
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数组操作符
比如我们把所有的年龄都加到数组中去
db.test.aggregate([{
"$group":{"_id":"$name","count":{"$push":"$age"}}
}])
/* 1 */
{
"_id" : "张三",
"count" : [
100
]
}
/* 2 */
{
"_id" : "小游",
"count" : [
20,
66
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 分组行为
有两个操作符不能用前面介绍的流式工作方式对文档进行处理,"$ g roup"是其中之一。大部分操作符的工作方式都是流式的,只要有新文档进入,就可以对新文档进行处理,但是"$group"必须要等收到所有的文档之后,才能对文档进行分组,然后才能将各个分组发送给管道中的下一个操作符。这意味着,在分片的情况下,$group"会先在每个分片上执行,然后各个分片上的分组结果会被发送到 mongos再进行最后的统一分组,剩余的管道工作也都是在 mongos(而不是在分片)上运行的。
# $unwind
拆分( unwind)可以将数组中的每一个值拆分为单独的文档。例如,如果有一篇拥有多条评论的博客文章,可以使用$ unwind将每条评论拆分为一个独立的文档,原始文档如下
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : [
"写代码",
"看番"
]
}
/* 2 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : [
"做坏事",
"当例子"
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
db.test.aggregate([{
"$unwind":"$like"
}])
/* 1 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : "写代码"
}
/* 2 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : "看番"
}
/* 3 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : "做坏事"
}
/* 4 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : "当例子"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# $sort
可以根据任何字段(或者多个字段)进行排序,与在普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一阶段进行排序,这时的排序操作可以使用索引。否则,排序过程就会比较慢,而且会占用大量内存。
db.test.aggregate([{
"$sort":{"age":-1}
}])
/* 1 */
{
"_id" : ObjectId("609cd4df71a920015b3da7ac"),
"name" : "张三",
"age" : 100,
"count" : 5,
"like" : [
"做坏事",
"当例子"
]
}
/* 2 */
{
"_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
"name" : "小游",
"age" : 20,
"count" : 10,
"like" : [
"写代码",
"看番"
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# $limit 和 $skip
$Limit会接受一个数字n,返回结果集中的前n个文档。
$skip也是接受一个数字n,丢弃结果集中的前n个文档,将剩余文档作为结果返回。在“普通”査询中,如果需要跳过大量的数据,那么这个操作符的效率会很低。在聚合中也是如此,因为它必须要先匹配到所有需要跳过的文档,然后再将这些文档丢弃。