1.查询条件
(1).比较操作符
"$lt", "$lte", "$gt", "$gte", "$ne"是全部的比较操作符,分别对应<,<=,>,>=,!=
查询年龄包含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"}})
(2).or查询
mongodb中有两种方式进行or查询:"or"更通用一点,可以在多个键中查询任意的给定值。
查询抽奖号码是725, 542, 390
db.raffle.find({"ticket_no" : {"$in" : [725, 542, 390]}})
“$in”非常灵活,允许您指定不同类型的标准以及值。例如,在逐步将用户的id号迁移成用户名的过程,查询时需要同时匹配id和用户名
db.users.find({"user_id" : {"$in" : [12345, "joe"]})
{ticket_no : {$in : [725]}} 和 {ticket_no : 725} 效果是一样的。
与"$in"相反的是"$nin","$nin"返回与条件不匹配的文档,以下是返回没有中奖的人
db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})
"$in"能对单个键做or查询,但是想要找到"ticket_no"为725 或 "winner" 为true的文档,此时只能使用"$or"
db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
"$or"可以包含其他条件
db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725, 542, 390]}},{"winner" : true}]})
注意:虽然"$or"在任何情况下都会正常工作,但是"$in"更高效。
(3).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 与正则表达式联合使用时极为有用,用来查找与那些特定模式不匹配的文档
(4). 条件语意
如果比较一下上一章的更新修改器和前面的查询文档,会发现以$开头的键位于在不同的位置。在查询中, $lt 在内层文档,而更新中 $inc 则是外层文档的键。基本可以肯定:条件语句是内层文档的键,而修改器则是外层文档的键。
可以对一个键应用多个条件,例如,要查找年龄为20-30的所有用户,可以在age键上使用 "$gt" 和 "$lt" :
db.users.find({"age" : {"$lt" : 30, "$gt" : 20}})
一个键可以有任意多个条件,但是一个键不能对应的多个更新修改器。例如,修改器文档不能同时含有
{"$inc" : {"age" : 1}, "$set" : {age : 40}}
因为修改了age两次,但是对于查询条件就没有这种限制。
有一些“元操作符”(metaoperators)也位于外层文档中,比如 "$and", "$or", "$nor" 。他们使用的形式类似:
db.users.find({"$and" : [{"x" : {"$lt" : 1}}, {"x" : 4}]})
这些查询会匹配那些x字段的值小于等于1并且等于4的文档,虽然这两个条件看起来是矛盾的,但是这是完全有可能的,如果x字段的值是这样一个数组{"x": [0, 4]},那么这个文档就与查询条件相匹配。
注意,查询优化器不会对 $and 进行优化,这与其他操作符不同,如果把上面查询改成下面这样,效率会更高:
db.users.find({"x" : {"$lt" : 1, "$in" : [4]}})
2.特定类型的查询
MongoDB具有可在文档中使用的多种类型。这些类型中的某些在查询时具有特殊的行为。
(1).null
null类型比较特别,它确实能匹配到自身,以下面的集合为例进行演示:
db.c.insertMany([{y:null},{y:1},{y:2}])
我们可以按预期方式查询其y键为null的文档:
wang> db.c.find({"y" : null})
{ "_id" : ObjectId("5f6aa19e436043d308441ae7"), "y" : null }
wang>
但是,null不仅自身匹配,而且还匹配不包含这个键的文档。 因此,查询值为空的键将返回所有缺少该键的文档:
wang> db.c.find({"z" : null})
{ "_id" : ObjectId("5f6aa19e436043d308441ae7"), "y" : null }
{ "_id" : ObjectId("5f6aa19e436043d308441ae8"), "y" : 1 }
{ "_id" : ObjectId("5f6aa19e436043d308441ae9"), "y" : 2 }
wang>
如果我们只想查找其值为null的键,则可以检查该键是否为null,还要通过$exists条件判定键值已存在。
db.c.find({"z" : {"$eq" : null, "$exists" : true}})
(2).正则表达式
“$regex” 为查询中的模式匹配字符串提供正则表达式功能。正则表达式对于灵活的字符串匹配非常有用,对于名为Joe或Joe的用户,我们可以使用正则表达式进行不区分大小写的匹配:
db.users.find( {"username" : {"$regex" : /joe/i } })
也可以旧的格式的正则表达式(i并不是必须的,可以省略)
db.users.find({"username" : /joey?/i})
注意:MongoDB可以利用索引来查询前缀常规表达式(例如,/^joey/)。 索引不能用于不区分大小写的搜索(/^joey/i)
(3).查询数组
查询数组元素的设计方式与查询标量的方式相同,例如,如果数组是水果列表,如下所示:
db.food.insertOne({"fruit" : ["apple", "banana", "peach"]})
1). $all
如果需要按多个元素匹配数组,可以使用 “$all” 匹配元素列表。例如,假设我们创建了一个包含三个元素的集合:
db.food.insertOne({"_id" : 1, "fruit" : ["apple", "banana", "peach"]})
db.food.insertOne({"_id" : 2, "fruit" : ["apple", "kumquat", "orange"]})
db.food.insertOne({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})
要通过查询找到包含“apple”和“banana”元素的所有文档,此时使用 “$all” :
db.food.find({fruit : {$all : ["apple", "banana"]}})
这里的顺序无关紧要,注意,第二个结果中banana在apple之前。要是对只有一个元素的数组使用all,就和不用all是一样的。例如,{fruit : {$all : ['apple']} 和 {fruit : 'apple'}的查询结果完全一样。
您还可以使用整个数组进行精确匹配查询。但是精确匹配对于缺少元素或者元素冗余的情况就不大灵了,例如,下面的方法会匹配之前的第一个文档:
db.food.find({"fruit" : ["apple", "banana", "peach"]})
但是下面这个就不会匹配:
db.food.find({"fruit" : ["apple", "banana"]})
这个也不会匹配:
db.food.find({"fruit" : ["banana", "apple", "peach"]})
要是想要查询数组特定位置的元素,需要使用key.index语法
db.food.find({"fruit.2" : "peach"})
数组下标都是从0开始,所以上面 表达式会用数组的第三个元素和peach进行匹配
2). $SIZE
$SIZE 对于查询数组来说是非常有意义的,顾名思义,可以用它来查询特定长度的数组,例如:
wang> db.food.find({"fruit" : {"$size" : 3}})
{ "_id" : 1, "fruit" : [ "apple", "banana", "peach" ] }
{ "_id" : 2, "fruit" : [ "apple", "kumquat", "orange" ] }
{ "_id" : 3, "fruit" : [ "cherry", "banana", "apple" ] }
wang>
得到一个长度范围内的文档是一种常见的查询, $size 并不能与其他查询条件(比如 "$gt" )组合使用,但是这种查询可以通过在文档中添加一个“size”键的方式来实现,这样每次向指定数组添加元素时,同时增加size的值,比如,原本这样更新:
db.food.update(criteria, {"$push" : {"fruit" : "strawberry"}})
以简单地更改为:
db.food.update(criteria,{"$push" : {"fruit" : "strawberry"}, "$inc" : {"size" : 1}})
递增速度非常快,因此任何性能损失都可以忽略不计这样您就可以执行如下查询:
db.food.find({"size" : {"$gt" : 3}})
不幸的是,这种技术在 “$addToSet” 操作符中不起作用。
3). $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 时将返回文档中的所有键。别的键说明符都是默认不返回未提及的键,这点与 $slice 不太一样,例如有如下博客文档:
wang> db.blog.posts.findOne()
{
"_id" : ObjectId("4b329a216cc613d5ee930192"),
"content" : "John",
"comments" : [
{
"comment" : "good post",
"author" : "Jim",
"votes" : 1
},
{
"comment" : "i thought it was too short",
"author" : "Claire",
"votes" : 3
},
{
"comment" : "free watches",
"author" : "Alice",
"votes" : -1
}
]
}
wang>
用 $slice 来获取最后一条评论。可以这样
wang> db.blog.posts.findOne({},{comments:{"$slice":-1}})
{
"_id" : ObjectId("4b329a216cc613d5ee930192"),
"content" : "John",
"comments" : [
{
"comment" : "free watches",
"author" : "Alice",
"votes" : -1
}
]
}
wang>
4).返回匹配数组元素
如果知道元素下标,那么 $slice 非常有用,但是有时我们希望返回与查询条件相匹配的任意一个数组元素,可以使用$操作符得到一个匹配的元素,对于上面的博客文章例子,可以如下方式得到Claire评论
wang> db.blog.posts.findOne({"comments.author" : "Claire"}, {"comments.$" : 1})
{
"_id" : ObjectId("4b329a216cc613d5ee930192"),
"comments" : [
{
"comment" : "i thought it was too short",
"author" : "Claire",
"votes" : 3
}
]
}
wang>
注意,这样只会返回第一个匹配的文档,如果Claire在这篇博客文章下写过多条评论,只有comments数组中的第一条评论会被返回。
5).数组和范围查询交互
文档中的标量(非数组元素)必须与查询条件的每个子句匹配。例如,如果使用 {"x" : {"$gt" : 10, "$lt" : 20}} 进行查询,只会匹配x键的值大于等于10并且小于等于20的文档,
但是,加入某个文档的x字段是一个数组,例如x键的某一个元素与查询条件的任意一条语句相匹配(查询条件中的每条语句可以匹配不同的数组元素),你们这个文档也会被返回。
理解此行为的最佳方法是查看示例。 假设我们有以下文档:
{"x" : 5}
{"x" : 15}
{"x" : 25}
{"x" : [5, 25]}
如果希望找到x键的值位于10和20之间的所有文档,直接想到的查询方式是使用:
db.test.find({"x" : {"$gt" : 10, "$lt" : 20}})
希望这个查询返回文档是{"x" : 15},然而,实际返回了两个文档
> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}})
{"x" : 15}
{"x" : [5, 25]}
5和25都不位于10和20之间,但是这个文档也返回了,因为25与查询条件中的第一个语句(大于10)相匹配,5与查询条件中的第二个语句(小于20)相匹配。
这使得针对数组的范围查询基本上没有用:范围可以匹配任何多个元素数组 有几种方法可以获取预期的行为。
首先可以使用 $elemMatch 要求mongodb同时使用查询条件中的两个语句与一个数组元素进行比较,但是这里有一个问题, $elemMatch 不会匹配非数组的元素:
> db.test.find({"x":{"$elemMatch":{"$gt":10,"$lt":20}}})> // no results
{"x" : 15}这个文档与查询条件不在匹配了,因为他的x字段是个数组
如果当前查询的字段上创建过索引(第5章会讲述索引相关内容),可以使用min()和max()将查询条件遍历的索引范围限制为 "gt"和" lt"
> db.test.find({"x" : {"$gt" : 10, "$lt" : 20}).min({"x" : 10}).max({"x" :
{"x" : 15}
现在,这个查询只会遍历值位于10和20之间的索引,不在与5和25进行比较,只有当前查询的字段上创建过索引时,才可以使用min()和max(),而且,必须为这个索引的所有字段指定min()和max().
在可能包含数组的文档上应用范围查询时,使用min()和max()是非常好的,如果在整个索引范围内对数组使用 "$gt“/”$lt" 查询,效率是非常低的,查询条件会与所有值进行比较,会查询每一个索引,而不仅仅是指索引范围内的值。
(4).查询内嵌文档
有2种方法可以查询内嵌文档:查询整个文档,或者只针对其键/值对进行查询。
查询整个内嵌文档与普通查询完全相同,例如:
db.people.insertOne({name:{first:"Joe",last:"Schmoe"},age:45})
{
"name" : {
"first" : "Joe",
"last" : "Schmoe"
},
"age" : 45
}
要查询姓名为Joe Schmoe的人可以这样:
db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}})
使用点表示法查询内嵌文档
db.people.find({"name.first":"Joe"})
当文档结构变得更加复杂以后,内嵌文档的匹配需要些许技巧,假设有文章若干,要找到Claire发表的2分以上的评论,博客结构文档如下:
db.blog.insertOne({content:"...",comments:[{author:"joe",score:3,comment:"nice post"},{author:"mary",score:6,comment:"terrible post"}]})
wang> db.blog.findOne()
{
"_id" : ObjectId("5f6d4e0219d601f0a1599b2e"),
"content" : "...",
"comments" : [
{
"author" : "joe",
"score" : 3,
"comment" : "nice post"
},
{
"author" : "mary",
"score" : 6,
"comment" : "terrible post"
}
]
}
wang>
不能使用如下查询方式进行查询
db.blog.find({"comments" : {"author" : "joe","score" : {"$gte" : 5}}})
内嵌文档的匹配,必须要整个文档完全匹配。而这个查询不会匹配comment键,使用如下查询方式也不行
wang> db.blog.findOne({"comments.author" : "joe", "comments.score" :{"$gte" : 5}})
{
"_id" : ObjectId("5f6d4e0219d601f0a1599b2e"),
"content" : "...",
"comments" : [
{
"author" : "joe",
"score" : 3,
"comment" : "nice post"
},
{
"author" : "mary",
"score" : 6,
"comment" : "terrible post"
}
]
}
wang>
因为符合author条件的评论和符合score条件的评论可能不是同一条评论,也就是说,会返回刚才显示的那个文档,因为author:"joe"在第一条评论 中匹配了,"score" : 6在第二条评论中匹配了。
要正确的指定一组条件,而不必指定每个键就需要使用 $elemMatch ,这种模糊的命名条件能用来查询条件中部分指定匹配内嵌数组中的单个内嵌文档,所以正确的写法应该是这样:
db.blog.findOne({"comments" : {"$elemMatch" : {"author" : "joe","score" : {"$gte" : 5}}}})
$elemMatch 将限定条件进行分组,仅当需要对一个内嵌文档的多个键操作时才会用到。