查询内嵌文档
数据准备
> db.blog.find().pretty() { "_id" : ObjectId("585694e4c5b0525a48a441b5"), "content" : "...", "comments" : [ { "comment" : "good post", "author" : "jim", "votes" : 15 }, { "comment" : "i thought it was too short", "author" : "yyb", "votes" : 3 }, { "comment" : "free watches", "author" : "alice", "votes" : 3 } ] } { "_id" : ObjectId("585f659898992d5a44085fc0"), "content" : "query", "comments" : [ { "comment" : "good post two", "author" : "yyb", "votes" : 10 }, { "comment" : "i thought it was vary good", "author" : "mmq", "votes" : 30 }, { "comment" : "free watches two", "author" : "lf", "votes" : 20 } ] } >
方式1:查询整个内嵌文档
与普通查询完全相同。但是,如果要查询一个完整的子文档,那么子文档必须精确匹配(顺序以及个数都要一样)。
> db.blog.find({"comments":{"author":"lf","votes":20}})
>
> db.blog.find({"comments":{"comment":"free watches two","author":"lf","votes":20}}).pretty() { "_id" : ObjectId("585f659898992d5a44085fc0"), "content" : "query", "comments" : [ { "comment" : "good post two", "author" : "yyb", "votes" : 10 }, { "comment" : "i thought it was vary good", "author" : "mmq", "votes" : 30 }, { "comment" : "free watches two", "author" : "lf", "votes" : 20 } ] } >
方式2:只针对其键/值对进行查询
> db.blog.find({"comments.author":"yyb","comments.votes":10}).pretty() { "_id" : ObjectId("585f659898992d5a44085fc0"), "content" : "query", "comments" : [ { "comment" : "good post two", "author" : "yyb", "votes" : 10 }, { "comment" : "i thought it was vary good", "author" : "mmq", "votes" : 30 }, { "comment" : "free watches two", "author" : "lf", "votes" : 20 } ] }
如果想要查找由yyb发表的11分以上的投票。不能直接使用以下方式
> db.blog.find({"comments":{"author":"yyb","votes":{"$gt":11}}}) >
或者
> db.blog.find({"comments.author":"yyb","comments.votes":{"$gt":11}})//本来没有,但却查出来两条 { "_id" : ObjectId("585694e4c5b0525a48a441b5"), "content" : "...", "comments" : [ { "comment" : "good post", "author" : "jim", "votes" : 15 }, { "comment" : "i thought it was too short", "author" : "yyb", "votes" : 3 }, { "comment" : "free watches", "author" : "alice", "votes" : 3 } ] } { "_id" : ObjectId("585f659898992d5a44085fc0"), "content" : "query", "comments" : [ { "comment" : "good post two", "author" : "yyb", "votes" : 10 }, { "comment" : "i thought it was vary good", "author" : "mmq", "votes" : 30 }, { "comment" : "free watches two", "author" : "lf", "votes" : 20 } ] }
$elemMatch
要正确地指定一组条件,而不必指定每个键,就需要使用 $elemMatch 。这种模糊的命名条件句能用来在查询条件中部分指定匹配数组中的单个内嵌文档。
$elemMatch将限定条件进行分组,仅当需要对一个内嵌文档的多个键操作时才会用到。
> db.blog.find({"comments":{"$elemMatch":{"author":"yyb","votes":{"$gt":11}}}})//无匹配结果
> db.blog.find({"comments":{"$elemMatch":{"author":"yyb","votes":{"$gte":10}}}}).pretty() { "_id" : ObjectId("585f659898992d5a44085fc0"), "content" : "query", "comments" : [ { "comment" : "good post two", "author" : "yyb", "votes" : 10 }, { "comment" : "i thought it was vary good", "author" : "mmq", "votes" : 30 }, { "comment" : "free watches two", "author" : "lf", "votes" : 20 } ] } >
$where
用它可以在查询中执行任意的js。这样就能在查询中做任何事情,为安全起见,应该严格限制或消除$where语句的使用。应该禁止终端用户使用任意的$where语句。
如果函数返回true,文档就作为结果集的一部分返回;如果为false,就不返回。
使用$where在速度上要比常规查询慢很多。每个文档都要从BSON转换成js对象,然后通过$where表达式来运行。而且不能使用索引。实在不得已,最好先使用常规查询(或索引)进行过滤,然后使用$where语句,这样组合使用可以降低性能损失。
$where语句最常见的应用就是比较文档中的两个键的值是否相等。比如:
> db.foo.insert({"apple":1,"banana":6,"pach":3}) WriteResult({ "nInserted" : 1 }) > db.foo.insert({"apple":8,"banana":4,"pach":4}) WriteResult({ "nInserted" : 1 }) > db.foo.find({"$where":function(){ ... for(var cur in this){ ... for(var other in this){ ... if(cur!=other && this[cur]==this[other]){ ... return true; ... } ... } ... } ... return false; ... }}); { "_id" : ObjectId("585f768398992d5a44085fc2"), "apple" : 8, "banana" : 4, "pach" : 4 } >
游标
数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效地控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者执行其他一些强大的操作。
> for(i=0;i<100;i++){ ... db.coll.insert({x:i}); ... } WriteResult({ "nInserted" : 1 }) > var cursor=db.coll.find() > while(cursor.hasNext()){ ... obj=cursor.next(); ... print(obj.x+"============"+obj._id); ... }
0============585f7c3a98992d5a44085fc3
1============585f7c3a98992d5a44085fc4
2============585f7c3a98992d5a44085fc5
3============585f7c3a98992d5a44085fc6
......
cursor.hasNext()检查是否有后续结果存在,然后用cursor.next()获取它。
游标类还实现了js的迭代接口,所以可以在forEach循环中使用:
> var coll=db.coll.find({"x":{"$lte":3}}) > coll.forEach(function(row){ ... print(row._id+"========="+row.x); ... }) 585f7d0298992d5a44086027=========0 585f7d0298992d5a44086028=========1 585f7d0298992d5a44086029=========2 585f7d0298992d5a4408602a=========3 >
调用find时,shell并不立即查询数据库,而是等待真正开始获得结果时才发送查询,这样在执行之前可以给查询附加额外的选项。
几乎游标对象的每个方法都返回游标本身,这样就可以按照任意顺序组成方法链。例如,下面几种表达是等价的:
> var cursor=db.coll.find().sort({"x":-1}).skip(10).limit(1)//实现分页的方式:先倒序,跳过10条,取1条
> var cursor=db.coll.find().limit(1).sort({"x":-1}).skip(10)
> var cursor= db.coll.find().skip(10).limit(1).sort({"x":-1})
此时,查询还没有真正执行,所有这些函数都只是构造查询。
> print(cursor) DBQuery: test.coll -> { "query" : { }, "orderby" : { "x" : -1 } }
limit、sort、和skip
limit限制返回结果的数量,sort排序,skip忽略一定数量的结果。
> var cursor=db.coll.find().sort({"x":-1}).skip(10).limit(10)//实现分页的方式:先倒序,跳过10条,取1条
> db.coll.find().limit(2) { "_id" : ObjectId("585f7d0298992d5a44086027"), "x" : 0 } { "_id" : ObjectId("585f7d0298992d5a44086028"), "x" : 1 }
......
> db.coll.find().skip(2) { "_id" : ObjectId("585f7d0298992d5a44086029"), "x" : 2 } { "_id" : ObjectId("585f7d0298992d5a4408602a"), "x" : 3 }
......
> db.coll.find().sort({"x":-1}) { "_id" : ObjectId("585f7d0298992d5a4408608a"), "x" : 99 } { "_id" : ObjectId("585f7d0298992d5a44086089"), "x" : 98 }
......
避免使用skip略过大量结果,用skip略过少量的文档还是不错的。但是要是数量非常多的话,skip就会变得很慢,通常可以利用上次的结果来计算下一次查询条件。
比如可以利用最后一个文档中的“date”的值最为查询条件,来获取下一页:
> var page1=db.coll.find().sort({"date":-1}).limit(10) > var lastest=null; > while(page1.hasNext()){ latest=page1.next(); print(latest.x)} > var page2=db.coll.find({"date":{"$gt":latest.date}}).sort({"date":-1}).limit(10)
比较顺序
MongoDB处理不同类型的数据是有一定顺序的。有时一个键的值可能是多种类型的。如果对这种混合类型的键排序,其顺序是预先定义好的。优先级从小到大,其顺序如下。
(1)最小值
(2)Null
(3)数字
(4)字符串
(5)对象/文档
(6)数组
(7)二进制数据
(8)对象id
(9)布尔型
(10)日期型
(11)时间戳
(12)正则表达式
(13)最大值
高级查询选项
简单查询
> var cursor=db.coll.find({"x":1})
封装查询
有一些选项可以用于对查询进行“封装”。
比如:
var cursor= db.coll.find({x:{“$lt”:10}}).sort({"x":-1})
实际情况不是将 {x:{“$lt”:10} 作为查询直接发送给数据库,而是先将查询封装在一个更大的文档中。
DBQuery: test.coll -> { "query" : { "x" : { "$lt" : 10 } }, "orderby" : { "x" : -1 } }
绝大多数驱动程序都提供了辅助函数,用于向查询中添加各种选项。比如:
> db.coll.find({"x":{"$lt":2}})._addSpecial("$showDiskLoc",true); { "_id" : ObjectId("585f7d0298992d5a44086027"), "x" : 0, "date" : ISODate("2016-12-25T10:02:02.499Z"), "$recordId" : NumberLong(104) } { "_id" : ObjectId("585f7d0298992d5a44086028"), "x" : 1, "date" : ISODate("2016-12-25T10:02:02.499Z"), "$recordId" : NumberLong(105) } >
获取一致结果
数据处理通常做法就是先把数据从MongoDB中取出来,然后做一些变换,最后在存回去。
结果比较少时,没什么问题。但是如果结果集比较大,MongoDB可能会多次返回同一个文档。原因是:体积变大的文档,可能无法保存回原先的位置。MongoDB会为更新后无法放回原位置的文档重新分配存储空间。
解决方案:对查询进行快照。这样查询就在_id索引上遍历执行,这样可以保证每个文档只被返回一次。
> db.coll.find().snapshot()
快照会使查询变慢,如非必要,不建议使用。
游标生命周期
看待游标的两种角度:客户端游标以及客户端游标表示的数据库游标。前面所说的游标都是客户端游标。
在服务器端,游标消耗内存和其他资源。游标遍历尽了结果之后,获取客户端发来消息要求终止,数据库就会释放这些资源。还有一些情况导致游标终止:
- 游标完成匹配结果的迭代时。
- 客户端的游标已经不再作用域内,驱动程序会向服务端发送一条特别的消息,让其销毁游标。
- 一个游标在10分钟之内没有使用。
数据库命令
在shell中使用的辅助函数,都是对数据库命令的封装,而提供的更加简单的接口。
> db.runCommand({"drop":"coll"}) { "ns" : "test.coll", "nIndexesWas" : 1, "ok" : 1 } > db.coll.drop() false
查看所有的数据库命令
> db.listCommands()
数据库命令总会返回一个包含ok键的文档,如果值是1,则表示成功,反之失败,那么命令的返回文档中就会有一个额外的键errmsg。用于描述失败的原因。
> db.runCommand({"drop":"coll"}) { "ok" : 0, "errmsg" : "ns not found", "code" : 26 }
MongoDB中的命令被实现为一种特殊类型的查询,这些特殊的查询会在$cmd集合上执行。
runCommand只是接受一个命令文档,并且执行与这个命令文档等价的查询。于是,drop命令会被转换为如下代码:
> db.$cmd.findOne({"drop":"coll"}) { "ok" : 0, "errmsg" : "ns not found", "code" : 26 }
有些命令需要管理员权限,而且要在admin数据库上才能执行。如果当前位于其他的数据库,但是需要执行一个管理员命令,可以使用 adminCommand
> db.runCommand({"shutdown":1}) { "ok" : 0, "errmsg" : "shutdown may only be run against the admin database.", "code" : 13 } > db.adminCommand({"shutdown":1}) 2016-12-25T21:32:31.474+0800 E QUERY [thread1] Error: error doing query: failed: network error while attempting to run command 'shutdown' on host '127.0.0.1:27017' : DB.prototype.runCommand@src/mongo/shell/db.js:135:1 DB.prototype.adminCommand@src/mongo/shell/db.js:153:16 @(shell):1:1