chapter4 查询

4.1 find简介

    MongoDB中使用find来进行查询。查询就是返回一个集合中文档的子集,子集的范围从0个文档到整个集合。find的第一个参数决定了要返回那些文档,这个参数是一个文档,用于指定查询条件。

>db.c.find()

    将批量返回集合c中的所有文档。

    想要查找"age"值为27的所有文档,直接将这样的键/值对写进查询文档就好了:

>db.users.find({"age":27})

    要是想匹配一个字符串,比如值为"joe"的"username"键,那么直接将键/值对写在查询文档中即可:

>db.users.find({"username":"joe"})
db.users.find({"username":"joe","age":27})

4.1.1 指定需要返回的键

    如果只对用户集合的"username"和"email"键感兴趣,可以:


    文档中有很多键,但是我们不希望结果中含有"fatal_weakness"键:

db.users.find({},{"fatal_weakness":0})

    使用这种方式,也可以把"_id"键剔除掉:

>db.users.find({},{"username":1,"_id":0})
{
  "username":"joe",
}

4.2 查询条件

4.2.1 查询条件

    "$lt","$lte","$gt","$gte"就是全部的比较操作符,分别对应<,<=,>和>=。

    查询18-30岁(含)的用户,就可以像下面这样:

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

    查找在2007年1月1日前注册的人,可以:

>start=new Date("01/01/2007")
>db.users.find({"registered":{"$lt":start}})

    查询所有名字不为joe的用户,可以:

>db.users.find({"username":{"$ne";"joe"}})

4.2.2 OR查询

    例如,抽奖活动的中奖号码是725、542、390.要找出全部的中奖文档的话,可以构建如下查询:

>db.raffle.find({"ticket_no":{"$in":[725,542,390]}})
>db.users.find({"user_id":{"$in":[12345,"joe]})

    这会匹配"user_id"等于12345的文档,也会匹配"user_id"等于"joe"的文档。

    查询返回所有没有中奖的人:

>db.raffle.find({"ticket_no":{"$nin":{752,542,390]}})

    希望匹配到中奖的"ticket_no",或者"winner"键的值为true的文档,就可以这么做:

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

4.2.3 $not

>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":{"$mod":[5,1]}}})

4.2.4 条件语义

    查找年龄为20-30的所有用户,可以在"age"键上使用"$gt"和"$lt":

>db.users.find({"age":{"$lt":30,"$gt":20}})

    一个键可以有任意多个条件,但是一个键不能对应多个更新修改器。例如,修改器文档不能同时含有{"$inc":{"age":1},"$set":{age:40}},因为修改了"age"两次。但是对于查询条件句就没有这种限定。

>db.users.find({"$and":[{"x":{"$lt":1}},{"x":4}]})

    这个查询会匹配那些"x"字段的值小于等于1并且等于4的文档。比如{"x":[0,4]},那么这个文档就与查询条件向匹配。如果把上面的查询改成下面这样,效率会更高:

>db.users.find({"x":{"$lt":1,"$in":[4]}})

4.3 特定类型的查询

> db.c.insert({"y":null})
WriteResult({ "nInserted" : 1 })
> db.c.insert({"y":1})
WriteResult({ "nInserted" : 1 })
> db.c.insert({"y":2})
WriteResult({ "nInserted" : 1 })
> db.c.find()
{ "_id" : ObjectId("5ab04d2791589385765220dd"), "y" : null }
{ "_id" : ObjectId("5ab04d2c91589385765220de"), "y" : 1 }
{ "_id" : ObjectId("5ab04d3191589385765220df"), "y" : 2 }
> db.c.find({"y":null})
{ "_id" : ObjectId("5ab04d2791589385765220dd"), "y" : null }
> db.c.find({"z":null})
{ "_id" : ObjectId("5ab04d2791589385765220dd"), "y" : null }
{ "_id" : ObjectId("5ab04d2c91589385765220de"), "y" : 1 }
{ "_id" : ObjectId("5ab04d3191589385765220df"), "y" : 2 }

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

> db.c.find({"z":{"$in":[null],"$exists":true}})

4.3.2 正则表达式

    查找所有名为Joe或者joe的用户,就可以使用正则表达式执行不区分大小写的匹配:

>db.users.find({"name":/joe/i})
    匹配如"joey"这样的键:
>db.users.find({"name":/joey?/i})
> db.foo.find({"bar":/baz/})
{ "_id" : ObjectId("5ab04f3091589385765220e0"), "bar" : /baz/ }

4.3.3 查询数组

> db.food.insert({"fruit":["apple","banana","peach"]})
WriteResult({ "nInserted" : 1 })
> db.food.find({"fruit":"banana"})
{ "_id" : ObjectId("5ab04f9391589385765220e1"), "fruit" : [ "apple", "banana", "peach" ] }

    这个查询好比我们对一个这样的(不合法)文档进行查询:{"fruit":"apple","fruit":"banana","fruit":"peach"}。

1.$all
> db.food.insert({"_id":1,"fruit":["apple","banana","peach"]})
WriteResult({ "nInserted" : 1 })
> db.food.insert({"_id":2,"fruit":["apple","kumquat","orange"]})
WriteResult({ "nInserted" : 1 })
> db.food.insert({"_id":3,"fruit":["cherry","banana","apple"]})
WriteResult({ "nInserted" : 1 })

    要找到既有"apple"又有"banana"的文档,可以使用"$all"来查询:

> db.food.find({fruit:{$all:["apple","banana"]}})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
{ "_id" : 3, "fruit" : [ "cherry", "banana", "apple" ] }
> db.food.find({"fruit":["apple","banana","peach"]})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
> db.food.find({"fruit":["apple","banana"]})
> db.food.find({"fruit":["banana","apple","peach"]})
> db.food.find({"fruit.2":"peach"})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
2.$size
> db.food.find({"fruit":{"$size":3}})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
{ "_id" : 2, "fruit" : [ "apple", "kumquat", "orange" ] }
{ "_id" : 3, "fruit" : [ "cherry", "banana", "apple" ] }

    得到一个长度范围内的文档是一种常见的查询。"$size"并不能与其他查询条件(比如"$gt")组合使用,但是这种查询可以通过在文档中添加一个"size"键的方式来实现。这样每一次向指定数组添加元素时,同时增加"size"的值。比如,原本这样的更新:

>db.food.uupdate(criteria,{"$push":{"fruit":"strawberry"}})

    就要变成下面这样:

> db.food.update(criteria,
... {"$push":{"fruit":"strawberry"},"$inc":{"size":1}})
>db.food.find({"size":{"$gt":3}})
3.$slice操作符

    假设现在有一个博客文章的文档,我们希望返回前10条评论,可以这样做:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":10}})

    也可以返回后10条评论,只要在查询条件中使用-10就可以了:

>db.blog.posts.findOne(criteria,{"comments":{"$slice":-10}})
>db.blog.posts.findOne(criteria,{"comments":{"$slice":[23,10]}})

    这个操作会跳过前23个元素,返回第24-33个元素。如果数组不够33个元素,则返回第23个元素后面的所有元素。



    用"$slice"来获取最后一条评论:


4.返回一个匹配的数组元素

    返回与查询条件相匹配的任意一个数组元素。可以使用$操作符得到一个匹配的元素。对于上面的博客文章示例,可以用如下的方式得到Bob的评论:


5.数组和范围查询的相互作用
{"x":5}
{"x":15}
{"x":25}
{"x":[5,25]}
> db.test.find({"x":{"$gt":10,"$lt":20}})
{"x":15}
{"x":[5,25]}
>db.test.find({"x":{"$gt":10,"$lt"20}).min({"x":10}).max("x":20})
{"x":15}

4.3.4 查询内嵌文档

> db.people.insert({"name":{"first":"Joe","last":"Schmoe"},"age":45})
WriteResult({ "nInserted" : 1 })
> db.people.find()
{ "_id" : ObjectId("5ab05aaf91589385765220e2"), "name" : { "first" : "Joe", "last" : "Schmoe" }, "age" : 45 }
要查询姓名为Joe Schmoe的人可以这样:
> db.people.find({"name":{"first":"Joe","last":"Schmoe"}})
{ "_id" : ObjectId("5ab05aaf91589385765220e2"), "name" : { "first" : "Joe", "last" : "Schmoe" }, "age" : 45 }
>db.people.find({"name.first":"Joe","name.last":"Schmoe"})

要找到有Joe发表的5分以上的评论。

    内嵌文档的匹配,必须要整个文档完全匹配。

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

4.4 $where查询

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

    希望返回两个键具有相同值的文档。只能用"$where"子句借助JavaScript来完成了:

> db.foo.find({"$where":function(){
... for(var current in this){
...   for(var other in this){
...     if(current!=other&&this[current]==this[other]){
...       return true;
...     }
...   }
... }
... return false;
... }});
{ "_id" : ObjectId("5ab05d7c91589385765220e4"), "apple" : 8, "spinach" : 4, "watermelon" : 4 }

    服务器端脚本

        在服务器上执行JavaScript时必须注意安全性。如果使用不当,服务器端JavaScript很容易受到注入攻击,与关系型数据库中的注入攻击类似。不过,只要在接受输入时遵循的一些规则,就可以安全地使用JavaScript。也可在运行mongod时指定--noscripting选项,完全关闭JavaScript执行。

    例如,假如你希望打印一句"Hello,name!",这里的name是由用户提供的。

> func="function(){print('Hello,"+name+"!');}"

    如果这里的name是一个用户定义的变量,它可能会是"');db.dropDatabase();print('"这样一个字符串,

>func="function(){pirnt('Hello,');db.dropDatabase();print('!');}"

    如果执行这段代码,你的整个数据库就会删除!

    为了避免这种情况,应该使用作用域来传递name的值。以Python为例:

func=pymongo.code.Code("function(){print('Hello,'+username+'!');}",{"username":name})

    现在数据库会输出如下的内容,不会有任何风险:

Hello,');db.dropDatabase();print('!

4.5 游标

    数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强大的操作。

    先创建一个简单的集合,而后做个查询,并用cursor变量保存结果:

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

    遍历:

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

    游标类还实现了JavaScript的迭代器接口,所以可以在foreach循环中使用:

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

    几乎游标对象的每个方法都返回游标本身,这样就可以按任意顺序组成方法链。例如,下面几种表达式时等价的:


>cursor.hasNext()

    这时,查询被发送服务器。shell立刻获取前100个结果或者前4MB数据(两者之中较小者),这样下次调用next或者hasNext是就不必再次连接服务器取结果了。

4.5.1 limit,skip和sort

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

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

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

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

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

    按照"username"升序及"age"降序排序。

>db.stock.find({"desc":"mp3"}).limit(50).sort({"price":-1})

    搜索mp3,每页返回50个结果,而且按照价格从高到低排序.

    点击"下一页"可以看到更多的结果,通过skip也可以非常简单地实现,只需要略过前50个结果就好了:

>db.stock.find({"desc":"mp3"}).limit(50).skip(50).sort({"price":-1})

比较顺序

    MongoDB处理不同类型的数据有一定顺序的。有时一个键的值可能是多种类型的,优先级从小到大,其顺序如下:

(1)最小值;

(2)null;

(3)数字(整型、长整型、双精度)

(4)字符串;

(5)对象/文档

(6)数组;

(7)二进制数据;

(8)对象ID

(9)布尔型

(10)日期型

(11)时间戳

(12)正则表达式

(13)最大值

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

    用skip略过少量的文档还是不错的。但是要数量非常多的话,skip就会变得非常慢,因为要先找到需要被略过的数据,然后再抛弃这些数据。

1.不用skip对结果分页


    不用skip的情况下实现分页,这取决于查询本身。例如,要按照"date"降序显示文档列表。可以用如下方式获取结果的第一页:

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

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



2.随机选取文档


    这种选取随机文档的做法效率太低:首先得计算总数(要是有查询条件就会很费时),然后用skip略过大量结果也会非常耗时。

    在插入文档时给每个文档都添加一个额外的随机键。

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

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

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

    偶尔会遇到产生的随机数比集合中所有的随机值都大的情况,那就将条件操作符换一个方向:

> if(result==null){
... result=db.foo.findOne({"random":{"$lt":random}})
... }

    例如,想在加州随机找一个水暖工,可以对"profession","state",和"random"建立索引:

>db.people.ensureIndex({"profession":1,"state":1,"random":1})

4.5.3 高级查询选项

    有两种类型的查询:简单查询(plain query)和封装查询(wrapped query)

var cursor=db.foo.find({"foo":"bar"}) // 简单查询
var cursor=db.foo.find({"foo":"bar"}).sort({"x":1}) // 封装查询

    实际情况不是将{"foo":"bar"}作为查询直接发送给数据库,而是先将查询封装在一个更大的文档中。shell会把查询从{"foo":"bar"}转换成{"$query":{"foo":"bar"},"$orderby":{"x":1}}。

  • $maxscan:integer

    指定本次查询中扫描数量的上限:

>db.foo.find(criteria)._addSpecial("$maxscan",20)
  • $min:document

    查询的开始条件。在这样的查询中,文档必须与索引的键完全匹配。查询中会强制使用给定的索引。在内部使用时,通常应该使用"$gt"代替"$min"。可以使用"$min"强制指定一次索引扫描的下边界,这在复杂查询中非常有用。

  • $max:document
  • $showDiskLoc:true

    在查询结果中添加一个"$diskLoc"字段,用于显示该条结果在磁盘上的位置。例如:


    文件号码显示了这个文档所在的文件。如果这里使用的是test数据库,那么这个文档就在test.2文件中.第二个字段显示的是该文档在文件中的偏移量。

4.5.4 获取一致结果

    数据处理通常的做法就是先把数据从MongoDB中取出来,然后做一些变换,最后再存回去:


    但是如果结果集比较大,MongoDB可能会多次返回同一个文档。




    应对这个问题的方法就是对查询进行快照(snapshot)。如果使用了这个选项,查询就在"_id"索引上遍历执行,这样可以保证每个文档只被返回一次。

    db.foo.find()改为:>db.foo.find().snapshot()

    快照会使查询变慢,所以应该只在必要时使用快照。

4.5.5 游标声明周期

    看待游标有两种角度:客户端的游标以及客户端游标表示的数据库游标。

    在服务器端,游标消耗内存和其他资源。游标遍历尽了结果以后,或者客户端发来消息要求终止,数据库将会释放这些资源。释放的资源可以被数据库另做他用。

    还有一种情况导致游标终止(随后被清理)。首先,游标完成匹配结果的迭代时,它会清除自身。另外,如果客户端的游标已经不再作用域内了,驱动程序会向服务器发送一条特别的消息,让其销毁游标。最后,游标会自动销毁。

4.6 数据库命令

    删除集合是使用"drop"数据库命令完成的:


>db.test.drop()

数据库命令工作原理

    数据库命令总会返回一个包含"ok"键的文档。如果"ok"是0,那么命令的返回文档就会有一个额外的键"errmsg"。


    MongoDB中的命令被实现为一种特殊类型的查询,这些特殊的查询会在$cmd集合上执行。runCommand只是接受一个命令文档,并且执行与这个命令文档等价的插叙。


    当MongoDB服务器得到一个在$cmd集合上的查询时,不会对这个查询进行通常的查询处理,而是会使用特殊的逻辑对其进行处理。几乎所有的MongoDB驱动程序都会提供一个类似runCommand的辅助函数,用于执行命令,而且命令总是能够以简单查询的方式执行。
























































































1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值