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

简介:

【数组】


查询数组很容易,对于数组,我们可以这样理解:数组中每一个元素都是这个键值对键的一个有效值,如下面的例子:我们要查询出售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.   















本文转自UltraSQL51CTO博客,原文链接: http://blog.51cto.com/ultrasql/1850791,如需转载请自行联系原作者




相关文章
|
12月前
|
NoSQL MongoDB 微服务
微服务——MongoDB常用命令——文档的分页查询
本文介绍了文档分页查询的相关内容,包括统计查询、分页列表查询和排序查询。统计查询使用 `count()` 方法获取记录总数或按条件统计;分页查询通过 `limit()` 和 `skip()` 方法实现,控制返回和跳过的数据量;排序查询利用 `sort()` 方法,按指定字段升序(1)或降序(-1)排列。同时提示,`skip()`、`limit()` 和 `sort()` 的执行顺序与编写顺序无关,优先级为 `sort()` > `skip()` > `limit()`。
413 1
|
12月前
|
JSON NoSQL MongoDB
微服务——MongoDB常用命令——文档基本CRUD
本文介绍了MongoDB中文档的基本操作,包括插入、查询、更新和删除。单个文档插入使用`insert()`或`save()`方法,批量插入用`insertMany()`。查询所有文档用`find()`,条件查询可在`find()`中添加参数,投影查询控制返回字段。更新文档通过`update()`实现,支持覆盖修改、局部修改(使用`$set`)和批量修改。列值增长可用`$inc`实现。删除文档用`remove()`,需谨慎操作以免误删数据。此外,文档键值对有序,区分大小写,不能有重复键。
269 1
|
12月前
|
NoSQL 测试技术 MongoDB
微服务——MongoDB实战演练——根据上级ID查询文章评论的分页列表
本节介绍如何根据上级ID查询文章评论的分页列表,主要包括以下内容:(1)在CommentRepository中新增`findByParentid`方法,用于按父ID查询子评论分页列表;(2)在CommentService中新增`findCommentListPageByParentid`方法,封装分页逻辑;(3)提供JUnit测试用例,验证功能正确性;(4)使用Compass插入测试数据并执行测试,展示查询结果。通过这些步骤,实现对评论的高效分页查询。
205 0
|
7月前
|
存储 JSON NoSQL
查询 MongoDB--SPL 轻量级多源混算实践 4
SPL 支持多种数据源连接,包括 MongoDB 等 NoSQL 数据库。通过外部库形式提供驱动,灵活扩展,可实现实时数据计算与混合分析。
|
存储 监控 NoSQL
【赵渝强老师】MongoDB文档级别的并发控制
MongoDB使用WiredTiger存储引擎在文档级别进行并发控制,允许多个写操作同时修改不同文档,但对同一文档的修改需序列化执行。引擎采用乐观锁和意向锁机制处理冲突。通过视频讲解、插入大量文档示例及使用`mongotop`和`db.serverStatus()`命令,演示了如何监控MongoDB的锁信息和读写统计,展示了数据库和集合级别的写锁情况。
352 29
|
SQL NoSQL Java
Java使用sql查询mongodb
通过MongoDB Atlas Data Lake或Apache Drill,可以在Java中使用SQL语法查询MongoDB数据。这两种方法都需要适当的配置和依赖库的支持。希望本文提供的示例和说明能够帮助开发者实现这一目标。
587 17
|
SQL NoSQL Java
Java使用sql查询mongodb
通过使用 MongoDB Connector for BI 和 JDBC,开发者可以在 Java 中使用 SQL 语法查询 MongoDB 数据库。这种方法对于熟悉 SQL 的团队非常有帮助,能够快速实现对 MongoDB 数据的操作。同时,也需要注意到这种方法的性能和功能限制,根据具体应用场景进行选择和优化。
544 9
|
存储 NoSQL MongoDB
MongoDB 查询分析
10月更文挑战第21天
160 1
|
8月前
|
NoSQL MongoDB 数据库
数据库数据恢复—MongoDB数据库数据恢复案例
MongoDB数据库数据恢复环境: 一台操作系统为Windows Server的虚拟机上部署MongoDB数据库。 MongoDB数据库故障: 工作人员在MongoDB服务仍然开启的情况下将MongoDB数据库文件拷贝到其他分区,数据复制完成后将MongoDB数据库原先所在的分区进行了格式化操作。 结果发现拷贝过去的数据无法使用。管理员又将数据拷贝回原始分区,MongoDB服务仍然无法使用,报错“Windows无法启动MongoDB服务(位于 本地计算机 上)错误1067:进程意外终止。”
|
8月前
|
缓存 NoSQL Linux
在CentOS 7系统中彻底移除MongoDB数据库的步骤
以上步骤完成后,MongoDB应该会从您的CentOS 7系统中被彻底移除。在执行上述操作前,请确保已经备份好所有重要数据以防丢失。这些步骤操作需要一些基本的Linux系统管理知识,若您对某一步骤不是非常清楚,请先进行必要的学习或咨询专业人士。在执行系统级操作时,推荐在实施前创建系统快照或备份,以便在出现问题时能够恢复到原先的状态。
811 79

推荐镜像

更多