MongoDB查询

1.1 find简介

MongoDB中使用find来进行查询。查询就是返回一个集合中文档的子集,子集合的范围从0个文档到整个集合。find第一个参数决定了要返回哪些文档,其形式也是一个文档,说明要执行查询的细节。

空的查询文档{}会匹配集合的全部内容。要是不指定查询文档,默认就是{}。

//返回集合c中的所有内容
> db.c.find()

当我们开始向查询文档中添加键/值对时,就意味着限定了条件。对于绝大多数类型来说,这种方式很简单明了。

//查找所有age为27的文档
> db.users.find({"age":27})
//查找所有用户名为“joe”且年龄为27的用户
>db.users.find({"username" : "joe","age" : 27})

1.1.1 指定返回的键

有时并不需要将文档中所有键/值对都返回。遇到这种情况,可以通过find(或者findOne)的第二个参数来指定想要的键。这样既节省传输的数据量,又能节省客户端解码文档时间和内存消耗。

如只对用户集合的“username”和“email”键感兴趣:

可以看到,“_id”这个键总是被返回,即便是没有指定也一样。

也可以用第二个参数来剔除查询结果中的某个键/值对

1.1.2 限制

查询使用上还是有些限制,数据库所关心的查询文档的值必须是常量。(在自己代码里可以是正常的变量)。也就是不能引用文档中其他键的值。如,想要查询库存,有原库存"in_stock"和已出售"num_sold"两个键,想通过两者比较来查询:

> db.stock.find({"in_stock" : "this.num_sold"})    //不可行

1.2 查询条件

查询不仅能像前面的那样精确匹配,还能匹配更加复杂的条件,比如范围,OR子句和取反。

1.2.1 查询条件

"$lt","$lte","$gt"和"$gte"就是全部的比较操作符,分别对应<,<=,>和>=。可以将其组合起来以便查找一个范围。

//查询在18~30岁(含)的用户
> db.users.find({"age" : {"$gte" : 18, "$lte" : 30}})
//查询在2007年1月1日前注册的人
> start = new Dage("01/01/2007")
> db.users.find({"registered" : {"$lt" : start}})

对于文档的键值不等于某个特定值的情况,就要使用"$ne",它表示不"不相等","$ne"能用于所有类型的数据。

//查询所有名字不为"joe"的用户
> db.users.find({"username" : {"$ne" : "joe"}})

1.2.2 OR查询

MongoDB中有两种方式进行OR查询。"$in"可以用来查询一个键的多个值。"$or"更通用一些,用来完成多个键的任意给定值。

//找出中奖号码是725,542,390.
> db.raffle.find({"ticket_no" : {"$in" : [725,542,390]}})

"$in"非常灵活,可以指定不同类型的条件和值。

//在逐步将用户名的ID号迁移成用户名的过程中,做兼顾二者的查询:
//匹配user_id等于12345的文档,也会匹配user_id等于joe的文档。
> db.users.find({"user_id" : {"$in" : [12345, "joe"]}})

与"$in"相对的是"$nin",将返回与数组中所有条件都不匹配的文档。

//返回所有没有中奖的人
> db.raffle.find({"ticket_no" : {"$nin" : [725,542,390]}})

"$in"能对单个键做OR查询,而"$or"接受一个包含所有可能条件的数组作为参数。

//查找ticket_no为725或者winner为true的文档
> db.raffle.find({"$or" : [{"ticket_no" : 725},{"winner" : true}]})

> db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725,542,390]}},
                                    {"winner": true}]})

使用普通的AND查询时,总是想尽可能地用最少的条件来限定结果的范围,OR型的查询正相反:第一个条件尽可能地匹配更多的文档,这样才是最为有效的。

1.2.3 $not

"$not"是元条件句,既可以用在其他条件之上。如取模运算符"$mod"会查询值除以第一个给定值,若余数等于第二个给定的值则返回改结果:

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

上面的查询返回"id_num"值为1,6,11,16等的用户。但要返回"id_num"为2,3,4,5,7,8,9,10,12等用户,就要用"$not"了:

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

1.2.4 条件句的规则

比较更新修改器和查询文档,会发现已$开头的键处在不同的位置。查询中,"$lt"在内层文档,而更新中"$inc"则是外层文档中的键。基本可以肯定:条件句是内层文档的键,而修改器则是外层文档的键。

一个键可以有多个条件,但是一个键不能对应多个更新修改器。

1.3 特定于类型的查询

MongoDB的文档可以使用多种类型的数据。其中有些在查询的时候会有特别的表现。

1.3.1 null

null有点奇怪,它确实能匹配自身:

但是,null不仅仅匹配自身,而且匹配“不存在的”。所有,这个匹配还会返回缺少这个键的所有文档:

如果仅仅想要匹配键值为null的文档,既要检查该键的值是否为null,还要通过"$exists"条件判定键值已经存在:

1.3.2 正则表达式

正则表达式能够灵活有效地匹配字符串。

//想要查找所有名为Joe或者joe的用户,使用正则表达式执行忽略大小写匹配:
> db.users.find({"name" : /joe/i})

系统可以接受正则表达式标识(i),但不是一定要有。

//匹配各种大小写组合的joe,还要匹配各种大小写组合形式的joey
> db.users.find({name : /joey?/i})

MongoDB使用Perl兼容的正则表达式(PCRE)库来匹配正则表达式。

1.3.3 查询数组

数组绝大多数情况下可以这样理解:每个元素都是整个键的值。

> db.food.insert({"fruit" : {"apple", "banana", "peach"}})

查询为:

> db.food.find({"fruit" : "banana"})

1. $all

如果需要通过多个元素来匹配数组,就要用"$all"。

顺序无关紧要,要是对只有一个元素的数组使用"$all",就和不用"$all"一样了,例如,{fruit:{$all:['apple']}}和{fruit:'apple'}

也可以用完整的数组精确匹配,但是,精确匹配对于有缺少或者冗余元素的情况就不大灵了。

要想查询数组指定位置的元素,则需要使用key.index语法指定下标。

//数组下标都是从0开始,所以表达式会用数组的第3个元素和"peach"匹配
> db.food.find({"fruit.2" : "peach"})

2. $size

可以用其查询指定长度的数组。

> db.food.find({fruit : {$size : 3}})

一种常见的查询需求就是需要一个长度范围。"$size"并不能与其他查询子句组合(比如"$gt"),但是这种查询可以在文档添加一个"size"键的方式来实现。这样每一次向指定数组添加元素的时候,同时增加"size"的值。

> db.food.update({"$push":{fruit : "strawberry"},"$inc" : {"size" : 1}})
//查询如下:
> db.food.find({"size" : {"$gt" : 3}})

3. $slice操作符

$slice返回一个子集合

//返回博客前10条评论
> db.blog.posts.find(criteria,{"comments" : {"$slice" : 10}})
//返回博客后10条评论
> db.blog.posts.find(criteria,{"comments" : {"$slice" : -10}})

"$slice"也可以接受偏移值和要返回的元素数量。

//跳过前23个元素,返回第24~第33个元素。如果数组不够33个元素,则返回第23个元素后面的所有元素。
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : [23,10]}})

除非特别声明,否则使用"$slice"时将返回文档中的所以键。别的键说明符都是默认不返回未提及的键,这点与"$slice"不太一样。

1.3.4 查询内嵌文档

查询内嵌有2种方法:查询整个文档,或者只针对其键/值对进行查询。

查询整个内嵌文档与普通查询完全相同。

查询姓名为Joe Schmoe的人可以这样:

但如果Joe决定添加一个代表中间名的键,这个查询就用不了,因为那样无法匹配整个内嵌文档了,这种查询还是与顺序相关的。

通常只针对内嵌文档的特定键值进行查询才是比较好的做法,这样即使数据模式改变,也不会导致所有查询因为要精确匹配而一下子都挂掉。

假设有博客文章若干,要找到Joe发表的5分以上评论

上面两条查询都不能查询到期望的结果:1因为内嵌文档匹配要求整个文档完全匹配,而这不会匹配"comment"键。2因为符合author条件的评论和符合score条件的评论可能不是同一条评论。

要正确地指定一组条件,而不用指定每个键,要使用"$elemMatch".这种模糊命名条件句能用来部分指定匹配数组中的单个内嵌文档的限定条件。

"$elemMatch"将限定条件进行分组,仅当需要对一个内嵌的多个键操作时才会用到。

1.4 $where查询

"$where"可以执行任意JavaSript作为查询的一部分。

例如:有个条目列表,其中的两个值相等则返回文档。

MongoDB没有一个$条件来做这个,所以只能用"$where"子句借助Javascript来完成:

不是非常必要是,一定要避免使用"$where"查询,因为它在速度比常规查询要慢很多,每个文档都要从BSON转换成Javascript对象,然后通过$where来运行,还不能利用索引。

1.5 游标

数据库使用游标来返回find的执行结果。

要迭代结果,可以使用游标的next方法。也可以使用hasNext来查看有没有其他结果。

cursor.hasNext()检查是否有后续结果存在,然后用cursor.next()将其获取。

游标还实现了迭代器接口可以在foreach循环使用。

以下几种表达是等价的:

这时,查询被发往服务器。shell立刻获取前100个结果或者前4MB数据。

1.5.1 limit、skip和sort

 最常用的查询选项就是限制返回结果的数量,忽略一定数量的结果并排序,所有的这些选项一定要在查询被派发到服务器之前添加。

限制结果数量,可在find后使用limit函数

//只返回3个结果
> db.c.find().limit(3)

skip与limit类似:

//略过前三个匹配的文档,然后返回余下的文档
> db.c.find().skip(3)

sort用一个对象作为参数:一组键/值对,键对应文档的键名,值代表排序的方向。排序方向可以是1(升序)或者-1(降序),如果指定了多个键,则按照多个键的顺序逐个排序。

//username升序及age降序
> db.c.find().sort({username:1,age:-1})

这3个方法可以组合使用,对于分页非常有用。

//每页返回50个结果,按照价格从高到底排序。
> db.stock.find({desc : "mp3"}).limit(50).sort({price : -1})
//点击下一页
> db.stock.find({desc : "mp3"}).limit(50).skip(50).sort({price : -1})

1.5.2 避免使用skip略过大量结果

用skip略过少量文档还是不错的,但是要是数量非常多的话,skip就会变的非常慢。

1.查询中不用skip

//获取第一页
> var page1 = db.foo.find().sort({date : -1}).limit(100)
//利用最后一个文档中"date"的值作为查询条件,来获取下一页。
var lastest = null;
//display fisrt page
while(page1.hasNext()){
     latest = page1.next();
     display(latest);   
}
//get next page
var page2 = db.foo.find({date : {$gt : latest.date}});
page2.sort({date : -1}).limit(100)

2.随机选取文档

从集合中随机挑选一个文档是个常见的问题。最笨的(很慢)做法是先计算文档总数,然后选择一个从0到文档数量直接的随机数,利用find做一个查询,略过这个随机数那么多的文档,这个随机数的取值范围为0到集合中文档的总数。

可以在插入文档时给每个文档添加一个额外的随机键,可以用Math.random()(产生一个0~1的随机数):

> var random =Math.random()
> result = db.foo.findOne({random : {$gt : random}})

> if(result==null){
     result = db.foo.findOne({random : {$lt : random}})
}
posted on 2016-03-14 21:56  gimin  阅读(385)  评论(0)    收藏  举报