您的位置:首页 > 数据库 > Mongodb

学习MongoDB--(4-1):MongoDB查询(基本查询条件操作符介绍)

2012-08-07 21:08 513 查看
从前面的学习中,我们得知MongoDB中使用find函数来进行查询。查询最终返回的是一个集合中文档的子集,子集合包括0个文档到这个集合中所有的文档。

【第一个查询参数】

find函数第一个参数是一个文档,其中给出了我们要查询集合中什么样文档的描述。如果我们要查询所有文档,可以不带任何参数调用find函数,或第一个参数为空文档{},如下例:

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
> db.people.find({});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
>


对于第一个代表查询参数的文档,我们该如何理解呢?比如我们执行 db.people.find({"age":18}),这个函数实际表示我们要查询键"age"的值为18的文档,键“age”的值不等于18或不存在这个键的文档均不会被查询到。再比如我们执行这个函数:db.people.find({"name":"jimmy", "age":18}),这个实际表示得是查询键“name”的值为"jimmy"并且键"age"的值为18的文档,即查询文档中各个条件是AND的关系!

在使用第一个参数时,我们需要注意一点:该查询文档中“键值对”中的值必须为常量!

【第二个查询参数】

上例中,我们不指定或只指定find函数第一个参数查询得到的文档,会包含原集合文档所有的键值对。这种情况对于某些键值对特别多的文档并不适用,因为我们可能只关心该文档的某几个键值对。这时我们可以使用find函数的第二个参数,来指定返回的键值对,这样还可以减少传输的数据量从而加快效率。第二个参数同样是个文档,如下例:

> db.people.find({},{"age":1,"name":1});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
> db.people.find({},{"age":1});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18 }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28 }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38 }
> db.people.find({},{"name":1});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "name" : "tim" }
> db.people.find({},{"name":1,"_id":0});
{ "name" : "tom" }
{ "name" : "jimmy" }
{ "name" : "tim" }
>

使用find函数第二个参数,对于这个文档我们有这些要注意的:

1》 对于非"_id"的所有键,其值要么同时不等于0(表明要查询该键值对),要么同时等于0(表明要忽略该键值对),否则执行报错。

2》 对于非“_id”的所有键,如果其值全部为0,则此时如果也指明了键“_id”的情况,则键“_id”的值必须为0,否则执行报错。

3》 对于键“_id”,如果不指名其值,则查询结果中肯定包含这个键值对,如果不想包含,可以指明其值为0即可。

综上,我们总结一下如果需要使用第二个参数,则使用的技巧为:指明所有需要得到的键的值为非0的数字,如果还要过滤键"_id",则再指明"_id"这个键值为0即可。

【查询条件】

上面提到的查询条件,都是精确匹配,即“=”多少。MongoDB中的查询显然还有更复杂的匹配。比如范围,OR子句和取反等。我们分别进行介绍。

“$lt”、“$lte”、“$gt”、“$gte” 就是所有的范围比较操作符,分别对应<、<=、>、>=。将他们组合应用,就可以查询值在某个范围内的文档了,比如我们要查询18-30岁(含)的所有用户:

> db.people.find({"age":{"$gte":18,"$lte":30}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
>

这种范围查询操作符,除用在值为数字的键之上外,对于值为日期的键的范围匹配也尤为好用!比如我们要查询注册日期在2007/05/01前的用户信息,我们可以这样写:

> db.user.find();
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb08d6acd1b2a3fb3170"), "name" : "tom", "age" : 29, "reg
istered" : ISODate("2009-07-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "r
egistered" : ISODate("2009-09-02T16:00:00Z") }
> db.user.find({"registered":{"$lte":new Date("2007/05/01")}});
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
>

有时我们需要查询某个键不等于某个值的文档,我们可以使用这个条件操作符"$ne",他表示不相等。如我们要查询注册用户名不等于“tom”的文档:

> db.user.find({"name":{"$ne":"tom"}});
{ "_id" : ObjectId("5020faf5d6acd1b2a3fb316f"), "name" : "tim", "age" : 40, "reg
istered" : ISODate("2007-03-02T16:00:00Z") }
{ "_id" : ObjectId("5020fb27d6acd1b2a3fb3171"), "name" : "jimmy", "age" : 18, "r
egistered" : ISODate("2009-09-02T16:00:00Z") }
>

条件操作符"$ne"适合于所有类型的值!

【OR查询】

MongoDB中有两种方式进行OR查询:“$in”可以用来查询一个键的多个值,“$or”则更通用一些,可以用来完成多个键值对的组合。我们也分别演示一下:我们要查询奖券号码为10,20,30 的所有投注者的姓名:

> db.raffle.find({})
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
> db.raffle.find({"ticket_no":{"$in":[10,20,30]}}, {"name":1})
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom" }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy" }
>

但如果还要求我们查询出奖券号码为10,20,30 或投注者姓名为“tim”的所有投注信息,我们单纯用"$in"是无法胜任的,我们可以用操作符"$or",“$or”操作符可以组合其他操作符如"$in"拼凑的条件:

> db.raffle.find({})
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
> db.raffle.find({"$or":[{"ticket_no":{"$in":[10,20,30]}},{"name":"tim"}]});
{ "_id" : ObjectId("50210091d6acd1b2a3fb3172"), "name" : "tim", "ticket_no" : 11 }
{ "_id" : ObjectId("5021009bd6acd1b2a3fb3173"), "name" : "tom", "ticket_no" : 20 }
{ "_id" : ObjectId("502100a8d6acd1b2a3fb3174"), "name" : "jimmy", "ticket_no" : 30 }
>

使用“$or”操作符,其值为一个条件数组,数组中各个条件最后通过or组合。使用这个条件操作符有一个最佳实践是:将最宽松的条件放在前面,这样可以加快文档匹配速度!

【$mod 和 $not】

$mod操作符,使用格式为 {"键":{"$mod":[num1, num2]}},查询“键”的值对num1取余,如果这个值等于num2,则整条文档符合条件。如我们要查询所有在其本命年的用户(年龄是12的整数倍):

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
> db.people.find({"age":{"$mod":[12,0]}});
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
>

$not是元条件符,即可以用于任何其他条件之上的,表明取反,还是上面的例子,我们这次要查所有不在其本命年的用户文档信息:

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
> db.people.find({"age":{"$not":{"$mod":[12,0]}}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
>

$not条件符配合正则表达式使用非常强大,这里先预报一下,我们后续会提到引入正则表达式的查询。

【特定于类型的查询】

查询某键,其值为null的文档时,我们会发现一个奇怪的现象,我们先看一个例子:

> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
> db.cc.find({"y":null});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
>

利用值为null的条件貌似可以查询正确的文档,但我们忽略了如果集合中存在没有键"y"的文档,会出现什么情况:

> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
> db.cc.find({"y":null});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
>

果然不出意外,没有这个键的文档同样匹配值为null这种条件,如果我们需要过滤掉这种文档,需要另外一个条件操作符$exists,指明这个键必须存在:

> db.cc.find();
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
{ "_id" : ObjectId("50210867d6acd1b2a3fb3177"), "x" : 0, "y" : 1 }
{ "_id" : ObjectId("5021086ad6acd1b2a3fb3178"), "x" : 0, "y" : 2 }
{ "_id" : ObjectId("5021097ed6acd1b2a3fb3179"), "x" : 0 }
> db.cc.find({"y":{"$in":[null],$exists:true}});
{ "_id" : ObjectId("50210861d6acd1b2a3fb3176"), "x" : 0, "y" : null }
>

我们发现,因为MongoDB中没有提供类似于"$eq"这种相等的条件操作符,所以“=null”的判断只能通过{"$in":[null]}来实现!

【正则表达式】

正则表达式在任何语言中都是操作字符串的一大利器!在MongoDB的查询中,其依然威力不减。正则表达式可以灵活的匹配字符串类型的值。如我们要查询所有姓名为“joy”开头并且忽略大小写的用户文档:

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
> db.people.find({"name":/joe.*/i});
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
>

Shell中写正则表达式的方式和JavaScript的一致,写在一对“ // ”之间的就是正则表达式。具体正则表达式的一些写法大家可以参考正则表达式的相关规范。我们上面提到了操作符$not和正则表达式的联合使用,我们这边也演示一下:

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
> db.people.find({"name":{"$not":/joe.*/i}});
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
>

我们可以看到使用$not是将其作为正则表达式的键,表明和这个正则表达式不匹配。我们在最前边也介绍了,MongoDB支持正则表达式这种数据类型,即“键值对”中允许“值”为正则表达式,对于这种键值对,正则表达式也可以匹配成功:

> db.people.find();
{ "_id" : ObjectId("501dffc605f64f64b0765c53"), "age" : 18, "name" : "tom" }
{ "_id" : ObjectId("501dffcc05f64f64b0765c54"), "age" : 28, "name" : "jimmy" }
{ "_id" : ObjectId("501dffd005f64f64b0765c55"), "age" : 38, "name" : "tim" }
{ "_id" : ObjectId("50210579d6acd1b2a3fb3175"), "age" : 36, "name" : "cliff" }
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
{ "_id" : ObjectId("50210f63d6acd1b2a3fb317d"), "age" : 99, "name" : /joe/i }
> db.people.find({"name":/joe/i});
{ "_id" : ObjectId("50210c32d6acd1b2a3fb317a"), "age" : 19, "name" : "Joe" }
{ "_id" : ObjectId("50210c3cd6acd1b2a3fb317b"), "age" : 29, "name" : "JoE" }
{ "_id" : ObjectId("50210c40d6acd1b2a3fb317c"), "age" : 29, "name" : "JoEy" }
{ "_id" : ObjectId("50210f63d6acd1b2a3fb317d"), "age" : 99, "name" : /joe/i }
>

但注意正则表达式的匹配必须是完全匹配,即正则表达式的写法完全相同才可以匹配成功(这个在实际应用中基本不会遇见的)。

MongoDB可以为前缀型的正则表达式(/^joe/i)查询使用索引,所以这种前缀型的正则表式查询速度会很快!

这里再稍微提一下find函数和findOne函数的区别,刚才说了,find函数返回的是一个子集,而findOne就是返回一条文档或null(未查询到结果),对于一些可以接受文档做参数的函数如insert,可以直接使用findOne的返回值作为参数。

最后就这基本的查询条件操作符部分稍作总结,因为我们前面介绍修改器操作符也是以"$"开头,如“$set”,"$inc"等,这里介绍的查询条件操作符也是以"$"开头,那使用上有哪些区分呢?修改器操作符都是外层文档的键,而查询条件的操作符基本都是内层文档的键(注意“$or”是一个例外)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐