MongoDB的创建、更新、删除文档

一、MongoDB 基础知识

1. 文档

文档是 MongoDB 的核心概念。文档就是键值对的一个有序集。每种编程语言表示文档的方法不大一样,但大多数编程语言都有一些相通的数据结构,比如映射(map)、散列(hash)或字典(dictionary)。例如,在 JavaScript 里面,文档被表示为对象:

{"greeting" : "Hello, world!"}

这个文档只有一个键 “greeting”,其对应的值为 “Hello, world!”。大多数文档会比这个简单的例子复杂得多,通常会包含多个键/值对:

{"greeting" : "Hello, world!", "foo" : 3}

从上面的例子可以看出,文档中的值可以是多种不同的数据类型(甚至可以是一个完整的内嵌文档)。

MongoDB 不但区分类型,而且区分大小写。例如,下面的两个文档是不同的:

{"foo" : 3}
{"foo" : "3"}

下面两个文档也是不同的:

{"foo" : 3}
{"Foo" : 3}

还有一个非常重要的事项需要注意, MongoDB 的文档不能有重复的键。例如,下面的文档是非法的:

{"greeting" : "Hello, world!", "greeting" : "Hello, MongoDB!"}

2. 集合

集合就是一组文档。如果将 MongoDB 中的一个文档比喻为关系型数据库中的一行,那么一个集合就相当于一张表。

集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。例如,下面两个文档可以存储在同一个集合里面:

{"greeting" : "Hello, world!"}
{"foo" : 5}

需要注意的是,上面的文档不光值的类型不同(一个是字符串,一个是整数),他们的键也完全不同。因为集合李阿敏可以放置任何文档。

3. 数据库

在 MongoDB 中,多个文档组成集合,而多个集合可以组成数据库

另外,有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。这些数据库如下所示。

  • admin。从身份验证的角度来讲,这是“root” 数据库。如果将一个用户添加到 admin 数据库,这个用户将自动获得所有数据库的权限。再者,一些特定的服务器端命令也只能从 admin 数据库运行,如列出所有数据库或关闭服务器。
  • local。这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。
  • config。MongoDB 用于分片设置,分片信息会存储在 config 数据库中。

二、创建、更新和删除文档

1. 插入

1.1 命令

insert 和 insertMany。

1.2 举例

单条数据插入

> db.blog.insert({"author":"tian","title":"my first mongodb blog"})
WriteResult({ "nInserted" : 1 })
>

查询结果:

> db.blog.find()
{ "_id" : ObjectId("500bb4b44daafbf976598437"), "author" : "tian", "title" : "my first mongodb blog" }

数据批量插入

> var res = db.collection.insertMany([{"b": 3}, {'c': 4}])
> res
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("571a22a911a82a1d94c02337"),
                ObjectId("571a22a911a82a1d94c02338")
        ]
}
1.3 说明

1)当我们要插入的集合(这里是blog)不存在时,mongodb会在第一次插入时自动创建一个;

2)插入的每一条文档,除了我们制定的键(这里有2个键,author和title),还会自动增加一个_id键,相当于关系型数据库的主键,如果我们没有指定的话。

该键对于一个集合必须是唯一的,它可以使任意类型,默认是ObjectId对象。

由于mongodb一开始设计就是用来作为分布式数据库的,因此没有采用自增长的方式来创建_id键,因为在不同的服务器上同步自增长主键费时又费力。

关于ObjectId,更多可以参考:http://www.mongodb.org/display/DOCS/Object+IDs

当然我们也可以自己指定:

> db.blog.insert({"_id":2012,"author":"tian","title":"my 2nd mongodb blog"})
> db.blog.find()
{ "_id" : ObjectId("500bb4b44daafbf976598437"), "author" : "tian", "title" : "my first mongodb blog" }
{ "_id" : 2012, "author" : "tian", "title" : "my 2nd mongodb blog" }

注意:findOne 也可以用来查询文档,只不过 find 查询的是所有的数据,返回的是一个数组对象; findOne 查出来的是查到的第一个对象

1.4 插入原理

当我们将数据插入到mongodb数据库时,数据会被转换成BSON的形式(BSON是mongodb存储数据的形式,类似JSON的,是轻量的二进制格式,能将mongodb的所有文档表示为字节字符串,数据库能理解BSON,存在磁盘上的文档也是BSON格式,更多请参考: http://www.bsonspec.org/),然后存入数据库。

在这个阶段,mongodb只检查2件事:

  • 1)是否包含_id键;
  • 2)文档是否超过16M,注意,这里的大小是转成BSON格式以后的大小,可以通过Object.bsonsize(your-object)查看大小;(以mongodb 1.8为准)

只要这两点满足,就会将文档原本的存入到数据库

这样做有好处也有副作用,副作用就是可以插入无效的数据,好处就是可以使数据库更安全,远离注入式攻击,因为插入不执行代码。

2. 删除

2.1 命令

remove

2.2 举例:
> db.blog.remove({"title":"my first mongodb blog"})
WriteResult({ "nRemoved" : 1 })
> db.blog.remove()
WriteResult({ "nRemoved" : 2 })
2.3 说明:
    1. 在上面的2个例子中,第一个例子的remove接受一个参数,用于限定要删除的文档,第二个例子没有指定参数,注意,这种操作是很危险的,因为它将删除整个集合里的文档,但是集合以及索引会被保留,第二个例子等价于db.blog.remove({})
    1. 删除是永久性的,不能撤销,也不能恢复
    1. 删除文档的速度相当快,如果要删除整个集合里的文档,可以采用db.your-collection.drop(),然后重建集合和索引,该方法速度非常快,唯一的缺点是整个集合都被删除了,包括索引
    1. 根据_id键来删除文档的效率是最高的
    1. 考虑一种比较极端,或者高并发可能发生的情况,当你要删除一个集合里的文档时,刚好有另一个进程在update其中的文档,在这种情况下,正被update的文档是不会被删除的,如果这并不是你想要的,可以通过制定参数KaTeX parse error: Expected '}', got 'EOF' at end of input: …author":"tian",atomic:true}),当然,这样做也是有副作用的,就是当我们执行remove操作的时候,将阻止其他操作。

3. 更新

3.1 命令

update

3.2 举例
> var mypost = db.blog.findOne({"_id":ObjectId("500bc4304daafbf976598439")})
> mypost.author="tian.chen"
tian.chen
> mypost
{
        "_id" : ObjectId("500bc4304daafbf976598439"),
        "author" : "tian.chen",
        "title" : "my first mongodb blog"
}
> db.blog.update({"_id":ObjectId("500bc4304daafbf976598439")},mypost)
> db.blog.findOne({"_id":ObjectId("500bc4304daafbf976598439")})
{
        "_id" : ObjectId("500bc4304daafbf976598439"),
        "author" : "tian.chen",
        "title" : "my first mongodb blog"
}
3.3 说明
  • 1)我们首先通过findOne来获取一个文档,并赋值给mypost,然后,修改mypost的author键,最后再通过db.blog.update更新文档
    1. 更新时匹配多个文档,更新的时候,由于第二个参数的存在就会产生重复的_id键,就会报错。怎么说呢,举个例子
> db.blog.find()
{ "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title": "my first mongodb blog", "age" : 28 }
{ "_id" : 2012, "author" : "tian.chen", "title" : "my 2nd mongodb blog", "age" : 6 }
> var tianpost=db.blog.findOne({"author":"tian.chen","age":6})
> tianpost
{
        "_id" : 2012,
        "author" : "tian.chen",
        "title" : "my 2nd mongodb blog",
        "age" : 6
}
> tianpost.age=26
26
> db.blog.update({"author":"tian.chen"},tianpost)
cannot change _id of a document old:{ _id: ObjectId('500bc4304daafbf976598439'),
 author: "tian.chen", title: "my first mongodb blog", age: 28.0 } new:{ _id: 201
2.0, author: "tian.chen", title: "my 2nd mongodb blog", age: 26.0 }

在这个例子中,我们的post集合里包含2个文档,我们要将第二个文档的age改成26,首先通过author和age获取一个文档并赋值给tianpost,在这里,tianpost也是具有_id键的,其值为2012,然后,我们将tianpost的age改成26后,然后通过author=tian.chen查找文档,将其update为tianpost,问题就出现在这里,通过author=tian.chen查找文档时,首先找到第一个文档,其_id为 ObjectId(‘500bc4304daafbf976598439’),而tianpost._id=2012,我们知道_id是不能修改的,结果就报错了!

为了避免这种情况,最好确保更新总是指定唯一文档。

3.4 使用修改器

在mongodb中通常文档只会有一部分要更新,利用原子的更新修改器,可以做到只更新文档的一部分键值,而且更新极为高效,更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整、增加、或者删除键,还可以操作数组和内嵌文档。增加、修改或删除键的时候,应该使用$修改器。

要把"foo"的值设备"bar",常见的错误做法如下:

db.coll.update(criteria,{"foo":"bar"})

这种情况是不对的,实际上这种做法会把整个文档用{“foo”:“bar”}替换掉,一定要使用以$开头的修改器来修改键/值对

假设有这样一个文档:

{
"_id" : 2012,
"age" : 26,
"author" : "tian.chen",
"pageviews" : 1,
"title" : "my 2nd mongodb blog"
}

其中的键pageviews表示该post被阅读的次数,每阅读一次就增加1,这时,我们可以使用$inc修改器

> db.blog.update({"_id":2012},{"$inc":{"pageviews":1}})
3.4.1 $set修改器

$set修改器用来指定一个键的值,如果这关键不存在就创建它,这对于修改来说是很方便的,因为我们通常修改的只是极个别的键的值的。

假设我们要修改下面文档的age:

{
"_id" : 2012,
"age" : 26,
"author" : "tian.chen",
"pageviews" : 1,
"title" : "my 2nd mongodb blog"
}

如果我们只是简单的用db.blog.update({"_id":2012},{“age”:28}),那么,将会用{“age”:28}替换掉整个文档,这个时候,$set修改器就很有用处了:

db.blog.update({"_id":2012},{"$set":{"age":28}})

当然,也可以修改多个:

db.blog.update({"_id":2012},{"$set":{"age":28,"author":"tian"}})

s e t 可 以 修 改 键 的 值 , 如 果 键 不 存 在 就 创 建 它 , 与 之 对 应 的 , 用 set可以修改键的值,如果键不存在就创建它,与之对应的,用 setunset可以删除键,如:

db.blog.update({"_id":2012},{"$unset":{"pageviews":1}})

这样,就会将文档中的pageviews键删除掉

3.4.2 $inc修改器

正如我们在上面看到的,$inc修改器用来增加键的值.

需要注意的是 i n c 只 能 应 用 于 整 数 、 长 整 数 、 双 精 度 浮 点 数 , 同 时 inc只能应用于整数、长整数、双精度浮点数,同时 incinc的键的值必须为数字,如果要修改其他类型,应使用上面的$set

3.4.3 数组修改器 $push & $pop

如果指定的键存在,$push就会向已有的数据末尾加入一个元素,如果没有,则会创建一个新的数组

如:

>db.blog.update({"_id":2012},{$push:{"comments":{"name":"jake","content":"nice post"}}})

查出来的结果为:

> db.blog.find()
{ "_id" : ObjectId("500bc4304daafbf976598439"), "author" : "tian.chen", "title": "my first mongodb blog", "age" : 28 }
{ "_id" : 2012, "age" : 28, "author" : "tian", "comments" : [ { "name" : "jake", "content" : "nice post" } ], "title" : "my 2nd mongodb blog" }

有时候,我们可能会希望,如果一个值在数组中不存在,就添加进去,可以用如下方式来实现,即通过$ne来实现:

> db.papers.insert({"title":"mongodb post","authors":["tian"]})

> db.papers.update({"authors":{"$ne":"harry"}},{$push:{"authors":"harry"}})

{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry" ], "title" : "mongodb post" }

也可用 a d d T o S e t 来 实 现 , 因 为 addToSet来实现,因为 addToSetne并不总是可行:

> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {"$addToSet":{"authors":"jerry"}})

>{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "jerry" ], "title" : "mongodb post" }

a d d T o S e t 还 有 一 个 妙 处 , 通 过 与 addToSet还有一个妙处,通过与 addToSeteach结合,可以插入多个值,当然,如果值已经在数组中,就不会被添加进去:

> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {"$addToSet":
... {"authors":{"$each":["tian","jerry","mike"]}}})


{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "
jerry", "mike" ], "title" : "mongodb post" }

“tian”,"jerry"已经存在,因此没有重复添加,"mike"不存在,被添加进来了。

$slice

还可以在添加数组时限制长度,可以使用 $slice 这样可以得到一个最多包含N个元素的数组

> db.blog.posts.update({"title": "A Blog Post"}, {"$push": {"comment": {"$each": [{"1": "a"},{"2": "b"},{"3": "c"},{"7": "d"},{"6": "f"}], "$slice": -2}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.blog.posts.find()
{ "_id" : ObjectId("5ccd977c7dd247457f95a421"), "title" : "A Blog Post", "content" : "...", "author" : { "name" : "joe", "email" : "joe@example.com" }, "comment" : [ { "7" : "d" }, { "6" : "f" } ] }

> db.blog.posts.update({"title": "A Blog Post"}, {"$push": {"comment": {"$each": [{"1": "a"},{"2": "b"},{"3": "c"},{"7": "d"},{"6": "f"}], "$slice": -10}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.blog.posts.find()
{ "_id" : ObjectId("5ccd977c7dd247457f95a421"), "title" : "A Blog Post", "content" : "...", "author" : { "name" : "joe", "email" : "joe@example.com" }, "comment" : [ { "7" : "d" }, { "6" : "f" }, { "1" : "a" }, { "2" : "b" }, { "3" : "c" }, { "7" : "d" }, { "6" : "f" } ] }

可以看到当 s l i c e 为 − 2 时 会 添 加 最 后 两 个 值 并 且 覆 盖 原 来 的 值 , slice为-2时会添加 最后两个值并且覆盖原来的值, slice2slice的值必须是负整数。

$sort

db.blog.posts.update({"title": "A Blog Post"}, {"$push": {"comment": {
              "$each": [{"1": "a", "rating": 6.6},{"2": "b", "rating": 7.9},{"3": "c", "rating": 9.0}], 
              "$slice": -10, 
              "$sort": {"rating": -1}}}})

这样会根据"rating"字段的值对数组中所有的对象进行排序, s l i c e " , " slice"," slice","sort"必须与”$push"配合使用

删除数组的元素可以用 p o p 或 pop或 poppull

KaTeX parse error: Expected '}', got 'EOF' at end of input: …要用于删除数组头部或尾部的值{pop:{key:-1}}和{$pop:{key:1}}

> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "jerry", "mike" ], "title" : "mongodb post" }
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {$pop:{"authors":1}}
... )
> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "tian", "harry", "jerry" ], "title" : "mongodb post" }
> db.papers.update({"_id": ObjectId("500c0098886e42d4a1a1bff5")},
... {$pop:{"authors":-1}})
> db.papers.find()
{ "_id" : ObjectId("500c0098886e42d4a1a1bff5"), "authors" : [ "harry", "jerry" ], "title" : "mongodb post" }

可以发现{KaTeX parse error: Expected 'EOF', got '}' at position 13: pop:{key:-1}}̲删除头部,而{pop:{key:1}}删除尾部的。

更经常的,我们希望通过值来判断,这时候就可以用KaTeX parse error: Expected '}', got 'EOF' at end of input: pull,{"pull":{key:value}}

3.4.4 数组的定位修改器 $

若是数组有多个值,而我们只想对其中一部分进行操作,有两种方法可以实现这种操作。

两种方法操作数组中的值:通过位置或定位操作符("$")

数组都是以0开头的,可以将下标直接作为键来选择元素。

> db.blog.findOne()
{
        "_id" : ObjectId("57709da84f533aa7535d46d3"),
        "title" : "a blog post",
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "nice post"
                },
                {
                        "name" : "bob",
                        "email" : "bob@example.com",
                        "content" : "good post"
                }
        ]
}
> db.blog.update({"title":"a blog post"},{$set:{"comments.1.name":"livan"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{
        "_id" : ObjectId("57709da84f533aa7535d46d3"),
        "title" : "a blog post",
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "nice post"
                },
                {
                        "name" : "livan",
                        "email" : "bob@example.com",
                        "content" : "good post"
                }
        ]
}

在很多情况下,不预先查询文档就不能知道要修改数组的下标,为了克服这种困难,mongodb提供了定位操作符"$",用来定位查询文档已经匹配的元素,并进行更新,定位符只更新第一个匹配的元素。

例如:用户john把名字改成了jim,就可以用定位符来替换评论中的名字:

db.blog.update({"comments.author":"john"},{$set:{"comments.$.author:"john"}})

可以理解为{“comments.author”:“john”}查询条件定位到第一个元素,就执行{KaTeX parse error: Expected '}', got 'EOF' at end of input: set:{"comments..author:“john”}},"$"定位符就表示找到的第一个元素

> db.blog.findOne()
{
        "_id" : ObjectId("57709da84f533aa7535d46d3"),
        "title" : "a blog post",
        "comments" : [
                {
                        "name" : "joe",
                        "email" : "joe@example.com",
                        "content" : "nice post"
                },
                {
                        "name" : "livan",
                        "email" : "livan@example.com",
                        "content" : "good post"
                }
        ]
}
> db.blog.update({"comments.name":"livan"},{$set:{"comments.$.email":"bob@example.com"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
3.5 upsert更新

upsert是一种特殊的更新,要是没有符合更新条件的文档,就会以这个条件和文档为基础创建一份新的文档。如果有匹配的文档,则正常更新。

假设我们有一个集合analytics,用来记录每个url的访问次数,每访问一次就给pageviews键加1,正常情况,我们需要判断当前访问的url有没有存在,如果没有,则添加,有则更新。

采用upsert,我们可以有更优雅的写法:

db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)

在这里,我们通过给update传递第三个参数表示upsert

3.6 save函数

save跟upsert有点类似,也是不存在时插入,存在时更新。不同的是save只有一个参数。看下面的例子:

save方法有更新和插入两种功能,到底是插入还是更新文档取决于save的参数。那么到底是依赖于哪个参数呢?继续看

If the document does not contain an _id field, then the save() method calls the insert() method. During the operation, the mongo shell will create an ObjectId and assign it to the _id field.

可以看到决定是插入一个文档还是更新,取决于_id参数。如果能根据_id找到一个已经存在的文档,那么就更新。如果没有传入_id参数或者找不到存在的文档,那么就插入一个新文档。

举一个官方的例子

不带_id参数

db.products.save( { item: "book", qty: 40 } )

结果

{ "_id" : ObjectId("50691737d386d8fadbd6b01d"), "item" : "book", "qty" : 40 }

MongoDb客户端驱动会自动为你生成一个默认ObjectId作为_id。

带_id参数,但是找不到一个已经存在的文档

db.products.save( { _id: 100, item: "water", qty: 30 } )

结果

{ "_id" : 100, "item" : "water", "qty" : 30 }

还是插入一个新文档,但是_id不会自动生成。

带_id参数,但是有存在的文档

db.products.save( { _id : 100, item : "juice" } )

结果

{ "_id" : 100, "item" : "juice" }

更新了文档

3.7 更新多个文档

在前面的例子中,我们都是只更新一个文档,mongodb目前也是默认只更新一个文档。我们可以通过对update指定第四个参数,来更新多个文档

mongodb_batch_update

在上面的例子中,我们对比了没传第四个参数和传第四个参数进行更新的区别,传第四个参数后,符合条件的都会更新。

更新完成后,可以通过db.runCommand({getLastError:1})来获取更新了多少文档。

本文转自

  1. (4)详解MongoDB的创建、更新、删除文档

  2. MongoDB 创建,更新和删除文档

  3. MongoDB save()方法和insert()方法的区别

  4. mongodb的修改器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值