实验准备
打开实验环境,在终端输入如下命令启动 MongoDB 服务,进入命令行交互客户端:
$ cd
$ sudo service mongodb start
$ mongo
aggregate 函数
聚合操作处理数据记录并返回计算结果。将来自多个文档的操作组值聚合在一起,并可以对分组的数据执行各种操作以返回单个结果,这也被称为分组查询。
MongoDB 提供了三种执行聚合的方法:聚合管道、map-reduce 函数和单一用途的聚合方法。下面主要介绍 aggregate 函数的用法。
聚合函数 aggregate 的基本用法如下:
db.COLLECTION_NAME.aggregate([
{$match: {x: 1}},
{$project: {_id: 0, name: 1, 语文: 1}}
{$limit: NUM},
{$skip: NUM},
{$sort: {name: 1, age: -1}},
{$group:{_id: $age}}
])
所有操作符均为可选,非必须:
$match 的作用与 find 函数相同
$limit 限制查询结果的数量
$skip 跳过一定数量的文档
$sort 对查询结果进行排序
$group 用于分组
数据准备
我们使用前一节实验中创建的 query 数据库的 stu 集合中的数据,可以复制下面的代码创建它们:
use query
db.stu.insertMany([
{ "name" : "大红", "gender" : "男", "语文" : 83, "数学" : 81 },
{ "name" : "大黄", "gender" : "女", "语文" : 83, "数学" : 20 },
{ "name" : "大蓝", "gender" : "男", "语文" : 84, "数学" : 99 },
{ "name" : "大绿", "gender" : "女", "语文" : 85, "数学" : 34 },
{ "name" : "大橙", "gender" : "男", "语文" : 86, "数学" : 88 },
{ "name" : "大青", "gender" : "女", "语文" : 87, "数学" : 93 },
{ "name" : "大紫", "gender" : "男", "语文" : 99, "数学" : 20 }
])
接下来依次介绍 aggregate 函数中的各种操作符的用法。
$match 匹配查询
使用 find 函数能做到的事,aggregate 都能做到。不过能用 find 函数完成的查询,就用 find 函数,aggregate 会稍复杂些。
例如查询全部男同学的数据:
# 使用 find 函数
> db.stu.find({gender: '男'})
{ "_id" : ObjectId("5e4df26740faabaac0fd3de6"), "name" : "大红", "gender" : "男", "语文" : 83, "数学" : 81 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3de8"), "name" : "大蓝", "gender" : "男", "语文" : 84, "数学" : 99 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dea"), "name" : "大橙", "gender" : "男", "语文" : 86, "数学" : 88 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dec"), "name" : "大紫", "gender" : "男", "语文" : 99, "数学" : 20 }
>
# 使用 aggregate 函数 + $match 操作符
> db.stu.aggregate(
... {$match: {gender: '男'}}
... )
{ "_id" : ObjectId("5e4df26740faabaac0fd3de6"), "name" : "大红", "gender" : "男", "语文" : 83, "数学" : 81 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3de8"), "name" : "大蓝", "gender" : "男", "语文" : 84, "数学" : 99 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dea"), "name" : "大橙", "gender" : "男", "语文" : 86, "数学" : 88 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dec"), "name" : "大紫", "gender" : "男", "语文" : 99, "数学" : 20 }
>
aggregate 函数的参数通常为列表,列表里面是多个操作符的键值对。列表不是必须的,是推荐写法。用列表的话,是这样:
> db.stu.aggregate([
... {$match: {gender: '男'}}
... ])
{ "_id" : ObjectId("5e4df26740faabaac0fd3de6"), "name" : "大红", "gender" : "男", "语文" : 83, "数学" : 81 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3de8"), "name" : "大蓝", "gender" : "男", "语文" : 84, "数学" : 99 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dea"), "name" : "大橙", "gender" : "男", "语文" : 86, "数学" : 88 }
{ "_id" : ObjectId("5e4df26740faabaac0fd3dec"), "name" : "大紫", "gender" : "男", "语文" : 99, "数学" : 20 }
>
$project 规定查询结果的字段
在 find 函数中,可以提供第 2 个参数用于标识查询结果显示哪些字段。在 aggregate 函数中使用 $project 操作符作为 key 来实现。
举例说明,查询男学生的名字和语文成绩:
# 使用 find 方法实现
> db.stu.find(
... {gender: '男'},
... {_id: 0, name: 1, 语文: 1}
... )
{ "name" : "大红", "语文" : 83 }
{ "name" : "大蓝", "语文" : 84 }
{ "name" : "大橙", "语文" : 86 }
{ "name" : "大紫", "语文" : 99 }
>
# 使用 aggregate 函数 + $match + $project 实现
> db.stu.aggregate([
... {$match: {gender: '男'}},
... {$project: {_id: 0, name: 1, 语文: 1}}
... ])
{ "name" : "大红", "语文" : 83 }
{ "name" : "大蓝", "语文" : 84 }
{ "name" : "大橙", "语文" : 86 }
{ "name" : "大紫", "语文" : 99 }
>
$limit & $skip 限制和跳过
这两个操作符的作用与 find 方法后面跟随的 limit 和 skip 函数的作用相同,对查询结果进行再次选择。
举例说明,查询男学生的名字和语文成绩,选择第 2 、3 条数据。也就是略过第一条数据后选择前两条数据:
# 使用 find 方法实现
> db.stu.find(
... {gender: '男'},
... {_id: 0, name: 1, 语文: 1}
... ).skip(1).limit(2)
{ "name" : "大蓝", "语文" : 84 }
{ "name" : "大橙", "语文" : 86 }
>
# 使用 aggregate 函数实现
> db.stu.aggregate([
... {$match: {gender: '男'}},
... {$project: {_id: 0, name: 1, 语文: 1}},
... {$skip: 1},
... {$limit: 1}
... ])
{ "name" : "大蓝", "语文" : 84 }
>
$group 分组查询
前面介绍的几个操作符的功能在 find 函数中都可以有办法实现。aggregate 函数的强大之处就在于它可以实现分组查询操作,这是 find 函数没有的功能。
分组查询所用的操作符是 $group ,同时还需要在分组后进行其它操作,例如求和、求平均值、求最大最小值、数量等。下面我们依次介绍相关的操作符。
$sum 求和
现在需要查询男学生和女学生的语文总得分,这需要用到分组查询操作。首先将所有文档按 gender 字段的值进行分组,相同值的文档归为一组。然后获取各组文档的语文成绩,求和。求和使用 $sum 操作符。
具体操作如下:
> db.stu.aggregate([
... {$group:
... {_id: '$gender',
... 语文总分数: {$sum: '$语文'}
... }
... }
... ])
{ "_id" : "女", "语文总分数" : 255 }
{ "_id" : "男", "语文总分数" : 352 }
>
对 $group 的 value 的键值对进行说明:
“_id” 字段为必须字段,其值为字符串,意为以 stu 集合的 gender 字段的值进行分组。注意 '$gender' 字符串前面要加美元符号。
“语文总分数” 为自定义字段,用于分组后处理的数据的 key 值,其对应的 value 为嵌套键值对。嵌套键值对的 key 为求和操作符,value 为被计算的字段名字的字符串,注意该字符串前面也要加美元符号。
如果需要对男学生和女学生的人数进行查询,此处可以利用 $sum 操作符这样做:
> db.stu.aggregate([
... {$group:
... {_id: '$gender',
... 学生人数: {$sum: 1}
... }
... }
... ])
{ "_id" : "女", "学生人数" : 3 }
{ "_id" : "男", "学生人数" : 4 }
>
这是一个小技巧,在使用 $sum 操作符求和时,求的是数值 1 的和,每次进行 +1 操作,最后的结果就是各组文档的数量。
$avg 求平均值
现需要查询男学生和女学生的语文平均分和数学平均分,同样需要使用分组查询。首先将所有文档按 gender 字段的值进行分组,相同值的文档归为一组。然后获取各组文档的语文成绩和数学成绩,分别求平均值。
具体操作如下:
> db.stu.aggregate([
... {$group:
... {_id: '$gender',
... 语文平均分: {$avg: '$语文'},
... 数学平均分: {$avg: '$数学'}
... }
... }
... ])
{ "_id" : "女", "语文平均分" : 85, "数学平均分" : 49 }
{ "_id" : "男", "语文平均分" : 88, "数学平均分" : 72 }
>
在 $group 操作符后面的字典里有三组键值对,其中 _id 字段为必填项,后面两组键值对分别使用 $avg 操作符计算语文和数学字段的平均值。
$max & $min 求最大最小值
现需要查询男学生和女学生的语文成绩的最高分数和数学成绩的最低分数,也是要用到分组查询。首先将所有文档按 gender 字段的值进行分组,相同值的文档归为一组。然后获取各组内语文最高分和数学最低分。
具体操作如下:
> db.stu.aggregate(
... {$group:
... {_id: '$gender',
... 语文最高分: {$max: '$语文'},
... 数学最低分: {$min: '$数学'}
... }
... }
... )
{ "_id" : "女", "语文最高分" : 87, "数学最低分" : 20 }
{ "_id" : "男", "语文最高分" : 99, "数学最低分" : 20 }
>
如上所示,使用 min 操作符获取 “数学” 字段的最小值。
$sort 排序
首先,我们查询男学生和女学生的数学成绩的最高分数和语文成绩的最高分数:
> db.stu.aggregate([
... {$group:
... {_id: '$gender',
... 数学最高分: {$max: '$数学'},
... 语文最高分: {$max: '$语文'}
... }
... }
... ])
{ "_id" : "女", "数学最高分" : 93, "语文最高分" : 87 }
{ "_id" : "男", "数学最高分" : 99, "语文最高分" : 99 }
>
现在要去将查询结果按照 “数学最高分” 降序排序,这就用到了 $sort 操作符了,具体如下:
> db.stu.aggregate([
... {$group:
... {_id: '$gender',
... 数学最高分: {$max: '$数学'},
... 语文最高分: {$max: '$语文'}
... }
... },
... {$sort:
... {数学最高分: -1}
... }
... ])
{ "_id" : "男", "数学最高分" : 99, "语文最高分" : 99 }
{ "_id" : "女", "数学最高分" : 93, "语文最高分" : 87 }
>
注意 $group 与 $sort 是并列关系,后者的 value 为嵌套键值对。嵌套键值对的 key 值为分组后的字段名,value 值为 1 或 -1 ,分别表示升序排序和降序排序。
网友评论