MongoDB更新文档(一)

TrumanWong
7/13/2024
TrumanWong

MongoDB 提供以下方法来更新集合中的文档:

方法说明
db.collection.updateOne()即使多个文档可能与指定筛选器匹配,最多也只更新与指定筛选器匹配的单个文档。
db.collection.updateMany()更新所有与指定过滤器匹配的文档。
db.collection.replaceOne()即使多个文档可能与指定筛选器匹配,最多也只替换与指定筛选器匹配的单个文档。

updateOneupdateMany 都将筛选文档作为第一个参数,将变更文档作为第二个参数,后者对要进行的更改进行描述。replaceOne 同样将筛选文档作为第一个参数,但第二个参数是一个用来替换所匹配的筛选文档的新文档。

更新文档是原子操作:如果两个更新同时发生,那么首先到达服务器的更新会先被执行,然后再执行下一个更新。因此,相互冲突的更新可以安全地迅速接连完成,而不会破坏任何文档:最后一次更新将“成功”。如果不想使用默认行为,则可以考虑使用文档版本控制模式。

文档替换

replaceOne 会用新文档完全替换匹配的文档。这对于进行大规模模式迁移的场景非常有用。

restaurant集合包含以下文档:

{ "_id" : 1, "name" : "Central Perk Cafe", "Borough" : "Manhattan" },
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "Borough" : "Queens", "violations" : 2 },
{ "_id" : 3, "name" : "Empire State Pub", "Borough" : "Brooklyn", "violations" : 0 }

如下操作将替换一个 name: "Central Perk Cafe" 的单个文档:

video> db.restaurant.replaceOne(
...       { "name" : "Central Perk Cafe" },
...       { "name" : "Central Pork Cafe", "Borough" : "Manhattan" }
...    )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

如果未找到匹配项,那么操作将返回:

{
  acknowledged: true,
  insertedId: null,
  matchedCount: 0,
  modifiedCount: 0,
  upsertedCount: 0
}

如果未找到匹配项,设置 upsert: true 则会插入此文档:

video> db.restaurant.replaceOne(
...       { "name" : "Pizza Rat's Pizzaria" },
...       { "_id": 4, "name" : "Pizza Rat's Pizzaria", "Borough" : "Manhattan", "violations" : 8 },
...       { upsert: true }
...    )
{
  acknowledged: true,
  insertedId: 4,
  matchedCount: 0,
  modifiedCount: 0,
  upsertedCount: 1
}

使用更新运算符

通常文档只会有一部分需要更新。可以使用原子的更新运算符update operator更新文档中的特定字段。更新运算符是特殊的键,可用于指定复杂的更新操作,比如更改、添加或删除键,甚至可以操作数组和内嵌文档。

现在集合包含以下文档:

{ "_id" : 1, "name" : "Central Perk Cafe", "Borough" : "Manhattan", "violations" : 3 },
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "Borough" : "Queens", "violations" : 2 },
{ "_id" : 3, "name" : "Empire State Pub", "Borough" : "Brooklyn", "violations" : 4 },
{ "_id" : 4, "name" : "Pizza Rat's Pizzaria", "Borough" : "Manhattan", "violations" : 7 }

$set

$set用来设置一个字段的值。如果这个字段不存在,则创建该字段。这对于更新模式或添加用户定义的键来说非常方便。

> db.restaurant.updateOne({"_id": 1}, {"$set": {"points": 75, "violations": 5}})
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.restaurant.findOne({"_id": 1})
{
  _id: 1,
  name: 'Central Perk Cafe',
  Borough: 'Manhattan',
  violations: 5,
  points: 75
}

应该始终使用 $ 修饰符来增加、修改或删除键。常见的错误做法是把键的值通过更新设置成其他值,如以下操作所示:

> db.restaurant.updateOne({"_id": 1}, {"points": 75, "violations": 5})
MongoInvalidArgumentError: Update document requires atomic operators

这会事与愿违。更新的文档必须包含更新运算符。

递增/递减操作

$inc 运算符可以用来修改已存在的键值或者在该键不存在时创建它。对于更新分析数据、因果关系、投票或者其他有数值变化的地方,使用这个会非常方便。

> db.restaurant.updateOne({"_id": 2}, {"$inc": {"points": 80}})
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
> db.restaurant.findOne({"_id": 2})
{
  _id: 2,
  name: 'Rock A Feller Bar and Grill',
  Borough: 'Queens',
  violations: 2,
  points: 80
}

_id为2的文档中points键之前不存在,因此 $inc 创建了这个键,并将值设置成了增加量:80。

$set 用法类似,$inc 是专门用来对数字进行递增和递减操作的。$inc 只能用于整型、长整型或双精度浮点型的值。如果用在其他任何类型(包括很多语言中会被自动转换为数值的类型,比如 nullbool以及数字构成的字符串)的值上,则会导致操作失败:

> db.strcounts.insertOne({"count": "1"})
{
  acknowledged: true,
  insertedId: ObjectId('6690f1ba4b5a0e2aed26d089')
}
> db.strcounts.update({}, {"$inc": {"count": 1}})
DeprecationWarning: Collection.update() is deprecated. Use updateOne, updateMany, or bulkWrite.
MongoServerError: Cannot apply $inc to a value of non-numeric type. {_id: ObjectId('6690f1ba4b5a0e2aed26d089')} has the field 'count' of non-numeric type string

同样,$inc 键的值必须为数字类型。不能使用字符串、数组或者其他非数字类型的值:

> db.restaurant.updateOne({"_id": 2}, {"$inc": {"points": "80"}})
MongoServerError: Cannot increment with non-numeric argument: {points: "80"}

如果需要修改其他类型的值,可以使用$set 或者下面要讲到的数组运算符。

updateMany

updateOne 只会更新找到的与筛选条件匹配的第一个文档。如果匹配的文档有多个,它们将不会被更新。要修改与筛选器匹配的所有文档,请使用 updateManyupdateMany 遵循与 updateOne 同样的语义并接受相同的参数。关键的区别在于可能会被更改的文档数量。

创建集合:

> db.restaurant.insertMany([
...     { "_id" : 1, "name" : "Central Perk Cafe", "violations" : 3 },
...     { "_id" : 2, "name" : "Rock A Feller Bar and Grill", "violations" : 2 },
...     { "_id" : 3, "name" : "Empire State Sub", "violations" : 5 },
...     { "_id" : 4, "name" : "Pizza Rat's Pizzaria", "violations" : 8 }
... ])
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4 } }

更新所有 violations 大于 4 的文档:

> db.restaurant.updateMany(
...       { violations: { $gt: 4 } },
...       { $set: { "Review" : true } }
...    )
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 2,
  modifiedCount: 2,
  upsertedCount: 0
}
> db.restaurant.find()
[
  { _id: 1, name: 'Central Perk Cafe', violations: 3 },
  { _id: 2, name: 'Rock A Feller Bar and Grill', violations: 2 },
  { _id: 3, name: 'Empire State Sub', violations: 5, Review: true },
  { _id: 4, name: "Pizza Rat's Pizzaria", violations: 8, Review: true }
]