MongoDB查询 之 数组、内嵌文档和$where

【数组】javascript


查询数组很容易,对于数组,咱们能够这样理解:数组中每个元素都是这个键值对键的一个有效值,以下面的例子:咱们要查询出售apple的水果店:java

    > 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的水果店:sql

    > 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”对数组内元素的顺序没有要求,只要所有包含的数组都能查询出来。数组查询也能够使用精确匹配的方式,即查询条件文档中键值对的值也是数组,如:mongodb

    { "_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的处理方式是彻底相同的匹配,即顺序与数量都要一致,上述中第一条文档和查询条件的顺序不一致,第三条文档比查询条件文档多一个元素,都没有被匹配成功!express


对于数组的匹配,还有一种形式是精确指定数组中某个位置的元素匹配,咱们前面提到,数组中的索引能够做为键使用,如咱们要匹配水果店售第二种水果是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做为键。app


"$size"条件操做符,能够用来查询特定长度的数组的,如咱们要查询卖3种水果的水果店:ide

    > 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.   

相关文章
相关标签/搜索