MongoDB文档更新操作

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

本文是MongoDB系列的第四篇文章,了解前面的文章有助于更好的理解本文:


文档替换

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

{
    "_id" : ObjectId("59f005402844ff254a1b68f6"),
    "name" : "三国演义",
    "authorName" : "罗贯中",
    "authorGender" : "男",
    "authorAge" : 99.0
}

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

{
    "_id" : ObjectId("59f005402844ff254a1b68f6"),
    "name" : "三国演义",
    "author" : {
        "name" : "罗贯中",
        "gender" : "男",
        "age" : 99.0
    }
}

我可以采用如下操作:

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

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

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

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

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

{ "_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,可以采用如下命令:

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

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

使用修改器

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

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

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

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

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

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

如下图:

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

$set修改器

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

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

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

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

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "罗贯中",
        "gender" : "男"
    }
}

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

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

修改结果如下:

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男"
    }
}

$inc修改器

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

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

执行结果如下:

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 99.0
    }
}

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

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

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

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 100.0
    }
}

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

数组修改器

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

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

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

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 100.0
    },
    "comments" : [ 
        "好书666"
    ]
}

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

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

结果如下:

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 100.0
    },
    "comments" : [ 
        "好书666", 
        "好书666啦啦啦啦"
    ]
}

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

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

结果如下:

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 100.0
    },
    "comments" : [ 
        "好书666", 
        "好书666啦啦啦啦", 
        "111", 
        "222", 
        "333"
    ]
}

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

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

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

{
    "_id" : ObjectId("59f042cfcafd355da9486008"),
    "name" : "三国演义",
    "author" : {
        "name" : "明代罗贯中",
        "gender" : "男",
        "age" : 100.0
    },
    "comments" : [ 
        "111", 
        "222", 
        "333", 
        "444", 
        "555"
    ]
}

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

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

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

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表示升序。 上面的命令执行两次之后(即插入两次),结果如下:

{
    "_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
        }
    ]
}

sort不能只和each。

$addToSet

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

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

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

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

$pop

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

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

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

$pull

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

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

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

$

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

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

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

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

查询条件查出来333的下标,符号就能将之修改。

save

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

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

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

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

参考资料:

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

原文发布于微信公众号 - 玩转JavaEE(gh_d1ca11234a30)

原文发表时间:2017-11-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏分布式系统和大数据处理

.Net 项目代码风格参考

代码风格没有正确与否,重要的是整齐划一,这是我拟的一份《.Net 项目代码风格参考》,供大家参考。

1482
来自专栏坚毅的PHP

关于java

大整数10进制转16进制问题 google了都没什么好的解决方法,因为要转换的十进制有300多位,long都装不下,没有直接可用的函数可以拿来用 王总的方法分享...

3398
来自专栏Android机器圈

Java设计模式总汇二(小白也要飞)

PS:上一篇我介绍了适配器设计模式、单例设计模式、静态代理设计模式、简单工厂设计模式,如果没有看过第一篇的小火鸡可以点这个看看http://www.cnblog...

3499
来自专栏老马寒门IT

Node入门教程(13)第十一章:mocha单元测试+should断言库+istanbul覆盖率测试+art-template

声明: 以下为老马的全栈视频教程的笔记,如果需要了解详情,请直接配合视频学习。视频全部免费,视频地址:https://ke.qq.com/course/2945...

1550
来自专栏何俊林

Android Multimedia框架总结(十一)CodeC部分之AwesomePlayer到OMX服务

前言:上篇文《Android Multimedia框架总结(十)》总结了音视频的输出过程,从今天开始分析Codec部分,今天分析的是AwesomePlayer到...

2608
来自专栏安恒网络空间安全讲武堂

从零基础到成功解题之0ctf-ezdoor

2174
来自专栏JackieZheng

探秘Tomcat——一个简易的Servlet容器

即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器。 背景知识   既然说到servlet容器这个名词,我们首先要了解它...

2295
来自专栏梧雨北辰的开发录

iOS面试知识总结之基本概念总结

凡经历过iOS面试的我们总会发觉,即使实际开发中做过许多项目,也难免为一个普通的面试题受挫。这也许不是因为我们技术不过关,而是因为在平时我们忽略了怎样将用到的知...

4127
来自专栏屈定‘s Blog

设计模式--责任链模式的思考

责任链模式: 客户端发出的请求,客户端本身并不知道被哪一个对象处理,而直接扔给对象链,该请求在对象链中共享,由对象本身决定是否处理. 当请求被处理后该链终止.本...

4823
来自专栏JackieZheng

在ssh中利用Solr服务建立的界面化站内搜索---solr2

     继上次匆匆搭建起结合solr和nutch的所谓站内搜索引擎之后,虽当时心中兴奋不已,可是看了看百度,再只能看看我的控制台的打印出每个索引项的几行文字,...

2219

扫码关注云+社区

领取腾讯云代金券