MongoDB普通话档的换代操作示例详细解释_MongoDB_脚

作者:计算机专家

前言

MongoDB是文档型数据库,每个文档(doc)表示数据的一项记录。相比关系型DB的row只能使用简单的数据类型,doc能够使用复杂的数据类型:内嵌doc,数组。MongoDB的数组是一系列元素的集合,使用中括号 [] 表示数组,例如:[1,2,3]的元素是整数值,[{name:"t5"}, {name:"t7"}],[ {name:"t5", age:21}, {name:"t7", age:22} ]的元素是doc。

$slice

如果希望数组的最大长度是固定的,那么可以将 $slice 和 $push 组合在一起使用,就可以保证数组不会超出设定好的最大长度。$slice 的值必须是负整数。

假设$slice的值为10,如果$push 后的数组的元素个数小于10,那么所有元素都会保留。反之,只有最后那10个元素会保留。因此,$slice 可以用来在文档中创建一个队列。

db.class.insert({"班级":"1班"})

WriteResult({ "nInserted" : 1 })
> db.class.update(
... {"班级":"1班"},
... {"$push":{"students":{
... "$each":["zs","ls","ww"],
... "$slice":-5}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{
        "_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : "1班",
        "students" : [
                "zs",
                "ls",
                "ww"
        ]
}

> db.class.update(
...  {"班级":"1班"},
...  {"$push":{"students":{
... "$each":["yyb","rhr","www","qqq","eee","rrr"],
... "$slice":-5}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{
        "_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : "1班",
        "students" : [
                "rhr",
                "www",
                "qqq",
                "eee",
                "rrr"
        ]
}
> 

也可以在清理元素之前使用$sort,只要向数组中添加子对象就需清理,先排序后保留指定的个数。

> db.class.update({},{ "班级" : "一班"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>  db.class.update(
...  {"班级":"一班"},
... {"$push":{"students":
...  {"$each":[{"name":"aaa","age":1},{"name":"bbb","age":2},{"name":"ccc","age":3}],
...  "$slice":-2,
...  "$sort":{"age":-1}}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{
        "_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : "一班",
        "students" : [
                {
                        "name" : "bbb",
                        "age" : 2
                },
                {
                        "name" : "aaa",
                        "age" : 1
                }
        ]
}
> 

在MongoDB中,更新单个doc的操作是原子性的。默认情况下,如果一个update操作更新多个doc,那么对每个doc的更新是原子性的,但是对整个update 操作而言,不是原子性的,可能存在前面的doc更新成功,而后面的doc更新失败的情况。由于更新单个doc的操作是原子性的,如果两个更新同时发生,那么一个更新操作会阻塞另外一个,doc的最终结果值是由时间靠后的更新操作决定的。

在MongoDB中,数组元素允许重复,元素的位置是固定的。如果两个数组相等,那么这两个数组的元素和及其位置都相同。

$ne与$addToSet

如果想将数组作为数据集使用,保证数组内的元素不会重复。可以在查询文档中用$ne或者$addToSet来实现。有些情况$ne根本行不通,有些时候更适合用$addToSet。

> db.papers.insert({"authors cited":["yyb"]})
WriteResult({ "nInserted" : 1 })
> db.papers.update({"authors cited":{"$ne":"Richie"}}, {"$push":{"authors cited":"Richie"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.papers.findOne()
{
        "_id" : ObjectId("5854c900e7d717fcb974637e"),
        "authors cited" : [
                "yyb",
                "Richie"
        ]
}
> db.papers.update({"authors cited":{"$ne":"Richie"}}, {"$push":{"authors cited":"Richie"}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
> 

 db.user.findOne()
{
        "_id" : ObjectId("5854cb40e7d717fcb974637f"),
        "username" : "joe",
        "emails" : [
                "joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com"
        ]
}

> db.user.update(
... ... {"username":"joe"},
... ... {"$addToSet":{"emails":"joe@gmail.com"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
>  db.user.update(
... ... ... {"username":"joe"},
... ... ... {"$addToSet":{"emails":"joe@hotmail.com"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{
        "_id" : ObjectId("5854cb40e7d717fcb974637f"),
        "username" : "joe",
        "emails" : [
                "joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com",
                "joe@hotmail.com"
        ]
}
> 

将$addToSet和$each组合起来,可以添加多个不同的值。而用$ne和$push组合就不能实现。

$addToSet与$push的区别:前者添加到数组中时会去重,后者不会。

>db.user.insert({ "username" : "joe"})
>  db.user.update(
... {"username" : "joe"},
... 
... {"$addToSet":
...  {"emails" :{"$each": [
... "joe@example.com",
...  "joe@gmail.com",
... "joe@yahoo.com",
... "joe@hotmail.com",
...  "joe@hotmail.com"]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{
        "_id" : ObjectId("5854ce5ce7d717fcb9746380"),
        "username" : "joe",
        "emails" : [
                "joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com",
                "joe@hotmail.com"
        ]
}
> 

我们在前面的文章中提到过文档的基本的增删改查操作,MongoDB中提供的增删改查的语法非常丰富,不清楚的朋友们可以参考这篇文章://www.jb51.net/article/87697.htm

创建示例collection,使用db.collection.insert()函数和数组参数,一次性向集合中插入3个doc。

从数组中删除元素

那么下面的本文我们主要来看看更新都有哪些好玩的语法。

user1={ name:"t1", age:21}
user2={ name:"t2", age:22}
user3={ name:"t3", age:23}

db.users.insert([user1,user2,user3])

$pop

可以从数组任何一端删除元素。

{“$pop”:{“key”:1}}从数组末尾删除一个元素

{“$pop”:{“key”:-1}}从数组头部删除一个元素

> db.class.findOne()
{
        "_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : "一班",
        "students" : [
                {
                        "name" : "bbb",
                        "age" : 2
                },
                {
                        "name" : "aaa",
                        "age" : 1
                }
        ]
}
> db.class.update(
... {"班级" : "一班"},
... {"$pop":{"students":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{
        "_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : "一班",
        "students" : [
                {
                        "name" : "bbb",
                        "age" : 2
                }
        ]
}
> 

文档替换

一,使用dot标记法(dot notation)访问数组元素

$pull

有时需要基于特定条件来删除,而不仅仅是依据元素位置,这时可以使用$pull。$pull会将所有匹配的文档删除,而不是只删除一个。

> db.list.insert(
... {"todo":["dishes","laundry","dry cleaning"]})
WriteResult({ "nInserted" : 1 })
> db.list.update({},{"$pull":{"todo":"laundry"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.list.findOne()
{
        "_id" : ObjectId("585690afc5b0525a48a441b4"),
        "todo" : [
                "dishes",
                "dry cleaning"
        ]
}
> 

数组操作符只能用于包含数组值的键。例如:不能将一个整数插入数组,也不能将一个字符串从数组中弹出。要修改标量值,使用$set或$inc。

假设我的集合中现在存了如下一段数据:

MongoDB使用 dot 访问数组的元素,或内嵌doc字段。

基于位置的数组修改器

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

{ "_id" : ObjectId("59f005402844ff254a1b68f6"), "name" : "三国演义", "authorName" : "罗贯中", "authorGender" : "男", "authorAge" : 99.0}

MongoDB uses the dot notation to access the elements of an array and to access the fields of an embedded document.

位置

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

> db.blog.insert(
... {
...     "content": "...",
...     "comments": [
...         {
...             "comment": "good post",
...             "author": "john",
...             "votes": 0
...         },
...         {
...             "comment": "i thought it was too short",
...             "author": "claire",
...             "votes": 3
...         },
...         {
...             "comment": "free watches",
...             "author": "alice",
...             "votes": -1
...         }
...     ]
... })
WriteResult({ "nInserted" : 1 })

> db.blog.update({"content":"..."},{"$inc":{"comments.0.votes":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{
        "_id" : ObjectId("585694e4c5b0525a48a441b5"),
        "content" : "...",
        "comments" : [
                {
                        "comment" : "good post",
                        "author" : "john",
                        "votes" : 1
                },
                {
                        "comment" : "i thought it was too short",
                        "author" : "claire",
                        "votes" : 3
                },
                {
                        "comment" : "free watches",
                        "author" : "alice",
                        "votes" : -1
                }
        ]
}
>

这是一本书,有书名和作者信息,但是作者是一个独立的实体,所以我想将之提取出来,变成下面这样:

users=
{
name:"t1",
age:21,
address: {phone:123,email:"xxx@163.com"},
followers:[{name:"a"},{name:"b"},{name:"c"}]
}

定位操作符$

如果无法知道要修改的数组的下标,可以使用定位操作符$,用来定位查询文档已经匹配的元素,并进行更新。

> db.blog.update(
... {"comments.author":"john"},
... {"$set":{"comments.$.author":"jim"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{
        "_id" : ObjectId("585694e4c5b0525a48a441b5"),
        "content" : "...",
        "comments" : [
                {
                        "comment" : "good post",
                        "author" : "jim",
                        "votes" : 1
                },
                {
                        "comment" : "i thought it was too short",
                        "author" : "claire",
                        "votes" : 3
                },
                {
                        "comment" : "free watches",
                        "author" : "alice",
                        "votes" : -1
                }
        ]
}
> 
{ "_id" : ObjectId("59f005402844ff254a1b68f6"), "name" : "三国演义", "author" : { "name" : "罗贯中", "gender" : "男", "age" : 99.0 }}

address 字段是内嵌文档,查找phone是123的doc

upsert

upsert是update()的第三个参数。表示没有则创建,有则正常更新。

> db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("58569d3cb6687ca8dfad4e01")
})
> db.analytics.findOne()
{
        "_id" : ObjectId("58569d3cb6687ca8dfad4e01"),
        "url" : "/blog",
        "pageviews" : 1
}
> db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>  db.analytics.findOne()
{
        "_id" : ObjectId("58569d3cb6687ca8dfad4e01"),
        "url" : "/blog",
        "pageviews" : 2
}
> 

另外一个问题是更新时,MongoDB只会匹配第一个更新的文档,假设我的MongoDB中有如下数据:

db.users.find({"addr.phone":123})

$setOnInsert

在创建文档的同时创建字段并为它赋值,但是在之后的所有更新操作中在,这个字段的值都不在改变。

$setOnInsert只会在文档插入时设置字段的值。

在预置或者初始化计数器时,或者对于不使用ObjectIds的集合来说,“$setOnInsert”是非常有用的。

> db.user.update({},{"$setOnInsert":{"createAt":new Date()}},true)
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("58569fe1b6687ca8dfad4e02")
})
> db.user.findOne()
{
        "_id" : ObjectId("58569fe1b6687ca8dfad4e02"),
        "createAt" : ISODate("2016-12-18T14:40:33.273Z")
}
> db.user.update({},{"$setOnInsert":{"createAt":new Date()}},true)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.user.findOne()
{
        "_id" : ObjectId("58569fe1b6687ca8dfad4e02"),
        "createAt" : ISODate("2016-12-18T14:40:33.273Z")
}
> 
{ "_id" : ObjectId("59f00d4a2844ff254a1b68f7"), "x" : 1 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68f8"), "x" : 1 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68f9"), "x" : 1 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68fa"), "x" : 2 }

followers 字段是数组,查询followers中存在name=“b”的doc

save

一个shell函数,不存在创建,反之则更新文档。

它只有一个参数:文档。要是这个文档含有“_id”键,save会调用upsert。否则会调用insert。在shell中快速对文档进行修改。

> db.user.save({"x":10,"y":20})
WriteResult({ "nInserted" : 1 })
> var x=db.user.findOne()
> x.num=43
43
> db.user.save(x)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{
        "_id" : ObjectId("5856a230c5b0525a48a441be"),
        "x" : 10,
        "y" : 20,
        "num" : 43
}

我想把所有x为1的数据改为99,我们很容易想到如下命令:

db.users.find({"followers.name":"b"})

更新多个文档

默认情况下,只会更新匹配的第一个文档。要更新多个文档,需要将update的第4个参数设置为true。

想要知道多文档更新到底更新了多少文档,可以运行getLastError命令。键n的值就是被更新文档的数量。

> db.coll.find()
{ "_id" : ObjectId("5856994bc5b0525a48a441b9"), "x" : "a" }
{ "_id" : ObjectId("58569966c5b0525a48a441ba"), "x" : "bbb" }
{ "_id" : ObjectId("5856996fc5b0525a48a441bb"), "x" : "c" }
> db.coll.update({},{"$set":{"x":10}},false,true )
WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 3 })
> db.runCommand({getLastError:1})
{
        "connectionId" : 1,
        "updatedExisting" : true,
        "n" : 3,
        "syncMillis" : 0,
        "writtenTo" : null,
        "err" : null,
        "ok" : 1
}
> 
db.sang_collect.update

二,修改字段的值

返回被更新的文档

通过findAndModify命令得到被更新的文档。返回的是修改之前的文档。

对于操作队列以及执行其他需要进行原子性取值和赋值的操作非常方便。

 ps=db.runCommand({"findAndModify":"processes", "query":{"status":"ready"}, 
"sort":{"pirority":-1}, 
"update":{"$set":{"status":"Running"}}})
{
        "lastErrorObject" : {
                "updatedExisting" : true,
                "n" : 1
        },
        "value" : {
                "_id" : ObjectId("5857c939d7a32a888bd79c47"),
                "pirority" : 2,
                "status" : "ready"
        },
        "ok" : 1
}

> db.processes.findOne("_id":ps.value._id)
2016-12-19T19:57:04.904+0800 E QUERY    [thread1] SyntaxError: missing ) after argument list @(shell):1:26

> db.processes.findOne({"_id":ps.value._id})
{
        "_id" : ObjectId("5857c939d7a32a888bd79c47"),
        "pirority" : 2,
        "status" : "Running"
}
> 

findAndModify可以使用update键也可以使用remove键。Remove键表示将匹配的文档从集合里面删除。

> ps=db.runCommand({"findAndModify":"processes", "query":{"status":"ready"}, 
"sort":{"priority":-1}, 
"remove":true}).value 
{
        "_id" : ObjectId("5857c9b1d7a32a888bd79c48"),
        "pirority" : 2,
        "status" : "ready"
}

>  db.processes.findOne({"_id":ps._id})
null

 

但我们发现执行结果却是这样:

在MongoDB中,修改操作主要使用两个修改器:$set 和 $inc,这两个修改器具有upsert特性:如果doc中存在相应的字段,那么修改该字段的值;如果doc中不存在相应的字段,那么在doc中创建新的字段。$inc用于整数型字段,增加或减少字段的值,$set用于任意数据类型,替换字段的值。

{ "_id" : ObjectId("59f00d4a2844ff254a1b68f7"), "x" : 99 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68f8"), "x" : 1 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68f9"), "x" : 1 }{ "_id" : ObjectId("59f00d4a2844ff254a1b68fa"), "x" : 2 }

1,使用$set修改器,增加followers数组,使用empty filter:{},表示更新集合中的所有doc

即只有第一条匹配的结果被更新了,其他的都没有变化。这是MongoDB的更新规则,即只更新第一条匹配结果。如果我们想将所有x为1的更新为x为99,可以采用如下命令:

db.users.updateMany(
{},
{$set:
   {
    followers:[ {name:"t5"},{name:"t7"} ]
   }
}
)
db.sang_collect.update({x:1},{$set:{x:99}},false,true)

图片 1

首先我们将要修改的数据赋值给$set,$set是一个修改器,我们将在下文详细讲解,然后后面多了两个参数,第一个false表示如果不存在update记录,是否将我们要更新的文档作为一个新文档插入,true表示插入,false表示不插入,默认为false,第二个true表示是否更新全部查到的文档,false表示只更新第一条记录,true表示更新所有查到的文档。

2,使用$inc修改器,增加age字段的值,每个doc的age字段的都增加1

使用修改器

db.users.updateMany(
{},
{$inc:{age:1}}
)

很多时候我们修改文档,只是要修改文章的某一部分,而不是全部,但是现在我面临这样一个问题,假设我有如下一个文档:

图片 2

{x:1,y:2,z:3}

3,$set 和 $inc的不同之处在于:$set是替换现有字段的值,而$inc是在现有字段值的基础上,增加或减少指定的数值

我现在想把这个文档中x的值改为99,我可能使用如下操作:

示例,使用$set修改age的值,更新结束后,每个doc的age字段都是1

db.sang_collect.update
db.users.updateMany(
{},
{$set:{age:1}}
)

但是更新结果却变成了这样:

三,修改数组

{ "_id" : ObjectId("59f02dce95769f660c09955b"), "x" : 99 }

如果要向数组中增加或删除一个元素,$set和$inc 都不能很好的满足这种需求,MongoDB有专用的 Array Operator,用于修改数组字段。

MongoDB帮我把整个文档更新了!要解决这个问题,我们可以使用修改器。

1,使用$push向doc中增加数组,或插入新的元素

$set修改器

$push:如果doc中存在相应的数组字段,那么向数组的尾部插入一个元素;如果doc中不存在相应的数组字段,那么向doc中创建一个数组字段,并初始化。

$set可以用来修改一个字段的值,如果这个字段不存在,则创建它。如下:

示例,第一次调用$push,由于doc中不存在comments字段,因此MongoDB向doc中新建comments 数组字段,并初始化数组

如果该字段不存在,则创建,如下:

db.users.updateMany(
{},
{$push:{comments:{msg:"c1"}}}
)

也可以利用$unset删除一个字段,如下:

示例,后续再次调用$push,向已有的数组字段的尾部追加一个元素

$set也可以用来修改内嵌文档,还以刚才的书为例,如下:

db.users.updateMany(
{},
{$push:{comments:{msg:"c2"}}}
)
{ "_id" : ObjectId("59f042cfcafd355da9486008"), "name" : "三国演义", "author" : { "name" : "罗贯中", "gender" : "男" }}

图示,左边是数组的第一个元素,右边是数组的最后一个元素,使用$push每次向数组的尾部追加一个元素

想要修改作者的名字,操作如下:

图片 3

db.sang_collect.update({name:"三国演义"},{$set:{"author.name":"明代罗贯中"}})

{ "_id" : ObjectId("59f042cfcafd355da9486008"), "name" : "三国演义", "author" : { "name" : "明代罗贯中", "gender" : "男" }}

2,向数组字段插入多个元素

$inc修改器

$push 修改器每次只能向数组字段的尾部插入一个元素,搭配使用$each 修改器,每次能向数组字段中插入多个元素

$inc用来增加已有键的值,如果该键不存在就新创建一个。比如我想给上文的罗贯中增加一个年龄为99,方式如下:

db.users.updateMany(
{},
{$push:
   {
     comments:{ $each:[ {msg:"c3"}, {msg:"c4"} ] }
   }
}
)
db.sang_collect.update({name:"三国演义"},{$inc:{"author.age":99}})

{ "_id" : ObjectId("59f042cfcafd355da9486008"), "name" : "三国演义", "author" : { "name" : "明代罗贯中", "gender" : "男", "age" : 99.0 }}

图示,使用$each,一次将多个元素插入到数组的尾部

加入我想给罗贯中增加1岁,执行如下命令:

图片 4

db.sang_collect.update({name:"三国演义"},{$inc:{"author.age":1}})

3,从数组字段的特定位置处开始插入元素

这是会在现有值上加1,结果如下:

使用$push 修改器只能将元素插入到数组字段的尾部,搭配使用$position 修改器,能够指定元素插入的开始位置,$position 必须和$each搭配使用。数组的下标是从0开始的。

{ "_id" : ObjectId("59f042cfcafd355da9486008"), "name" : "三国演义", "author" : { "name" : "明代罗贯中", "gender" : "男", "age" : 100.0 }}
db.users.updateMany
(
{},
{$push:
    {comments:
        {
           $each:[ { msg:"c5"}, {msg:"c6"} ],
           $position:2
        }
    }
}
)

注意$inc只能用来操作数字,不能用来操作null、布尔等。

图示,使用$position 指定元素插入的开始位置,将c5,c6 依次插入都数组的2,3位置处,由于数组的小标是从0开始的,c5,c6 是数组的第3,4个元素图片 5

数组修改器

如果不使用$position 修改器,那么 $push 每次都向数组的末尾写入元素;使用$position 修改器,指定$push插入元素的开始位置。

数组修改器有好几种,我们分别来看。

$position :The $position modifier specifies the location in the array at which the $push operator insert elements. Without the $position modifier, the $push operator inserts elements to the end of the array.

$push可以向已有数组末尾追加元素,要是不存在就创建一个数组,还是以我们的上面的book为例,假设book有一个字段为comments,是一个数组,表示对这个book的评论,我们可以使用如下命令添加一条评论:

4,限制数组中元素的数量

db.sang_collect.update({name:"三国演义"},{$push:{comments:"好书666"}})

在$push 元素时,使用$slice=MaxNum限制数组元素的最大数量。只要没有达到最大数量,就会向数组中插入新的元素,直到达到最大值。$slice必须和$each搭配使用,如果数组字段的元素的数量已经达到最大值,根据MaxNum值的不同,会有不同的行为:

此时不存在comments字段,系统会自动帮我们创建该字段,结果如下:

  • 如果MaxNum=0,表示将数组清空;
  • 如果MaxNum是正整数,表示数组只保留前面的MaxNum个元素;
  • 如果MaxNum是负整数,表示数组只保留后面的MaxNum个元素;
{ "_id" : ObjectId("59f042cfcafd355da9486008"), "name" : "三国演义", "author" : { "name" : "明代罗贯中", "gender" : "男", "age" : 100.0 }, "comments" : [ "好书666" ]}

示例,保留每个comments的最后5个元素

本文由杏彩发布,转载请注明来源

关键词: