【数组】
查询数组很容易,对于数组,我们可以这样理解:数组中每一个元素都是这个键值对键的一个有效值,如下面的例子:我们要查询出售apple的水果店:
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("502251a309248743250688e1"), "name" : "good fruit", "fruits" : [ "banana", "pear", "orange" ] } { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] } > db.fruitshop.find({"fruits":"apple"}); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] } >
我们发现只要包含苹果的数组都能被查询出来。如果要通过多个元素来匹配数组,就需要条件操作符"$all",比如我们要查询既卖apple又卖banana的水果店:
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("502251a309248743250688e1"), "name" : "good fruit", "fruits" : [ "banana", "pear", "orange" ] } { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] } > db.fruitshop.find({"fruits":{"$all":["apple","banana"]}}); { "_id" : ObjectId("502251c109248743250688e2"), "name" : "good fruit", "fruits" : [ "banana", "apple", "tomato" ] } >
我们看,使用“$all”对数组内元素的顺序没有要求,只要全部包含的数组都能查询出来。数组查询也可以使用精确匹配的方式,即查询条件文档中键值对的值也是数组,如:
{ "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] } > db.fruitshop.find({"fruits":["apple","orange","pear"]}); { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } >
如果是精确匹配的方式,MongoDB的处理方式是完全相同的匹配,即顺序与数量都要一致,上述中第一条文档和查询条件的顺序不一致,第三条文档比查询条件文档多一个元素,都没有被匹配成功!
对于数组的匹配,还有一种形式是精确指定数组中某个位置的元素匹配,我们前面提到,数组中的索引可以作为键使用,如我们要匹配水果店售第二种水果是orange 的水果店:
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] } > db.fruitshop.find({"fruits.1":"orange"}); { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] } >
数组索引从0开始,我们匹配第二种水果就用furits.1作为键。
"$size"条件操作符,可以用来查询特定长度的数组的,如我们要查询卖3种水果的水果店:
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } { "_id" : ObjectId("502253c109248743250688e5"), "name" : "good fruit", "fruits" : [ "apple", "orange", "pear", "banana" ] } > db.fruitshop.find({"fruits":{"$size":3}}); { "_id" : ObjectId("5022518d09248743250688e0"), "name" : "big fruit", "fruits" : [ "apple", "pear", "orange" ] } { "_id" : ObjectId("5022535109248743250688e4"), "name" : "fruit king", "fruits" : [ "apple", "orange", "pear" ] } >
但条件操作符"$size"不能和其他操作符连用如“$gt”等,这是这个操作符的一个缺陷。使用这个操作符我们只能精确查询某个长度的数组。如果实际中,在查询某个数组时,需要按其长度范围进行查询,这里推荐的做法是:在这个文档中额外增加一个“size”键,专门记录其中数组的大小,在对数组进行"$push"操作同时,将这个“size”键值加1。如下所示:
> db.fruitshop.find({"name":"big fruit"}); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry" ], "name" : "big fruit", "size" : 4 } > db.fruitshop.update({"name":"big fruit"}, ... {"$push":{"fruits":"banana"}, "$inc":{"size":1}}, false, false); > db.fruitshop.find({"name":"big fruit"}); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit", "size" : 5 } >
但这个方式和修改器"$addToSet"没法配合使用,因为你无法判断这个元素是否添加到了数组中!
find函数的第二个参数用于查询返回哪些键,他还可以控制查询返回数组的一个子数组,如下例:我只想查询水果店售卖说过数组的前两个:
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit" } { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange", "pear" ], "name" : "fruit king" } { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange", "pear", "banana" ], "name" : "good fruit" } > db.fruitshop.find({}, {"fruits":{"$slice":2}}); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear" ], "name" : "big fruit" } { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange" ], "name" : "fruit king" } { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange" ], "name" : "good fruit" } >
“$slice”也可以从后面截取,用负数即可,如-1表明截取最后一个;还可以截取中间部分,如[2,3],即跳过前两个,截取3个,如果剩余不足3个,就全部返回!
> db.fruitshop.find(); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "apple", "pear", "orange", "strawberry", "banana" ], "name" : "big fruit" } { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "apple", "orange", "pear" ], "name" : "fruit king" } { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "apple", "orange", "pear", "banana" ], "name" : "good fruit" } > db.fruitshop.find({}, {"fruits":{"$slice":-1}}); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "banana" ], "name" : "big fruit" } { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ "pear" ], "name" : "fruit king" } { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "banana" ], "name" : "good fruit" } > db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}}); { "_id" : ObjectId("5022518d09248743250688e0"), "fruits" : [ "strawberry", "banana" ], "name" : "big fruit" } { "_id" : ObjectId("5022535109248743250688e4"), "fruits" : [ ], "name" : "fruit king" } { "_id" : ObjectId("502253c109248743250688e5"), "fruits" : [ "banana" ], "name" : "good fruit" } >
如果第二个参数中有个键使用了条件操作符"$slice",则默认查询会返回所有的键,如果此时你要忽略哪些键,可以手动指明!如:
> db.fruitshop.find({}, {"fruits":{"$slice":[3,6]}, "name":0, "_id":0}); { "fruits" : [ "strawberry", "banana" ] } { "fruits" : [ ] } { "fruits" : [ "banana" ] } >
【内嵌文档】
查询文档有两种方式,一种是完全匹查询,另一种是针对键值对查询!内嵌文档的完全匹配查询和数组的完全匹配查询一样,内嵌文档内键值对的数量,顺序都必须一致才会匹配,如下例:
> db.staff.find(); { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 } { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 } { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 } > db.staff.find({"name":{"first":"joe","middle":"bush"}}); { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 } >
针对内嵌文档特定键值对的查询是最常用的!通过点表示法来精确表示内嵌文档的键:
> db.staff.find(); { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 } { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 } { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 } > db.staff.find({"name.first":"joe", "name.middle":"bush"}); { "_id" : ObjectId("50225fc909248743250688e6"), "name" : { "first" : "joe", "middle" : "bush", "last" : "Schmoe" }, "age" : 45 } { "_id" : ObjectId("50225fe209248743250688e7"), "name" : { "first" : "joe", "middle" : "bush" }, "age" : 35 } { "_id" : ObjectId("50225fff09248743250688e8"), "name" : { "middle" : "bush", "first" : "joe" }, "age" : 25 } >
我们看,这样查询,所有有效文档均被查询到了!通过点表示法,可以表示深入到内嵌文档内部的键!利用“点表示法”来查询内嵌文档,这也约束了在插入文档时,任何键都不能包含“.” !!
当内嵌文档变得复杂后,如键的值为内嵌文档的数组,这种内嵌文档的匹配需要一些技巧,如下例:
> db.blogs.findOne(); { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [ { "author" : "joe", "score" : 3, "comment" : "just so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] } > db.blogs.find({"comment.author":"joe", "comment.score":{"$gte":5}}); { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [ { "author" : "joe", "score" : 3, "comment" : "j ust so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] } >
我们想要查询评论中有叫“joe”并且其给出的分数超过5分的blog文档,但我们利用“点表示法”直接写是有问题的,因为这条文档有两条评论,一条的作者名字叫“joe”但分数只有3,一条作者名字叫“jimmy”,分数却给了5!也就是这条查询条件和数组中不同的文档进行了匹配!这不是我们想要的,我们这里是要使用一组条件而不是单个指明每个键,使用条件操作符“$elemMatch”即可!他能将一组条件限定到数组中单条文档的匹配上:
> db.blogs.findOne(); { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [ { "author" : "joe", "score" : 3, "comment" : "just so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] } > db.blogs.find({"comment":{"$elemMatch":{"author":"joe", "score":{"$gte":5}}}}); > db.blogs.find({"comment":{"$elemMatch":{"author":"joe", "score":{"$gte":3}}}}); { "_id" : ObjectId("502262ab09248743250688ea"), "content" : ".....", "comment" : [ { "author" : "joe", "score" : 3, "comment" : "j ust so so!" }, { "author" : "jimmy", "score" : 5, "comment" : "cool! good!" } ] } >
这样做,结果是正确的!利用条件操作符“$elemMatch”可以组合一组条件,并且还能达到的“点表示法”的模糊查询的效果!
【$where】
上面提到的所有的键值对的查询方式,我们也可以看出,已经很强大了!但如果实际中真的遇到一种情况无法用上述方式实现时,不用慌,MongoDB为我们提供了终极武器:"$where",用他可以执行任意JavaScript作为查询的一部分!最典型的应用:一个文档,如果有两个键的值相等,就选出来,否则不选:
> db.fruitprice.find(); { "_id" : ObjectId("50226b4c3becfacce6a22a5b"), "apple" : 10, "banana" : 6, "pear" : 3 } { "_id" : ObjectId("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 } > db.fruitprice.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("50226ba63becfacce6a22a5c"), "apple" : 10, "watermelon" : 3, "pear" : 3 } >
我们可以看出,使用"$where"其实就是写了一个javascript函数,MongoDB在查询时,会将每个文档转换成一个javascript对象,然后扔到这个函数中去执行,通过返回结果来判断其是否匹配!在实际使用中,尽量避免使用"$where" 条件操作符,因为其性能很差!在执行过程中,需要把每个档案转化为javascript对象!如果不可避免,则尽量这样写:find({"other":"......",......,"$where":""}),即将"$where"放最后,作为结果调优,让常规查询作为前置过滤条件!这样能减少一些性能损失!
$where补充:
引用自:https://docs.mongodb.com/manual/reference/operator/query/where/
定义:
“
Use the $where operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system. The $where provides greater flexibility, but requires that the database processes the JavaScript expression or function for each document in the collection. Reference the document in the JavaScript expression or function using either this or obj .
”
限制:
“
-
Do not use global variables.
-
$where evaluates JavaScript and cannot take advantage of indexes. Therefore, query performance improves when you express your query using the standard MongoDB operators (e.g., $gt, $in).
-
In general, you should use $where only when you can’t express your query using another operator. If you must use $where, try to include at least one other standard query operator to filter the result set. Using $where alone requires a collection scan.