博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MongoDB查询 之 数组、内嵌文档和$where
阅读量:6243 次
发布时间:2019-06-22

本文共 12634 字,大约阅读时间需要 42 分钟。

【数组】

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

引用自:

定义:   

“    
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,如需转载请自行联系原作者
你可能感兴趣的文章
码栈开发手册(五)---可视化方式开发(模块详解--浏览图)
查看>>
每天一个设计模式之装饰者模式
查看>>
基于自定义日志打印的UDAF调试
查看>>
JVM源码分析之Metaspace解密
查看>>
CSS 各种定位(position)方式的区别
查看>>
每周聚划算 超值软件汇总:云市场迎新年大礼包 专场五折封顶劲省2100元
查看>>
【区块链之技术进阶】扒一扒某乎上面对于区块链的理解(二)
查看>>
如何从PostgreSQL源码分析哪些操作需要超级用户权限 - 阿里云rds superuser提供了哪些权限...
查看>>
用java进行面向对象编程,面向对象是什么意思
查看>>
博拉科技浅谈中国企业的智能制造之路
查看>>
[LeetCode]--29. Divide Two Integers
查看>>
php如何获取原生请求体
查看>>
java web开发 高并发处理
查看>>
PHP 高级编程之多线程(二)
查看>>
ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)
查看>>
AFNetworking 和 ASIHTTPRequest
查看>>
Qt之自定义界面(实现无边框、可移动)
查看>>
MS SQL修改数据库名称
查看>>
【RMAN】使用RMAN duplicate复制同机数据库
查看>>
概率论快速学习03:概率公理补充
查看>>