mongodb高级操作(2)-查询

1.查询文档find介绍

mongodb中使用find来进行查询.find的第一个参数决定了要返回哪些文档,这个参数是一个文档,用于指定查询条件.
如果不指定条件默认就是{},那么就是查询所有文档.
    
    
    
    
> db.test.find()
{ "_id" : ObjectId("573c858c323f7f2e2ccb0e17"), "name" : "brent", "age" : 43, "status" : "done" }
{ "_id" : ObjectId("573c86d3017c5eb7d08aed6d"), "name" : "bob", "age" : 1, "status" : "done" }
{ "_id" : ObjectId("573c88fe017c5eb7d08aed6e"), "name" : "tom", "age" : 10, "status" : "done" }
{ "_id" : ObjectId("573c8bd3323f7f2e2ccb0e18"), "name" : "brent", "age" : 30, "status" : "done" }
向查询中指定键值,意味着限定查询条件,查询简单的类型只要指定要查找的值就行了:
    
    
    
    
> db.test.find({"name":"brent"})
{ "_id" : ObjectId("573c858c323f7f2e2ccb0e17"), "name" : "brent", "age" : 43, "status" : "done" }
{ "_id" : ObjectId("573c8bd3323f7f2e2ccb0e18"), "name" : "brent", "age" : 30, "status" : "done" }
可以在查询中指定多个键值对,以逗号隔开,这样的意思是条件1 AND 条件2 AND ....的意思:
    
    
    
    
> db.test.find({"name":"brent","age":43})
{ "_id" : ObjectId("573c858c323f7f2e2ccb0e17"), "name" : "brent", "age" : 43, "status" : "done" }

指定返回的键

有时并不是需要返回所有的键,这时可以通过指定find或者findOne的 第二个参数来指定想要的键,例如只想查询"name"为"brent"的name和age键:
>  db . test . find ({ "name" : "brent" },{ "name":1,"age":1 })
    
    
    
    
{ "_id" : ObjectId("573c858c323f7f2e2ccb0e17"), "name" : "brent", "age" : 43 }
{ "_id" : ObjectId("573c8bd3323f7f2e2ccb0e18"), "name" : "brent", "age" : 30 }
默认情况下_id都是会返回的,也可以使用第二个参数来剔除某个键,例如我们不希望得到status的键:
    
    
    
    
> db.test.find({"name":"brent"},{"status":0})
{ "_id" : ObjectId("573c858c323f7f2e2ccb0e17"), "name" : "brent", "age" : 43 }
{ "_id" : ObjectId("573c8bd3323f7f2e2ccb0e18"), "name" : "brent", "age" : 30 }
使用这种方法还可以将_id列剔除:
    
    
    
    
> db.test.find({"name":"brent"},{"status":0,"_id":0})
{ "name" : "brent", "age" : 43 }
{ "name" : "brent", "age" : 30 }

限制

查询传递的参数必须是常量,例如如果想查询一个文档中的某两个列相等的情况是不行的.

2.查询条件

查询除了上面简单介绍的精确匹配,还有更加复杂的查询,比如范围查询,or,and,取反等等

查询条件

"$lt","$lte","$gt","$gte"就是全部的比较操作符.分别对应着<,<=,>和>=,可以将这些组合起来以便查找一个范围的值.
例如下面的例子查找age大于20小于30的文档:
    
    
    
    
> db.test2.find()
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
    
    
    
    
> db.test2.find({"age":{"$gt":20,"$lt":30}})
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
对于文档的键值不等于某个特定的值,就要使用"$ne"了,他表示不相等.下面例子要查找name不等于brent的用户:
    
    
    
    
> db.test2.find({"name":{"$ne":"brent"}})
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }

OR查询

有两种方式进行OR查询: "$in"可以用来查询一个键的多个值, "$or"可以在多个键中查询任意给定的值.
例如下面要查询age为10,14的文档:
    
    
    
    
> db.test2.find({"age":{"$in":[10,14]}})
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
使用"$in"的时候,可以指定不同类型的条件和值,如果"$in"的数组只有一个值,那么和直接匹配是一样的. "$in""nin"是相对的,"nin"返回数组中不匹配的文档.查询age不为10,14的文档:
    
    
    
    
> db.test2.find({"age":{"$nin":[10,14]}})
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
"$in"只能对单个键做OR查询,如果想对多个键做匹配OR查询那么可以使用"$or","$or"接受一个包含所有可能的数组作为条件.
例如下面这个查询name为bob或者age为10的文档:
    
    
    
    
> db.test2.find({"$or":[{"name":"bob"},{"age":10}]})
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
还可以将"$or"和"in"联合起来使用:
     
     
     
     
> db.test2.find({"$or":[{"age":{"$in":[10,28]}},{"name":"bob"}]})
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }

$not

"$not"是元条件句,即可应用于任何其它条件之上,拿"$mod"来说,"$mod"会查询的值除以第一个给定的值,若余数等于第二个值则匹配成功
例如find({"id":{"$mod":[5,1]}})会匹配到1,6,11,16...的数,但是如果你要匹配2,3,4,5,7,8,9这些数就可以使用"$not"
find({"id":{ "$not":{"$mod":[5,1]}}})

3.特定类型的查询

null类型

null类型可以匹配本身,而且会匹配不包含这个键的文档,所以这种匹配还会返回缺少这个键的文档.先在集合中插入一个name为null的文档
    
    
    
    
> db.test2.insert({"name":null})
WriteResult({ "nInserted" : 1 })
> db.test2.find()
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
{ "_id" : ObjectId("573e80569e178b5475b29d8c"), "name" : null }
试着查找name为空的文档,可以匹配,如果查找没有的键job,那么会匹配所有的文档:
    
    
    
    
> db.test2.find({"name":null})
{ "_id" : ObjectId("573e80569e178b5475b29d8c"), "name" : null }
     
     
     
     
> db.test2.find({"job":null})
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
{ "_id" : ObjectId("573e80569e178b5475b29d8c"), "name" : null }
如果仅仅想要匹配键值为null的文档,既要检查改键值是否为null,还要通过 "$exists"条件判断键值已存在
    
    
    
    
> db.test2.find({"job" : {"$in":[null],"$exists":true}})

正则表达式

正则表达式能够灵活的匹配字符串,mongodb使用perl的正则表达式pcre库来匹配正则表达式.建议在查询前现在shell中检查一下语法,确保匹配与设想的一致.例如下面匹配不区分大小写的bob文档:
    
    
    
    
> db.test2.find({"name":/bob/i})
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
{ "_id" : ObjectId("573e853c9e178b5475b29d8d"), "name" : "BOB" }

查询数组

$all

查询数组元素与查询标量值是一样的,例如有下面一个集合:
    
    
    
    
> db.test3.find()
{ "_id" : ObjectId("573e874c9e178b5475b29d8e"), "name" : "brent", "fav" : [ "game", "film", "read" ] }
{ "_id" : ObjectId("573e87849e178b5475b29d8f"), "name" : "bob", "fav" : [ "drink", "football", "runing" ] }
{ "_id" : ObjectId("573e87cc9e178b5475b29d90"), "name" : "jack", "fav" : [ "read", "basketball", "drink" ] }
查询fav为drink的文档,它会成功匹配所有fav中包含drink的文档:
    
    
    
    
> db.test3.find({"fav":"drink"})
{ "_id" : ObjectId("573e87849e178b5475b29d8f"), "name" : "bob", "fav" : [ "drink", "football", "runing" ] }
{ "_id" : ObjectId("573e87cc9e178b5475b29d90"), "name" : "jack", "fav" : [ "read", "basketball", "drink" ] }
如果需要通过多个元素来匹配数组,那么就要使用 "$all"了,这样就会匹配一组元素,下面例子匹配fav中包含drink和read的文档
    
    
    
    
> db.test3.find({"fav":{"$all":["drink","read"]}})
{ "_id" : ObjectId("573e87cc9e178b5475b29d90"), "name" : "jack", "fav" : [ "read", "basketball", "drink" ] }
这里的顺序无关紧要.
还可以对数组进行精确匹配,精确匹配需要数组完全一致,不能有缺少和冗余的元素,而且顺序也一定要一致,例如下面的例子:
    
    
    
    
> db.test3.find({"fav":["drink"]})
> db.test3.find({"fav":["drink","running","football"]})
> db.test3.find({"fav":["drink","football","runing"]})
{ "_id" : ObjectId("573e87849e178b5475b29d8f"), "name" : "bob", "fav" : [ "drink", "football", "runing" ] }
要想插叙数组特定位置的元素,需要使用 key.index的语法指定下标,例如查找fav第二个元素为film的文档,注意index是从0开始的:
    
    
    
    
> db.test3.find({"fav.1":"film"})
{ "_id" : ObjectId("573e874c9e178b5475b29d8e"), "name" : "brent", "fav" : [ "game", "film", "read" ] }

$size

"$size"用来查询数组的长度.例如查找fav长度为2的文档:
    
    
    
    
> db.test3.find({"fav":{"$size":2}})
{ "_id" : ObjectId("573e8b719e178b5475b29d91"), "name" : "tom", "fav" : [ "chess", "cooking" ] }
"$size"不能与其它的查询条件组合使用,例如"%gt"

$slice操作符

find的第二个参数返回指定的键.这个特别的"$slice"操作符可以返回某个键匹配的数组元素的一个子集.
例如我想返回前2个fav的值:
    
    
    
    
> db.test3.findOne({"name":"brent"},{"fav":{"$slice":2}})
{
"_id" : ObjectId("573e874c9e178b5475b29d8e"),
"name" : "brent",
"fav" : [
"game",
"film"
]
如果想要返回后2个的值,就写-2就可以了.
"$slice"也可以指定偏移量和返回的元素数量,例如:
    
    
    
    
> db.test3.findOne({"name":"brent"},{"fav":{"$slice":[1,2]}})
{
"_id" : ObjectId("573e874c9e178b5475b29d8e"),
"name" : "brent",
"fav" : [
"film",
"read"
]
}
这表示跳过前面1个,从第二个开始返回两个元素,如果元素不够则返回所有的元素.
除非特别声明,否则使用"$slice"将返回文档中的全部键.

返回一个匹配的数组元素

如果知道元素的下标,那么"$slice"非常有用,如果想返回与查询条件相匹配的任意数组元素,使用$操作符 .
例如下面得到name为bob的,并且显示任意一个评论:
    
    
    
    
> db.test4.findOne()
{
"_id" : ObjectId("573f390c9e178b5475b29d92"),
"name" : "brent",
"comment" : [
{
"name" : "bob",
"content" : "good"
},
{
"name" : "jack",
"content" : "repost"
}
]
}
     
     
     
     
> db.test4.find({"comment.name":"bob"},{"comment.$":1})
{ "_id" : ObjectId("573f390c9e178b5475b29d92"), "comment" : [ { "name" : "bob", "content" : "good" } ] }

数组和范围查询的相互作用

如果文档中的某一个字段是一个数组,而这时使用范围查询的匹配,例如"$gt","$lt",那么只要数组中的任意一个元素与之匹配,则也会返回.
例如当我们想找到10<x<20的文档的时候,有一个文档的x是一个数组有两个值[5,25],那么这个文档将也会被匹配,这是因为5是<20,而25>10
所以满足10<x<20,虽然并不是任意一个元素都满足此条件.
有几种方法可以避免这个问题.可以使用 "$elemMatch",要求mongodb同时使用查询条件中的两个语句与一个数组元素进行比较,但是"$elemMatch" 不会匹配非数组元素.
    
    
    
    
> db.test.find({"x":{"$elemMatch":{"$gt":10,"$lt",20}}})
如果当前查询的字段上创建有索引,可以使用min()和max()将查询条件比例的索引范围限制为"$gt"和"$lt"的值:
    
    
    
    
> db.test.find({"x":{"$gt":10,"$lt":20}}).min({"x":10}).max({"x":20})

查询内嵌文档

查询内嵌文档可以使用.键的方式查询,例如下面这个文档
    
    
    
    
> db.test5.findOne()
{
"_id" : ObjectId("5740142abc583612a8464117"),
"name" : {
"first" : "kevin",
"last" : "love"
}
}
如果要查询name为kevin love的人:
    
    
    
    
> db.test5.find({"name.first":"kevin","name.last":"love"})
{ "_id" : ObjectId("5740142abc583612a8464117"), "name" : { "first" : "kevin", "last" : "love" } }

4.$where查询

使用"$where"子句,可以在查询中执行任意的javascript.这样就能在查询中做几乎任何事情,为了安全,应该严格限制或者消除"$where"语句的使用
"$where"最为常用的应用就是比较文档中的两个键是否相等.
不是非常必要的时候不要使用"$where",因为速度上比常规的查询要慢很多,而且无法使用索引,每个文档都要从bson转换成javascipt对象,然后通过"$where"来运行.
在服务器上使用javascipt的时候需要注意,如果使用不当服务器端javascript很容易收到注入攻击.可以在mongod启动的时候指定--noscripting选项

5.游标

数据库使用游标返回find的结果.客户端对游标的实现通常能够对最终结果进行有效的控制.要想从shell中创建游标,先对其查询,然后将结果分配给一个局部变量.
    
    
    
    
> for(i=0;i<100;i++){
... db.t1.insert({x:i})
... }
WriteResult({ "nInserted" : 1 })
     
     
     
     
> var cur=db.t1.find()
这样的好处是可以一次查看一条结果,如果结果没有房子变量中,mongodb shell就会自动迭代(测试发现shell会每次显示20条结果).
而我们要迭代结果,可以使用 next方法,也可以使用 hasNext来查看游标中是否还有其它结果.
    
    
    
    
> while(cur.hasNext()){
... obj=cur.next();
... }
{ "_id" : ObjectId("57401df5bc583612a846417b"), "x" : 99 }
游标还实现了javascript的迭代器接口,可以在forEach循环中使用:
    
    
    
    
> var cur=db.t1.find()
> cur.forEach(function(x){
... print(x.name);
... });
调用find的时候,shell并不立刻查询数据库,耳塞等待真正开始要求获得结果的时候才发送查询,几乎所有的游标对象的方法都返回游标笨死,这样就可以按照任意顺序组成方法链. 所以下面的几个表达式是相同的:
    
    
    
    
> var cur=db.t1.find().sort({"x":1}).limit(1).skip(10);
> var cur=db.t1.find().limit(1).sort({"x":1}).skip(10);
> var cur=db.t1.find().skip(10).limit(1).sort({"x":1});

limit,skip和sort

最常用的查询选项就是限制返回的结果数量,忽略一定的数据的结果和排序.
要限制数量结果,在find后面使用limit函数,例如要返回3条结果可以这样使用:
   
   
   
   
> db.t1.find().limit(3)
{ "_id" : ObjectId("57401df5bc583612a8464118"), "x" : 0 }
{ "_id" : ObjectId("57401df5bc583612a8464119"), "x" : 1 }
{ "_id" : ObjectId("57401df5bc583612a846411a"), "x" : 2 }
skip与limit类似,skip(99)会跳过前99条文档,然后返回余下的文档
    
    
    
    
> db.t1.find().skip(99)
{ "_id" : ObjectId("57401df5bc583612a846417b"), "x" : 99 }
sort接受一个对象作为参数键对应键名,值代表排序方向,1为升序,-1为降序,如果指定多个键,则按照键的指定顺序逐个排序,例如:
    
    
    
    
> db.test2.find().sort({"name":1,"age":-1})
{ "_id" : ObjectId("573e80569e178b5475b29d8c"), "name" : null }
{ "_id" : ObjectId("573e853c9e178b5475b29d8d"), "name" : "BOB" }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14 }
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28 }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10 }
我们发现null是默认是排在升序的第一个,而如果指定的键没有的话是也是排在上面的.
这三个方法可以组合起来使用.这对于分页非常有用.例如想要看一个商品mp3的前50名按照价格从高到低排序:
    
    
    
    
> db.stock.find({"desc":"mp3"}).limit(50).sort({"price":-1})
然后查看下面50条的时候可以使用skip跳过前50条即可:
    
    
    
    
> db.stock.find({"desc":"mp3"}).limit(50).sort({"price":-1}).skip(50)
然而过多的过滤会影响性能.
mongodb在比较不同类型是有一定顺序的,有时一个键有多个类型,那么进行排序的时候是安装类型的优先级进行的.例如上面我们就发现null的优先级就比较低.下面是优先级的从小到大排序:
(1)最小值;
(2)null;
(3)数字(整形,长整形,双精度);
(4)字符串
(5)对象/文档
(6)数组
(7)二进制数据
(8)对象ID
(9)布尔型
(10)日期型
(11)时间戳
(12)正则表达式
(13)最大值

避免使用skip略过大量结果

使用skip跳过大量数据的时候,需要先找到被略过的数据,再进行抛弃,如果数量非常大的话,效率是非常低的,速度会变得很慢.
1.不用skip对结果分页
前面我们已经讲了分页的方法.先使用limit再使用skip跳过前一次的limit的结果.
然而我们可以找到另外的方法进行排序.例如上面的例子查找MP3价格的前50条,我们可以使用下面的语句先找出前50的价格.
>var page1= db . stock . find ({ "desc" : "mp3" }). limit ( 50 ). sort ({ "price" :- 1 })
然后利用最后一个文档中的price作为条件获取下一页,即比最后一个文档中的price更低的价格.
显示最后一页:
    
    
    
    
while(page1.hasNext()){
latest=page1.next();
display(latest)
}
那么下一页就是价格比latest.price更小的50个文档:
    
    
    
    
var page2=db.stock.find({"price":{"$lt":latest.price}});
pages.sort({"price":1}).limit(100);
这样查询中就没有skip了.
2.随机选取文档
可以利用 Math.random()产生一个随机数(0-1),在创建文档时候添加一个随机列,在查询的时候使用random来进行"$lt"或者"$gt"进行匹配.

高级查询选项

有两张类型的查询:简单查询和封装查询.简单查询就像普通的:
    
    
    
    
> db.test2.find({"name":"brent"})
有一些的选择可以用于对查询的封装,例如:
    
    
    
    
> db.t1.find({"name":"brent"}).sort({"x":1})
实际的查询是先将查询封装在一个更大的文档中,shell会把查询从{"name":"brent"}转换成{"$query":{"name":"brent"},"$orderby":{"x":1}}
绝大多数驱动程序提供了辅助函数,用于向查询中添加各种选项,下面列举了其它一些有用的选项:
"$maxscan":integer
指定本次查询中扫描文档数量的上限.
    
    
    
    
> db.t1.find()._addSpecial("$maxscan",1)
"$min":document
指定查询的开始条件,查询会强制使用索引,而且强制扫描索引的下边界.这在复杂查询中很有用
"$max":document
同理如上.强制使用索引,并指定扫描索引的上边界.
"$showDiskLoc:true
在查询结果中添加一个"$showdiskLoc"字段用于显示该条结果在磁盘上的位置 
    
    
    
    
> db.test2.find()._addSpecial("$showDiskLoc",true)
{ "_id" : ObjectId("573e72449e178b5475b29d89"), "name" : "brent", "age" : 28, "$recordId" : NumberLong(1) }
{ "_id" : ObjectId("573e73149e178b5475b29d8a"), "name" : "brent", "age" : 10, "$recordId" : NumberLong(2) }
{ "_id" : ObjectId("573e73ae9e178b5475b29d8b"), "name" : "bob", "age" : 14, "$recordId" : NumberLong(3) }
{ "_id" : ObjectId("573e80569e178b5475b29d8c"), "name" : null, "$recordId" : NumberLong(4) }
{ "_id" : ObjectId("573e853c9e178b5475b29d8d"), "name" : "BOB", "$recordId" : NumberLong(5) }
显示记录的ID.

6.数据库命令

在数据库操作,管理以及监控中,数据库命令都是非常有用的.例如删除集合使用"drop"数据库命令完成.
    
    
    
    
> db.runCommand({"drop":"t1"})
{ "ns" : "suq.t1", "nIndexesWas" : 1, "ok" : 1 }
也许你对shell辅助函数比较熟悉,其实这些辅助函数就是 封装的数据库命令,例如上面的数据库命令也可以使用db.t1.drop()完成.
在shell中运行 db.listCommands()查看所有的数据库命令
数据库命令总是会返回一个包含ok键的文档,如果ok为1说明命令执行成功,如果为0则说明命令执行失败.
如果返回的是0,则还有一个额外的errmsg键,它描述命令执行失败的原因,例如下面再次运行drop命令:
    
    
    
    
> db.runCommand({"drop":"t1"})
{ "ok" : 0, "errmsg" : "ns not found", "code" : 26 }
runCommand命令其实等价于执行一个为$cmd的文档的查询,例如上面的命令等价于:
    
    
    
    
> db.$cmd.findOne({"drop":"t1"})
{ "ok" : 0, "errmsg" : "ns not found", "code" : 26 }
只是这个查询在mongodb内部会进行特殊的处理,几乎所有的Mongodb驱动程序都会提供一个类似runCommand的辅助函数.
有些命令需要管理员权限才能执行,而且有的需要在admin数据库上才能执行.如果当前位于其它数据库,但是需要执行一个管理员命令,可以使用adminCommand而不是runCommand:
    
    
    
    
> db.adminCommand({shutdown:1})
2016-05-21T21:28:43.942+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
 
2016-05-21T21:28:43.943+0800 I NETWORK [thread1] trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2016-05-21T21:28:43.944+0800 W NETWORK [thread1] Failed to connect to 127.0.0.1:27017, reason: errno:111 Connection refused
2016-05-21T21:28:43.944+0800 I NETWORK [thread1] reconnect 127.0.0.1:27017 (127.0.0.1) failed failed
另外注意mongodb中的数据库命令与字段顺序是有关的.命令名称必须是第一个字段.!




























你可能感兴趣的:(mongodb,find)