前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~
主页: oliver尹的主页
格言: 跌倒了爬起来就好~
来个关注吧,点个赞吧,谢谢
《MongoDB》Mongo Shell中的基本操作-更新操作一览
- 一、简介
- 二、内容概述
- 三、更新文档
- 3.1 db.collection.update()
- 注意点
- 3.2 更新操作符
- $set 更新或新增字段
- $unset 删除字段
- $rename 重命名字段
- $inc 加减字段值
- $mul 相乘字段值
- $min 比较较小字段值
- $max 比较较大字段值
- 3.3 数组更新操作符
- $addToset 添加元素
- $pop 移除元素
- $pull 选择性删除
- $pullAll 选择性删除
- $push 添加元素
- 3.4 配置参数
- multi 多篇更新
- upsert 更新或创建
- 四、小结
一、简介
本篇记录备份的是Mongo DB的一些基础知识,包括文档长什么样子,Mongo Shell中的CRUD这四种基本操作,什么是CRUD?C(Creadt、创建)R(Read、读取)U(Update、更新)D(Delete、删除),人话就是增、删、改、查等等;
注意,本文中的示例命令都是基于Mongo Shell的,并不是直接运行在类似于node代码中的~
二、内容概述
本文主要记录分享了MongoDB中的更新操作以及更新操作时的操作符
三、更新文档
当通过insert()方法将文档在MongoDB中创建之后,肯定是需要对文档进行更新的,比如用户点击了修改密码,这些操作不是新增,是对现有数据进行修改;
3.1 db.collection.update()
更新文档,具体模版如下:
db.<collection>update(<query>,<update>,<option>)
- query: 代表更新文档时筛选文档的条件,简单的说就是要更新某个文档的前提是你得找到这个文档;
- update: 代表更新文档时需要更新的内容;
- option: 代表声明了一些更新操作的参数;
在不使用 更新操作符 的情况下,使用db.collection.update()将会 直接替换集合中的文档;更新操作符等会再看;
以上方的这个模版为例,且不使用更新操作符看一个具体的示例:
db.test.update({name:"oliver"},{name:"oliver",age:20})
简单说明:在test这个集合中找到了name值是oliver的数据,并且将这条数据 替换 成了{name:“oliver”,age:20},假如这条数据 本来还有其他的字段,这些字段都将会消失,因为是替换操作
注意点
在更新操作中有一个需要注意的问题
- 文档主键也就是_id是不可以更改的,在上例中其实也已经看到了,在update中并没有包含_id字段,如果一定要写_id,比如
db.test.update({name:"oliver"},{_id:"demotest",name:"oliver",age:20})
那么 _id这个值必须和原来的这个文档的_id值保持一致,如果不一致,那么就会报错,更新失败;
- 当update中查询到多篇文档时,只有 第一篇 符合查询条件的文档会被更新,比如
db.test.update({balance:{$gt:20,$lt:80}},{name:"bill",balance:50,gender:"M"})
在test集合中查询所有balance大于20,小于80的文档,将其更新成name等于bill,balance等于50,gender等于M
从结果看,只有第一篇被更新了,第二篇并没有被更新,因此说明update的整篇文档更新只能作用在单一文档上,这是一个局限~
3.2 更新操作符
$set 更新或新增字段
使用$set更新某个字段或者在某个文档中创建字段,基本模版如下:
db.<collection>.update({ $set : {<field1>:<value1>,...,<fieldn>:<valuen>} })
具体示例如下
db.test.update(
{name: "jack"},
$set:{
balance: 3000,
info: {
dateOpened: new Date("2016-05-18T16:00:00z")
branch:"branch1"
}
})
首先在update的第一个参数name:jack,意思是找到name等于jack的数据,之后对这条数据进行更新,更新的字段是balance和info,将balance的值更新为3000,将info的值更新成一个对象,这个对象里有一个branch,且其值是branch1,如果jack这条数据不存在balance或者info,那么将为这条数据添加这两个字段并且加上对应的值;
如果更新的字段是 文档中的内嵌字段,比如上例中info里面的dateOpened,info本身就是一个对象,那么更新方式应该如下:
db.test.update(
{name: "jack"},
$set:{
"info.dateOpened": new Date("2022-09-18T17:00:00z"),
})
如果更新的字段是 数组内的字段,那么更新方式应该如下:
db.test.update(
{name: "jack"},
$set:{
"contact.0": 752746873,
})
contact是一个数组,.0代表的就是数组内的下标,意思就是将contact下的第0位更新成752746873,在数组内新增也是同样的,为某个新位置添加对应的值即可,值得注意的是,如果下标的位置超出了原本的长度,比如contact的长度是3,但现在我们在第5位添加了数据,那么第4位就会自动补全为null;
$unset 删除字段
使用$unset删除文档中的某个字段,基本模版如下:
db.<collection>.update({ $unset : {<field1>:"",...,<fieldn>:""} })
具体示例如下
db.test.update(
{name: "jack"},
$unset:{
balance: "",
"info.branch": ""
})
在test集合中找到name等于jack的文档,删掉balance这个字段,同时也删掉info这个对象下的branch这个字段;
如果,删除的字段在文档中本身就不存在,那么文档本身将不会收到任何影响~
如果要 删除数组内的某个字段,那么命令应该这么写:
db.test.update(
{name: "jack"},
$unset:{"contact.0": ""})
值得注意的是,删除内容并不会改变数组的长度,只是会将对应位置的值变成null;
$rename 重命名字段
使用$rename重命名文档中的某个字段,基本模版如下:
db.<collection>.update({ $rename : {<field1>:<newName1>,...,<fieldn>:<newNamen>} })
首先要注意的是,如果重命名的字段在文档中并不存在,那么文档将不会有任何改变;如果重命名的字段在文档中已经存在了,那么原来存在的字段会被抹去,这个要非常注意;举个例子吧
db.test.update(
{name: "jack"},
$rename:{
name: "contact"
})
contact这个字段在文档中本身就已经存在了,我们知道这个字段原来是一个数组,那么如果我们通过rename将name改成contact,那么contact这个字段原来值会被抹去
所以使用时得非常注意与小心,实际上,如果我们使用rename已经存在的字段,它在内部会先做一个unset删除,在做一个set新增;
如果重命名的字段是 内嵌文档中的字段,那么我们改已以下方式重命名:
db.test.update(
{name: "karen"},
$rename:{
"info.branch": "branch",
"balance":"info.balance"
})
什么意思呢?简单的说就是将info.branch路径的字段重命名成branch,将balance重命名到info下的balance,实际造成的结果就有点类似于将字段移动,
那么 数组中的字段重命名 呢?
db.test.update(
{name: "karen"},
$rename:{
"contact.3.primaryEmail": "primaryEmail"
})
答案是 不行,MongoDB会报错,它会不允许,同理,反向放入数组也不行,也会报错
$inc 加减字段值
使用$inc可以更新的某个 数字类型的字段值,简单的说就是用来进行数字运算,基本模版如下:
db.<collection>.update({ $inc : {<field1>:<newName1>,...,<fieldn>:<newNamen>} })
看个例子
db.test.update(
{name: "karen"},
$inc:{
"balance": 0.5
})
得到的结果就是给name等于karen的文档下的balance值 加上0.5,同理,如果给的是一个负数,如下例
db.test.update(
{name: "karen"},
$inc:{
"balance": -0.5
})
得到的结果就是给name等于karen的文档下的balance值 减去0.5;
注意的是,如果操作的字段并 不存在于文档上,那么字段与字段值会被作为初始数据添加到文档上,比如"balance"这个字段不存在文档上,那么该文档会被添加一个"balance": -0.5的字段;
$mul 相乘字段值
使用$mul可以更新的某个 数字类型的字段值,简单的说就是用来进行数字运算,基本模版如下:
db.<collection>.update({ $mul : {<field1>:<newName1>,...,<fieldn>:<newNamen>} })
看个例子
db.test.update(
{name: "karen"},
$mul:{
"balance": 2
})
得到的结果就是给name等于karen的文档下的balance值 乘以2,同理,如果给的是一个负数,如下例
db.test.update(
{name: "karen"},
$mul:{
"balance": 0.5
})
得到的结果就是给name等于karen的文档下的balance值 乘以0.5;
注意的是,如果操作的字段并 不存在于文档上,那么字段与字段值会被作为初始数据添加到文档上且值和$inc不同,它的值是0,比如"balance"这个字段不存在文档上,那么该文档会被添加一个"balance": 0的字段;
$min 比较较小字段值
使用$min可以更新的某个字段值,简单的说就是用来进行字段比较,小的那一个会被最终留下,基本模版如下:
db.<collection>.update({ $min : {<field1>:<newName1>,...,<fieldn>:<newNamen>} })
看个例子
db.test.update(
{name: "karen"},
$min:{
"info.balance": 5000
})
意思是,先找到name值等于karen的文档,然后对info.balance的值进行比较,如果原文档中的Info.balance值比较小,那么会保留原来的值,如果5000比较小,那么会将info.balance的值更新成5000;
除了数字类型外,min操作符还可以用在其他类型上,比如时间,可以比较时间哪个小,小的那个会被留下, 另外,如果更新的字段不存在,那么$min操作符会将值初始化进文档;
如果,两者的类型不一样,比如 info.balance的值是一个数字,我们将其更新成Null,
db.test.update(
{name: "karen"},
$min:{
"info.balance": null
})
最终,文档的info.balance真的会被更新成null,因为在mongodb中它其实是有一个内部的大小排序的,排序如下
Null < Number < Symbol, String < Object < Array < BinData < ObjectId < Boolean < Date < Timestamp < Regular Expression
由于null比Number要小,因此上面例子中的更新会将null替换掉数字;
$max 比较较大字段值
使用$max可以更新的某个字段值,简单的说就是用来进行字段比较,大的那一个会被最终留下,基本模版如下:
db.<collection>.update({ $max : {<field1>:<newName1>,...,<fieldn>:<newNamen>} })
看个例子
db.test.update(
{name: "karen"},
$max:{
"info.balance": 5000
})
意思是,先找到name值等于karen的文档,然后对info.balance的值进行比较,如果原文档中的Info.balance值比较小,那么会保留5000,如果5000比较小,那么会将info.balance的值将不会变动,还是保留原来的值;
除了数字类型外,min操作符还可以用在其他类型上,比如时间,可以比较时间哪个大,大的那个会被留下,另外,如果更新的字段不存在,那么$max操作符会将值初始化进文档;
如果,两者的类型不一样,比如 info.balance的值是一个数字,我们将其更新成Null,
db.test.update(
{name: "karen"},
$min:{
"info.balance": null
})
最终,文档的info.balance会按规则进行排序,大的那个将会被留下,排序如下
Null < Number < Symbol, String < Object < Array < BinData < ObjectId < Boolean < Date < Timestamp < Regular Expression
由于null比Number要小,因此上面例子中的更新将不会被执行,Number依然会被保留下来;
3.3 数组更新操作符
$addToset 添加元素
基本使用方式如下
db.<collection>.update({ $addToset : {<field1>:<value1>} })
具体示例如下:
db.test.update(
{name: "jack"},
{$addToset:{contact: "chind"}})
如果添加的值在待添加的数组中已存在,则会添加失败不会被添加进去,但注意的是,如果添加的值是复杂类型,那么判断会比较复杂,需要值完全相同且顺序也完全相同才会被判定是否已存在数组中;
$pop 移除元素
$pop用来删除数组中的元素,但值得注意的是,只能删除第一个元素或者最后一个元素,基本使用方式如下
db.<collection>.update({ $pop : {<field1>:<1|-1>} })
具体示例如下:
db.test.update(
{name: "jack"},
{$pop:{contact: 1}})
代表删除contact这个数组中最后一个元素,1代表最后一个,-1代表第一个;
$pull 选择性删除
基本使用方式如下
db.<collection>.update({ $pull : {<field1>:<value|condition>} })
具体示例如下:
db.test.update(
{name: "jack"},
{$pull:{contact: {$regex:/hi/}}}
)
找到所有name值是jack的文档,删除contact中所有包含hi的字段,这里的包含是通过正则进行匹配的,另外pull也可以删除特定的元素,示例如下:
db.test.update(
{name: "jack"},
{$pull:{contact: "222222"}}
)
另外,通过pull去删除一个复杂类型的数据时,比如删除的不再是一个字符串,而是一个对象,通过pull删除时只需要提供对象中的一个键值对接口删除整个数据,或者提供全量的值但顺序可以不一样,这个和pullAll不一样,pullAll需要 全部提供且顺序也一样;
$pullAll 选择性删除
基本使用方式如下
db.<collection>.update({ $pullAll : {<field1>:[<value1>,<value2>]} })
具体示例如下:
db.test.update(
{name: "jack"},
{$pullAll:{contact: ['222222','333333']}}
)
找到所有name值是jack的文档,删除contact中所有值是222222或者333333的值;
另外,通过pullAll去删除一个复杂类型的数据时,比如删除的不再是一个字符串,而是一个对象,那么参数必须是顺序也是一摸一样的才会被删掉;
$push 添加元素
基本使用方式如下
db.<collection>.update({ $push : {<field1>:<value1>...} })
具体示例如下:
db.<collection>.update({ $push : {newArray:[1,2,3]} })
这样[1,2,3]这个数组将会被添加进newArray里,如果说想要将1,2,3分别添加进行newArray而不是作为一个整体添加进去则需要通过$each操作符
db.<collection>.update({ $push : {newArray:{$each:[1,2,3]}} })
另外push比addtoset更加强大的地方在于存储位置的调整,比如
db.<collection>.update({ $push : {newArray:{$each:[1,2,3],$position:0}} })
这个的意思就是说,1,2,3会被存储到下标为第0位的这个位置;
还可以进行排序,通过 sort 这个操作符进行操作,值得注意的是$sort这个操作符不可以单独使用,必须和 each 操作符一起使用
db.<collection>.update({ $push : {newArray:{$each:[1,2,3],$sort:1}} })
从小到大进行排序,同理,-1则代表从大到小排序;
$slice 操作符代表截取
db.<collection>.update({ $push : {newArray:{$each:[1,2,3],$slice:-8}} })
代表截取到这数的8位进行保存,对于剩下的就不再进行保存了,直接丢弃,同理,如果值是正数,那么就代表正着数的8位进行保存
如果position,sort,slice被一起使用时,那么顺序永远是,先通过$position向指定位置进行保存,在进行排序,最后进行截取;
3.4 配置参数
在上面我们知道,update有三个参数
db.<collection>update(<query>,<update>,<option>)
第三个参数中,有几个比较重要的配置项,首先是multi,是否允许更新多篇文档
multi 多篇更新
{multi:<boolean>}
具体示例
db.test.update({name:"oliver"},{name:"oliver",age:20},{multi:true})
那么所有的数据都将会被改写,但值得注意的是,更新多个文档的操作虽然在单一线程中执行,但是线程在执行的过程中可能被挂起,以便其他线程也有机会对数据进行操作;因此,多个文档被操作因此不能保证原子性,也就代表结果不一定是对的,比如挂起阶段,某个文档被其他线程给删了,那等到挂起的线程继续操作时,文档已经改变了,或者干脆已经没有了;
upsert 更新或创建
update在没有匹配到文档时是否进行创建,默认时不会进行创建的;
{upsert:<boolean>}
具体示例
db.test.update({name:"oliver"},{name:"oliver",age:20},{upsert:true})
简单的说,假如在test中没有匹配到name为oliver的文档,此时会在test中创建一篇{name:“oliver”,age:20}的文档
四、小结
本文主要记录了在常规数据库操作中文档更新的一些用法~更新的命令非常简单,都是db.collection.update(),但查询时候的操作符非常复杂,需要经常使用与回顾~