首页

3.4MongoDB的基本操作

关灯 护眼    字体:

上一章 目录 下一章




增、查、改、删是所有数据库必备的功能。本节将介绍如何使用  MongoDB  来实现这四个功能。



3.4.1  实例3:创建数据库与集合,写入数据


实例描述

在Robo  3T中进行如下操作。

(1)创建一个名为“chapter_3”的数据库,以及其中的多个集合。

(2)往集合里逐条插入数据。

(3)往集合里批量插入数据。

使用Robo  3T打开刚刚安装完成的MongoDB,可以看到A区域是空的,还没有数据库,如图3-21所示。

图3-21  MongoDB是空的

1.创建数据库与集合

(1)鼠标右击“小电脑”图标,在弹出的菜单中选择“Create  Database”命令,如图3-22所示。

(2)在弹出的对话框中输入数据库的名字,单击“Create”按钮完成数据库的创建,如图3-23所示。

图3-22  选择“Create  Database”命令

图3-23  输入数据库名字并单击Create按钮

(3)新创建的数据库会出现在  A  区域中。单击数据库左边的小箭头将其展开,然后右击“Collections(0)”文件夹,在弹出的菜单中选择“Create  Collection...”命令,如图3-24所示。

(4)在弹出的对话框中输入集合的名字,然后单击“Create”按钮(如图3-25所示)创建一个集合。

图3-24  选择“Create  Collection...”命令

图3-25  输入集合名字并单击“Create”按钮

(5)创建完集合后,原来的“Collections(0)”变成了“Collections(1)”。由此可以推测:括号里面的数字表示这个数据库里面有多少个集合。单击“Collections(1)”左侧的小箭头将其展开,可以看到集合“example_data_1”已经创建好了。双击集合名字,可以看到当前集合里什么都没有,如图3-26所示。

图3-26  空集合什么都没有

2.插入单条数据

插入单条数据的命令为“insertOne()”。

Robo  3T自带插入数据的功能。但是本书不准备介绍。本书会直接介绍如何在C区域执行MongoDB命令插入数据。

(1)创建一条JSON字符串。例如:

{"name":  "张小二",  "age":  17,  "address":  "浙江"}

(2)对C区域的内容做一些修改。

原来是:

db.getCollection('example_data_1').find({})

修改为:

db.getCollection('example_data_1').insertOne({"name":  "  张小二  ",  "age":  17,"address":  "浙江"})

(3)使用Windows与Linux的读者,可以按键盘上的“Ctrl  +  R”组合键;使用macOS的读者按“Command  +  R”组合键。运行后的界面如图3-27所示。可以看到,一条数据已经插入到了MongoDB中。

图3-27  插入数据

提示:

还可以通过单击Robo  3T上面的绿色三角形来运行命令。

(4)在  A  区域双击集合“example_data_1”,从新打开的选项卡中可以看到数据已经成功插入,如图3-28所示。

图3-28  数据已经成功插入

被插入的数据就是JSON字符串:

{"name":  "张小二",  "age":  17,  "address":  "浙江"}

提示:

JSON字符串必须使用双引号,不过这个规定在MongoDB中并非强制性的,用单引号也没有问题。例如,在C区域执行以下命令:

db.getCollection('example_data_1').insertOne({'name':  ’王小六’,  'age':  25,  'work':  ’厨师’})

插入以后,集合“example_data_1”中的数据如图3-29所示。

图3-29  插入第二条数据

如果将Python的字典直接复制到MongoDB的insertOne命令中,则绝大部分情况下这些字典都可以直接使用,只有极少数情况下需要做一些修改。3.4节将会讲到这些少数情况。

提示:

MongoDB还允许Key不带引号,直接写成{name:  ’王小六’,  age:  25,  work:  ’厨师’}。但这种写法存在一些局限性,并且会导致MongoDB的命令不方便平滑移植到Python中。因此,建议读者一律使用带单引号的写法或者带双引号的写法。

3.调整插入的字段

(1)任意修改、添加、删除字段。

在图3-29中,第1条数据没有“work”这个字段,第2条数据没有“address”这个字段。这就说明:在MongoDB里,插入数据的字段是可以任意修改、添加、删除的。

例如,再插入一条新的数据:

这一次所有的字段都和前两条数据不一样,但  MongoDB  仍然可以轻松处理——遇到新来的字段,加上去就是了,没什么大不了的,如图3-30所示。

图3-30  遇到新的字段,MongoDB会自动添加上去

(2)插入同一个字段,但格式却不同。

即使是同一个字段,其数据格式也可以不一样。

例如,再插入一条数据:

添加后的数据如图3-31所示。

图3-31  同一个字段的数据格式也可以不一样

提示:

“能不能做”是一回事,“应不应该做”是另一回事。虽然MongoDB能够处理同一个字段的不同数据类型,也可以随意增减字段,但并不意味着应该这样做。

在设计数据库时,应尽量保证同一个字段使用同一种类型的数据,并提前考虑好应该有哪些字段。

3.批量插入数据

批量插入数据的命令是“insertMany”。现在把一个包含很多个字典的列表传给“insertMany”。

列表为:

对应的MongoDB批量插入语句为:

运行后返回的数据如图3-32所示。

图3-32  批量插入数据

提示:

可以通过换行和缩进让代码更美观、易读。换行和缩进不影响代码功能。

运行以后的集合数据如图3-33所示。

图3-33  插入数据以后的集合

无论是插入一条数据还是插入多条数据,每一条数据被插入  MongoDB  后都会被自动添加一个字段“_id”。“_id”读作“Object  Id”,它是由时间、机器码、进程pid和自增计数器构成的。

“_id”始终递增,但绝不重复。

●  同一时间,不同机器上面的“_id”不同。

●  同一机器,不同时间的“_id”也不同。

●  同一机器同一时间批量插入的数据,“_id”依然不同。

提示:

_id的前8位字符转换为十进制就是时间戳。例如“5b2f2e24e0f42944105c81d2”,前8位字符“5b2f2e24”转换为十进制就是时间戳“1529818660”,对应的北京时间是“2018-06-24  13:37:40”。



3.4.2  实例4:查询数据


实例描述

对数据集example_data_1进行如下查询:

(1)查询所有数据。

(2)查询特定数据:查询“age”为25岁的员工。

(3)查询特定数据:查询“age”不小于25的所有记录。

(4)限定返回的数据字段类型。

在Robo  3T中双击集合名字,实际上是自动执行了以下这条查询语句:

db.getCollection('example_data_1').find({})

下面先来了解一下查询结果的三种显示模式。

1.三种显示模式

Robo  3T显示出来的查询结果如图3-34所示。注意右上角方框框住的三个图标。

图3-34  查询并返回所有数据

Robo  3T对于返回的数据有三种组织方式,从左到右分别是:“树形模式(Tree  Mode)”“表格模式(Table  Mode)和“文本模式(Text  Mode)”。

提示:

这三种显示模式是Robo  3T提供的,不是MongoDB的功能。

(1)树形模式。

优点是:可以直观地看到每一条记录有哪些字段,每一个字段是什么内容和什么格式,如图3-35所示。

弊端是:每次都要单击每一条记录左边的三角形,非常麻烦。

图3-35  树形模式

(2)表格模式(本书用得最多的显示模式)。

优点是:便于对数据整体有一个全面的认识。在表格模式里可以看到很多行数据,便于观察数据的全貌、对比不同记录的相同字段,如图3-36所示。

弊端是:不能显示嵌入式文档的内容。

图3-36  表格模式

(3)文本模式(如图3-37所示)。

优点是:便于对数据进行复制/粘贴,便于对特殊格式数据进行深入认识。

弊端是:一屏只能显示少量内容,要反复拖动滚动条才能完整看完数据;不方便不同记录之间进行对比。

图3-37  文本模式

2.查询固定值数据

(1)查询所有数据。

如要查询所有数据值,则直接使用下面两种写法的任意一种即可:

db.getCollection('example_data_1').find()



db.getCollection('example_data_1').find({})

(2)查询特定数据。

如要查询某个或者某些具体字段,则可以使用下面的语法来查询。如果有多个字段,则这些字段需要同时满足。

例如,对于数据集  example_data_1,要查询所有“age”字段为25的记录。则查询语句可以写为:

db.getCollection('example_data_1').find({'age':  25})

查询结果如图3-38所示。

图3-38  查询所有“age”为25的记录

由于“age”为25的记录有两条,于是需要进一步缩小查询范围——再增加一个限制条件:

运行结果如图3-39所示。

总结一下,“find”的参数相当于一个字典。字典的  Key  就是字段名,字典的值就是要查询的值。如果字典有多个Key,则这些字段需同时满足。

图3-39  多个查询条件同时满足

3.查询范围值数据

如要查询的字段值能够比较大小,则查询时可以限定值的范围。例如,对数据集example_data_1,要查询所有“age”字段不小于25的记录,则需要使用大于等于操作符“$gte”。查询语句如下:

db.getCollection('example_data_1').find({'age':  {'$gte':  25}})

运行效果如图3-40所示。

图3-40  查询范围数据

查询某个范围的数据会用到的操作符见表3-2。

表3-2  范围操作符及其意义

使用范围操作符的查询语句格式如下:

可以看出,在使用范围操作符后,原本填写被查询值的地方现在又变成了一个字典。这个字典的Key是各个范围操作符,而它们的值是各个范围的边界值。

【举例1】  查询所有“age”大于21并小于等于24的数据。

查询语句如下:

db.getCollection('example_data_1').find({'age':  {'$lt':  25,  '$gt':  21}})

运行效果如图3-41所示。

图3-41  “age”大于21并且小于等于24的所有记录

【举例2】  查询所有“age”大于21并小于等于24的数据,且“name”不为“夏侯小七”的记录,见代码3-5。

代码3-5  查询“age”大于21并小于等于24,且“name”不为“夏侯小七”的数据

运行效果如图3-42所示。

图3-42  查询的结果

4.限定返回哪些字段

“find”命令可以接收两个参数:第1个参数用于过滤不同的记录,第2个参数用于修改返回的字段。如果省略第2个参数,则MongoDB会返回所有的字段。

如要限定字段,则查询语句的格式如下:

其中,用于限定字段的字典的Key为各个字段名。其值只有两个——0或1。

●  如果值为0,则表示在全部字段中剔除值为0的这些字段并返回。

●  如果值为1,则表示只返回值为1的这些字段。

例如,查询数据集example_data_1,但不返回“address”和“age”字段。查询语句如下:

db.getCollection('example_data_1').find({},  {'address':  0,  'age':  0})

运行结果为如图3-43所示。

图3-43  不返回“address”字段和“age”字段

再例如,要求只返回name字段和age字段,则查询语句如下:

db.getCollection('example_data_1').find({},  {'name':  1,  'age':  1})

运行效果如图3-44所示。

图3-44  只返回“name”和“age”字段

读者可能已经发现,不论是选择“只返回某些字段”还是“不返回某些字段”,结果里始终有“_id”。这是因为,“_id”比较特殊,它是默认要返回的,除非明确说明不需要它。即,如果不想要“_id”,则必须在限定字段的字典中把“_id”字段的值设为0,如图3-45所示。

图3-45  明确申明不需要“_id”

如果不考虑“_id”,则限定字段的字典里面的值只可能全都是0或全都是1,不可能1和0混用,一旦混用则MongoDB就会报错。这从逻辑上很好理解:

(1)如果只要A、B、C,则没有提到的自然都是不需要的。

(2)如果除A、B、C外其他的全都要,则没有提到的自然全都是需要的。

提示:

只有“_id”很特别,不论其他字段的值是0还是1,如果不需要返回“_id”,则需要把它的值设为0。

5.修饰返回结果

(1)满足要求的数据有多少条——count()命令。

如果想知道满足要求的数据有多少条,则可以使用“count()”命令。

例如,要查询所有“age”字段大于21的记录有多少条,则查询语句如下:

db.getCollection('example_data_1').find({'age':  {'$gt':  21}}).count()

运行结果如图3-46所示。返回数字“6”表示有6条记录满足要求。

图3-46  查询结果条数

(2)限定返回结果——“limit()”命令。

如果查询的结果非常多,则可能需要限定返回结果。此时就需要使用“limit()”命令。它的用法如下:

●  如果限制返回的条数为一个数字,则表示最多返回这么多条记录。如果超过限定条数,则只返回限定的条数。

●  如果不足限定的条数,则有多少就返回多少。

例如,对于数据集example_data_1,限制只返回4条数据。

具体命令如下:

db.getCollection('example_data_1').find().limit(4)

运行效果如图3-47所示。

图3-47  最多返回4条记录

(3)对查询结果进行排序——“sort()”命令。

有时也需要对查询结果进行排序,此时需要使用“sort()”命令。使用方法如下:

其中,字段的值为-1表示倒序,为1表示正序。

例如,对所有“age”大于21的数据,按“age”进行倒序排列。查询语句如下:

db.getCollection('example_data_1').find({'age':  {'$gt':  21}}).sort({'age':  -1})

运行结果如图3-48所示。

图3-48  将查询结果倒序排列



3.4.3  实例5:修改数据


实例描述

数据集  example_data_1,“name”为“王小六”的这个记录是没有“address”字段的。现在需要为它增加这个字段,同时把“work”从“厨师”改为“工程师”。

(1)更新集合中的单条数据。

(2)批量更新同一个集合中的多条数据。

修改操作也就是更新(Update)操作,对应的  MongoDB  命令为“updateOne()”和“updateMany()”。

这两个命令只有以下区别,它们的参数完全一致。

●  updateOne:只更新第1条满足要求的数据。

●  updateMany:更新所有满足要求的数据。

下面以“updateMany”为例来介绍更新记录的操作。

1.更新操作的语法

更新操作的语法如下:

updateMany的第1个参数和“find”的第1个参数完全一样,也是一个字典,用来寻找所有需要被更新的记录。

第2个参数是一个字典,它的Key为“$set”,它的值为另一个字典。这个字典里面是需要被修改的字段名和新的值。

2.举例

修改“name”为“王小六”的文档,添加“address”字段,并把“work”字段从“厨师”改为“工程师”。更新语句见代码3-6。

代码3-6  修改name为“王小六”的文档

运行效果如图3-49所示。

图3-49  更新字段信息

再次查看数据集,发现“王小六”的信息已经发生了变化,如图3-50所示。

图3-50  数据发生了变化



3.4.4  实例6:删除数据


实例描述

例如,要从数据集example_data_1中删除字段“hello”值为“world”的这一条记录。

(1)从集合中删除单条数据。

(2)从集合中批量删除多条数据。

只要会查询数据,就会删除数据。为了防止误删数据,一般的做法是先查询要删除的数据,然后再将查出的数据删除。

(1)查询字段“hello”中值为“world”的这一条记录。

具体如下:

db.getCollection('example_data_1').find({'hello':  'world'})

运行效果如图3-51所示。

图3-51  首先查询出需要删除的记录

(2)把查询语句的“find”修改为“deleteOne”(如果只删除第1条满足要求的数据),或把查询语句的“find”修改为“deleteMany”(如果要删除所有满足要求的数据)。

具体命令如下:

db.getCollection('example_data_1').deleteMany({'hello':  'world'})

运行效果如图3-52所示。

图3-52  删除数据

(3)在返回的数据中,“acknowledged”为“true”表示删除成功,“deletedCount”表示一共删除了1条数据。

(4)再次查询example_data_1,发现已经找不到被删除的数据了,如图3-53所示。

图3-53  已经找不到被删除的数据了

提示:

慎用删除功能。一般工程上会使用“假删除”,即:在文档里面增加一个字段“deleted”,如果值为0则表示没有删除,如果值为1则表示已经被删除了。

默认情况下,deleted字段的值都是0,如需要执行删除操作,则把这个字段的值更新为1。而查询数据时,只查询deleted为0的数据。这样就实现了和删除一样的效果,即使误操作了也可以轻易恢复。



3.4.5  实例7:数据去重


实例描述

在数据集example_data_1中,进行以下两个去重操作。

(1)对“age”字段去重。

(2)查询所有“age”大于等于24的数据,再对“age”进行去重。

去重操作用到的命令为“distinct()”。格式如下:

db.getCollection('example_data_1').distinct(’字段名’,  查询语句的第一个字典)

distinct()可以接收两个参数:

●  第1个参数为字段名,表示对哪一个字段进行去重。

●  第2个参数就是查询命令“find()”的第1个参数。distinct命令的第2个参数可以省略。

1.对“age”字段去重

对“age”字段去重的语句如下:

db.getCollection('example_data_1').distinct('age')

运行效果如图3-54所示。

图3-54  对“age”字段去重

在MongoDB中返回的数据是一个数组,里面是去重以后的值。

2.对满足特定条件的数据去重

首先查询所有“age”大于等于24的数据,然后对“age”进行去重。具体语句见代码3-7。

代码3-7  对“age”大于等于24的记录的“age”字段去重

运行结果如图3-55所示。

图3-55  先筛选再去重

也许有读者会问,能否去重以后再带上其他字段呢?答案是,用“distinct()”命令不能实现。要实现这个功能,需要学习第7章的内容。


上一章 目录 下一章