目录
聚合管道 aggregate(操作列表[{},{},{}])
高级操作
聚合管道 aggregate(操作列表[{},{},{}])
在mysql里面,我们可以输出平均值,最大值,最小值,总数,方差等数理统计的一些参数,如果你问在MongoDB里面可不可以,我只能说你想要的没有不可以,哈哈哈。技术的改革和迭代总是在维护我们这些使用者的权益,只要想要的功能,开发者会尽所能的开发出发相应的功能,下面我们就来看看吧!
这些集合运算是在聚合管道里面的:
$avg: 求平均值
$sum: 求和
$max: 求最大值
$min: 求最小值
$group:聚合字段
count()数量
var 查询条件={'grade':2019,'class':1,'major':'大数据'}; db.students.find(查询条件).count()
distinct()
db.students.distinct('courses.course')
$avg平均值聚合
$group:{_id:groupby字段名,'聚合字段名':{$集合操作符:$字段名}} : 注意在字段里面必须要加入$符号,这样才能取到该字段对应的值
_id:groupby字段名:相当于mysql里面要按照什么字段聚合,而对于后面的集合操作运算符就类似于MySQL里面的一些数据提取和运算
按照专业进行分组最后来求出每一个专业的平均分!
// 按专业取所有学生的平均身高 db.getCollection("students").aggregate([ {$group:{_id:"$major", avgHeight:{$avg:"$height"}}} ])
小例子
// 按专业求女生平均身高低于170的专业平均身高,并排序 /* SQL: select AVG(height) as avgHeight, major as _id from students where gender=0 group by major having avgHeight>170 sorted by avgHeight DESC */ db.getCollection("students").aggregate([ //第一步,查数据 {$match:{gender:0}} //第二步,限制返回字段 ,{$project:{_id:0,major:1, grade:1, class:1, height:1}} //第三步,分组求平均值 // _id: group by的字段, 字段名要加$符号前缀,表示是一个字段名 // avgHeight: 新生成的平均值字段名 // $avg: 平均值操作符,它的值为要求平均值的字段名,注意加$前缀 ,{$group:{_id:"$major", avgHeight:{$avg:"$height"}} //第四步,筛选聚合结果 ,{$match:{avgHeight:{$lte:170}}} //第五步,排序,按照avgHeight的值从大到小排序, -1: DESC, 1:ASC ,{$sort:{avgHeight:-1}} ])
批处理框架
对于MongoDB里面的查询和高级聚合操作,我们发现细节的东西有的多,我们应该如何去书写我们的查询代码呢,我的建议首先就是要有一个大的思维,我们已经学习了很多的小模块的,我们的目的是如何把这些和我们的日常开发结合在一起,在遇到实际的业务问题我们就可以很好地解决这些。
use cqust; db.students.renameCollection('stu')
使用该方法可以进行查询,我们之前的find命令也可以实现这个,但是在函数里面进行批量的操作我们还是需要这种方法的。
// 聚合 // 1. count() distinct() db.stu.count() db.stu.count({'gender':1}) // 男生数量
db.stu.distinct("major") // 专业名称唯一列表 db.stu.distinct("courses.course") // 课程名称唯一列表
// cursor自带的统计函数 db.stu.find().count() db.stu.find().sort() // sort by _id db.stu.find().sort({'body.height':1}) //1:升序 小->大 db.stu.find().sort({'body.weight':-1}) //-1:降序
至于我们应该如何记住这个属性代表的是什么:1最原始的想法就是:从小到大(升序)
批处理结构重复代码
最初的方法
// 2. aggregate pipline聚合管道 // db.stu.find({},{}) var 查询条件 = {gender:0} var 返回条件 = {_id:0, name:1, sno:1, gender:1} db.stu.find(查询条件,返回条件)
结构思维
如何在使用我们的代码以及方法的时候可以快速的达到我们的要求
// 步骤 = {$步骤操作符:{步骤操作文档}} // 查询女生条目 var 步骤1 = {$match:{gender:0}} var 批处理 = [步骤1] db.stu.aggregate(批处理)
// 步骤 = {$步骤操作符:{步骤操作文档}} // 查询女生的姓名、性别、学号、身高字段 var 查询条件 = {gender:0} var 返回条件 = {_id:0, 'name':1, 'sno':1, 'gender':1, 'body.height':1} var 步骤1 = {$match:查询条件} // $match 查询匹配 var 步骤2 = {$project:返回条件} // $project 返回条件 var 批处理 = [步骤2,步骤1] //var 批处理 = [步骤1,步骤2] db.stu.aggregate(批处理)
// 查询女生的平均身高字段 // select '女生平均身高', AVG(a.height) from (select height from stu where gender=0) a var 查询条件 = {gender:0} var 返回条件 = {_id:0, '身高':'$body.height'} var 步骤1 = {$match:查询条件} // 查询出所有的女生 var 步骤2 = {$project:返回条件} // 的身高 //var 批处理 = [步骤1,步骤2] //db.stu.aggregate(批处理) // $group:{_id:by字段, 聚合结果字段:{$聚合操作:}, } var 步骤3 = {$group:{_id:'女生平均身高',平均身高:{$avg:'$身高'}}} var 批处理 = [步骤1,步骤2,步骤3] db.stu.aggregate(批处理)
// 查询女生的平均身高字段 // select gender,AVG(height) '平均身高' from stu group by gender having gender=0 var 步骤1 = {$group:{_id:'$gender',平均身高:{$avg:'$body.height'}}} //先分组计算平均身高 var 查询条件 = {_id:0} // var 返回条件 = {_id:1, '平均身高':1} var 步骤2 = {$match:查询条件} // 从分组计算结果中筛选出女生(_id:0)的记录 // var 步骤3 = {$project:返回条件} // 的身高 var 管道 = [步骤1,步骤2] db.stu.aggregate(管道)
同样的道理,这里首先按照性别将身高进行分组,然后利用集合操作就可以计算出我们想要的数据结果了,然后我们查询条件里面的$match:查询条件 就可以只查询我们想要查询的数据
// 统计各专业学生的平均身高 var s1 = {$group:{_id:'$major', 平均身高:{$avg:'$body.height'}}} //先分组计算平均身高 var ps = [s1] db.stu.aggregate(ps)
// 统计各专业班级的学生平均身高 var s1 = {$group:{_id:{专业:'$major',班级:'$class'}, 平均身高:{$avg:'$body.height'}}} //先分组计算平均身高 var ps = [s1] db.stu.aggregate(ps)
注意:当我们需要利用多个聚合字段进行操作的时候,我们依然和MySQL一样,按照逗号分割,这样就可以达到多个字段的分组,同时我们应该注意用$字段名取出我们的字段数据值
// 统计各专业班级的学生平均身高,并按专业排序 var s1 = {$group:{_id:{专业:'$major',班级:'$class'}, 平均身高:{$avg:'$body.height'}}} //先分组计算平均身高 var s2 = {$sort:{'_id.专业':1, '_id.班级':-1}} var ps = [s1,s2] db.stu.aggregate(ps)
这里增加了一个排序功能,利用$sort:{字段名:-1}
// 统计各专业男女生平均身高,按身高排序 var s1 = {$group:{_id:{专业:'$major',性别:'$gender'}, 平均身高:{$avg:'$body.height'}}} //先分组计算平均身高 var s2 = {$sort:{'平均身高':-1}} var ps = [s1,s2] db.stu.aggregate(ps)
这里把两个批处理的步骤放在了一个列表里面,这就是我们框架,首先有一个大的列表,然后这个大的列表里面可以存放很多小的处理,这样就是批处理的核心思想了
// 统计各专业学生的身高最大和最小值, 按专业排序 // _id.专业 最大身高$max 最小身高$min var 分组聚合步骤 = {$group:{'_id':'$major', 最大身高:{$max:'$body.height'} , 最小身高:{$min:'$body.height'} }} var 排序步骤 = {$sort:{_id:1}} var 批处理 = [分组聚合步骤,排序步骤] db.stu.aggregate(批处理)
框架如下:
var step = {$match:{}} // 查询匹配 var step = {$group:{}} // 分组 {$max:$field} {$min:$field} {$avg:$field} {{$sum:$field}} var step = {$project:{}} // 返回条件 var step = {$sort:{}}
解开数组
// 使用project可以解开嵌入文档 var 查询条件 = {gender:0} var 返回条件 = {_id:0, '身高':'$body.height'} //var 返回条件 = {_id:0, 'body.height':1} var 步骤1 = {$match:查询条件} // 查询出所有的女生 var 步骤2 = {$project:返回条件} // 的身高 db.stu.aggregate([步骤1, 步骤2])
// 使用unwind解开数组 db.test.insert( { 'name':'tanguangyu', 'hobit':['reading','football','pingpong'] } ) db.test.find({name:'tanguangyu'})
返回的是一个嵌入式文档或者数组,这并不是我们需要的东西
db.test.aggregate([{$unwind:'$hobit'}])
这才是我们需要的东西
// 统计分布式数据库课程的平均分 var 解开courses = {$unwind:'$courses'} var 筛选分布式课程记录 = {$match:{'courses.course':/分布式/}} var 统计成绩的平均值 = {$group:{_id:'$courses.course', '平均分':{$avg:'$courses.score'}}} var 批处理 = [解开courses,筛选分布式课程记录,统计成绩的平均值] db.stu.aggregate(批处理)
// 统计所有课程的平均分 var 解开courses = {$unwind:'$courses'} var 统计成绩的平均值 = {$group:{_id:'$courses.course', '平均分':{$avg:'$courses.score'}}} var 批处理 = [解开courses,统计成绩的平均值] db.stu.aggregate(批处理)
var 解开courses = {$unwind:'$courses'} var 统计成绩的平均值 = {$group:{_id:'$courses.course', '平均分':{$avg:'$courses.score'}}} var 重命名Id字段 = {$project:{_id:0,'课程名':'$_id','平均分':1}} var 筛选分布式课程 = {$match:{'课程名':/分布式/}} var 批处理 = [解开courses,统计成绩的平均值,重命名Id字段,筛选分布式课程] db.stu.aggregate(批处理)
var 解开courses数组 = {$unwind:'$courses'} var 解开courses文档 = {$project:{_id:0,'课程名':'$courses.course','课程成绩':'$courses.score'}} var 统计成绩的平均值 = {$group:{_id:'$课程名', '平均分':{$avg:'$课程成绩'}}} var 重命名Id字段 = {$project:{_id:0,'课程名':'$_id','平均分':1}} var 筛选分布式课程 = {$match:{'课程名':/分布式/}} var 批处理 = [解开courses数组,解开courses文档,统计成绩的平均值,重命名Id字段,筛选分布式课程] db.stu.aggregate(批处理)
对于数组的操作,我们想要把它解开进行操作,我们就可以使用这个方法进行操作,然后利用批处理的框架进行思维构造,这样就不会不知道怎么去写了
总结
1.拿到一个实际问题,我们首先要把问题放在数据集里面进行思考,考虑这个问题到底是什么,在数据里面应该处于哪一个位置,我们应该如何去构建我们的语法
2.批处理框架要学会搭建,这样就不会写代码的时候过于的考虑这一个代码是不是会导致我们的数据查询不完整
3.需不需要解开数组,需要的话,我们就可以利用 var 解开数组 ={$unwind:'数组的顶端'},然后再去考虑你要的数据集,也就是我们所说的解开文档,利用 var 解开文档={$project:{_id:0,'新字段名':‘旧字段名值’}},其实这样做的目的是为了把我们的字段名进行一个解开的操作
4.然后我们利用group进行聚合或者分组,也可以进行相关的集合操作
5.进行字段的重命名,这个时候就是真正的需要我们规范化的命名了
6.最后再来一个筛选,看你是否需要这个,按照自己的实际场景结合
7.批处理的列表,将上述的所有变量步骤,放进去即可
8.db.collection.aggregate(批处理)
如果说步骤有这么多的话,那么我们也可以按照自己的想法和需求进行,比如对于在嵌入式的文档操作里面,一般都会有数组的类型,那么我们是不是要考虑解开数组,然后在解开数组里面的文档,再其次我们就可以进行group里面的分组和集合运算了,之后我们可以考虑我们要不要去重命名这个字段,方便与我们下一步的操作,也就是我们要显示的字段,最后在一个大的数据集里面我们想要再去缩小一下范围,我们就可以考虑一下是不是需要利用正则表达式或者其他方法来,缩减我们的数据查询范围。
总之我们在进行MongoDB里面的数据集查询时候,要想事半功倍一定要有一个模板框架和思维数据,不然就会是“竹篮打水一场空”
// 分班级计算分布式课程不及格的学生人数 // 步骤1, 先找到全校选了分布式数据库课程,而且成绩小于60的学生 // a: 解开课程数组 // b: 为了好看,解开课程嵌入文档 // c: 查找课程名和成绩 // 步骤2, 然后在上面的结果中分班统计数量 // a: 为了好看,解开_id文档 // b: 为了好看,排个序 var 步骤1a = {$unwind : "$courses"} var 步骤1b = {$project : {_id:0,'major':1,'grade':1,'class':1,'coursename':'$courses.course', 'coursescore':'$courses.score'}} var 步骤1c = {$match : {'coursename':/分布式数据库/, 'coursescore':{$lt:60}}} var 步骤2 = {$group : {'_id':{'major':"$major",'grade':"$grade",'class':"$class"}, 人数:{$sum:1}}} var 步骤2a = {$project : {_id:0,专业:"$_id.major",年级:"$_id.grade",班级:"$_id.class",分布式数据库不及格人数:"$人数"}} var 步骤2b = {$sort : {"专业":1,"年级":1,"班级":1}} var 步骤列表 = [步骤1a,步骤1b,步骤1c,步骤2,步骤2a,步骤2b] db.getCollection("stu").aggregate(步骤列表)