1、初识Mongo
概述:与关系型数据库不同,MongoDB 的数据以类似于 JSON 格式的二进制文档存储,通常称这种格式为Bson,Bson不仅支持JSON中已有的数据类型,还增加了一些额外的数据类型,例如日期和二进制数据,以提高存储效率和解析速度。。
文档型的数据存储方式的优势:
- 文档的数据类型可以对应到语言的数据类型,如数组类型(Array)和对象类型(Object)
- 文档可以嵌套,有时关系型数据库涉及几个表的操作,在 MongoDB 中一次就能完成,可以减少昂贵的连接花销;
- 文档不对数据结构加以限制,不同的数据结构可以存储在同一张表
使用场景:
网站数据
Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性
缓存
由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载
大尺寸、低价值的数据
使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费,在此之前,很多时候程序员往往会选择传统的文件进行存储
高伸缩性的场景
Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案
用于对象及JSON 数据的存储
Mongo 的BSON 数据格式非常适合文档化格式的存储及查询
MongoDB的应用:
游戏场景
使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
物流场景
使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
社交场景
使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
物联网场景
使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
直播
使用 MongoDB 存储用户信息、礼物信息等
是否选用MongoDB:只要满足下表中的一个需求就可以使用:
应用特征 | Yes/No |
---|---|
应用不需要复杂事务及复杂join支持 | yes |
新应用,需求会变,数据模型无法确定,想快速迭代开发 | ? |
应用需要2000-3000以上的读写QPS(更高也可以) | ? |
应用需要TB甚至 PB 级别数据存储 | ? |
应用发展迅速,需要能快速水平扩展 | ? |
应用需要大量的地理位置查询、文本查询 | ? |
应用需要99.999%高可用 | ? |
MongoDB(MongoDB属于非关系型数据库)与关系型数据库的比较:
Bson介绍:BSON是一种类似于JSON的二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。BSON有三个特点:轻量性、可遍历性、高效性。
数据类型 说明 document举例 String 字符串 {key:“cba”} Integer 整型数值 {key:2} Boolean 布尔型 {key:true} Double 双精度浮点数 {key:0.23} ObjectId 对象id,用于创建文档的id {_id:new ObjectId()} Array 数组 {arr:[“jack”,“tom”]} Timestamp 时间戳 { createTime: new Timestamp() } object 内嵌文档 {student:{name:“zhangsan”,age:18}} null 空值 {key:null} Date或者ISODate 日期时间 {birthday:new Date()} Code 代码 {setPersonInfo:function(){}}
2、Mongo的安装与启动
2.1、Mongo的安装
①、进入Mongo官网(Download MongoDB Community Server | MongoDB)
②、选择正确的版本下载(根据下图操作):
③、将下载的压缩包解压,再将解压后的目录重命名为一个简洁的名字后放入D盘根目录中(目录名不要带 . ):
2.2、Mongo的启动
2.2.1、启动方式一
启动命令:
mongod.exe --dbpath=path --logpath=path
- dbpath 指定数据存储位置
- logpath 指定日志存储在哪个位置
注意:指定的路径一定要存在。
①、进入mongodb7_0_12目录(解压后重命名并放在D盘的目录),以cmd的方式打开:
点击路径位置输入cmd并回车:
此时就打开了cmd:
②、在mongodb7_0_12目录下新建两个目录,用于存放数据和日志:
由于日志是写入具体的文件,所以需要进入新建的log目录中新建一个文本文件用于保存日志:
③、在刚才打开的cmd命令行中启动mongo:
# 手动切换到bin目录
cd ./bin# 启动mongo并指定数据和日志的存放目录
mongod.exe --dbpath="D:\mongodb_5_0_28\data" --logpath="D:\mongodb_5_0_28\log\mylog.txt"
运行结果如下:
④、根据第一步的步骤再次打开一个cmd并输入如下命令以连接已启动的mongo数据库:
# 进入bin目录
cd ./bin# 启动mongo
mongo
运行结果如下:
⑤、通过如下命令查看所有的表:
show dbs;
运行结果如下:
2.2.2、启动方式二
①、完成启动方式一中的第②步,提前准备放数据和日志的目录以及存放日志的文件;
②、在mongodb_5_0_28目录下创建一个后缀为bat的文件,并写入如下内容后保存退出:
D:\mongodb_5_0_28\bin\mongod.exe --dbpath="D:\mongodb_5_0_28\data" --logpath="D:\mongodb_5_0_28\log\mylog.txt"
③、双击我们创建的start.bat文件,运行结果如下:
④、完成启动方式一中的第③步即可操作mongo了!
3、通过配置文件管理参数
问题
启动MongoDB时,编写参数太麻烦
解决方案
通过配置文件统一管理
①、首先在mongo的根目录下创建一个后缀名为conf的文件,创建完成后编辑该文件:
②、在文件中写入如下内容并保存退出(路径需要根据自己的实际情况改写):
# 数据库路径
dbpath=D:\mongodb_5_0_28\data
# 日志输出文件路径
logpath=D:\mongodb_5_0_28\log\mylog.txt
# 错误日志采用追加模式
logappend=true
# 启用日志文件,默认启用
journal=true
# 这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
# 端口号 默认为27017
bind_ip=0.0.0.0
# 启用权限验证,使用账号密码登录
# auth=true
③、修改mongo根目录下创建的start.bat文件:
D:\mongodb_5_0_28\bin\mongod -f D:\mongodb_5_0_28\mongo.conf
运行结果如下:
④、进入如下路径:D:\mongodb_5_0_28\bin,双击mongo.exe文件:
运行结果如下,说明我们的配置文件已经生效:
4、Linux安装Mongo
①、根据网络视频跟做完成一个Linux虚拟机的创建;
②、根据下图获取mongo的Linux版本地址(点击此处进入):
③、通过如下命令下载mongo:
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.28.tgz
如果报错说wget:未找到命令,先执行如下命令安装wget再执行上方的命令下载mongo:
yum install wget -y
运行结果如下:
④、解压下载的压缩文件并对解压后的文件重新命名:
tar -zxvf mongodb-linux-x86_64-rhel70-5.0.28.tgz
运行结果如下:
对解压后的文件重命名:
mv mongodb-linux-x86_64-rhel70-5.0.28 mongodb5
运行结果如下:
⑤、将mongo的bin目录增加到环境变量中并生效配置文件:
# 进入mongo根目录下的bin目录
cd mongodb5/bin/# 获取bin目录的完整路径(将获取到的路径复制,后面要用)
pwd# 编辑配置文件
vi ~/.bash_profile# 进入文件后先按i切换到插入模式
i# 找到要修改的PATH属性进行修改(以下是我修改后的内容作为参考)
PATH=/home/muxi/mongodb5/bin:$PATH:$HOME/.local/bin:$HOME/bin# 修改完成后先按ESC键退出插入模式(ESC键在键盘左上角)
ESC# 输入:wq后回车即可保存并退出文件
:wq
修改后的文件内容如下:主要是将mongo目录下的bin目录添加到了PATH属性中,并用:将多个属性值进行分割:
通过如下命令生效配置文件:
. ~/.bash_profile
⑥、提前创建存放数据和日志的目录以及存放日志的文件:
# 由于要创建目录和文件,需要切换到管理员
sudo root# 创建多级目录
mkdir -p /var/mongo/data
mkdir -p /var/mongo/log# 创建存放日志的文件
touch /var/mongo/log/mylog.log
⑦、后台启动mongo:
# 后台运行mongo
mongod --dbpath /var/mongo/data/ --logpath /var/mongo/log/mylog.log -fork# 查看mongo进程验证是否启动成功
ps aux | grep mongod
运行结果如下:
⑧、连接mongo数据库:
mongo
运行结果如下:
此时,我们的mongo就完成了安装和启动,接下来可以考虑使用配置文件简化启动命令。
⑨、创建配置文件简化启动命令:
# 结束当前运行的mongo
Ctrl + C# 进入mongo的根目录
cd ..# 创建配置文件
touch mongo.cfg# 编辑配置文件
vim mongo.cfg
为文件添加的内容如下:
#数据库路径
dbpath=/var/mongo/data
#日志输出文件路径
logpath=/var/mongo/log/mylog.log
#错误日志采用追加模式
logappend=true
#启用日志文件,默认启用
journal=true
#这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
#端口号 默认为27017
bind_ip=0.0.0.0
port=27017
⑩、验证配置文件是否生效:
# 关闭以及启动的mongo进程
# 首先获取mongo的进程号
ps aux | grep mongodkill -9 mongod的进程号
运行结果如下:
并且输入mongo后也显示连接失败进一步说明我们已经成功关闭了mongo:
指定配置文件在后台运行:
mongod -f mongo.cfg --fork
运行结果如下:
连接数据库:
mongo
运行结果如下:
5、图形管理工具之Robo 3T
下载:https://download.studio3t.com/studio-3t/windows/2022.6.1/studio-3t-x64.zip
提示:安装时无脑下一步即可(可以调整软件的安装位置)。
实操:通过Robo 3T连接本地的mongo数据库(连接前一定要打开本地的mongo,可以直接打开前面创建的启动脚本D:\mongodb_5_0_28\start.bat)
测试结果如下:
点击连接后,我们就可以通过Robo 3T对mongo数据库进行操作了!
6、MongoDB基础命令
①、查看数据库
概述:列出所有在物理上存在的数据库。
show dbs;
②、切换数据库/创建数据库
概述:如果数据库不存在,则指向数据库,但不会立即创建,直到插入数据或创建集合时数据库才被创建。
use 数据库名;
③、删除当前数据库
概述:删除当前指向的数据库,如果指向的数据库不存在,则什么也不做。
use 数据库名 db.dropDatabase()
④、创建集合
概述:创建一个集合。
注意:无需手动创建集合, 向不存在的集合中第一次添加数据时,集合会自动被创建出来。
db.createCollection("集合名")
⑤、查看集合
showtables; // 只在老版本中支持 show collections;
⑥、删除集合
db.集合名.drop();
实操:通过图形管理工具Robo 3T实操基础命令(需要提前完成第5节的实操以连接本地的mongo数据库)。
提示:以下所有操作都在Robo 3T中的intelliShell中完成,下面是Robo 3T的使用说明:
1.查看所有的数据库:
show dbs;
运行结果如下:
2.切换数据库:
// 切换到存在的数据库local
use local;
//切换到不存在的数据库(即创建数据库)
use newdb
运行结果如下:
3.创建集合:
// 创建一个名为student的集合
db.createCollection("student")
运行结果如下:
由于我们是在新建的数据库中创建了集合,此时刷新数据库就能看到名为newdb的数据库了:
4.查看集合:
show collections;
运行结果如下:
5.删除集合:
db.student.drop();
运行结果如下:
7、文档的添加
概述:MongoDB中的数据以文档的形式存储,而MongoDB又将文档存储在集合中。集合类似于关系数据库中的表。如果集合不存在,MongoDB 会在首次存储该集合的数据时创建该集合。
语法:
db.集合名.函数名()
函数名 | 含义 |
---|---|
save( ) | 保存文档。文档不存在时,新创建一个文档; 文档存在,更新文档 |
insert( ) | 插入文档,可以保存单文档,也可以是多文档 |
insertOne( ) | 插入一个文档 |
insertMany( ) | 插入多个文档 |
注意:插入文档时,如果不指定_id参数,MongoDB会为文档分配一个唯一的ObjectId。
实操:通过实操掌握如何为mongo添加文档。
// 创建并切换到一个新的数据库
use Tsinghua;// 为指定的集合添加文档
db.student.save({"name":"muxi","age":"24","score":"88"})
运行结果如下:
接下来查看文档内容:
student集合中的文档内容如下:
此时我们有一个新的需求,要求将name的值改为muxikeqi,根据上面表中提到的文档存在时,会更新文档,接下来执行如下程序来验证一下:
db.student.save({"name":"muxikeqi","age":"24","score":"88"})
运行后查看集合选项卡(查看时要按Ctrl+R进行刷新):
欸?怎么新增了一条数据,而不是修改原有数据?哦,原来mongo判断文档是否存在的依据是根据_id属性的值进行判断,因此要实现name属性值更改的需求时还要指定_id,完整代码如下:
// 这里的_id要根据自己操作时的_id属性的值进行修改
db.student.save({"_id" : ObjectId("66b40121db9f8c197f3ce023"),"name":"muxikeqi","age":"24","score":"88"})
运行结果如下:
运行后查看集合选项卡(查看时要按Ctrl+R进行刷新):
通过观察id和name属性的值,此时我们修改name属性值的需求已经正确实现了。
接下来尝试为已有集合student插入文档:
// insert()插入单条数据的格式与save()一样
db.student.insert({"name":"张三","age":"18","score":"90"})
// 插入多条数据时需要用[]将多组数据括起来
db.student.insert([{"name":"李四","age":"20","score":"92"},{"name":"王五","age":"23","score":"89"}])
运行后的集合选项卡(查看时要按Ctrl+R进行刷新):
最后实操一下inserOne()和insertMany(),其实它们的功能完全能通过insert()方法实现,这里主要是要了解:
db.student.insertOne({"name":"niko"})
db.student.insertMany([{"name":"donk","age":17},{"name":"magixx"}])
运行后的集合选项卡(查看时要按Ctrl+R进行刷新):
8、文档的修改
语法:
db.集合名.函数名()
函数名 | 含义 |
---|---|
update( <query> , <update> ,{multi: <boolean>} ) | 参数query:查询条件,类似sql语句update中where部分 参数update:更新操作符,类似sql语句update中set部分 参数multi:可选,默认是false,表示只更新找到的第一条记录,值为true表示把满足条件的文档全部更新 |
updateOne( <query> , <update> ) | 更新一条数据 |
updateMany( <query> , <update> ) | 更新多条数据 |
replaceOne(<query> , <update> ) | 只能更新整文档 |
提示:更新的字段若不存在,会生成一个相应字段。
实操:通过本实操掌握mongo如何修改文档。
首先尝试updata()方法的用法,将 集合下的 文档中 属性name的值为muxikeqi的值修改为muxi:
db.student.update({"name" : "muxikeqi"},{"name" : "muxi"})
修改后的集合选项卡(查看时要按Ctrl+R进行刷新):
通过观察上图不难得出结论:mongo中通过update()方法修改文档时,默认是更新整个文档,并且默认只会修改找到的第一个文档。
下面先将第一条数据还原,方便后面的测试:
db.student.update({"name" : "muxi"},{"name" : "muxikeqi","age" : "24","score" : "88"})
接下来尝试只修改第一个name属性值为muxikeqi的age属性:
// 需要修改文档中单个属性值时可以通过$set属性实现
// 注意:即使指定的文档中没有age字段也会创建该字段并修改
db.student.update({"name" : "muxikeqi"},{$set:{"age" : 28}})
修改后的集合选项卡(查看时要按Ctrl+R进行刷新):
接下来尝试将所有name属性值为muxikeqi的age属性值修改为30:
// 同时修改多个文档时,需要添加{multi:true}
db.student.update({"name":"muxikeqi"} , {$set:{"age":30}} , {multi:true})
修改后的集合选项卡(查看时要按Ctrl+R进行刷新):
至此,我们已经了解了update()方法的默认机制,如何只修改文档中的单个属性,如何同时修改所有文档的操作方法。
下面了解一下updateOne()和updateMany()的用法,它们的功能都能通过update()方法实现:
db.student.updateOne({"name":"张三"},{$set:{"score":100}})
db.student.updateMany({"name":"muxikeqi"},{$set:{"score":99}})
修改前:
修改后:
最后了解一下replaceOne:
// 这里不用$set是因为会报错
db.student.replaceOne({"name":"李四"},{"age":25})
修改前:
修改后:
9、文档的删除
语法:
db.集合名.函数名()
函数名 | 含义 |
---|---|
remove( <query> ) | 参数query:匹配符合的删除条件数据 |
deleteOne( <query> ) | 更新一条数据 |
deleteMany( <query> ) | 更新多条数据 |
实操:通过本实操掌握mongo中删除文档的方法(需要提前拷贝多份,方便后期的学习):
// remove()默认删除多个文档
db.student.remove({"name":"muxikeqi"})
// deleteOne()默认删除一个文档
db.student.deleteOne({"score":100})
// deleteMany()默认删除多个文档
db.student1.deleteMany({"name":"muxikeqi"})
student集合运行后的结果为:
student1运行后的结果:
接下来尝试删除集合中的所有文档:
// 两种方法的{}都不能省略,否则会报错
db.student.remove({})
db.student1.deleteMany({})
运行后两个集合都没有任何文档:
10、文档的查询
概述:若要从集合中选择文档,可以使用 find() 或者 findOne() 方法。若要选择集合中的所有文档,请将空文档作为查询筛选器文档传递给该方法。
语法:
db.集合名.函数名()
函数名 | 含义 |
---|---|
find( <{条件文档}> ) | 查找到所有匹配数据 |
findOne( <{条件文档}> ) | 只返回匹配的第一个数据 |
运算符:
语法 操作 格式 $eq 等于 {:} $lt 小于 {:{$lt:}} $lte 小于或等于 {:{$lte:}} $gt 大于 {:{$gt:}} $gte 大于或等于 {:{$gte:}} $ne 不等于 {:{$ne:}} $or 或 {$or:[{},{}]} $in 在范围内 {age:{$in:[val1,val2]}} $nin 不在范围内 {age:{$nin:[val1,val2]}}
模糊匹配
概述:使用//或$regex编写正则表达式。
示例:
db.person.find({name:/^zs/}) db.person.find({name:{$regex:'^zs'}}})
自定义查询
概述:使用$where后面写一个函数,返回满足条件的数据
示例:
db.person.find({$where:function(){return this. Age>20}}) # 高版本已经不支持了 db.person.find({$where:"this.age==23"}); db.person.find("this.age >23"); db.person.find('this.country=="吴国" || this. Age==23');
limit
概述:用于读取指定数量的文档。
语法:
db.集合名称.find().limit(NUMBER)
skip
概述:用于跳过指定数量的文档。
语法:
db.集合名称.find().skip(2)
sort
概述:用于对结果集进行排序。
语法:
db.集合名称.find().sort({字段:1,...})
- 参数为1时表示升序排列
- 参数为-1时表示降序排列
count
概述:用于统计结果集中文档条数。
语法及演示:
db.集合名称.find({条件}).count() db.集合名称.count({条件})db.stu.find({gender: true}).count() db.stu.count({age:{$gt:20},gender: true})
$exists
概述:判断是否有某个字段。
语法:
db.集合名称.find({'field':{$exists: true}})
dictinct
概述:用于去重。
语法:
db.集合名称.distinct(field) db.集合名称.distinct(field,{过滤条件 })
实操前准备:准备用于查询的集合,并为集合添加文档:
// 为集合添加数据用于后期查询
db.person.insert([{"name":"司马懿","country":"魏国","age":35},
{"name":"张辽","country":"魏国","age":34},
{"name":"徐晃","country":"魏国","age":24},
{"name":"夏侯惇","country":"魏国","age":23},
{"name":"夏侯渊","country":"魏国","age":23},
{"name":"庞德","country":"魏国","age":23},
{"name":"张郃","country":"魏国","age":34},
{"name":"李典","country":"魏国","age":41},
{"name":"乐进","country":"魏国","age":34},
{"name":"典韦","country":"魏国","age":12},
{"name":"曹洪","country":"魏国","age":21},
{"name":"曹仁","country":"魏国","age":11},
{"name":"诸葛亮","country":"蜀国","age":20},
{"name":"关羽","country":"蜀国","age":32},
{"name":"张飞","country":"蜀国","age":23},
{"name":"马超","country":"蜀国","age":53},
{"name":"黄忠","country":"蜀国","age":23},
{"name":"赵云","country":"蜀国","age":32},
{"name":"魏延","country":"蜀国","age":42},
{"name":"关平","country":"蜀国","age":12},
{"name":"周仓","country":"蜀国","age":42},
{"name":"关兴","country":"蜀国","age":23},
{"name":"张苞","country":"蜀国","age":12},
{"name":"周瑜","country":"吴国","age":32},
{"name":"吕蒙","country":"吴国","age":11},
{"name":"甘宁","country":"吴国","age":23},
{"name":"太史慈","country":"吴国","age":23},
{"name":"程普","country":"吴国","age":24},
{"name":"黄盖","country":"吴国","age":28},
{"name":"韩当","country":"吴国","age":23},
{"name":"周泰","country":"吴国","age":29},
{"name":"蒋钦","country":"吴国","age":19},
{"name":"丁奉","country":"吴国","age":17},
{"name":"徐盛","country":"吴国","age":27}
])
实操:通过本实操掌握mongo查询数据的方法。
首先尝试用find()获取全部的文档:
db.person.find()
运行结果如下:
接下来用findOne()获取所有的文档:
db.person.findOne()
运行结果如下:
总结:在查询所有文档时,用find()查询时能获取集合中所有的文档,而用findOne()查询时只能获取到第一个文档。
下面尝试获取指定条件的文档:
// 获取集合中age属性小于12的所有文档
db.person.find({"age":{$lt:12}})
运行结果如下:
下面尝试有多个判断条件时有一个满足条件即可的情况(下面演示age属性的值小于12或大于50的情况):
db.person.find({$or:[{"age":{$lt:12}},{"age":{$gt:50}}]})
运行结果如下:
下面尝试判断条件的同一属性满足多个特定值即可的情况:
db.person.find({"age":{$in:[34,35,36]}})
运行结果如下:
下面尝试利用模糊匹配查询所有name值第一个字是 '张' 的文档:
db.person.find({"name":/^张/})
运行结果如下:
下面展示模糊匹配的另一种写法:
db.person.find({"name":{$regex:"^周"}})
运行结果如下:
下面演示limit()方法获取指定条数的数据:
// 获取匹配到的前三条数据(前三个文档)
db.person.find().limit(3)
运行结果如下:
下面演示通过skip()跳过指定条数的数据后再通过limit()方法获取指定条数的文档:
db.person.find().skip(1).limit(2)
运行结果如下:
下面演示通过sort()对年龄做排序处理并且用limit()限制排序的个数:
db.person.find().limit(3).sort({"age":-1})
运行结果如下:
下面演示通过count()方法获取当前集合有多少个文档(多少条数据):
db.person.find().count()
运行结果如下:
下面演示获取文档中存在某个字段的文档:
db.person.find({"name":{$exists:true}})
运行结果如下:
下面演示通过dictinct()对属性值进行去重:
db.person.distinct("age")
运行结果如下:
最后演示一下自定义查询:
// 这里的this表示person这个集合
db.person.find({$where:"this.age>=42"})
运行结果如下:
下面演示自定义查询要同时满足多个条件时的用法:
db.person.find({$where:"this.age>=40 && this. Country=='魏国'"})
运行结果如下:
11、聚合操作
概述:MongoDB 中聚合(aggregate)主要用于处理多个文档(诸如统计平均值,求和等),并返回计算后的数据结果。
11.1、分组和过滤
语法:
db.集合名称.aggregate([{管道:{表达式}}])
11.1.1、管道命令之$group
概述:$group 是所有聚合命令中用的最多的一个命令,用于将集合中的文档分组,可用于统计结果。
示例:
db.stu.aggregate({$group:{_id:"$country",// 这里的counter是随便取的一个变量名counter:{$sum:1}}}
)
注意:
- db.集合名.aggregate 是语法,所有的管道命令都需要写在其中;
- _id 表示分组的依据,按照哪个字段进行分组,例如:使用$gender表示选择gender字段进行分组;
- $sum:1 表示把每条数据作为1进行统计,统计的是该分组下面数据的条数。
11.1.2、管道命令之$match
概述:$match 用于进行数据的过滤,是在能够在聚合操作中使用的命令,和 find 区别在于$match 操作可以把结果交给下一个管道处理,而 find 不行。
示例:查询年龄大于20的魏国的人数。
db.person.aggregate([{$match:{age:{$gt:20}}},{$group:{_id:"$country",counter:{$sum:1}}}])
11.1.3、常用表达式
语法:
表达式:'$列名'
常用表达式:
$sum
: 计算总和, $sum:1 表示以⼀倍计数$avg
: 计算平均值$min
: 获取最⼩值$max
: 获取最⼤值$push
: 在结果⽂档中插⼊值到⼀个数组中
实操:通过本实操掌握分组和过滤的用法。
首先尝试利用管道命令$group对person集合中的country字段进行分组,并且统计每个分组有多少个文档:
db.person.aggregate([{ $group : // 分组中每多一条数据,number就会加1{"_id":"$country" , "number":{$sum:1}}}]
)
运行结果如下:
接下来对上面的程序做升级,要求对country分组后,每个分组中还要包含所有被包含的name属性值:
db.person.aggregate([{$group:{"_id":"$country","names":{$push:"$name"},"number_country":{$sum:1}}}])
运行结果如下:
下面再次对上面的程序做升级,在分组前先过滤出所有年龄大于20的文档,然后再对过滤出的文档做分组处理:
db.person.aggregate([{$match:{age:{$gt:20}}},{$group:{"_id":"$country","names":{$push:"$name"},"number_persons":{$sum:1}}}
])
运行结果如下:
总结:
// 单个聚合操作
db.集合名.aggregate(管道命令:{聚合关键字 : info})// 多个聚合操作
db.集合名.aggregate([管道命令1:{聚合关键字:info},管道命令2:{聚合关键字:info},...])
11.2、排序与分页
11.2.1、管道命令之$sort
概述:$sort 用于将输入的文档排序后输出,1表示升序排序,-1表示降序排序。
示例:
// 查询人物,按照年龄升序
db.person.aggregate([{$sort:{age:1}}])// 查询每个国家的人数,并排序
db.person.aggregate([{$group:{_id:"$country",counter:{$sum:1}}},{$sort:{counter:-1}}
])
11.2.2、管道命令之 $skip 和 $limit
概述:$limit用于限制返回数据的条数,$skip用于跳过指定的文档数,并返回剩下的文档数。
注意:同时使用 $limit 和 $skip 时先使用 $skip 再使用 $limit。
示例:
// 查询2条信息
db.person.aggregate([{$limit:2}
])// 查询从第三条开始的信息
db.person.aggregate([{$skip:3}
])// 查询每个国家的人数,按照人数升序,返回第二条数据
db.person.aggregate([{$group:{_id:"$country",counter:{$sum:1}}},{$sort:{counter:-1}},{$skip:1},{$limit:1}
])
11.2.3、管道命令之$project
概述:$project用于修改文档的输入输出结构,字段值0表示不显示,字段值1表示显示。
示例:
// 查询人物的姓名、年龄,不显示ID
db.person.aggregate([{$project:{_id:0,name:1,age:1}}])// 查询每个国家的人数,只显示数量
db.person.aggregate([{$group:{_id:"$country",counter:{$sum:1}}},{$project:{_id:0,counter:1}}])
注意:
- _id 与其他字段共同设置时,0只能设置在 _id 上;
- 设置字段时,除了 _id 字段,其他默认取反。
实操:通过本实操掌握mongo中排序与分页的操作。
首先尝试 $sort 的用法:
// 对person集合中age字段做降序排序
db.person.aggregate([{$sort:{age:-1}}])
运行结果如下:
下面对country进行分组,并统计每个contry的人数,最后通过人数做降序输出:
db.person.aggregate([{$group:{"_id":"$country","number_man":{$sum:1}}},{$sort:{number_man:-1}}
])
运行结果如下:
下面演示聚合分页,也是对上面的程序做升级:
db.person.aggregate([{$group:{"_id":"$country","num_person":{$sum:1}}},{$sort:{num_person:-1}},{$skip:1},{$limit:1}
])
运行结果如下:
下面演示 $project 控制文档输出:
db.person.aggregate([{$group:{"_id":"$country","num_person":{$sum:1},"age_avg":{$avg:"$age"}}},{$sort:{num_person:-1}},{$project:{num_person:1,age_avg:1}} // 注意:不要同时设置0和1,否则会报错,但是_id设置为0,其它属性设置为1是被允许的
])
运行结果如下:
最后演示一下 $project 的特殊用法:
db.person.aggregate([{$group:{"_id":"$country","num_person":{$sum:1},"age_avg":{$avg:"$age"}}},{$sort:{num_person:-1}},// 注意:当设置某部分字段的显示方式后,没有指定的默认取反(例如这里将_id设置为不显示,num_person和age_avg没有指定,它们默认不显示,取反后就是显示){$project:{_id:0}}
])
运行结果如下:
12、索引
12.1、索引的基本用法
概述:索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录,这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时。
创建索引的语法:
db.集合名.createIndex(keys, options)
提示:语法中 Key 值为要创建的索引字段,1 为指定按升序创建索引,如果想按降序来创建索引指定为 -1 即可。
注意:
- 在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名
- MongoDB默认所有的集合在_id字段上有一个索引。
示例:
db.person.createIndex({"name":1})
查看索引的语法:
// 默认情况下_id是集合的索引
db.集合名.getIndexes()
删除索引的语法:
db.集合名.dropIndex({'索引名称':1})
实操:通过本实操掌握mongo如何创建,查看和删除索引。
首先查看当前person集合中有哪些索引:
// 查看索引
db.person.getIndexes()
运行结果如下:
然后新建一个索引,然后再查看索引:
// 创建新的索引
db.person.createIndex({"name":1})
// 再次查看索引
db.person.getIndexes()
运行结果如下:
下面删除新建的索引,然后再查看索引:
// 删除新建的索引
db.person.dropIndex({"name":1})
// 再次查看索引
db.person.getIndexes()
运行结果如下:
接下来我们尝试验证一下索引是否能提高查询效率,首先准备若干条数据:
for(i=0;i<120000;i++){db.index01.insert({"name":"muxikeqi"+i,"exp":i})
};
查看当前数据集有多少条数据:
db.index01.find().count()
运行结果如下:
获取正常检索muxikeqi99999所消耗的时间:
db.index01.find({"name":"muxikeqi99999"}).explain("executionStats")
运行结果如下,可知查找花费了37ms:
接下来为name属性添加索引并检索:
db.index01.createIndex({"name":1})
db.index01.find({"name":"muxikeqi99999"}).explain("executionStats")
运行结果如下,可知查询花费了0ms:
总结:索引能极大地提高查询速度,数据量越大越需要使用索引。
12.2、唯一索引与复合索引
12.2.1、唯一索引
概述:在默认情况下mongdb的索引的值是可以相同的,创建唯一索引之后,数据库会在插入数据的时候检查创建索引域的值是否存在,如果存在则不会插入该条数据,但是创建索引仅仅能够提高查询速度,同时降低数据库的插入速度。
语法:
db.集合名.createIndex({"字段名":1}, {"unique": true})
基操:利用唯一索引进行数据去重(根据唯一索引指定的字段的值,如果相同,则无法插入数据)
db.person.createIndex({"name":1}, {"unique": true})
db.person.insert({name: 'test10000'})
12.2.2、复合索引
概述:在进行数据去重的时候,可能用多个字段来做数据的唯一性,这个时候可以考虑建立复合索引来实现。
语法:
db.collection_name.createIndex({字段1:1,字段2:1})
注意点:
-
根据需要选择是否需要建立唯一索引
-
索引字段是升序还是降序在单个索引的情况下不影响查询效率,但是带复合索引的条件下会有影响
-
数据量巨大并且数据库的读出操作非常频繁的时候才需要创建索引,如果写入操作非常频繁,创建索引会影响写入速度
实操:通过本实操掌握唯一索引与复合索引的创建。
在创建唯一索引之前,需要提前将index01集合中为name字段添加的索引删除,然后再为name添加唯一索引:
db.index01.dropIndex({"name":1})
db.index01.createIndex({"name":1},{'unique': true})
接下来验证唯一索引,添加一个已经存在的数据:
db.index01.insert({"name":"muxikeqi123"})
运行结果如下,很明显唯一索引已经生效了:
或者直接去查看索引验证:
db.index01.getIndexes()
运行结果如下:
下面尝试为name和age字段创建复合索引:
db.index01.createIndex({"name":1,"age":1})
运行结果如下:
最后验证一下复合索引是否创建成功:
db.index01.getIndexes()
运行结果如下:
13、Python操作mongo
官方文档:PyMongo 4.8.0 documentation
13.1、环境安装
概述:在Pycharm中的终端(Alt+F12)中输入如下代码安装pymongo第三方库。
pip install pymongo==4.2.0
运行结果如下:
13.2、使用样例
引入第三方模块
import pymongo
连接、创建客户端
client = pymongo.MongoClient("localhost", 27017) client = pymongo.MongoClient('mongodb://localhost:27017/')
获取指定数据库person
db = client.person db = client['person']
获取指定集合movie
collection = db.movie collection = db['movie']
添加数据
#增加一条 m1={name:'300集',actor:'高总',level:10} m1_id = movie.insert_one(s1).inserted_id #增加多条 mids = movie.insert_many([movie1,movie2])
注意:原insert方法也可以实现上面的功能,但是在PyMongo 3.x的版本已经不推荐使用了。
查找数据
- find() 返回一个生成器对象
- find_one() 返回一条数据
result = movie.find_one() result = movie.find_one({'name':'1200集'}) result = movie.find_one({'_id':OjectId('5932a80115c2606a59e8a049')}) result = movie.find_one({level:{'$gt':1}}) results = movie. Find()
比较符号:1
符号 含义 示例 $lt 小于 {'age': {'$lt': 20}} $gt 大于 {'age': {'$gt': 20}} $lte 小于等于 {'age': {'$lte': 20}} $gte 大于等于 {'age': {'$gte': 20}} $ne 不等于 {'age': {'$ne': 20}} $in 在范围内 {'age': {'$in': [20, 23]}} $nin 不在范围内 {'age': {'$nin': [20, 23]}} 功能符号:
符号 含义 示例 示例含义 $regex 匹配正则表达式 {'name': {'$regex': '^M.*'}} name以M开头 $exists 属性是否存在 {'name': {'$exists': True}} name属性存在 $type 类型判断 {'age': {'$type': 'int'}} age的类型为int $mod 数字模操作 {'age': {'$mod': [5, 0]}} 年龄模5余0 $text 文本查询 {'$text': {'$search': 'Mike'}} text类型的属性中包含Mike字符串 $where 高级条件查询 {'$where': 'obj.fans_count == obj.follows_count'} 自身粉丝数等于关注数
获取文档个数
count = movie.count_documents()
排序
results = collection.find().sort('name', pymongo.ASCENDING)
偏移、分页
collection.find().sort('name', pymongo.ASCENDING).skip(2).limit(2)
更新
args = {"name": "曹操"} student.update_one(args, {'$set':{field:value}}}) result = collection.update_many(query, {'$set': {field: value}})
注意:update也可以实现上面的功能,但是在PyMongo 3.x的版本已经不推荐使用了。
删除
result = collection.remove({'name': '300集'}) # 4.2版本不支持 result = collection.delete_one({'name': '300集'}) result = collection.delete_many({'age': {'$lt': 25}})
实操:通过本实操掌握Python操作mongo的用法。
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]# 操作数据
def add_data():"""添加数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])if __name__ == '__main__':add_data()
运行结果如下:
接下来尝试修改已有数据:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]# 操作数据
def add_data():"""添加数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})if __name__ == '__main__':# add_data()update_data()
运行结果如下:
接下来尝试删除集合中的所有文档:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]# 操作数据
def add_data():"""添加数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})if __name__ == '__main__':# add_data()# update_data()delete_data()
运行结果如下:
接下来尝试做无参查询操作:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]
# c3中的数据较多,方便做查询
c3 = db1.person# 操作数据
def add_data():"""添加数据insert_one()用于向指定集合中插入一条数据insert_many()用于向指定集合中插入多条数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据update_one用于修改第一个符合条件的文档update_many用于修改所有符合条件的文档"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据delete_one()用于删除第一个符合条件的文档delete_many()用于删除所有符合条件的文档"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})def search_data():"""查询数据find()用于查询所有符合条件的文档find_one()用于查询第一个符合条件的文档"""resp_all = c3.find()for resp in resp_all:print(resp)if __name__ == '__main__':# add_data()# update_data()# delete_data()search_data()
运行结果如下:
接下来尝试操作有参查询:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]
# c3中的数据较多,方便做查询
c3 = db1.person# 操作数据
def add_data():"""添加数据insert_one()用于向指定集合中插入一条数据insert_many()用于向指定集合中插入多条数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据update_one用于修改第一个符合条件的文档update_many用于修改所有符合条件的文档"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据delete_one()用于删除第一个符合条件的文档delete_many()用于删除所有符合条件的文档"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})def search_data():"""查询数据find()用于查询所有符合条件的文档find_one()用于查询第一个符合条件的文档"""resp_all = c3.find()for resp in resp_all:print(resp)def search_data_args():"""查询指定条件的数据"""rsp_all = c3.find({"age":{"$gt":30}})for rsp in rsp_all:print(rsp)if __name__ == '__main__':# add_data()# update_data()# delete_data()# search_data()search_data_args()
运行结果如下:
下面继续对search_data_args()函数做升级,添加排序功能:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]
# c3中的数据较多,方便做查询
c3 = db1.person# 操作数据
def add_data():"""添加数据insert_one()用于向指定集合中插入一条数据insert_many()用于向指定集合中插入多条数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据update_one用于修改第一个符合条件的文档update_many用于修改所有符合条件的文档"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据delete_one()用于删除第一个符合条件的文档delete_many()用于删除所有符合条件的文档"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})def search_data():"""查询数据find()用于查询所有符合条件的文档find_one()用于查询第一个符合条件的文档"""resp_all = c3.find()for resp in resp_all:print(resp)def search_data_args():"""查询指定条件的数据"""rsp_all = c3.find({"age":{"$gt":30}})# 获取的游标对象需要通过遍历获取具体的元素for rsp in rsp_all:print(rsp)def search_data_args_sort():"""查询指定条件的数据并做降序处理"""rsp_all = c3.find({"age": {"$gt": 30}}).sort("age",pymongo.DESCENDING)for rsp in rsp_all:print(rsp)if __name__ == '__main__':# add_data()# update_data()# delete_data()# search_data()# search_data_args()search_data_args_sort()
运行结果如下:
下面尝试对排序后的结果做分页处理:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]
# c3中的数据较多,方便做查询
c3 = db1.person# 操作数据
def add_data():"""添加数据insert_one()用于向指定集合中插入一条数据insert_many()用于向指定集合中插入多条数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据update_one用于修改第一个符合条件的文档update_many用于修改所有符合条件的文档"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据delete_one()用于删除第一个符合条件的文档delete_many()用于删除所有符合条件的文档"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})def search_data():"""查询数据find()用于查询所有符合条件的文档find_one()用于查询第一个符合条件的文档"""resp_all = c3.find()for resp in resp_all:print(resp)def search_data_args():"""查询指定条件的数据"""rsp_all = c3.find({"age":{"$gt":30}})# 获取的游标对象需要通过遍历获取具体的元素for rsp in rsp_all:print(rsp)def search_data_args_sort():"""查询指定条件的数据并做降序处理"""rsp_all = c3.find({"age": {"$gt": 30}}).sort("age",pymongo.DESCENDING)for rsp in rsp_all:print(rsp)def search_data_args_plus():"""查询指定条件的数据并做降序处理,打印时做分页处理"""rsp_all = c3.find({"age": {"$gt": 30}}).sort("age", pymongo.DESCENDING).skip(4).limit(4)for rsp in rsp_all:print(rsp)if __name__ == '__main__':# add_data()# update_data()# delete_data()# search_data()# search_data_args()# search_data_args_sort()search_data_args_plus()
运行结果如下:
最后演示pymongo如何获取集合中所有的数据条数:
# 引入三方模块
import pymongo# 创建连接(默认连接本机)
client = pymongo.MongoClient()# 获取数据库实例(如果指定的数据库实例不存在就自动创建)
db1 = client.Tsinghua
db2 = client['PyMyMongo']# 获取集合(如果指定的集合不存在就自动创建)
c1 = db1.user
c2 = db2["user"]
# c3中的数据较多,方便做查询
c3 = db1.person# 操作数据
def add_data():"""添加数据insert_one()用于向指定集合中插入一条数据insert_many()用于向指定集合中插入多条数据"""data1 = {"name":"张三","age":20,"score":89}data2 = {"name":"李四","age":22,"score":92}c1.insert_one(data1)c1.insert_many([data1,data2])def update_data():"""修改数据update_one用于修改第一个符合条件的文档update_many用于修改所有符合条件的文档"""# 指定要修改的数据args = {"name":"张三"}# c1.update_one(args,{"$set":{"age":30}})c1.update_many(args,{"$set":{"age":40}})def delete_data():"""删除数据delete_one()用于删除第一个符合条件的文档delete_many()用于删除所有符合条件的文档"""# 删除一条指定特征的数据# c1.delete_one({"name":"张三"}}# 删除所有数据c1.delete_many({})def search_data():"""查询数据find()用于查询所有符合条件的文档find_one()用于查询第一个符合条件的文档"""resp_all = c3.find()for resp in resp_all:print(resp)def search_data_args():"""查询指定条件的数据"""rsp_all = c3.find({"age":{"$gt":30}})# 获取的游标对象需要通过遍历获取具体的元素for rsp in rsp_all:print(rsp)def search_data_args_sort():"""查询指定条件的数据并做降序处理"""rsp_all = c3.find({"age": {"$gt": 30}}).sort("age",pymongo.DESCENDING)for rsp in rsp_all:print(rsp)def search_data_args_plus():"""查询指定条件的数据并做降序处理,打印时做分页处理"""rsp_all = c3.find({"age": {"$gt": 30}}).sort("age", pymongo.DESCENDING).skip(4).limit(4)for rsp in rsp_all:print(rsp)def count_data():"""获取当前集合中文档的总数"""# 注意:count_documents()方法也可以统计指定条件的文档个数count = c3.count_documents({})print(f"c3中文档的总数为:{count}")if __name__ == '__main__':# add_data()# update_data()# delete_data()# search_data()# search_data_args()# search_data_args_sort()# search_data_args_plus()count_data()
运行结果如下: