1. 更新文档
mongodb提供了更新操作文档的操作方法,在示例中主要使用了集中常用的方法:
db.collection.updateOne(<filter>, <update>, <options>)
db.collection.updateMany(<filter>, <update>, <options>)
db.collection.replaceOne(<filter>, <update>, <options>)
在示例测试的过程中,主要会用到一下数据,可以通过插入的方式执行:
db.inventory.insertMany( [ { item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status: "A" }, { item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" }, { item: "mat", qty: 85, size: { h: 27.9, w: 35.5, uom: "cm" }, status: "A" }, { item: "mousepad", qty: 25, size: { h: 19, w: 22.85, uom: "cm" }, status: "P" }, { item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "P" }, { item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" }, { item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" }, { item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }, { item: "sketchbook", qty: 80, size: { h: 14, w: 21, uom: "cm" }, status: "A" }, { item: "sketch pad", qty: 95, size: { h: 22.85, w: 30.5, uom: "cm" }, status: "A" } ] );
1.1 在集合中更新文档
mongodb本身提供了很多的操作用于更新,具体的更新语法为:
{ <update operator>: { <field1>: <value1>, ... }, <update operator>: { <field2>: <value2>, ... }, ... }
1.2 更新单个文档
mongodb中,可以使用db.collection.updateOne
方法实现单个文档更新,在更新操作中,可以设置:
- 设置过滤器,确定那些数据将会被更新
- 更新操作,确定那些属性被更新,更新的新值
- 设置数据被更新的时间
例如:将item=paper的文档中,将size.ucom更新为cm, status更新为P
, 则对应的更新为:
db.inventory.updateOne( { item: "paper" }, { $set: { "size.uom": "cm", status: "P" }, $currentDate: { lastModified: true } } )
更新操作完成后,则返回的结果信息为:
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
更新语句当中,主要包含了两个操作符:
- 通过
$set
操作符设置size.uom的值为cm, status的值为P
$currentDate
操作符用于更新lastModified
属性的值为当前时间,如果该属性不存在,则创建lastModified
属性
在执行完成后返回的报文中,主要包含了以下字段:
- acknowleged = true: 表示执行操作成功
- insertedId: 插入数据编号,该次为更新,所以返回了null
- matchedCount: 在执行语句时,过滤条件匹配到的文档数量
- modifiedCount: 修改记录条数
- upsertedCount: …
1.3 更新多个文档
多文档更新使用updateMany()
方法实现,使用和updateOne
保持一致,例如:更新 < 50的文档size.com为in,status更新为P
, 则对应查询为:
db.inventory.updateMany( { "qty": { $lt: 50 } }, { $set: { "size.uom": "in", status: "P" }, $currentDate: { lastModified: true } } )
则对应返回结果为
{ acknowledged: true, insertedId: null, matchedCount: 12, modifiedCount: 12, upsertedCount: 0 }
从返回结果中,可以看出一共匹配了12条记录,更新成功12条记录。
1.4 替换文档属性
除了能够更新文档数据之外,也可以通过replaceOne
操作替换属性数据,例如替换item值为paper的属性
, 则操作语句为:
db.inventory.replaceOne( { item: "paper" }, { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] } )
则返回结果为
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
从返回结果中可以看出,匹配1条数据,修改1条数据。我们查询db.inventory.find({item: "paper"})
时,则对应的修改数据为:
[ { _id: ObjectId("6358adad77ef9e18649ae29c"), item: 'paper', instock: [ { warehouse: 'A', qty: 60 }, { warehouse: 'B', qty: 40 } ] }... ]
可以看出是整个替换文档所有字段的值,因此和其他原有的数据存在一定的差别。
1.5 更新行为
1.5.1 原子性
mongodb中所有写操作都只是在单个文档上具备原子性,因此当批量操作时,如果其中某一个文档更新失败,可能导致之前更新完成的文档不会回滚,之后的文档不会更新。
1.5.2 _id
字段
- 在更新时,不能更新已有文档的
_id
字段的值 - 在替换时,也不能更新已有文档的
_id
字段的值
1.5.3 字段顺序
mongodb对于写操作而言,保证了文档中字段的顺序,但是以下情况除外:
- 在文档中,
_id
字段始终都是在首位 - 在执行更新操作,对字段重命名时,会导致文档字段的重排序
1.5.4 acknowledge
对于写操作,可以调整acknowledge的等级以满足业务需求。
1.6 支持更新操作符
在不同版本中,mongodb对于更新操作字段排序有不同的处理方式。
- 在4.4版本以及更早版本中,update操作是按照字典顺序对字段排序
- 在5.0版本中,如果字段名称为字符串则按照字典顺序排序,如果包含了数字,则按照数字顺序排序
Fields
Name | Description |
---|---|
$currentDate |
为字段设置当前时间值 |
$inc |
为字段值增加指定数字,做加法计算 |
$min |
只更新字段的值小于指定值的文档字段值 |
$max |
只更新字段的值大于指定值的文档字段值 |
$mul |
将字段的值乘以指定的数字,并为该字段赋值 |
$rename |
对字段重新命名 |
$set |
为字段设置新值 |
$setOnInsert |
如果更新文档时,是新增一个文档则执行、如果只是更新文档,则不执行 |
$unset |
从文档中移除指定字段 |
Array
Operators
Name | Description |
---|---|
$ |
充当占位符,更新查询条件中匹配的第一个元素。 |
$[] |
充当占位符,更新查询条件匹配文档所有元素 |
$[<identifier>] |
充当占位符,更新匹配arrayFilters条件的所有元素。 |
$addToSet |
只有当数组中不包含相同元素时,想数组中增加元素 |
$pop |
移除数组中第一或者最后一个元素 |
$pull |
移除数组匹配指定查询的元素 |
$push |
向数组中追加元素 |
$pullAll |
从数组中移除所有匹配值元素 |
Modifiers
Name | Description |
---|---|
$each |
修改push和addToSet运算符,以附加多个项用于数组更新。 |
$position |
修改$push 操作符,指定向指定位置插入元素 |
$slice |
修改$push 操作以限制修改数组的长度 |
$sort |
修改$push 操作符,重排序数组中中的文档 |
Bitwise
Name | Description |
---|---|
$bit |
对整数值执行按位“AND”、“OR”和“XOR”更新。 |
1.7 通过聚合管道更新数据(Aggregation Pipeline)
从mongodb4.2开始,就可以使用聚合管道操作数据。聚合管道可以通过不同的阶段组合而成,具体阶段包括:
在开始之前,还是使用官网的例子作为学习数据,具体数据如下:
db.students.insertMany([ { _id: 1, test1: 95, test2: 92, test3: 90, modified: new Date("01/05/2020") }, { _id: 2, test1: 98, test2: 100, test3: 102, modified: new Date("01/05/2020") }, { _id: 3, test1: 95, test2: 110, modified: new Date("01/04/2020") } ])
1.7.1 实例1
通过db.collection.updateOne()
方法更新_id=3
的文档数据,我们可以通过聚合通道实现,对应更新语句如下:
db.students.updateOne( { _id: 3 }, [ { $set: { "test3": 98, modified: "$$NOW"} } ] )
上面更新中使用了聚合管道实现,通过$set
阶段将字段test3
的值设置为98, 并且设置modified
的时间为当前时间。这里使用到了管道变量NOW
来获取当前时间,如果需要获取管道变量,这是需要通过$$
语法获取。
1.7.2 实例2
还是使用官方的数据用于测试。
db.students2.insertMany([ { "_id" : 1, quiz1: 8, test2: 100, quiz2: 9, modified: new Date("01/05/2020") }, { "_id" : 2, quiz2: 5, test1: 80, test2: 89, modified: new Date("01/05/2020") }, ])
在实例2中我们通过updateMany()
方法规范文档中的字段,保证在集合中的文档应该都包含相同的字段,不存在的字段默认值为0, 则对应的更新语句为:
db.students2.updateMany( {}, [ { $replaceRoot: { newRoot: { $mergeObjects: [ { quiz1: 0, quiz2: 0, test1: 0, test2: 0 }, "$$ROOT" ] } } }, { $set: { modified: "$$NOW"} } ] )
- 在以上语句中,通过
$replaceRoot
配合$mergeObjects
使用,通过表达式设置quiz1, quiz2, test1, test2
的默认值为0,其中ROOT
代表了当前正在更新的文档,在管道中获取通过$$
表达式来获取,因此$$ROOT
代表了当前正在更新的文档。因此当前包含默认值的文档会覆盖已经存在的文档。 $set
则为modified字段设置当前时间。
1.7.3 实例3
还是使用官方的实例数据,
db.students3.insert([ { "_id" : 1, "tests" : [ 95, 92, 90 ], "modified" : ISODate("2019-01-01T00:00:00Z") }, { "_id" : 2, "tests" : [ 94, 88, 90 ], "modified" : ISODate("2019-01-01T00:00:00Z") }, { "_id" : 3, "tests" : [ 70, 75, 82 ], "modified" : ISODate("2019-01-01T00:00:00Z") } ]);
通过这是实例计算平均值以及评级信息,则具体的操作语句如下:
db.students3.updateMany( { }, [ { $set: { average : { $trunc: [ { $avg: "$tests" }, 0 ] }, modified: "$$NOW" } }, { $set: { grade: { $switch: { branches: [ { case: { $gte: [ "$average", 90 ] }, then: "A" }, { case: { $gte: [ "$average", 80 ] }, then: "B" }, { case: { $gte: [ "$average", 70 ] }, then: "C" }, { case: { $gte: [ "$average", 60 ] }, then: "D" } ], default: "F" } } } } ] )
- set“操作主要实现计算数组中的平均值并设置modified的时间为当前时间,为了计算平均值,这里使用了“trunc
配合
$avg“来计算平均值,并将计算的结果赋值到average字段 - 第二个
$set
依赖了第一个计算的平均值结果,通过$switch
来判断平均值一次来得到具体的评级信息。
1.7.4 实例4
实例四是对数组元素的操作,实现向数组中追加元素。插入数据操作为:
db.students4.insertMany([ { "_id" : 1, "quizzes" : [ 4, 6, 7 ] }, { "_id" : 2, "quizzes" : [ 5 ] }, { "_id" : 3, "quizzes" : [ 10, 10, 10 ] } ])
在操作语句中,为_id=2
的文档的属性quizzes
追加两个元素,则对应操作语句为:
db.students4.updateOne( { _id: 2 }, [ { $set: { quizzes: { $concatArrays: [ "$quizzes", [ 8, 6 ] ] } } } ] )
1.7.5 实例5
实例5是对文档中数组字段进行遍历和计算,并在遍历过程中能够使用临时变量暂存结果。还是使用官方的实例数据。
db.temperatures.insertMany([ { "_id" : 1, "date" : ISODate("2019-06-23"), "tempsC" : [ 4, 12, 17 ] }, { "_id" : 2, "date" : ISODate("2019-07-07"), "tempsC" : [ 14, 24, 11 ] }, { "_id" : 3, "date" : ISODate("2019-10-30"), "tempsC" : [ 18, 6, 8 ] } ])
这里主要记录了温度的信息,才是的摄氏度的方式记录,实例的目的是将摄氏度转换为华氏温度,则对应的更新为:
db.temperatures.updateMany( { }, [ { $addFields: { "tempsF": { $map: { input: "$tempsC", as: "celsius", in: { $add: [ { $multiply: ["$$celsius", 9/5 ] }, 32 ] } } } } } ] )
在以上操作中,新增一个字段tempsF
用处存储转换后的温度,然后通过$map
遍历文档中的tempsC
字段数组,文档中的字段通过$
进行获取,其中临时变量为celsius
,并结合$add
和$multiply
计算最终的结果,并将结果放入到新的tempsF
字段中。
2. 删除文档
删除文档操作相比于其他操作来说是比较简单的,这里主要包含了集中操作:
- 删除全部文档
- 根据条件删除文档
- 只删除一个文档
还是使用官方的实例用于测试:
db.inventory.insertMany( [ { item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" }, { item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "P" }, { item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" }, { item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" }, { item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }, ] );
2.1 删除所有文档
删除所有文档只需要执行deleteMany()
操作即可,但是该操作必须传入参数。删除所有只需要将请求参数置空即可。
db.inventory.deleteMany({})
2.2 根据条件删除
删除时传入的条件根据插入和更新时的用法比较类似,例如删除status=A
的文档数据,则对应的操作为:
db.inventory.deleteMany({status: "A"})
2.3 删除单个文档
删除单个文档需要使用deleteOne()
方法实现,同时删除单个文档也可以传入过滤条件。例如删除status=D
的数据,则对应的语句为:
db.inventory.deleteOne({status: "D"})
2.4 删除行为
删除行为具备以下特性:
- 删除操作并不会删除索引信息,甚至删除所有文档也不会删除索引
- 删除操作的原子性只在单个文档的操作上,如果是批量删除时,不会回滚已经删除的文档
- 可以调整acknowledge的等级以满足需求。