mongodb基础操作(二)——更新删除

1. 更新文档

mongodb提供了更新操作文档的操作方法,在示例中主要使用了集中常用的方法:

在示例测试的过程中,主要会用到一下数据,可以通过插入的方式执行:

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的等级以满足需求。
Show 1 Comment

1 Comment

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注