MongoDB 更新文档操作笔记

Mongo shell 更新文档操作

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

方法 描述
db.collection.updateOne() 即使多个文档可能与指定的过滤器匹配,也最多更新一个与指定的过滤器匹配的文档。
3.2版中的新功能。
db.collection.updateMany() 更新所有与指定过滤器匹配的文档。
3.2版中的新功能。
db.collection.replaceOne() 即使多个文档可能与指定过滤器匹配,也最多替换一个与指定过滤器匹配的文档。
3.2版中的新功能。
db.collection.update() 更新或替换与指定过滤器匹配的单个文档,或更新与指定过滤器匹配的所有文档。
默认情况下,该 db.collection.update() 方法更新单个文档。要更新多个文档,请使用 multi 选项。

另外的方法

以下方法还可以更新集合中的文档:

有关更多方法和示例,请参见各个方法的参考页。

db.collection.update() 语法

db.collection.update(<query>, <update>, <options>)

更新整篇文档

  1. 如果 <update>文档不包含任何更新操作符,db.collection.update() 将会使用 <update> 文档直接替换集合中符合 <query> 筛选条件的文档。
  2. 文档主键 _id 不可以更改。
  3. 在使用 <update> 文档替换整篇被更新文档时,只有 第一篇 符合 <query> 筛选条件的文档会被更新。

更新特定字段

  1. 如果 <update> 文档中只包含更新操作符,db.collection.update() 将会使用 <update> 文档更新集合中符合 <query> 筛选条件文档中的特定字段

文档更新操作符

1. $set 更新或者新增字段

语法:{ $set: { <newField>: <expression>, ... } }

  • 如果向现有的数组字段范围以外的位置添加新值,数组字段的长度会扩大,未被赋值的数组成员将被设置为 "null"

示例:

# 更新 jack 的银行账户余额和开户行信息
> db.accounts.find({name:"jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"balance" : 1000,
	"contact" : [
		"111",
		"Alabama",
		"US"
	]
}

# 更新 jack 的银行账户余额和开户行信息
> db.accounts.update(
    {name:"jack"}, 
    {$set: 
        {
            balance: 200, 
            info: {dataOpened: new Date("2018-06-08T16:30:00Z"), branch: "branch1"}
        }
     }
 )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.getCollection('accounts').find({name: "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"balance" : 200,
	"contact" : [
		"111",
		"Alabama",
		"US"
	],
	"info" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z"),
		"branch" : "branch1"
	}
}

# 更新内嵌字段(内嵌数组字段)
> db.accounts.update(
...     {name:"jack"},
...     {$set:
...         {
...             "info.branch": "branch2",
...             "contact.1": "222"
...         }
...      }
...  )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.getCollection('accounts').find({name: "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"balance" : 200,
	"contact" : [
		"111",
		"222",
		"US"
	],
	"info" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z"),
		"branch" : "branch2"
	}
}

# 内嵌数组字段增加值
> db.accounts.update(
...     {name: "jack"},
...     {
...         $set: {"contact.5": "222"}
...     }
... )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.getCollection('accounts').find({name: "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"balance" : 200,
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z"),
		"branch" : "branch2"
	}
}
>
2. $unset 删除字段

语法{ $unset: { <field1>: "", ... } }

  • $unset 命令中对字段的赋任何值,对字段的删除没有任何影响
  • $unset 命令中字段根本不存在,那么文档内容将保持不变
  • 使用 $unset 命令删除数组的某一元素时,这个元素不会被删除,只会被赋值 "null", 而数组的长度不会改变。
    示例:```
# 删除 jack 的银行账户余额和开户地点
> db.accounts.update(
...     {name: "jack"},
...     {
...         $unset: {balance: "", "info.branch": ""}
...     }
... )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.getCollection('accounts').find({name: "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}

3. $rename 重命名字段

语法{$rename: { <field1>: <newName1>, <field2>: <newName2>, ... } }

  • 如果 $rename 重命名的字段不存在,那么文档内容不会被改变
  • 如果新的重命名字段名已存在,那么原有的这个字段也会被覆盖
  • 内嵌文档字段的重命名
    • 通过重命名可以实现移动字段的效果
    • 重命名数组中内嵌文档的字段是不可以的
    • 新的重命名字段也不能是数组中的元素
    • $rename 命令中的新旧字段都不能指向数组中的元素

\(rename 的新字段存在时,\)rename 命令会先 $unset 新旧字段,然后 $set 新字段

// 1. 重名字段示例
> db.accounts.find({"name": "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}
> db.accounts.update({"name": "jack"}, {$rename: {"info": "info_re"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
> db.accounts.find({"name": "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info_re" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}

// 2. 重名字段不存在示例
> db.accounts.update({"name": "jack"}, {$rename: {"info_1": "info_xxx"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.accounts.find({"name": "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info_re" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}

// 3. 如果新的重命名字段名已存在,那么原有的这个字段也会被覆盖 示例
> db.accounts.find({"name": "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : [
		"111",
		"222",
		"US",
		null,
		null,
		"222"
	],
	"info_re" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}
>
> db.accounts.update({"name": "jack"}, {$rename: {"info_re": "contact"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "jack"}).pretty()
{
	"_id" : ObjectId("63287530f8526f895273d037"),
	"name" : "jack",
	"contact" : {
		"dataOpened" : ISODate("2018-06-08T16:30:00Z")
	}
}

// 4. 通过重命名可以实现移动字段的效果
> db.accounts.find({"name": "blice"}).pretty()
{
	"_id" : ObjectId("6329d648f5bd549a110fc5d0"),
	"name" : "blice",
	"balance" : 90,
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		}
	],
	"info" : {
		"dateOpene" : ISODate("2017-01-02T16:00:00Z"),
		"branch" : "branch1"
	}
}
> db.accounts.update({"name": "blice"}, {$rename: {"info.branch": "branch", "balance": "info.blance"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find({"name": "blice"}).pretty()
{
	"_id" : ObjectId("6329d648f5bd549a110fc5d0"),
	"name" : "blice",
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		}
	],
	"info" : {
		"dateOpene" : ISODate("2017-01-02T16:00:00Z"),
		"blance" : 90
	},
	"branch" : "branch1"
}
>

// 重命名数组中内嵌文档的字段是不可以的
> db.accounts.update({"name": "blice"}, {$rename: {"contact.3.primaryEmail": "primaryEmail"}})
WriteResult({
	"nMatched" : 0,
	"nUpserted" : 0,
	"nModified" : 0,
	"writeError" : {
		"code" : 2,
		"errmsg" : "The source field cannot be an array element, 'contact.3.primaryEmail' in doc with _id: ObjectId('6329d648f5bd549a110fc5d0') has an array field called 'contact'"
	}
})

// 新的重命名字段也不能是数组中的元素
> db.accounts.update({"name": "blice"}, {$rename: {"branch": "contact.3.branch"}})
WriteResult({
	"nMatched" : 0,
	"nUpserted" : 0,
	"nModified" : 0,
	"writeError" : {
		"code" : 2,
		"errmsg" : "The destination field cannot be an array element, 'contact.3.branch' in doc with _id: ObjectId('6329d648f5bd549a110fc5d0') has an array field called 'contact'"
	}
})
4. inc $mul 加减字段值(正数增加,负数减少)

命令:

{ $inc: { <field1>: <amount1>, <field2>: <amount2>, ... } }

{ $mul: { <field1>: <number1>, ... } }

  • inc 加减字段值
    • 正数增加,负数减少
    • 如果字段不存在,会增加相应的字段,值为指定增加或减少的值
  • $mul 相乘字段值
    • 大于1相乘,0~1小数相除
    • 如果字段不存在,会增加相应的字段,值为0
  • inc $mul 只能应用在数字类型字段上
5. $min $max 比较之后更新字段

命令:

{ $min: { <field1>: <value1>, ... } }

{ $max: { <field1>: <value1>, ... } }

  • $min 比较原本的值和指定值,最终取这两个中最小的值
  • $max 比较原本的值和指定值,最终取这两个中最大的值
  • $min $max 不仅可以在数字类型字段使用,其他类型也可以使用,比如:时间类型,字符串类型 ...
  • 如果字段不存在,会新增字段,并且值设为 $min $max 指定的值
  • 如果被更新的字段类型和更新的类型不一致,$min $max 命令会安照BSON数据类型排序 规则进行比较

数组更新操作符

1. $addToSet 如果元素不存在将向数组添加元素

*命令: { $addToSet: { <field1>: <value1>, ... } }

  • 如果要插入的值已经存在于数组中,则不会被再添加重复值。
  • 注意: 使用 $addToSet 插入数组和文档时,插入值中字段的顺序和已有值重复的时候,才算作重复值被忽略,例如数组中元素是 文档或者数组时,如果顺序不一样,则不会认为是重复值。
  • $addToSet 会将数组类型当做元素插入数组字段中,成为内嵌数组
  • 如果想多个元素直接添加到数组字段中,则需要使用 $each 操作符
  • 如果数组字段不存在,这个字段将被添加到原文档中
// - `$addToSet` 会将数组类型当做元素插入数组字段中,成为内嵌数组
// - 如果想多个元素直接添加到数组字段中,则需要使用 `$each` 操作符
> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		}
	]
}
>
> db.accounts.update({"name": "blice"}, {$addToSet: {"contact": ["test1", "test2"]}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		]
	]
}
> db.accounts.update({"name": "blice"}, {$addToSet: {"contact": {$each: ["test1", "test2"]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1",
		"test2"
	]
}

2. $pop 从数组的首或者尾移除元素

命令: { $pop: { <field>: <-1 | 1>, ... } }

  • 可以对数组中的数组元素进行 $pop 操作
  • 如果数组中所有元素都清除了,依然会保留空数组,字段不会被清除
> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1",
		"test2"
	]
}
>
> db.accounts.update({"name": "blice"}, {$pop: {"contact": 1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"1111",
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1"
	]
}
> db.accounts.update({"name": "blice"}, {$pop: {"contact": -1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "blice"}, {"contact": 1, "_id": 0}).pretty()
{
	"contact" : [
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1"
	]
}

3. $pull 从数组有选择的移除元素

命令: { $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }

  • 如果数组元素本身就是一个内嵌数组,可以使用 $elemMatch 来对内嵌数组进行筛选
  • $pull 命令会删去包含指定的文档字段和字段值的文档元素,字段排列顺序不需要完全匹配,区别于 $pullAll
> db.accounts.find({"name": "bili"}, {contact: 1, "_id": 0}).pretty()
{
	"contact" : [
		"beijing",
		"China",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1"
	]
}
>
> db.accounts.update({"name": "bili"}, {$pull: {"contact": {$regex: /Chi/}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.accounts.find({"name": "bili"}, {contact: 1, "_id": 0}).pretty()
{
	"contact" : [
		"beijing",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1"
	]
}
>


> db.accounts.find({"name": "bili"}, {contact: 1, "_id": 0}).pretty()
{
	"contact" : [
		"beijing",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		[
			"test1",
			"test2"
		],
		"test1"
	]
}
> db.accounts.update({"name": "bili"}, {$pull: {"contact": {$elemMatch: {$eq: "test1"}}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.accounts.find({"name": "bili"}, {contact: 1, "_id": 0}).pretty()
{
	"contact" : [
		"beijing",
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		"test1"
	]
}

4. $pullAll 从数组有选择的移除元素

语法: { $pullAll: { <field1>: [ <value1>, <value2> ... ], ... } }

  • $pullAll$pull 命令对比:

    { $pullAll: { <field1>: [ <value1>, <value2>]} }

    相当于

    { $pull: { <field1>: {$in: [<value1>, <value2>]}}}
  • $pullAll$pull,如果要删除的元素是一个数组(内嵌数组),数组元素的值和排列顺序都必须和被删除的数组完全一样
  • $pullAll$pull, 如果要删除的元素是一个文档(内嵌文档),$pullAll 需要数组元素的值和排列顺序都必须和被删除的数组完全一样,才会删除,而 $pull 只需文档类型的元素包含指定值,就会被删除

5. $push 向数组中添加元素

语法: { $push: { <field1>: <value1>, ... } }

  • $push$addToSet 命令一样,如果指定数组字段不存在于文档中,这个字段将会添加原文档中。
  • 如果过滤到多个文档时,$push将会对每个文档进行操作,详见官方文档示例
  • 配合 $each 可以向数组字段中添加多个元素
  • 更多用法详见官方文档示例

6. $ 充当占位符以更新与查询条件匹配的第一个元素。

7. $[] 充当占位符,为匹配查询条件的文档更新数组中的所有元素。

> db.accounts.find({"name": "bili"}, {"contact": 1}).pretty()
{
	"_id" : ObjectId("634440158bad614c1c907498"),
	"contact" : [
		[
			"11111",
			"2222"
		],
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		"test1"
	]
}

> db.accounts.update({"name": "bili"}, {$set: {"contact.0.$[]": "00"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
> db.accounts.find({"name": "bili"}, {"contact": 1}).pretty()
{
	"_id" : ObjectId("634440158bad614c1c907498"),
	"contact" : [
		[
			"00",
			"00"
		],
		{
			"primaryEmail" : "xxx@gmail.com",
			"secondaryEmail" : "yyy@gmail.com"
		},
		"test1"
	]
}

更新操作 <options>

db.collection.update(<query>, <update>, <options>)
  • <options> 文档提供了 update 命令的更多选项
  • {multi: <boolean>} 更新多个文档
    • 在默认情况下,即使筛选条件对应了多篇文档,update 命令任然只会更新 一篇 文档
    • 注意,MongoDB 只能保证 单个文档 操作的原子性,不能保证多个文档操作的原子性
    • 如果需要保证多个文档操作的原子性,就需要使用 MongoDB > 4.0 版本引入的事务功能
posted @ 2022-10-12 00:56  郭赫伟  阅读(180)  评论(0编辑  收藏  举报