MongoDB学习
二. 创建、更新及删除文档
1. 插入并保存文档
db.foo.insert({'bar': 'bar'})
这个操作会给document增加一个_id键(要是原理没有的话),然后保存到MongoDB中。
如果插入多个document,应该使用批量插入,他接受一个由文档组成的数组。批量插入只能用于将多个document插入到一个collection,不可用于插入多个collection。
插入的原理和作用:
当执行插入的时候,驱动程序会把数据转换成BSON,然后将其送入数据库。数据库解析BSON,检查是否包含_id且文档小于4MB,不做数据验证。
2. 删除文档
db.users.remove()
- 这样删除不会删除collection本身,也不会删除索引。
- 从删除速度上来讲,删除一个collection并重建索引比删除一个collection中的所有document通常更快
3. 更新文档
使用update来更新document,通常有两个参数,第一个是查询文档,用于查找到用于删除的文档,另一个是修改器文档,描述对document做哪些修改。
注意:当我们更新文档时,最好使用类如_id这样的,可以单独选出唯一文档的条件,否则可能报错
修改器
$inc
$inc可以用来增加已有键的值,如果这个键不存在,则创建这个键
假设存在以下结构:
{ "_id": ObjectId("......"), "url": "www.example.com", "pageviews": 52 }
db.analytics.update( {'url': 'example'}, {'$inc': { {'pageviews': 1} } )
$set
set修改器用来指定一个键的值,如果这个键不存在就创建它。也可以使用 unset删除键。
> db.users.findOne() { "_id": ObjectId("123456"), "name": "joe", "age": 30, "sex": "male", "location" : "HZ" }
使用$set**添加**一个键:
> db.users.update( {"_id": "123456"}, {"$set": {"favorite book": "peace and war"} ) { "_id": ObjectId("123456"), "name": "joe", "age": 30, "sex": "male", "location" : "HZ", "favorite book": "peace and war" }
使用$set**修改**一个键的值(可以更换这个键的值得数据类型!):
db.users.update( {"_id": "123456"}, {"$set": {"favorite book": "what's up!"} ) { "_id": ObjectId("123456"), "name": "joe", "age": 30, "sex": "male", "location" : "HZ", "favorite book": "what's up!" }
使用$unset**删除**一个键:
db.users.update( {"_id": "123456"}, {"$unset": {"favorite book": 1} )
也可以使用$set修改内嵌文档
数组修改器
$push,如果这个键不存在,则创建一个数组,如果存在且是一个数组则插入到尾部。
> db.blog.posts.findOne() { "_id": ObjectId('123456'), "title": "A blog post", "content": "......" } > db.blog.posts.update( {"title": "A blog post"}, { "$push": { "comments": { "name": "ldd", "title": "nice post", "content": "lalala" } } } ) > db.blog.posts.findOne() { "_id": ObjectId('123456'), "title": "A blog post", "content": "......", "comments": [ { "name": "ldd", "title": "nice post", "content": "lalala" } ] }
$addToSet,如果数组中已经存在这个值,则什么也不做;否则加入数组
> db.users.findOne(); { "_id": ObjectId("123456"), "username": "ldd", "emails": [ "ldd@example.com", "ldd@gmail.com", "ldd@yahoo.com" ] } // 第一次添加时因为已经有了这个邮箱,所以原来的document不变 > db.users.update( { "_id": "123456" }, { "$addToSet": { "eamils": "ldd@gmail.com" } } ) > db.users.findOne(); { "_id": ObjectId("123456"), "username": "ldd", "emails": [ "ldd@example.com", "ldd@gmail.com", "ldd@yahoo.com" ] } // 第二次添加成功 > db.users.update( { "_id": "123456" }, { "$addToSet": { "eamils": "ldd@hotmail.com" } } ) > db.users.findOne(); { "_id": ObjectId("123456"), "username": "ldd", "emails": [ "ldd@example.com", "ldd@gmail.com", "ldd@yahoo.com", "ldd@hotmail.com" ] }
使用 addToSet和 each添加一个数组到数组
db.users.update( {"_id": "ObjectId('123456')"}, {"$addToSet": { "emails": { $each: [ "ldd@example.com", "ldd@gmail.com", "ldd@yahoo.com", "ldd@hotmail.com" ] } }} )
使用$pop删除一端的元素,
{"$pop": {"key": 1}}
从数组末尾删除一个元素,{"$pop": {"key": -1}}
从头删除一个元素。使用$pull在列表中进行精确的删除,它会删除所有匹配的数据
{"$pull": { "todo": "laundry" }}
数组的定位修改器
如果数组中有多个数据,可以使用$来匹配第一个匹配到的数据。
> db.blog.posts.findOne() { "_id": ObjectId('123456'), content: "......", comments: [ { "name": "ldd", "title": "nice post", "content": "lalala", "votes": 0 }, { "name": "lll", "title: "a nice post", "content": "wowoow", "votes": 0 } ] } db.blog.update( {"comments.author": "ldd"}, {"$set": {"comments.$.name": "zhu"}} )
upsert
upsert是一种特殊的更新,要是没有文档符合更新条件,就以这个条件以及document更新为基础创建一个新的document,如果找到了文档则正常更新。
要使用upsert,只要把update的第三个参数设为true即可。
比如说我们给一个博客加pageview,一开始的时候并没有这个url,使用upsert可以初始化,之后有了这个url,则正常更新(更实际的应该是在初始化这个博客的时候就把pageview赋为0了把?)
blog = db.posts.findOne({"url": "/blog"});
if (blog) {
blog.pageview ++;
db.posts.save(blog);
}
else {
db.posts.save({"url": "/blog", "pageview": 1});
}
如果使用upsert,上面的代码用一句话代替:
db.posts.update({"url": "/blog"}, {$inc: {"pageview": 1}}, true);
在shell中快速修改document的方法。save方法被调用时,如果文档中有_id键,则自动使用upsert,否则不会,调用插入。
> var x = db.foo.findOne();
> x.num = 42;
42
>db.foo.save(x);
更新多文档
通常情况下,update操作只会操作第一个满足匹配条件的文档。因此,如果有多个document满足条件则其他的不会被update。
要让所有满足匹配条件的document都update,则应让第四个参数为true。
比如我们要让在特定日期过生日的用户收到礼物:
db.users.update(
{birthday: "1994/01/19"},
...,
{$set: {"gift": "Happy birthday}},
false,
true
)
那么我们如何知道有多少文档被更新了呢???
> db.count.update({x: 1}, {$inc: {x: 1}, false, true)
> db.runCommand({getLastError: 1})
{
"err": null,
"updateExisting": true,
"n": 5,
"ok": true
}
其中,n表示修改的document的个数,updateExisting为true表示修改的是已有document。
返回更新的文档
如果时序关系很重要的话,则可以考虑findAndModify操作
一个例子:有如下形式的document
{
"_id": ObjectId('123456'),
"status": "READY",
"priority": n
}
我们要从READY中选出priority最高的进行操作。
ps = db.processes.find({"status": "READY"})
.sort({"priority": -1})
.limit(1)
.next();
db.processes.update({"_id": ps._id}, {"$set": {"status": "RUNNING"}});
do_something(ps);
db.processes.update({"_id": ps._id}, {"$set": {"status": "DONE"}});
如果线程A执行第二句话之前,线程B也执行了第一句话,则出现冲突。
这时候使用findAndModify:
> ps = db.runCommand({
"findAndModify": "processes",
"query": {"status": "READY"},
"sort": {"priority": -1},
"update": {"$set": {"status": "RUNNING"}}
})
{
"ok": 1,
"value": {
"_id": ObjectId("123456"),
"priority": 1,
"status: "READY"
}
}
先返回结果,后更新
> db.processes.find({"_id": ps.value._id})
{
"_id": ObjectId("123456"),
"priority": 1,
"status: "RUNNING"
}
所以,程序就变成了这个样子:
> ps = db.runCommand({
"findAndModify": "processes",
"query": {"status": "READY"},
"sort": {"priority": -1},
"update": {"$set": {"status": "RUNNING"}}
}).value
> do_somethind(ps)
> db.processes.update({"_id": ps._id"}, {"$set": {"status": "DONE"}})