数组算得上是MongoDB中最重要的数据类型了,对于数组的常见操作有添加、修改、删除等。
上一节我们已经学习了$inc、$set 、$unset等修改器,另外还有$pop、$push、$addToSet等数组修改器。今天我们就来重点学习一下数组修改器的内容。
沿袭上一篇的风格,对于每一个修改器,我们尽量使用shell来实践一下。
一、$push
如果指定的key已经存在,则向数组末尾加入一个元素,如果不存在,则会创建一个新的数组。
db.student.findOne({"sex":1,'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"name" : "lily",
"age" : 31,
"sex" : 1,
"address" : "bj"
}
> db.student.update({"sex":1,'name':'lily'},
... {$push:{
... 'comment':[
... {'name':'zhangsan','content':'nice','time':new Date()},
... {'name':'lisi','content':'not bad','time':new Date()}]}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({"sex":1,'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"name" : "lily",
"age" : 31,
"sex" : 1,
"address" : "bj",
"comment" : [
[
{
"name" : "zhangsan",
"content" : "nice",
"time" : ISODate("2018-07-07T07:48:39.173Z")
},
{
"name" : "lisi",
"content" : "not bad",
"time" : ISODate("2018-07-07T07:48:39.173Z")
}
]
]
}
自动创建了一个comment数组并插入了两个评论元素。
注:个人在进行代码测试的时候发现数组中可以插入重复的元素,下面的代码就在comment数组中插入了{'name':'demo'}的重复文档。
db.student.find()
{ "_id" : ObjectId("5b3f782598781e88f5e3462f"), "name" : "xiaoming", "sex" : 1, "age" : 2 }
{ "_id" : ObjectId("5b401a87d3c72e6c8d190cf7"), "date" : ISODate("2018-07-07T01:42:31.010Z") }
{ "_id" : ObjectId("5b40d1ea5c99dd00d4d1585a"), "name" : "lucy", "age" : 1 }
{ "_id" : ObjectId("5b40d2275c99dd00d4d1585b"), "name" : "wangwu", "age" : 1 }
{ "_id" : ObjectId("5b3f79a5d16c252df9d143fc"), "age" : 31, "name" : "lily", "sex" : 1, "address" : "bj", "comment" : [ [ { "name" : "zhangsan", "content" : "nice", "time" : ISODate("2018-07-08T02:54:38.459Z") }, { "name" : "lisi", "content" : "not bad", "time" : ISODate("2018-07-08T02:54:38.459Z") } ], { "name" : "test" } ] }
> db.student.update({'name':'lily'},{$push:{'comment':{'name':'test'}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.find()
{ "_id" : ObjectId("5b3f782598781e88f5e3462f"), "name" : "xiaoming", "sex" : 1, "age" : 2 }
{ "_id" : ObjectId("5b401a87d3c72e6c8d190cf7"), "date" : ISODate("2018-07-07T01:42:31.010Z") }
{ "_id" : ObjectId("5b40d1ea5c99dd00d4d1585a"), "name" : "lucy", "age" : 1 }
{ "_id" : ObjectId("5b40d2275c99dd00d4d1585b"), "name" : "wangwu", "age" : 1 }
{ "_id" : ObjectId("5b3f79a5d16c252df9d143fc"), "age" : 31, "name" : "lily", "sex" : 1, "address" : "bj", "comment" : [ [ { "name" : "zhangsan", "content" : "nice", "time" : ISODate("2018-07-08T02:54:38.459Z") }, { "name" : "lisi", "content" : "not bad", "time" : ISODate("2018-07-08T02:54:38.459Z") } ], { "name" : "test" }, { "name" : "test" } ] }
而在实际使用过程中我们是不希望在数组中出现重复的元素的。那如果我们想要实现这部分功能又要如何做呢?你可能想到了,插入之前先查询一下,没有的话再进行插入。那问题来了,查询一个文档是不是在数组里面又要如何操作?流程是不是有点繁琐,有没有现成的命令呢?
答案是可能的,$addToSet就是用来完成这部分功能的。
下面我们使用$addToSet来再执行上面的插入操作
db.student.update({'name':'lily'},{$addToSet:{'comment':{'name':'test'}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
[
{
"name" : "zhangsan",
"content" : "nice",
"time" : ISODate("2018-07-08T02:54:38.459Z")
},
{
"name" : "lisi",
"content" : "not bad",
"time" : ISODate("2018-07-08T02:54:38.459Z")
}
],
{
"name" : "test"
},
{
"name" : "test"
}
]
}
这个时候你可能又有需求了,能不能批量插入元素到数组中呢?答案也是肯定的,这个时候就需要请出$each了,$each和$addToSet就是一个黄金搭档。
现在,我们想数组中批量插入几条评论
db.student.update({'name':'lily'},
... {$addToSet:{
... 'comment':{$each:[{"name":'test1'},{'name':'test2'},{'name':'test3'}]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
[
{
"name" : "zhangsan",
"content" : "nice",
"time" : ISODate("2018-07-08T02:54:38.459Z")
},
{
"name" : "lisi",
"content" : "not bad",
"time" : ISODate("2018-07-08T02:54:38.459Z")
}
],
{
"name" : "test"
},
{
"name" : "test"
},
{
"name" : "test1"
},
{
"name" : "test2"
},
{
"name" : "test3"
}
]
}
OK,上面讲得都是插入文档,如果我想弹出数组头尾部的一些文档呢?答案是使用$pop
二、$pop
语法:{$pop:{key:1}}弹出尾部元素 {$pop:{key:1}}弹出头部元素
1、弹出尾部一个元素
db.student.update({'name':'lily'},{$pop:{'comment':1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
[
{
"name" : "zhangsan",
"content" : "nice",
"time" : ISODate("2018-07-08T02:54:38.459Z")
},
{
"name" : "lisi",
"content" : "not bad",
"time" : ISODate("2018-07-08T02:54:38.459Z")
}
],
{
"name" : "test"
},
{
"name" : "test"
},
{
"name" : "test1"
},
{
"name" : "test2"
}
]
}
2、弹出头部一个元素
db.student.update({'name':'lily'},{$pop:{'comment':-1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
{
"name" : "test"
},
{
"name" : "test"
},
{
"name" : "test1"
},
{
"name" : "test2"
}
]
}
注:使用$pop时有一个局限:只能弹出头尾部的单个元素。-1/1只是用来标识弹出头尾而言的,使用其他正负数也可以,但是只会弹出一个。
db.student.update({'name':'lily'},{$pop:{'comment':-2}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
{
"name" : "test"
},
{
"name" : "test1"
},
{
"name" : "test2"
}
]
}
> db.student.update({'name':'lily'},{$pop:{'comment':-5}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
{
"name" : "test1"
},
{
"name" : "test2"
}
]
}
如果我们想要弹出某些某条件的元素呢?没错,就是用下面的$pull
三、$pull
弹出数组中满足某条件的所有元素
db.student.update({'name':'lily'},{$pull:{'comment':{'name':'test1'}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
{
"name" : "test2"
}
]
}
讲到这里,你可能会说,如果我想要修改某个位置的元素,有没有简单的方法呢?答案也是肯定的。
四、定位操作符$
定位操作符经常和$set结合起来使用,下面我们给comment数组中{'name':'test2'}新增一个评论数量字段。
db.student.update({'comment.name':'test2'},{$inc:{'comment.$.count':1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.student.findOne({'name':'lily'})
{
"_id" : ObjectId("5b3f79a5d16c252df9d143fc"),
"age" : 31,
"name" : "lily",
"sex" : 1,
"address" : "bj",
"comment" : [
{
"name" : "test2",
"count" : 1
}
]
}
当然,$也有局限性:只更新第一个匹配的元素,关于这点的改进我们后续会再学习。
OK,相信上面的一些修改器已经能cover我们大部分的需求了。
总结:通过上篇文章以及本片文章的学习,我们会发现MongoDB修改器的一个巨大优势:会在key、文档信息不存在时自动进行一个新增操作,这个优势在实际使用中会节约大量的代码提升不小的性能,而且还能解决部分竞态问题。