MongoDB学习笔记三:查询

时间:2022-04-24 23:43:53

MongoDB中使用find来进行查询。查询就是返回一个集合中文档的子集,子集合的范围从0个文档到整个集合。find的第一个参数决定了要返回哪些文档,其形式也是一个文档,说明要执行的查询细节。
空的查询文档{}会匹配集合的全部内容。要是不指定查询文档,默认就是{}。
例如:
> db.c.find()
将返回集合c中的所有内容。
查找所有"age"的值为27的文档:
> db.users.find({"age" : 27})
查找所有值为"joe"的"username"键:
> db.users.find({"username" : "joe"})
可以通过向查询文档加入多个键/值对的方式来将多个查询条件组合在一起。例如,查询所有用户名为"joe"且年龄为27岁的用户:
> db.users.find({"username" : "joe", "age" : 27})
『指定返回的键』
有时并不需要将文档中的所有键/值对都返回。遇到这种情况,可以通过find(或者findOne)的第二个参数来指定想要的键。
例如,如果只对用户集合的"username"和"email"键感兴趣,可以使用如下查询返回这些键:
> db.users.find({}, {"username" : 1, "email" : 1})
也可以用第二个参数来剔除查询结果中的某个键/值对。例如,文档中有很多键,但是不希望结果中含有"fatal_weakness"键:
> db.users.find({}, {"fatal_weakness" : 0})
也可以用来防止返回"_id":
> db.users.find({}, {"username" : 1, "_id" : 0})
查询条件
比较操作符"$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}})
使用条件操作符"$ne"表示"不相等"。
例:查询所有名字不为"joe"的用户:
> db.users.find({"username" : {"$ne" : "joe"}})
"$ne"能用于所有类型的数据。
『OR查询』
MongoDB中有两种方式进行OR查询:"$in"用来查询一个键的多个值;"$or"用来完成多个键值的任意给定值。
对于单一键要是有多个值与其匹配的话,就要用"$in"加一个条件数组。例如,抽奖活动的中奖号码是725、542和390.要找出全部这些中奖数据,可以构建如下查询:
> db.raffle.find({"ticket_no" : {"$in" : [725, 542, 390]}})
"$in"可以指定不同的类型的条件和值。例如,在逐步将用户名的ID号迁移成用户名的过程中,要做兼顾二者的查询:
> db.users.find({"user_id" : {"$in" : [12345, "joe"]}})
这会匹配"user_id"等于12345的文档,也会匹配"user_id"等于"joe"的文档。
如果"$in"对应的数组只有一个值,那么和直接匹配这个值效果是一样的。例如,{ticket_no : {$in : [725]}}等价于{ticket_no : {$in : 725}}。
与"$in"相对的是"$nin",将返回与数组中所有条件都不匹配的文档。要是想返回所有没有中奖的人,就可以用如下方法进行查询:
> db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})
查询将会返回没有那些号码的人。
"$or"接受一个包含所有可能条件的数组作为参数。例:找"ticket_no"为725或者"winner"为true的文档:
> db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
"$or"可以含有其他条件句。例如,如果想要将"ticket_no"与那三个值匹配上,外加"winner"键,就可以这么做:
> db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725, 542, 390]}}, {"winner" : true}]})
『$not』
"$not"是元条件句,即可以用在任何其他条件之上。例:对于取模运算符"$mod"来说。"$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]}})
!"$not"与正则表达式联合使用的时候极为有用,用来查找那些与特定模式不符的文档。
『条件句的规则』
条件句是内层文档的键,而修改器则是外层文档的键。
一个键可以由多个条件,但是一个键不能对应多个更新修改器。
『特定于类型的查询』
"null"不仅能匹配自身,而且能匹配"不存在的"。
如果仅仅想要匹配键值为null的文档,既要检查该键的值是否为null,还要通过"$exists"条件判定值已经已存在:
> db.c.find({"z" : {"$in" : [null], "$exists" : true}})
『正则表达式』
例:想要查找所有名为Joe或者joe的用户,就可以使用正则表达式执行忽略大小写的匹配:
> db.users.find({"name" : /joe/i})
匹配各种大小写的joe以及joey:
> db.users.find({"name" : /joe?/i})
MongoDB使用Perl兼容的正则表达式(PCRE)库来匹配正则表达式,PCRE支持的正则表达式语法都能被MongoDB所接受。
MongoDB可以为前缀正则表达式(比如/^joey/)查询创建索引,所以这种类型的查询会非常高效。
正则表达式也可以匹配自身。虽然几乎没有人直接将正则表达式插入到数据库中,但是万一这么做了,也是可以用自身匹配的:
> db.foo.insert({"bar" : /baz/})
> db.foo.find({"bar" : /baz/})
查询数组
数组绝大多数情况下可以这样理解:每一个元素都是整个键的值。例如,如果数组是一个水果清单,比如下面这样:
> db.food.insert({"fruit" : ["apple", "banana", "peach"]})
下面的查询:
> db.food.find({"fruit" : "banana"})
会成功匹配该文档。
①$all
通过多个元素来匹配数组,使用"$all"。
例如,假设创建包含3个元素的如下集合:
> db.food.insert({"_id" : 1, "fruit" : ["apple", "banana", "peach"]})
> db.food.insert({"_id" : 2, "fruit" : ["apple", "kumquat", "orange"]})
> db.food.insert({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})
要找到既有"apple"又有"banana"的文档,就得用"$all"来查询:
> db.food.find(fruit : {$all : ["apple", "banana"]})
db.food.insert({"_id" : 1, "fruit" : ["apple", "banana", "peach"]})
db.food.insert({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})
要是想查询数组指定位置的元素,则需使用key.index语法指定下标,如:
> db.food.find({"fruit.2" : "peach"})
数组下标从0开始,上面的表达式会用数组的第3个元素和"peach"匹配。
②$size
"$size"用于查询指定长度的数组。例:
> db.food.find({"fruit" : {"$size" : 3}})
③$slice操作符
find的第二个参数是可选的,可以指定返回那些键。"$slice"返回数组的一个子集合。
例如,假设现在有一个博客文章的文档,要想反悔前10条评论,可以:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : 10}})
也可以返回后10条评论,只要-10就可以了:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -10}})
"$slice"也可以接受偏移值和要返回的元素数量,来返回中间的结果:
> db.blog.posts.findOne(criteria, {"comments" : {"$slice" : [23, 10]}})
这个操作会跳过前23个元素,返回第24个~第33个元素。如果数组不够33个元素,则返回第23个元素后面的所有元素。
使用"$slice"默认返回文档中的所有键。
『查询内嵌文档』
有两种方法查询内嵌文档:查询整个文档,或者只针对键/值对进行查询。
例,对于如下文档:
{
"name" : {
"first" : "Joe"
"last" : "Schmoe"
},
"age" : 45
}
要查询姓名为Joe Schmoe的人可以这样:
> db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}})
可以使用点表示法查询内嵌的键:
> db.people.find({"name.first" : {"first" : "Joe", "last" : "Schmoe"}})
例:假设有博客文章若干,要找到由Joe发表的5分以上的评论。要正确地指定一组条件,而不用指定每个键,要使用"$elemMatch"。这种模糊的命名条件句能用来部分指定匹配数组中的单个内嵌文档的限定条件:
> db.blog.find({"comments" : {"$elemMatch" : {"author" : "joe", "score" : {"$gte" : 5}}}})
"$elemMatch"将限定条件进行分组,仅当需要对一个内嵌文档的多个键操作时才会用到。
『$where查询』
使用"$where"可以执行任意JavaScript作为查询的一部分。
最典型的应用就是比较文档中的两个键的值是否相等。例如,有个条目列表,如果其中的两个值相等则返回文档。如下示例:
> db.foo.insert({"apple" : 1, "banana" : 6, "peach" : 3})
> db.foo.insert({"apple" : 8, "spinach" : 4, "watermelon" : 4})
第二个文档中,"spinach"和"watermelon"的值相同,所以需要返回该文档。MongoDB似乎用于不会提供一个$条件符来做这个,所以只能用"$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;
}});
如果函数返回true,文档就作为结果的一部分被返回;如果为false,则不然。
也可以用一个字符串来指定"$where"查询。下面两种表达式是完全等价的:
> db.foo.find({"$where" : "this.x + this.y == 10"})
> db.foo.find({"$where" : "function() { return this.x + this.y == 10; }"})
"$where"在速度上比常规查询慢很多。
『游标』
要想从shell中创建一个游标,首先要对集合填充一些文档,然后对其执行查询,并将结果分配给一个局部变量(用var生命的变量就是局部变量)。这里,先创建一个简单的几何,而后做个查询,并用cursor变量保存结果:
> for(i=0; i<100; i++) {
db.collection.insert({x : i});
}
> var cursor = db.collection.find();
要跌待结果,可以使用游标的next方法。也可以使用hasNext来查看有没有其他结果。典型的结果遍历如下:
> while (cursor.hasNext()) {
obj = cursor.next();
// do stuff
}
cursor.hasNext()检查是否有后续结果存在,然后用cursor.next()将其获得。
游标类还实现了迭代器接口,可以在foreach循环中使用。
> var cursor = db.people.find()
> cursor.forEach(function(x) {
print(x.name);
});
『limit、skip和sort』
要限制结果数量,可在find后使用limit函数。例如,只返回3个结果,可以这样:
> db.c.find().limit(3)
忽略掉前3个匹配的文档,然后返回余下的文档:
> db.c.find().skip(3)
sort用一个对象作为参数:一组键/值对,键对应文档的别名,值代表排序的方向。排序方向可以是1(升序)或者-1(降序)。如果指定了多个键,则按照多个键的顺序逐个排序。例如,要按照"username"升序及"age"降序排序,可以这样写:
> db.c.find().sort({username : 1, age : -1})
这3个方法可以组合使用。这对于分页非常有用。例如,你有个在线商店,有人想搜索mp3。若是想每页返回50个结果,而且按照价格从高到低排序,可以这样写:
> db.stock.find({"desc" : "mp3"}).limit(50).sort({"price" : -1})
点击“下一页”可以看到更多的结果,通过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)最大值
『避免使用skip略过大量结果』
1.不用skip的结果进行分页
最简单的分页方法是用limit返回结果的第一页,然后将每个后续页面作为相对于开始的偏移量返回。
> // do not use: slow for large skips
> var page1 = db.foo.find(criteria).limit(100)
> var page2 = db.foo.find(criteris).skip(100).limit(100)
> var page3 = db.foo.find(criteris).skip(200).limit(100)
...
然而,一般来讲可以找到一种方法实现不用skip的分页,这取决于查询本身。例如,要按照"date"降序显示文档,可以用如下方式获取结果的第一页:
> var page1 = db.foo.find().sort({"date" : -1}).limit(100)
然后,可以利用最后一个文档中"date"的值作为查询条件,来获取下一页:
var latest = null;

// dispaly first page
while (page1.hasNext()) {
latest = page1.next();
display(latest);
}

// get next page
vat page2 = db.foo.find({"date" : {"$gt" : latest.date}});
page2.sort({"date" : -1}).limit(100);
这样查询中就没有skip了。
2.随机选取文档
例:想随机找一个加州的水暖工,可以对"profession"、"state"和"random"建立索引:
> db.people.ensureIndex({"profession" : 1, "state" : 1, "random" : 1})
这样就能很快得出一个结果了。
『高级查询选项』
查询分为包装的和普通的两类。
普通的查询:
> 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
指定查询最多扫描的文档数量
· $min : document
查询的开始条件
· $max : document
查询的结束条件
· $hint : document
指定服务器使用哪个索引进行查询
· $explain : boolean
获取查询执行的细节(用到的索引、结果数量、耗时等),而并非真正执行查询。
· $snapshot : boolean
确保查询的结果是在查询执行那一刻的一致快照
获取一致结果:当使用了"$snapshot"选项,查询就是针对不变的集合视图运行的。
游标内幕:客户端游标以及客户端游标表示的数据库游标。