接着上章,继续介绍MongoDB的查询。

Querying on Embedded Documents

有两种方式查询嵌入式的子Documents:查询整个Document或者查询个别的键值对。

查询整个子Document和正常的查询是一样的。

我们有一个document:

{
"name":{
"first":"Joe",
"last":"Schmoe"
},
"age":45
}

我们可以查找名为Joe Schmoe的人:

db.people.find({"name":{"first":"Joe","last":"Schmoe"}})

然而,查询一个子Document必须精确匹配。假如Joe决定添加一个中间名字段,那么上面这个查询就不成立了。

并且,这种查询类型同样是顺序敏感,{"last":"Schmoe","first":"Joe"},这样查询也是不匹配的。

通常针对子文档的key进行查找就不会出现上述的情况了:

db.people.find({"name.last":"Schmoe","name.first":"Joe"})

下来我们来看这条数据:

{
"content":"...",
"comments":[
{
"author":"joe",
"score":3,
"comment":"nice post"
},
{
"author":"mary",
"score":6,
"comment":"terrible post"
}
]
}

现在我们想要查找comments为joe并且得分大于5.

首先,我们不能这样查询

db.blog.find({"comments":{"author":"joe","score":{"$gte":5}}})

内嵌文档匹配要求整个文档完全匹配,而不是匹配comments这个key。

同样我们不能用

db.blog.find({"comments.author":"joe","comments.score":{"$gte":5}})

因为符合author条件的评论和符合score的评论可能不是同一条。也就是说会返回刚才显示的那个document。因为"author"在第一条评论中实现,"score"在第二条评论中实现。

正确的该如何呢?

db.blog.find({"comments":{"$elemMatch":{"author":"joe","score":{"$gte":5}}}})

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

$where Queries

键值对是极富表现力的查询方式,但是有些查询他们也不能表达。

这时候,"$where"子句查询就出场了,它允许你在你的查询中执行任意的Javascript

最典型的应用就是比较文档中的两个键的值是否相等,例如有个条目列表,如果其中的两个值相等则返回文档。例:

db.foo.insert({"apple":1,"banana":6,"peach":3})
db.foo.insert({"apple":8,"spinach":4,"watermelon":4})

第二个文档中,"spinach"和"watermelon"的值相同,所以需要返回该文档。

db.foo.find({"$where":function(){
for(var current in this){
for(var other in this){
if(current !=other && this[current]==this[other]){
return ture;
}
}
}
return false;
}})

如果函数返回为true,文档就作为结果的一部分被返回。

在非必要时,不要用"$where":它的查询速度比一般查询要慢很多。

Cursors

数据库使用游标来返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效的控制。

你可以限制结果的数量,略过部分结果,根据任意方向任意键的组合对结果进行各种排序,或者执行其他的一些功能强大的操作。

想从Shell中创建一个游标,首先要对集合填充一些文档,然后对其进行查询,并将结果分配给一个局部变量。这里,创建一个简单集合,而后做查询,并用cursor保存结果:

for(i=0;i<100;i++){
db.collection.insert({x:i});
}
var cursor = db.collection.find()

这么做的好处是一次可以查看一条结果。如果将结果放在全局变量或者没有放在变量中,MongoDB Shell会自动迭代,自动显示最开始的若干文档。

也就是在这之前我们看到的种种例子,一般大家只想通过Shell看看集合里面有什么,而不是想在其中运行程序,这样的设计就很合适。

要迭代结果,可以使用游标的next方法。也可以使用hasNext来查看有没有其他的结果。典型的遍历如下:

while(cursor.hasNext()){
obj = cursor.next();
//do stuff
}

游标类还实现了迭代接口,所以可以在forEach循环中使用。

var cursor = db.people.find();
cursor.forEach(function(x){
print(x.name)
})

当使用find的时候,shell并不立即查询数据库,而是等待真正开始要求获得结果的时候才发送查询,这样在执行之前可以给查询附加额外的选项。

几乎所有游标对象的方法都返回游标本身,这样就可以按任意顺序组成方法链。如下面几种是等价的:

var cursor = db.foo.find().sort({"x":1}).limit(1).skip(10);
var cursor = db.foo.find().limit(1).sort({"x":1}).skip(10);
var cursor = db.foo.find().skip(10).limit(1).sort({"x":1});

此时,查询还没有执行,所有这些函数都只是构造查询。假设我们执行

cursor.hasNext();

这时,查询发往服务器。shell立即获取钱100个结果或者前4M数据(两者之间较小者),这样下次调用next或者hasNext时就不必兴师动众跑到服务器上去了。

客户端用光了第一组结果,shell会再次连接数据库,并要求更多的结果。这个过程一直会持续到游标耗尽或者结果全部返回。

Limits,Skips,and Sorts

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

限制数量:

db.c.find().limit(3);

上面只返回3个结果,要是匹配的结果不到3个,则返回匹配数量的结果。limit指定的是上限,而非下限。

skip和limit相似

db.c.find().skip(3);

上面的操作会略过前三条匹配的结果,然后返回剩余的文档。如果集合里面能匹配的文档少于3个,则不会返回任何文档。

sort里的参数:1升序,-1降序。若指定了多个键,则按照多个键的顺序逐个排序:

db.c.find().sort({"username":1,"age":-1})

我们业务逻辑中的分页查询就可以通过上面的方法来实现。

Avoiding Large Skips

skip对于少量document还是不错的。但是数据很多的话,skip就会变的很慢,所以要尽量避免。

通常可以向文档本身内置查询条件,来避免过大的skip,或者利用上一次的结果来进行下一次查询。

Paginating results without skip

最简单的分页方法就是用limit返回结果的第一页,然后将每个后续作为相对开始的偏移量返回。

然而,一般来讲可以找到一种方法实现不用skip的分页,这取决于查询本身,例如,要按照date降序显示文档。可以用如下方式获取第一页。

var page1 = db.foo.find().sort({"date":-1}).limit(100)

然后,可以利用最后一个文档中的"date"的值作为查询条件,来获取下一页:

var latest = null;

while(page1.hasNext()){
latest = page1.next();
display(latest);
} var page 2 = db.foo.find({"date":{"$gt":latest.date}});
page2.sort({"date":-1}).limit(100);

Finding a random document

从集合里面随机挑选一个文档算是比较常见的问题。最笨的(也是很慢的)做法就是先计算文档总数,然后选择一个从0到文档数量之间的随机数,利用

find做一次查询,略过这个随机数那么多的文档。这个随机数的取值范围为0到集合中文档的总数。

var total = db.foo.count();
var random = Math.floor(Math.random()*total)
db.foo.find().skip(random).limit(1)

这种方法太低效,首先要计算总数(有条件时会很费时),然后大量的skip也会非常耗时。

那该怎么做?我们可以在插入文档时给每个文档都添加一个额外的键。例如在Shell中,可以用Math.random():

db.people.insert({"name":"joe","random":Math.random()})
db.people.insert({"name":"john","random":Math.random()})
db.people.insert({"name":"jim","random":Math.random()})

这样,想从集合中查找一个随机文档,只要计算个随机数并将其作为查询条件就好了,完全不用skip:

var random = Math.random();
result = db.foo.findOne({"random":{"$get":random}}) 

MongoDB 学习四 : 查询(续)的更多相关文章

  1. 【转】MongoDB学习笔记(查询)

    原文地址 MongoDB学习笔记(查询) 基本查询: 构造查询数据. > db.test.findOne() { "_id" : ObjectId("4fd58ec ...

  2. [转载]MongoDB学习 (五):查询操作符(Query Operators).1st

    本文地址:http://www.cnblogs.com/egger/archive/2013/05/04/3059374.html   欢迎转载 ,请保留此链接๑•́ ₃•̀๑! 查询操作符(Quer ...

  3. [转载]MongoDB学习 (四):创建、读取、更新、删除(CRUD)快速入门

    本文介绍数据库的4个基本操作:创建.读取.更新和删除(CRUD). 接下来的数据库操作演示,我们使用MongoDB自带简洁但功能强大的JavaScript shell,MongoDB shell是一个 ...

  4. MongoDB学习笔记-查询

    MongoDB中使用find或findOne函数执行查询 find函数 db.c.find()--查询集合c所有 db.c.find({“name”:”zhangsan”}) 注意:查询条件的值必须是 ...

  5. mongodb学习(五) 查询

    1. 按条件查询: db.users.find({"name":"MM1"}) 2.find的第二个参数可以指定要返回的字段:这里1 表示要显示的字段,0 表示 ...

  6. MongoDB学习--高级查询 [聚合Group]

    Group大约需要一下几个参数. key:用来分组文档的字段.和keyf两者必须有一个 keyf:可以接受一个javascript函数.用来动态的确定分组文档的字段.和key两者必须有一个 initi ...

  7. mongodb学习(四)CRUD操作

    CRUD操作: 1. 插入操作: 直接使用 insert可执行单个操作,也可以执行批量操作 书上的batchInsert会报错.似乎被废弃了. db.foo.insert({"bar&quo ...

  8. MongoDB学习记录

    一.操作符 "$lt" :"<""$lte" :"<=""$gt" :"> ...

  9. SODBASE CEP学习(四)续:类SQL语言EPL与Storm或jStorm集成-使用分布式缓存

    流式计算在一些情况下会用到分布式缓存,从而实现(1)想把统计或计算结果保存在分布缓存中.供其他模块或其他系统调用. (2)某一滑动时间窗体上计数.比如实时统计1小时每一个Cookie的訪问量.实时统计 ...

随机推荐

  1. Python入门--16--模块

    模块的定义: 模块是一个包含所有你定义的函数和变量的文件,其后缀是.py.模块可以被别的程序引入,以使用该模块中的函数等功能 比如 import random secret=random.randin ...

  2. Django REST framework 的TokenAuth认证及外键Serializer基本实现

    一,Models.py中,ForeignKey记得要有related_name属性,已实现关联对象反向引用. app_name = models.ForeignKey("cmdb.App&q ...

  3. Codeforces Round #317 [AimFund Thanks-Round] (Div. 2) Minimization dp

    原题链接:http://codeforces.com/contest/572/problem/D 题意 给你个数组A和n,k,问你排列A后,下面的最小值是多少. 题解 先排个序,要填充像1,1+k,1 ...

  4. 第四章——SQLServer2008-2012资源及性能监控(1)专家

    http://blog.csdn.net/dba_huangzj/article/details/8614817

  5. GOF 23种设计摩搜-建造者模式

    • 场景: – 我们要建造一个复杂的产品.比如:神州飞船,Iphone.这个复杂的产品的创建.有这样 一个问题需要处理: • 装配这些子组件是不是有个步骤问题? – 实际开发中,我们所需要的对象构建时 ...

  6. webpack+vue起步

    本文基于vue1.x 基于vue2.x&webpack2.x请移步至 Vue2.x踩坑与总结Webpack2.x踩坑与总结 记得第一次知道Vue.js是在勾三股四大大的微博,那时候他开始翻译v ...

  7. Unity3D:Gizmos画圆(原创)

    Unity3D引擎技术交流QQ群:[21568554] Gizmos是场景视图里的一个可视化调试工具. 在做项目过程中.我们常常会用到它,比如:绘制一条射线等. Unity3D 4.2版本号截至.眼下 ...

  8. 转帖:HttpStatusCode状态说明C#版

    Continue 等效于 HTTP 状态 100.Continue 指示客户端可能继续其请求. SwitchingProtocols 等效于 HTTP 状态 101.SwitchingProtocol ...

  9. sql的一些知识_高级

    1.视图 http://www.cnblogs.com/wang666/p/7885934.html 2.存储过程 http://www.cnblogs.com/wang666/p/7920748.h ...

  10. C语言-回溯例4

    1,问题提出 日本数学家桥本吉彦教授于1993年10月在我国山东举行的中日美三国数学教育研讨会上向与会者提出以下填数趣题: 把1,2,...,9这9个数字填入下式的九个方格中(数字不得重复),使下面 ...