创建、更新及删除文档
1. 插入并保存文档
1.1 基本的插入操作db.foo.insert({“bar” :“bar”}),向当前库的foo集合中插入一个文档,这个操作会给文档增加一个”_id”键(要是原来没有的话),然后将其保存到MongoDB中。
1.2 批量插入
2. 删除文档
>db.users.remove() 此命令会删除users集合中所有的文档。但不会删除集合本身,所有的索引也会保留。remove函数可以接受一个查询文档作为可选参数,给定这个参数以后,只有符合条件的文档才被删除。例如,假设要删除mailing.list集合中所有”optout”为true的人:
>db.mailing.list.remove({“opt-out”: true})
删除数据是永久性的,不能撤销,也不能恢复。
删除文档通常会很快,但是要清除整个集合,直接删除集合(然后重建索引)会更快。
>db.drop_collection(“bar”) 这种删除直接删除集合本身及其所有索引。(MongoDB权威指南》中提到的db.drop_collection("集合名")在Shell中无法执行,调用db.help()查看数据库可以调用的方法中也没有这个方法。但数据库提供了这个方法: db.dropDatabase() ,不接受任何参数,会将数据库中所有的集合全部删除。调用 db.集合名.help() 查看Shell中为集合提供的所有函数,发现其有一个drop函数,用于删除当前的集合。)
3. 更新文档
文档存入数据库以后,就可以使用update方法来修改它。update有两个参数,一个是查询文档,用来找出要更新的文档,另一个是修改器(modifier)文档,描述对找到的文档做哪些更改。
更新操作是原子的:若是两个更新同时发生,先到达服务器的先执行,接着执行另外一个。所以,互相有冲突的更新可以火速传递,并不会相互干扰: 最后的更新会取得”胜利”.
3.1文档替换
更新最简单的情形就是完全用一个新文档替代匹配的文档。这适用于模式结构发生了较大变化的时候。
案例一:
原有文档:
> joe =db.users.findOne({"name" : "joe"})
{
"_id" :ObjectId("51ac331d1ef07ea1f0ac8dd6"),
"name" :"joe",
"friends" :32,
"enemies" :2
}
期待改造后的文档:
{ "_id" :ObjectId("51ac389a4ce1b5b65b8b6277"),
"username" : "joe",
"relationships" : { "friends" : 32,
"enemies" : 2 }
}
改造步骤如下:
> joe =db.users.find({"name":"joe"})
{ "_id" :ObjectId("51ac389a4ce1b5b65b8b6277"),
"name" : "joe",
"friends" : 32,
"enemies" : 2 }
> joe.username =joe.name;
joe
> joe.relationships ={"friends":joe.friends,"enemies":joe.enemies};
{ "friends" : 32,"enemies" : 2 }
> delete joe.friends;
true
> delete joe.enemies;
true
> delete joe.name;
true
>db.users.update({"name" : "joe"},joe);
>db.users.find({"username":"joe"})
{ "_id" :ObjectId("51ac389a4ce1b5b65b8b6277"),
"username" : "joe",
"relationships" : {"friends" : 32, "enemies" : 2 } }
案例二:
> db.users.find()
{ "_id" : ObjectId("51ac3dc15c55cf9cb4ddbe6a"),"name" : "joe", "age" : 65 }
{ "_id" : ObjectId("51ac3dd25c55cf9cb4ddbe6b"),"name" : "joe", "age" : 20 }
{ "_id" : ObjectId("51ac3deb5c55cf9cb4ddbe6c"),"name" : "joe", "age" : 49 }
> joe =db.users.findOne({"name":"joe","age":20});
{ "_id" : ObjectId("51ac3dd25c55cf9cb4ddbe6b"),"name" : "joe", "age" : 20 }
> joe.age++;
20
>db.users.update({"name":"joe"},joe)
cannot change _id of a document old:{ _id:ObjectId('51ac3dc15c55cf9cb4ddbe6a'), name: "joe", age: 65.0 } new:{_id: Obj
ectId('51ac3dd25c55cf9cb4ddbe6b'), name: "joe", age: 21.0}
更新失败原因:当调用update时,数据库会查找一个与{“name”:”joe”}匹配的文档,找到的第一个就是那个65岁的joe,然后数据库尝试用变量joe中的内容替换找到的文档,但是发现集合里面已经有一个具有同样”_id”的文档,所以更新失败,因为”_id”值必须唯一。为了只够这种情况,最好确保更新总是指定唯一文档,例如通过像“_id”这样的键来匹配。
3.2使用修改器
通常文档只会有一部分要更新。利用原子的更新修改器,可以使得这种部分更新极为高效。更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整、增加或者删除键,还可能是操作数组或者内嵌文档。使用修改器时,”_id”的值是不能改变。(注意,整个文档替换时是可以改变”_id”的。)其他键值,包括其他唯一索引的键都是可以更改的。
1) “$set”修改器入门
“$set”用来指定一个键的值”。如果这个键不存在,则创建它。这对更新模式或者增加用户定义键来说非常方便。
> db.users.find()
{ "_id" :ObjectId("51ac48455c55cf9cb4ddbe6d"),
"name" : "joe",
"age" : 30,
"sex" : "male",
"location" : "Wisconsin" }
// 通过$set修改器给文档增加一个键值对
>db.users.update({"_id" :ObjectId("51ac48455c55cf9cb4ddbe6d")},
{"$set" :{"favorite book" : "war and peace"}})
> db.users.find()
{ "_id" : ObjectId("51ac48455c55cf9cb4ddbe6d"),
"age" : 30,
"favorite book" : "war and peace",
"location" : "Wisconsin",
"name" :"joe",
"sex" :"male" }
//通过$set修改器修改文档中某个键值对的值
>db.users.update({"name":"joe"},{"$set":{"favoritebook":"green eggs and ham"}})
> db.users.find()
{ "_id" : ObjectId("51ac48455c55cf9cb4ddbe6d"),"age" : 30, "favorite book" : "green eggs and ham","location" : "Wiscon
sin", "name" : "joe", "sex" :"male" }
//通过$set修改器修改文档中某个键值对的值的数据类型
>db.users.update({"name" : "joe"},{"$set" :{"favorite book" : ["cat's cradle","foundationtrilogy","ender's game"]}})
> db.users.find()
{ "_id" : ObjectId("51ac48455c55cf9cb4ddbe6d"),"age" : 30, "favorite book" : [ "cat's cradle","foundation trilogy", "e
nder's game" ], "location" : "Wisconsin","name" : "joe", "sex" : "male" }
//通过unset修改器删除文档中的某个键值对
>db.users.update({"name" : "joe"},{"$unset" :{"favorite book":1}})
> db.users.find()
{ "_id" : ObjectId("51ac48455c55cf9cb4ddbe6d"),"age" : 30, "location" : "Wisconsin","name" : "joe", "sex" : "male" }
> db.blog.posts.find()
{ "_id" : ObjectId("51ac4ee7c34ebb0597e2e8a6"),
"title": "A Blog Post",
"content" : "...",
"author": { "name" : "joe",
"email" :"joe@example.com" } }
//使用$set修改器修改嵌入文档
>db.blog.posts.update({"author.name" :"joe"},{"$set" : {"author.name" : "joeschmoe"}})
> db.blog.posts.find()
{ "_id" :ObjectId("51ac4ee7c34ebb0597e2e8a6"),
"author" : { "email" :"joe@example.com", "name" : "joe schmoe" },
"content" : "...",
"title" : "A Blog Post" }
2) 增加和减少
“$inc”修改器用来增加已有键的值,或者在键不存在时创建一个键。示例:
>db.games.insert({"game" : "pinball","user" :"joe"})
> db.games.find()
{"_id" : ObjectId("51ac5660c34ebb0597e2e8a7"),"game" : "pinball", "user" : "joe" }
>db.games.update({"game" : "pinball","user" :"joe"},{"$inc" : {"score" : 50}})
> db.games.find()
{"_id" : ObjectId("51ac5660c34ebb0597e2e8a7"),"game" : "pinball", "score" : 50,"user" : "joe" }
>db.games.update({"game" : "pinball","user" :"joe"},{"$inc" : {"score" : 10000}})
> db.games.find()
{ "_id" :ObjectId("51ac5660c34ebb0597e2e8a7"), "game" :"pinball", "score" : 10050, "user" :"joe" }
注意:”$inc”键的值必须为数字。不能使用字符串、数组或其他非数字的值。否则就会”Modifier “$inc” allowed for numbers only”
3) 数组修改器
>db.blog.posts.insert({"title" : "A blog post","content" : "..."})
> db.blog.posts.find()
{"_id" : ObjectId("51ac5f93c34ebb0597e2e8a8"),
"title": "A blog post",
"content": "..." }
// 如果comments不存在,会先创建这个键值对,然后再push数组元素
>db.blog.posts.update({"title" : "A blog post"},
{"$push" :{"comments" :
{"name" :"joe",
"email" :"joe@example.com",
"content" :"nice post."}
> db.blog.posts.find()
{"_id" : ObjectId("51ac5f93c34ebb0597e2e8a8"),
"comments": [ { "name" : "joe",
"email": "joe@example.com",
"content": "nice post." } ],
"content" : "...",
"title" : "A blog post" }
//第二次push直接加入这个元素
>db.blog.posts.update({"title" : "A blog post"},
{"$push" :{"comments" :
{"name" :"joe",
"email" :"joe@example.com",
"content" :"nice post."}
> db.blog.posts.find()
{"_id" : ObjectId("51ac5f93c34ebb0597e2e8a8"),
"comments": [ { "name" :"joe",
"email": "joe@example.com",
"content": "nice post." },
{"name" : "joe",
"email": "joe@example.com",
"content": "nice post." } ],
"content" : "...",
"title" : "A blog post" }
//如果Richie不在authors cited这个数组中,就把它加进数组中
>db.papers.update({“authorscited” : {“$ne” : “Richie”}},{$push : {“authorscited” : “Richie”}})
//也可以用”$addToSet”来避免重复
>db.users.update({“_id”: ObjectId(“4b2d75476cc613d5ee930164”)},{“$addToSet”: {“emails” : “joe@gmail.com”}})
//将”$addToSet”和”$each”结合起来,可以添加多个不同的值
>do.users.update({“_id”:ObjectId(“4b2d75476cc613d5ee930164”)},{“$addToSet”:{“emails” :{“$each”: [“1@163.com”,”2@163.com”,”3@163.com”]}}})
从数组中删除元素
有几个从数组中删除元素的方法。若是把数组看成队列或者栈,可以用”$pop”,这个修改器可以从数组任何一端删除元素。{$pop : {key : 1}}从数组末尾删除一个元素,{$pop : {key : -1}}则从头部删除。
删除指定的元素,可以用pull
>db.lists.insert({"todo" :["dishes","laundry","dry cleaning"]})
> db.lists.update({},{"$pull": {"todo" : "laundry"}})
> db.lists.find()
{ "_id" :ObjectId("51ac6c2ac34ebb0597e2e8a9"), "todo" : ["dishes", "dry cleaning" ] }
4) 数组定位修改器
//查询出文档,将文档的comments数组的首元素的votes值加1
>db.blog.update({“post”:post_id},{“$inc”:{“comments.0.votes”:1}})
//将文档中comments数组的子文档的author为John全部更改为Jim
>db.blog.update({“comments.author”:”John”},{“comments.$.author”:{Jim}})
3.3 upsert
upsert是一种特殊的更新。要是没有文档符合更新条件,就会以这个条件和更新文档为基础创建一个新的文档。如果找到了匹配的文档,则正常更新。upsert非常方便,不必预置集合,同一套代码可以既建又更新文档。
update的第三个参数表示这是个upsert,使用upsert可以避免竞态问题,更高效而且是原子性的。
示例:
>db.math.remove()
>db.math.update({"count":25},{"$inc":{"count":3}},true)
>db.math.findOne()
{ "_id": ObjectId("51ad4ebc98dfc1b8e0359a83"), "count" : 28 }
>
save函数
save是一个shell函数,可以在文档不存在时插入,存在时更新,它只有一个参数:文档。要是这个文档含有”_id”键,save会调用upsert。否则,会调用插入。程序员可以非常方便地使用这个函数在shell中快速修改文档。
3.4更新多个文档
默认情况下,更新只能对符合匹配条件的第一个文档执行操作。要是有多个文档符合条件,其余的文档就没有变化。要使所有匹配到的文档都得到更新,可以设置update的第4个参数为true。
今后可能会更改update的行为(服务器可能默认会更新所有匹配的文档,只有第4个参数为false才会只更新一个),所以建议每次都显式表明要不要做多文档更新。
多文档更新对模式迁移非常有用,还可以在对特定用户发布新功能的时候使用。例如,假设要给所有在特定日期过生日的用户一份礼物,就可以使用多文档更新,将”gift”增加到他们的帐号。
>db.users.update({birthday :“10/13/1978”},{$set : {gift:”Happy Birthday!”}},false,true)
想要知道多文档更新到底更新了多少文档,可以运行getLastError命令。键”n”的值就是要的数字。
>db.count.update({x: 1},{$inc : {x : 1}},false,true)
>db.runCommand({getLastError: 1})
{
“err”: null,
“updatedExisting”: true,
“n”: 5,
“ok”: true
}
这里的”n”为5,说明有5个文档被更新了。”updatedExisting”为true,说明是对已有的文档进行更新。
3.5返回已更新的文档
用getLastError仅能获得有限的信息,并不能返回已更新的文档。这个可以通过findAndModify命令来做到。
findAndModify的调用方式和普通的更新略有不同,还有点慢,这是因为它要等待数据库的响应。这对于操作查询及执行其他需要取值和同仁风格的原子性操作来说是十分方便的。
假设我们有一个集合,其中包含以一定顺序运行的进程。其中每个进程都被表示为具有如下形式的文档:
{
“_id”: ObjectId(),
“status”: state,
“priority”: N
}
“states”是一个字符串,可以是”READY”、”RUNNING”或”DONE”。要找到状态为”READY“的具有最高优先级的任务”,运行进程函数,然后更新其状态为”DONE”。将已经就绪的进程按照优先级排序,然后将优先级最高的进程的状态更新为”RUNNINT”。完成了以后,就把状态改为”DONE”。就像下面这样:
>ps = db.processes.find({“status” : “READY”}).sort({“priority”: -1}).limit(1).next()
>db.processes.update({“_id” : ps._id},{“$set” : {“status”: “RUNNINT”}})
>do_something(ps)
>db.processes.update({“_id” : ps._id},{“$set” : {“status”: “DONE”}})
这个算法在多线程下是不安全的,do.something(ps)存在多线程并发的风险。
可以使用下面的方式来处理:
处理方式:
(1) var cursor = db.processes.find({“status”: “READY”}).sort({“priority” : -1}).limit(1);
while ((ps = cursor.next()) != null){
ps.update({“_id”:ps._id,”status”:”READY”},{“$set”:{“status”:”RUNNING”}});
varlastOp = db.runCommand({getlasterror:1});
if(lastOp.n== 1){
do_something(ps);
db.processes.update({“_id”:ps._id},{“$set”:{“status”:”DONE”}});
}
cursor= db.processes.find({“status” : “READY”}).sort({“priority”:-1}).limit(1);
}
(2) findAndModify命令
>ps = db.runCommand({“findAndModify”:”processes”,
“query”: {“status” : “READY”},
“query” : {“priority” : -1},
“update” : {“$set” : {“status”:”RUNNING”}}})
{
“ok”: 1,
“value”: {
“_id”: ObjectId(“--------------”),
“priority”: 1,
“status”: “READY”
}
}
findAndModify命令原子更新并返回修改的记录。默认情况下,返回的文档是修改前的文档。要返回修改后的文档,应该把new选项设置为true。如果value值为null,则表示没有修改文档。
>do_something(ps)
>db.process.update({“_id”:ps._id},{“$set”:{“status”: “DONE”}})
findAndModify的详细使用请参见文档。