MongoDB--修改和查询

来源:互联网 发布:山西省建筑业网络快报 编辑:程序博客网 时间:2024/04/29 06:39

为什么要介绍这些

MongoDB作为一个通用性数据库,从基本概念和一些操作的具体实现上跟MySQL等关系型数据库不太一样。这导致MongoDB有许多特性的语法。本文主要从MongoDB的修改,查询以两个方面去看这些特性。

MongoDB Shell

为什么要介绍MongoDB Shell?因为MongoDB Shell是我们学习MongoDB至关重要的工具。MongoDB Shell是一个JavaScript shell,可以在shell中使用命令行与MongoDB实例交互。
当安装好MongoDB之后,通过命令

mongod --config /path/mongod.conf

启动MongoDB实例。然后通过命令

mongo

去链接本地的MongoDB实例,一般会得到输出结果

MongoDB shell version:2.4.0connection to: test>

第一行表示了MongoDB的版本,第二行表示连接到的MongoDB库,默认就是test库。在第三行就能输入需要使用的命令了。
既然是一个JavaScript shell,那么它能够执行一些JavaScript的程序(这不是本章主要讨论的内容),更重要的是,它是一个连接MongoDB的客户端。通过db命令可以查看当前的库是什么,use {dbname}能够切换库,show dbs可以查看所有的库,show collections可以查看库下所有的集合。

MongoDB的修改

上一篇文章《MongoDB基础知识》中简单的介绍过插入文档,更新文档,删除文档的操作。插入删除等都比较简单,MongoDB的更新语法比较多样化。

1.第一种方法适用于大量的文档修改,语法如下

db.{collectionName}.update({criteria},{document})

criteria指的是查询条件的文档,后面的document是需要条换的文档。如

>var joe = db.users.findOne({"name":"joe"});>joe.age = 20;>...//一堆修改>db.users.update({"name":"joe"}, joe);

通过语法可以将name为joe的文档替换成新的joe文档。
2.$set修改器
如果习惯MySQL的语法,那么对于修改都是set后面接上一串fieldName=value。MongoDB其实也有类似的功能,它就是$set。下面是$set的用法

> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")},{"$set" : {"favorite book" : "War and Peace"}})

这段语句就是将_id为ObjectId(“4b253b067525f35f94b60a31”)的文档中的key为favorite book的值改成War and Peace。因为MongoDB的文档很灵活,$set是可以改变key的类型,如原来favorite book的值是字符串,但是它也能改成数组

> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")},{"$set" : {"favorite book" : ["War and Peace", "Cat's Cradle",...]}})

与MySQL不同的是,如果MySQL中需要删除一个字段的值,只能将字段的value设置成null,空字符串或者0之类的数值。但是MongoDB是通过另外一个操作$unset

> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")},{"$unset" : {"favorite book" : 1}})

这个操作其实是将这个键给删除了,即再次查询文档,就没有键favorite boo了。当然,还可以通过$set又将它加上。
最后需要说明的是,$set在级联文档中的作用是一样的

db.blog.posts.update({"author.name" : "joe"},{"$set" : {"author.name" : "joe schmoe"}})

这样就会把blog.posts集合的内嵌文档author中的name属性改掉。
3.增加和减少
MongoDB中对value为数值的键的操作是通过修改器$inc,如

>db.games.insert({"game" : "pinball", "user" : "joe"})>db.games.update({"game" : "pinball", "user" : "joe"},{"$inc" : {"score" : 50}})>db.games.findOne(){    "_id" : ObjectId(...),    "game" : "pinball",    "user" : "joe",    "score" : 50}

score这个键原来并不存在,所以“$inc”创建了这个键,并把值设定成增加量:50。$inc只能作用于整型、长整形或双精度浮点型的值。
4.修改数组

  • $push
 >db.blog.posts.update({"title":"A blog post"}, {"$push" : {"comments" : {"name" : "joe", "email" : "...", "content" : "..."}}})

上面示例即找到title为A blog post的文档,然后将一个comment的子文档插入到该文档的comments字段中,comments字段是一个数组。在执行操作前,comments可以不存在。
- $each

 db.stock.ticker.update({"_id" : "GOOG", {"$push" : {"hourly" : {"$each" : [22,33,44]}}}})

上面示例即找到_id为GOOG的文档,然后将数组[…]中的每一项逐个插入到hourly字段中。
- $slice

 db.movies.update({"genre" : horror},{"$push" : {"top10" : {"$each" : ["Nightmare on Elm Street", "Saw"], "$slice" : -10}}})

上面示例表示,将一个数组的电影插入到文档的top10字段中,但是仅保留最后的10个。$slice只能是负数。
- $addToSet
$addToSet跟$push字段的含义差不多,不一样的是,$addToSet不会加入重复的数据。
- $pop
$pop的用法与$push类似,它把数组看成一个队列或者栈,它能够指定从哪个地方删除元素。如

 {"$pop" : {"{key}" : 1 | -1}}

1表示从数组末尾删除,-1表示从头删除。

对于数组还有两个需要知道的东西是数字下标定位符($)。数字下表如:

comments.0.votes

表示comments数组的第一个子文档的votes字段。
而定位符$

comments.$.author

指的是改变匹配上的文档的comments数组中的子文档author字段,但是$定位符只改变匹配上的第一个文档。

MongoDB中有个特性叫upsert,这指的是,如果匹配的文档没有,那么直接做插入操作。要使用该特性,需要为update函数加上第三个参数,并置为true。

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

MongoDB中有个操作符叫做$setOnInsert,从字面上看就是在insert的时候设置。比如说createTime这种字段,只需要在第一次创建的时候设置一次就可以了,之后无需更改,就可以使用这个操作符来替代$set。

默认情况下,update只会更新匹配上的第一个文档,如果想要更新所有的文档,需要将update函数的第四个参数设置成true。

MongoDB的查询

在MongoDB中想要查询,需要使用函数find

选取返回字段

想要制定返回的字段,需要设置find函数的第二个参数。

db.user.find({},{"username":1,"email":1})

上面示例表示返回username和email两个字段,如果将1指定成0,那么就是不返回该字段。

查询条件

查询条件需要写在find函数的第一个参数中。

 db.users.find({"username" : "joe"})

上面示例指的是查找users集合中username为joe的文档。这种查询方式相当于是MySQL中的=。
那么如果字段做其它操作呢?
- 数值运算:$lt,$lte,$gt,$gte,$mod
这四个操作符分别对应<,<=,>,>=,分别是less than,less than and equal,greater than,greater than and equal的缩写。

 db.users.find({"age" : {"$gte" : 18, "lte" : 30}})

上面示例表示找出年龄大于等于18岁,小于等于30岁的用户文档。
MongoDB还有取模运算符:

 db.users.find({"id_num" : {"$mod" : [5, 1]}})

$mod后跟着的两个数字,5表示需要去除的数字,1表示需要保留下来的值。上面语句即为:id_num%5==1

  • $or
    如果我们在条件中指定多个匹配项,如
db.users.find({"name" : "joe", "age" : 18})

上面示例表示,找到name为joe,年龄是18岁的用户文档。这是一个典型的and操作。那么如何使用or呢?没错,就是$or操作符。

db.users.find({"$or" : [{"name" : "joe"}, {"age" : 18}]})

上面示例表示找到name为joe或者年龄为18的文档。

  • $not

    db.users.find({"age" : {"$not" : 10}})

    上面示例表示查找age部位10的users文档

  • $in和$nin
    这两个操作符很好理解,一个表示在范围内,一个表示不在范围内

    db.users.find({"age" : {"$in" : [12,13,14]}})db.users.find({"age" : {"$nin" : [12,13,14]}})
  • $exists
    这个操作符表示存不存在。谈到这个操作符的时候不得不先说一下MongoDB中的一个特殊的类型null。null本身是一个类型,你可以这么使用它

    db.users.find("blog" : null)

    这个语句查询的是blog字段为null的文档。但是它不仅仅能匹配上字段值为null的文档,如果文档没有blog字段,它也会被匹配上。那么如何精确的达到我们的目的呢,这就需要$exists出马了。

    db.users.find("blog" : {"$in" : [null], "$exists" : true})

    这就表示必须要blog字段存在,才是复合要求的文档。(注意这里为什么用$in。因为1、如果你直接使用:那么后面应该跟的是值,而不是文档。2、没有$eq操作符)

  • $size
    $size操作符其实是跟数组有关系的,它能获取数组的大小。

    db.users.find("star" : {"$size" : 3})

    这个查询表示查找star里有3个好评的用户文档。

  • $slice
    这里又出现了一个$slice,这个$slice也是表示去除部分数据,但是此处的$slice可以使用正整数也可以使用负整数。正整数表示保留前n个数组元素,而负整数表示保留最后n个元素。

  • $elemMatch
    对于子文档的查询,一般我们使用类似{“comments.author” : “joe”}即可。但是如果涉及到使用子文档的多个key查询是,可能会比较麻烦,这里有个简便点的写法即使用$elemMatch

    db.blog.find({"comments" : {"$elemMatch" : {"author" : "joe", "score" : {"$gte" : 4}}}})
  • $where
    在文章开头我们简介过MongoDB shell,它的实质是一个JavaScript shell。$where的用法就是可以在查询语句中使用函数如{“$where” : function}

顺便介绍一下,我们大部分时候在Terminal中查询,都不会接收find函数的返回值。查找的文档会直接被输出在Terminal中。其实find函数是有返回值的,它的返回值是一个游标。这个游标可以像JavaScript中的数组一般使用,如果使用hasNext,next接口可以在while语句中使用。如果使用forEach可以传入一个function。

  • 简单查询和封装查询
    在前面我们使用的查询如db.foo.find({“foo” : “bar”})就是一个简单查询。但是如果涉及到例如排序
 db.foo.find({"foo" : "bar"}).sort({"x" : 1})

实际情况不是直接把查询{“foo” : “bar”}发送到数据库,而是把整个查询封装成另外一个文档{“$query” : {“foo” : “bar”}, “$orderby” : {“x” : 1}},然后发送给数据库。

  • $maxscan,$min,$max和$showDiskLoc
    这届都是特殊的查询选项,分别表示最大扫描数量,索引最小范围,索引最大范围以及展示磁盘位置。
 db.foo.find(criteria)._addSpecial("$maxscan", 20)

即表示最大扫描20个文档。
$min和$max指定查询的键必须和索引完全匹配。一个表示下界一个表示上界,而在平时的查询中我们一般使用$gt和$lt去替代。
最后$showDiskLoc如果指定为true那么会在返回的文档中增加一个$diskLoc子文档,表示文档在哪个file中,并且它所处的offset是多少。
- 函数min,max,limit,skip和sort
find函数并不是语句的终结,它还能带上min,max,limit,skip,sort等函数。很好理解,min和max表示指定某个字段的下界和上界

 db.foo.find().min({"x":10}).max({"x":20})

表示x最小是10,最大是20,这里不包含equal。

sort是个排序函数,最匹配的结果文档进行指定排序。

db.foo.find().sort({"x" : 1 | -1}); 

如果指定为1,表示升序,如果是-1,表示降序。

limit是限制返回的个数,而skip表示跳过多少个文档。这两个函数组合使用可以实现分页效果。

db.foo.find().skip(10).limit(10)

即表示跳过10个文档,取接下来的10个文档。但是需要注意的是,如果略过大量的文档,可能会产生性能问题。

总结

本文主要介绍了MongoDB中修改和查找相关的语法特性。可以看出MongoDB的语法虽然跟MySQL有比较大的差别,但是功能上是一个不缺。但是因为某些实现方式不太一样,在使用的时候需要注意。(比如分页的操作)
需要注意的是,针对MongoDB的操作大都是通过函数和文档来表达的。如果update,save,find,sort,skip,limit等函数。而每个函数的参数,大都是需要传入一个文档(即一个类似json对象的结构体)。这中语法思路贯穿了整个MongoDB,在之后的文章将讲述MongoDB的聚合框架,更是将这个特性发挥到最大。

0 0