MongoDB 文档更新操作

我们在前面的文章中提到过文档的基本的增删改查操作,MongoDB 中提供的增删改查的语法非常丰富,本文我们主要来看看更新都有哪些好玩的语法。

文档替换

假设我的集合中现在存了如下一段数据:

1
2
3
4
5
6
7
{
"_id" : ObjectId("59f005402844ff254a1b68f6"),
"name" : "三国演义",
"authorName" : "罗贯中",
"authorGender" : "男",
"authorAge" : 99.0
}

这是一本书,有书名和作者信息,但是作者是一个独立的实体,所以我想将之提取出来,变成下面这样:

1
2
3
4
5
6
7
8
9
{
"_id" : ObjectId("59f005402844ff254a1b68f6"),
"name" : "三国演义",
"author" : {
"name" : "罗贯中",
"gender" : "男",
"age" : 99.0
}
}

我可以采用如下操作:

p248

另外一个问题是更新时,MongoDB 只会匹配第一个更新的文档,假设我的 MongoDB 中有如下数据:

1
2
3
4
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f7"), "x" : 1 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f8"), "x" : 1 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f9"), "x" : 1 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68fa"), "x" : 2 }

我想把所有 x 为 1 的数据改为 99,我们很容易想到如下命令:

1
db.sang_collect.update({x:1},{x:99})

但我们发现执行结果却是这样:

1
2
3
4
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f7"), "x" : 99 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f8"), "x" : 1 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f9"), "x" : 1 }
{ "_id" : ObjectId("59f00d4a2844ff254a1b68fa"), "x" : 2 }

即只有第一条匹配的结果被更新了,其他的都没有变化。这是 MongoDB 的更新规则,即只更新第一条匹配结果。如果我们想将所有 x 为 1 的更新为 x 为 99,可以采用如下命令:

1
db.sang_collect.update({x:1},{$set:{x:99}},false,true)

首先我们将要修改的数据赋值给 $set,$set 是一个修改器,我们将在下文详细讲解,然后后面多了两个参数,第一个 false 表示如果不存在 update 记录,是否将我们要更新的文档作为一个新文档插入,true 表示插入,false 表示不插入,默认为 false,第二个 true 表示是否更新全部查到的文档,false 表示只更新第一条记录,true 表示更新所有查到的文档。

使用修改器

很多时候我们修改文档,只是要修改文章的某一部分,而不是全部,但是现在我面临这样一个问题,假设我有如下一个文档:

1
{x:1,y:2,z:3}

我现在想把这个文档中 x 的值改为 99,我可能使用如下操作:

1
db.sang_collect.update({x:1},{x:99})

但是更新结果却变成了这样:

1
{ "_id" : ObjectId("59f02dce95769f660c09955b"), "x" : 99 }

如下图:

p249

MongoDB 帮我把整个文档更新了!要解决这个问题,我们可以使用修改器。

$set 修改器

$set 可以用来修改一个字段的值,如果这个字段不存在,则创建它。如下:

p250

如果该字段不存在,则创建,如下:

p251

也可以利用 $unset 删除一个字段,如下:

p252

$set 也可以用来修改内嵌文档,还以刚才的书为例,如下:

1
2
3
4
5
6
7
8
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "罗贯中",
"gender" : "男"
}
}

想要修改作者的名字,操作如下:

1
db.sang_collect.update({name:"三国演义"},{$set:{"author.name":"明代罗贯中"}})

修改结果如下:

1
2
3
4
5
6
7
8
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男"
}
}

$inc 修改器

$inc 用来增加已有键的值,如果该键不存在就新创建一个。比如我想给上文的罗贯中增加一个年龄为 99,方式如下:

1
db.sang_collect.update({name:"三国演义"},{$inc:{"author.age":99}})

执行结果如下:

1
2
3
4
5
6
7
8
9
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 99.0
}
}

加入我想给罗贯中增加 1 岁,执行如下命令:

1
db.sang_collect.update({name:"三国演义"},{$inc:{"author.age":1}})

这是会在现有值上加 1,结果如下:

1
2
3
4
5
6
7
8
9
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 100.0
}
}

注意 $inc 只能用来操作数字,不能用来操作 null、布尔等。

数组修改器

数组修改器有好几种,我们分别来看。
$push 可以向已有数组末尾追加元素,要是不存在就创建一个数组,还是以我们的上面的 book 为例,假设 book 有一个字段为 comments,是一个数组,表示对这个 book 的评论,我们可以使用如下命令添加一条评论:

1
db.sang_collect.update({name:"三国演义"},{$push:{comments:"好书666"}})

此时不存在 comments 字段,系统会自动帮我们创建该字段,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 100.0
},
"comments" : [
"好书666"
]
}

此时我们可以追加评论,如下:

1
db.sang_collect.update({name:"三国演义"},{$push:{comments:"好书666啦啦啦啦"}})

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 100.0
},
"comments" : [
"好书666",
"好书666啦啦啦啦"
]
}

如果想一次添加 3 条评论,可以结合 $each 一起来使用,如下:

1
db.sang_collect.update({name:"三国演义"},{$push:{comments:{$each:["111","222","333"]}}})

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 100.0
},
"comments" : [
"好书666",
"好书666啦啦啦啦",
"111",
"222",
"333"
]
}

我们可以使用 $slice 来固定数组的长度,假设我固定数组的长度为 5,如果数组中的元素不足 5 个,则全部保留,如果数组中的元素超过 5 个,则只会保留最新的 5 个,如下:

1
db.sang_collect.update({name:"三国演义"},{$push:{comments:{$each:["444","555"],$slice:-5}}})

注意 $slice 的值为负数,运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"_id" : ObjectId("59f042cfcafd355da9486008"),
"name" : "三国演义",
"author" : {
"name" : "明代罗贯中",
"gender" : "男",
"age" : 100.0
},
"comments" : [
"111",
"222",
"333",
"444",
"555"
]
}

我们还可以在清理之前使用 $sort 对数据先进行排序,然后再清理比如我有一个 class 文档,如下:

1
2
3
4
{
"_id" : ObjectId("59f07f3649fc5c9c2412a662"),
"class" : "三年级二班"
}

现在向这个文档中插入 student,每个 student 有姓名和成绩,然后按照成绩降序排列,只要前 5 条数据,如下:

1
db.sang_collect.update({class:"三年级二班"},{$push:{students:{$each:[{name:"张一百",score:100},{name:"张九九",score:99},{name:"张九八",score:98}],$slice:5,$sort:{score:-1}}}})

$sort 的取值为 -1 和 1,-1 表示降序,1 表示升序。
上面的命令执行两次之后(即插入两次),结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"_id" : ObjectId("59f07f3649fc5c9c2412a662"),
"class" : "三年级二班",
"students" : [
{
"name" : "张一百",
"score" : 100.0
},
{
"name" : "张一百",
"score" : 100.0
},
{
"name" : "张九九",
"score" : 99.0
},
{
"name" : "张九九",
"score" : 99.0
},
{
"name" : "张九八",
"score" : 98.0
}
]
}

$slice和$sort不能只和$push一起使用,还要加上$each。

$addToSet

我们可以在插入的时候使用 $addToSet,表示要插入的值如果存在则不插入,否则插入,如下:

1
db.sang_collect.update({name:"三国演义"},{$addToSet:{comments:"好书"}})

上面的命令执行多次之后,发现只成功插入了一条数据。也可以将 $addToSet 和 $each 结合起来使用,如下:

1
db.sang_collect.update({name:"三国演义"},{$addToSet:{comments:{$each:["111","222","333"]}}})

$pop

$pop 可以用来删除数组中的数据,如下:

1
db.sang_collect.update({name:"三国演义"},{$pop:{comments:1}})

1 表示从 comments 数组的末尾删除一条数据,-1 表示从 comments 数组的开头删除一条数据。

$pull

使用 $pull 我们可以按条件删除数组中的某个元素,如下:

1
db.sang_collect.update({name:"三国演义"},{$pull:{comments:"444"}})

表示删除数组中值为 444 的数据。

$

既然是数组,我们当然可以通过下标来访问,如下一行操作表示将下标为 0 的(第一个 comments) comments 修改为 999:

1
db.sang_collect.update({name:"三国演义"},{$set:{"comments.0":"999"}})

可是有的时候我并不知道我要修改的数据处于数组中的什么位置,这个时候可以使用 $ 符号来解决:

1
db.sang_collect.update({comments:"333"},{$set:{"comments.$":"333-1"}})

查询条件查出来 333 的下标,$ 符号就代码这个下标,然后通过 $ 符号就能将之修改。

save

save 是 shell 中的一个函数,接收一个参数,这个参数就是文档,如果文档中有 _id 参数 save 会执行更新操作,否则执行插入操作,使用 save 操作我们可以方便的完成一些更新操作。

p253

类似于如下命令则表示一个插入操作(因为没有 _id ):

1
db.sang_collect.save({x:111})

好了,MongoDB 的更新操作我们就先介绍这么多,有问题欢迎留言讨论。

参考资料:

  1. 《MongoDB权威指南第2版》

喜欢这篇文章吗?扫码关注公众号【江南一点雨】【江南一点雨】专注于 SPRING BOOT+微服务以及前后端分离技术,每天推送原创技术干货,关注后回复 JAVA,领取松哥为你精心准备的 JAVA 干货!

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×