第三部分:MongoDB高级操作
5 聚合分组统计(管道)
5.1 聚合aggregate
- 聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,
- 可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。
- db.集合名称.aggregate({管道:{表达式}})
- 先传入数据到stu(学生)集合中
db.stu.insert({name:“郭靖”, hometown:“蒙古”, age:20, gender:true})
db.stu.insert({name:“黄蓉”, hometown:“桃花岛”, age:18, gender:false})
db.stu.insert({name:“华筝”, hometown:“蒙古”, age:18, gender:false})
db.stu.insert({name:“黄药师”, hometown:“桃花岛”, age:40, gender:true})
db.stu.insert({name:“段誉”, hometown:“大理”, age:16, gender:true})
db.stu.insert({name:“段王爷”, hometown:“大理”, age:45, gender:true})
db.stu.insert({name:“洪七公”, hometown:“华山”, age:18, gender:true})
注意:
MongoDB语句可以现在pycharm中编写,然后复制到CMD窗口中
现在pycharm中使用ctrl+c复制命令,然后CMD窗口点击鼠标右键,就自动粘贴了
可以一次复制多行,有时候会出现粘贴不上去的情况
- db.集合名称.aggregate({管道:{表达式}})
- 管道和表达式都可以有多个
- 管道:
在mongodb中,⽂档处理完毕后, 通过管道进⾏下⼀次处理
常用管道如下:
$group: 将集合中的⽂档分组, 可⽤于统计结果
$match: 过滤数据, 只输出符合条件的⽂档
$project: 修改输⼊⽂档的结构, 如重命名、 增加、 删除字段、 创建计算结果
$sort: 将输⼊⽂档排序后输出
$limit: 限制聚合管道返回的⽂档数
$skip: 跳过指定数量的⽂档, 并返回余下的⽂档
$unwind: 将数组类型的字段进⾏拆分 - 表达式:
处理输⼊⽂档并输出
语法:表达式:’$列名’
常⽤表达式:
$sum: 计算总和,
$sum:1 表示以⼀倍计数
$avg: 计算平均值
$min: 获取最⼩值
$max: 获取最⼤值
$push: 在结果⽂档中插⼊值到⼀个数组中
$first: 根据资源⽂档的排序获取第⼀个⽂档数据
$last: 根据资源⽂档的排序获取最后⼀个⽂档数据
5.2 $group分组统计
- $group注意点
- $group对应字典中有几个键,结果中对应就有几个值
- 分组依据必须放在_id的值中
- 取不同的字段的时候需要使用country,$age
- 取字典嵌套的字典中的值的时候_id.country
- 能够同时按照多个组进行分组
- 管道依次按顺序处理,常用处理顺序过滤-分组-重整
实例
- 语法格式:
先写分组,必须使用_id作为键,然后里面的_id代表以哪个字段进行分组
后面的字段是分组里面进行统计,求和,求平均值,最大值等等
分组里面的键都是自己指定的,值是集合里面有的字段,前面都需要$符号 - 统计整个文档
- _id为null
- 整个文档求和求平均值
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …d:null, count:{sum:1}, mean_age:{age"}}}
)
- 指定条件进行分组,然后统计
- 按性别分组(注意KaTeX parse error: Expected 'EOF', got '}' at position 12: 符号不能遗漏,还要注意}̲不要遗漏) db.stu.ag…group:{_id:"$gender"}}
) - 按性别分组,然后统计每个性别的和,同时求最小值
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲, min_age:{min:"$age"}}}
) - 按家乡进行分组,统计个数,求平均年龄
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"hometown", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲, mean_age:{avg:"$age"}}}
)
5.3 $project重构
- 修改文档结构,修改输出的格式
_id字段的键使用gender进行替换
_id:0 不显示_id字段
count:1 显示count字段
avg_age:1 显示avg_age字段
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲, avg_age:{avg:“KaTeX parse error: Expected 'EOF', got '}' at position 5: age"}̲}}, {project:{_id:0, gender:”$_id", count:1, avg_age:1}}
)
5.4 $match过滤
- 选择年龄大于20的学生,观察男性和女性的人数
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: match:{age:{gt:16}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲}}, {project:{_id:0, gender:"$_id", count:1}}
) - 选择年龄大于20或者家乡是蒙古或大理的学生,观察男性和女性的人数
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: match:{or:[{age:{KaTeX parse error: Expected 'EOF', got '}' at position 6: gt:16}̲},{hometown:{in:[“蒙古”, “大理”]}}]}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲}}, {project:{_id:0, gender:"$_id", count:1}}
) - 综合练习:
- 统计出每个country/province下的userid的数量(同一个userid只统计一次)
db.dv3.insert({“country” : “china”, “province” : “sh”, “userid” : “a” } )
db.dv3.insert({ “country” : “china”, “province” : “sh”, “userid” : “b” } )
db.dv3.insert({ “country” : “china”, “province” : “sh”, “userid” : “a” } )
db.dv3.insert({ “country” : “china”, “province” : “sh”, “userid” : “c” } )
db.dv3.insert({ “country” : “uk”, “province” : “bj”, “userid” : “da” } )
db.dv3.insert({ “country” : “china”, “province” : “bj”, “userid” : “fa” } ) - 所有字段进行分组,可以达到去重的效果,上面第一行于第三行数据相同
db.dv3.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …{_id:{country:"country", province:“userid”}}}
) - 上面去重后的结果继续按照国家和省份进行分组,然后统计每个分组的和
- 第一次分组后第二次分组,第二次分组使用第一次的分组的_id
- 分组后的结果重整结构
db.dv3.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …{_id:{country:"country", province:“userid”}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …{_id:{country:"_id.country", province:“KaTeX parse error: Expected 'EOF', got '}' at position 14: _id.province"}̲, count:{sum:1}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …ject:{country:"_id.country”, province:"$_id.province", count:1, _id:0}}
)
5.5 sort排序,skip,limit
- 学生按年龄升序排列
db.stu.aggregate(
{$sort:{age:1}}
) - 学生按性别分组,统计个数,然后按个数降序排列
db.stu.aggregate(
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"gender", count:{KaTeX parse error: Expected 'EOF', got '}' at position 6: sum:1}̲}}, {sort:{count:-1}}
) - skip和limit
- 先跳过两个,然后选定两个
db.stu.aggregate({KaTeX parse error: Expected 'EOF', got '}' at position 7: skip:2}̲,{limit:2})
5.6 unwind拆分
- 将⽂档中的某⼀个数组类型字段拆分成多条, 每条包含数组中的⼀个值
- 语法:db.集合名称.aggregate({字段名称’})
db.t2.insert({_id:1,item:‘t-shirt’,size:[‘S’,‘M’,‘L’]})
db.t2.aggregate({size’})
结果如下:
{ “_id” : 1, “item” : “t-shirt”, “size” : “S” }
{ “_id” : 1, “item” : “t-shirt”, “size” : “M” }
{ “_id” : 1, “item” : “t-shirt”, “size” : “L” } - 字段unwind拆分后,统计长度
- 插入数据
db.t3.insert({username:“Alex”, tags:[“C+”, “Java”, “Python”]}) - 拆分tags,统计拆分后的文档个数
db.t3.aggregate({KaTeX parse error: Expected 'EOF', got '}' at position 24: …sername:"Alex"}}̲, {unwind:"KaTeX parse error: Expected 'EOF', got '}' at position 6: tags"}̲, {group:{_id:null, sum:{$sum:1}}}) - 先匹配到用户,然后对tags进行拆分,然后按null(不指定字段,所有文档分组)分组,然后统计分组的个数
6 创建索引
- 先插入数据,使用js语法
for(i=0;i<10000;i++){db.t12.insert({name:“test”+i,age:i})}
数据较多,插入较慢,一分钟左右
查看数据条数
db.t12.find().count() - 查找数据,并查看耗费时间
db.t12.find().count()
100000
db.t12.find({name:“test30000”})
{ “_id” : ObjectId(“5cf8d888e1d09b780e764cd9”), “name” : “test30000”, “age” : 30000 }
db.t12.find({age:30000}).explain(‘executionStats’)
{…
“executionStats” : {
“executionSuccess” : true,
“nReturned” : 1,
“executionTimeMillis” : 82,
“totalKeysExamined” : 0,
“totalDocsExamined” : 100000,
…
}
上面花费时间82ms
- 建立索引之后对比:
语法:db.集合.ensureIndex({要建立索引的字段:1}),1表示升序, -1表示降序
具体操作:
db.t12.ensureIndex({name:1})
{
“createdCollectionAutomatically” : false,
“numIndexesBefore” : 1,
“numIndexesAfter” : 2,
“ok” : 1
}
db.t12.find({name:“test30000”}).explain(‘executionStats’)
“inputStage” : {
“stage” : “IXSCAN”,
“nReturned” : 1,
“executionTimeMillisEstimate” : 0,
“works” : 2,
建立索引后查找时间0ms - 查看索引
db.t12.getIndexes()
[
{
“v” : 2,
“key” : {
“_id” : 1
},
“name” : “id”,
“ns” : “test.t12”
},
{
“v” : 2,
“key” : {
“name” : 1
},
“name” : “name_1”,
“ns” : “test.t12”
}
]
上面结果中有两个字典,key就是创建索引的字段
数据库默认都会以_id字段创建默认索引,默认也是升序
“name” : 1 就是我们刚刚给name字段创建的索引,创建索引后查询速度明显变快- 删除索引:
db.t12.dropIndex(‘索引名称’)
db.t12.dropIndex({name:1})
{ “nIndexesWas” : 2, “ok” : 1 }
删除之后就只有一个默认索引了 - 在默认情况下创建的索引均不是唯一索引。
创建唯一索引(可以对数据进行去重,name字段相同的但是只会创建一个索引,自动排除重复的):
db.t12.ensureIndex({“name”:1},{“unique”:true})
创建唯一索引并消除重复:
db.t12.ensureIndex({“name”:1},{“unique”:true,“dropDups”:true})
建立联合索引(什么时候需要联合索引):
db.t12.ensureIndex({name:1,age:1}) - 爬虫数据去重
- 使用数据库建立的关键字段(一个或者多个)的唯一索引进行去重
- URL地址对应数据不会变的情况,URL地址能够唯一判别一条数据的
- 使用场景:
URL地址持久化存储在redis集合中,具体步骤:
拿到URL地址,判断url是否在redis的url集合中
存在:说明url已经请求过,不再请求
不存在:url没请求过,请求,同时存储url到该集合中 - 布隆过滤器
位操作,将url地址通过算法转换为对应的位置
- 根据数据本身进行去重
- 选择特定的字段,使用加密算法(md5,sha1)将字段进行加密,生成字符串
- 后续新来一条数据,同样的方法进行加密,如果得到的字符串在redis集合中
- 说明存在,对数据更新,如果不存在,则直接插入该数据
7 数据库备份与恢复
- 启动本地的mongodb数据库,不需要使用mongo连接数据库
- 直接启动后使用备份和恢复命令即可对mongodb数据库中所有的dbs进行备份操作
- 要想将其他数据恢复早mongodb中,也是在该级操作
- 数据备份:
参考教程:http://www.baidu.com/link?url=I_GAWYpj6q4l_P4fmTfwd8CxCokr0sFvaZB7yhXT4vG-9BEx1pyXus1DocoxqogW7jUZ_7XbMunaCTlGDIk2S5gSQid5YE1Kd-WTR_YEvdq - 备份的语法:
mongodump -h dbhost -d dbname -o dbdirectory
-h: 服务器地址, 也可以指定端⼝号
-d: 需要备份的数据库名称
-o: 备份的数据存放位置, 此⽬录中存放着备份出来的数据
-使用mongo连接数据库,可以看到该数据库的地址和端口号
C:\WINDOWS\system32>net start mongodb
请求的服务已经启动。
请键入 NET HELPMSG 2182 以获得更多的帮助。
C:\WINDOWS\system32>mongo
MongoDB shell version v4.0.7
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb
Implicit session: session { “id” : UUID(“dd815f12-50ec-499d-a162-b5ee7f3003d5”) }
MongoDB server version: 4.0.7
…
exit
bye
- 备份test数据库到H盘的文件夹中:
mongodump -h 127.0.0.1:27017 -d test -o H:\mongodb\mongobeifen
C:\WINDOWS\system32>mongodump -h 127.0.0.1:27017 -d test -o H:\mongodb\mongobeifen
2019-06-09T13:38:50.248+0800 writing test.t12 to
2019-06-09T13:38:50.338+0800 writing test.stu to
2019-06-09T13:38:50.339+0800 writing test.dv3 to
2019-06-09T13:38:50.339+0800 writing test.test1000 to
2019-06-09T13:38:50.343+0800 done dumping test.stu (7 documents)
2019-06-09T13:38:50.344+0800 writing test.t3 to
2019-06-09T13:38:50.347+0800 done dumping test.dv3 (6 documents)
2019-06-09T13:38:50.348+0800 done dumping test.test1000 (4 documents)
2019-06-09T13:38:50.349+0800 done dumping test.t3 (1 document)
2019-06-09T13:38:50.916+0800 done dumping test.t12 (100000 documents) - 数据恢复
恢复语法:
mongorestore -h dbhost -d dbname --dir dbdirectory
-h: 服务器地址
-d: 需要恢复的数据库实例
–dir: 备份数据所在位置
mongorestore -h 127.0.0.1:27017 -d douban --dir H:\mongodb\mongobeifen\douban