zoukankan      html  css  js  c++  java
  • MongoDB查询

    find简介

    MongoDB中使用find来进行查询。查询就是返回一个集合中文档的子集,子集的范围从0个文档到整个集合。find中的第一个参数决定了要返回的哪些文档,这个参数是一个文档,用于指定查询条件。

    空的查询文档(例如{})会匹配集合的全部内容。要不是指定查询文档,默认就是{}. 例如:

    > db.c.find()

    将批量返回集合c中所有的文档。

    使用条件

    > db.users.find({"age": 27})

    指定需要返回的键

    有时并不需要将文档中所有的键/值对都返回。遇到这种情况,可以通过find、findOne的第二参数来指定想要的键。这样做机会节省传输的数据量,又能节省客户端解码文档的时间和内存消耗。

    例如,如果只对用户集合的“username”和"email"键感兴趣,可以使用如下方式返回这些键:

    > db.users.find({}, {"username": 1, "email": 1})

    这种情况默认"_id"这个键总是被返回的,即使没有指定要返回这个键。

    如果我们不希望结果中含有"fatal_weakness"键那么可以如下操作:

    > db.users.find({}, {"username": 1, "_id": 0})

    这样就可以把"_id"键剔除掉。

    有一些查询上的限制

    当查询时,传递给数据库的查询文档的值必须是常量。也就是不能引用文档中其他键的值。例如保存库存有"in_stock"(剩余库存)和"num_sold"(已出售)两个键,想通过下列查询比较是行不通的。

    > db.stock.find({"in_stock": "this.num_sold"})

    查询条件

    查询条件

    "$lt","$lte","$gt"和"$gte"就是全部的比较操作符,分别对应<、<=、>、和>=。可以将其组合起来以便查找一个范围的值。例如查找18-30岁(含)的用户,就可以像下面这样

    >db.users.find({"age": {"$gte": 18, "lte": 30}})

    这样就可以找到"age"字段大于等于18,小于等于30的所有文档。

    这样的范围查询对日期极为有用。例如要查找在2007年1月1日前注册的人,可以像下面这样。

    > start = new Date("01/01/2007")
    > db.users.find({"registered": {"$lt": start}})

    如果是想查询文档的某个键/值不等于某个特定值的情况下,就要使用另一种条件操作符“$ne”,它表示不相等,若是想查询所有名字部位joe的用户,就可以如下查询

    > db.users.find({"username": {"$ne": "joe"}})
    

      

    OR查询

    MongoDB中有两种方式进行OR查询: "$in"可以用来查询一个键的多个值;"$or"更通用一些,可以在多个键中查询任意的给定值。

    > db.raffle.find({"ticker_no": {"$in": [725, 542, 390]}})
    

      "$in"非常灵活可以指定不同类型的条件和值。例如,在逐步将用户的ID好前已成用户名的过程中,查询需要同时匹配ID和用户名:

    这会匹配"user_id"等于12345的文档,也会匹配"user_id"等于"joe"的文档。

    要是"$in"对应的数组只有一个值,那么和直接匹配这个值的效果一样例如 {ticket_no: {"$in": [725]}}  和 {"ticker_no": 725} 效果一样。

    与"$in"相对的是"$nin","$nin"将返回与数组中所有的条件都不匹配的文档比如:

    > db.raffle.find({"ticket_no": {"$nin": [725, 543, 490]}})
    

      

    "$in"可以对单个键做OR 查询,但想要找到"ticket_no"为725或者"winner"为true的文档应该怎么办呢?对于这种情况可以使用 $or

    > db.raffle.find({"$or": [{"ticket_no": 725}, {"winner": true}]})
    

      还可以这样使用:

    > db.raffle.find({"$or": [{"ticket_no": {"$in": [725, 345, 789]}},{"winner": true}]})
    

      

    $not

    "$not"是元条件语句,即可以用在任何其他条件之上。就拿取模运算符"$mod"来说。"$mod"会将查询的值除以第一个给定值,若余数等于第二个给定值则匹配成功:

    > db.users.find({"id_num": {"$mod: [5, 1]}})
    

      上面的查询会返回"id_num"值为1,6,11,16等用户。但是要想返回"id_num"为2、3、4、5、7、8、9、10等用户就要使用"$not"了:

    >db.users.find({"id_num": {"$not": {"$mod": [5, 1]}}})
    

      

    条件语义

    如果比较一下更新修改器和前面的查询文档,就会发现以$开头的键位于在不同的位置。在查询中,"$lt"在内层文档,而更新中"$inc"则是外层文档的键。基本可以肯定:条件语句是内层文档的键,而修改器则是外层文档的键。

    可以对一个键应用多个条件。例如,要查找年龄为20~30的所有用户,可以在"age"键上使用"$gt"和"$lt":

    > db.users.find({"age": {"$lt": 30, "$gt": 20}})
    

      一个键可以有任意多个条件,但是一个键不能对应多个修改器。例如,修改器文档不能勇士含有{"$inc": {"age": 1}, {"$set": {"age": 40}}},因为修改了"age"两次。但是对于查询条件句就没有这种限定。

    有一些元操作符也位于外层文档中,比如"$and"、"$or"和"$nor"。他们的使用形式类似:

    >db.users.find({"$and": [{"x": {"$lt": 1}}, {"x": 4}]})
    

      这个会查询匹配哪些"x"字段的值小于1并且等于4的文档,虽然看起来是矛盾的,但是完全是可能的,比如,如果"x"字段的值是这样的一个{"x": [0, 4]},那么这个文档就与查询条件匹配,但是查询优化器不会对"$and"进行优化,这与其他操作符不同。如果把上面的查询改成下面这样,效率更加高

    >db.users.find({"x": {"$lt": 1, "$in": [4]}})
    

      

    特定类型的查询

    在MongoDB中有一些在查询时会有特殊的表现

    null

    null类型的行为有点奇怪他可以匹配自身,所以有一个包含如下文档的集合:

    > db.c.find()
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7467"), "y" : null }
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7468"), "y" : 1 }
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7469"), "y" : 2 }
    

      就可以按照预期的方式查询"y"键为null的文档:

    > db.c.find({"y": null})
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7467"), "y" : null }
    

      但是null不仅会匹配某个键的值为null的文档,而且还会匹配不包含这个键的文档,所以这种匹配还会返回缺少这个键的所有文档:

    > db.c.find({"z": null})
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7467"), "y" : null }
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7468"), "y" : 1 }
    { "_id" : ObjectId("5cd17b2891b417d2e2ad7469"), "y" : 2 }
    

      如果仅想匹配键值为null的文档,既要检查该键的值是否为null,还要通过"$exists"判断这个键值是不是存在:

    > db.c.find({"z": {"$in": [null], "$exists": true}})
    

      因为没有 "$eq"所以使用"$in"效果是一样的

    正则表达式

    正则表达式可以灵活有效的匹配字符串。例如,想要查找所有名为Joe或者joe的用户,就可以使用正则表达式执行不区分大小写的匹配:

    > db.users.find({"name": /joe/i})
    

      系统可以接受正则表达式的标志(i),但是不是一定要有。现在已经匹配了各种大小写组合形式的joe,如果还希望匹配"joey"这样的值,那么可以稍微修改一下

    > db.users.find({"name": /joe?/i})
    

      正则表达式也可以匹配自身

    查询数组

    查询数组与查询标量的值是一样的。例如有一个水果列表,如下所示:

    > db.food.insert({"friut": ["apple", "banana", "peach"]})
    

      下面查询:

    > db.food.find({"friut": "banana"})
    { "_id" : ObjectId("5cd17ef091b417d2e2ad746e"), "friut" : [ "apple", "banana", "peach" ] }
    

      会成功匹配这个文档,好像我们在查询一个不合法的文档: {"fruit": "apple", "fruit": "banana", "fruit": "peach"} 

    $all

    > db.food.find()
    { "_id" : ObjectId("5cd1800f91b417d2e2ad746f"), "fruit" : [ "apple", "banana", "peach" ] }
    { "_id" : ObjectId("5cd1800f91b417d2e2ad7470"), "fruit" : [ "apple", "kumquat", "orange" ] }
    { "_id" : ObjectId("5cd1800f91b417d2e2ad7471"), "fruit" : [ "cherry", "banana", "apple" ] }
    

     我们要找到既有"apple"又有"banana"的文档,可以使用"$all"来查询:

    > db.food.find({"fruit": {"$all": ["apple", "banana"]}})
    { "_id" : ObjectId("5cd1800f91b417d2e2ad746f"), "fruit" : [ "apple", "banana", "peach" ] }
    { "_id" : ObjectId("5cd1800f91b417d2e2ad7471"), "fruit" : [ "cherry", "banana", "apple" ] }
    

      如果想查询数组特定位置的元素,需使用key.index语法指定下标:

    > db.food.find({"fruit.2": "peach"})
    { "_id" : ObjectId("5cd1800f91b417d2e2ad746f"), "fruit" : [ "apple", "banana", "peach" ] }
    

      

    $size

    “$size”对于查询数组来说也非常有用。顾名思义,可以用它来查询特定长度的数组。例如:

    > db.food.find({"fruit": {"$size": 3}})
    { "_id" : ObjectId("5cd1800f91b417d2e2ad746f"), "fruit" : [ "apple", "banana", "peach" ] }
    { "_id" : ObjectId("5cd1800f91b417d2e2ad7470"), "fruit" : [ "apple", "kumquat", "orange" ] }
    { "_id" : ObjectId("5cd1800f91b417d2e2ad7471"), "fruit" : [ "cherry", "banana", "apple" ] }
    

      得到一个长度范围内的文档是一种常见的查询。"$size"并不能与其他查询条件(“$gt”)联合使用,但是这用查询可以在文档中添加一个"size"字段来时间每次"$push", “size”字段就加一,之后可以对这个字段进行查询。

    > db.food.update(criteria, {"$push": {"fruit": "strawbery"}, {"$inc": {"size": 1}}})
    

      但是这种技巧不能和"$addToSet" 操作符同时使用

    $slice

    find的第二个参数是可选的,可以指定需要返回的键。这个特别的"$slice"操作符可以返回某个键匹配的数组元素的一个子集。

    加入,假设现在有一个博客文章的文档,我们希望返回前十条评论可以这样做:

    > db.blog.posts.findOne(criteria, {"comments": {"slice": 10}})
    

      也可以返回后面10条评论,只要在查询条件中使用-10就可以了:

    > db.blog.posts.findOne(criteria, {"comments": {"slice": -10}})
    

      "$slice"也可以指定偏移值以及希望返回的元素的数量,来返回元素集合中间位置的某些结果:

    > db.blog.posts.findOne(criteria, {"comments": {"$slice": [23, 10]}})
    

      这个操作会跳过前23个元素,返回第24~33个元素。如果数组不够33个元素,则返回第23个元素后面的所有元素。

    返回一个匹配的数组元素

    如果知道元素的下标,那么"$slice"非常有用。但是有时我们希望返回与查询条件相匹配的任意一个数组元素。可以使用$操作符得到一个匹配的元素。对于上面的博客文章示例,可以用如下的方式得到Bob的评论:

    > db.blogs.find({"comments.name": "bob"}, {"comments.$": 1})
    {
          "_id": ObjectId("dfadfafajkldflajdfk32230942903"),
         "comments" : {
               "name": "bob",
               "email": "bob@example.com",
               "content": "good post"
        }  
    }

    注意这样只会返回第一个匹配的文档。如果Bob在这篇博客文章下写过多条评论,只有“comments”数组中的第一条评论会被返回

    数组和范围查询的相互作用

      文档中的标量(非数组元素)必须与查询条件中的每一条语句想匹配。例如,如果使用{"x": {"$gt": 10, "$lt": 20}}进行查询,只会匹配"x"键的值大于10 并且小于20的文档。但是,加入某个文档的"x"字段是一个数组,如果"x"键的某个一元素与查询条件的任意一条语句相匹配(查询条件中的每条语句可以匹配不同的数组元素),那么这个文档也会被返回。

    下面用一个例子来详细说明这种情况。假如有如下所示的文档:

    > db.test.find()
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b37"), "x" : 5 }
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b38"), "x" : 15 }
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b39"), "x" : 25 }
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b3a"), "x" : [ 5, 25 ] }

    如果希望找到"x"键位于10和20之间的所有文档,直接想到的查询方式是使用

    > db.test.find({"x": {"$lt": 20, "$gt": 5}})
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b38"), "x" : 15 }
    { "_id" : ObjectId("5cd2c18cb54d3c27ab3c6b3a"), "x" : [ 5, 25 ] }

    5和25都不位于10和20之间,但是这个文档也返回了,因为25与条件查询中的第一个语句(大于10)相匹配5与查询条件中的第二个语句(小于20)相匹配。

    这使对数组使用范围查询没有用:范围会匹配任意多元素数组。有几种凡是可以得到预期的行为。

    首先,可以使用"$elemMatch"要求同时使用查询条件中的两个语句与一个数组元素比较,但是这个有一个问题,"elemMatch"不会匹配费数组元素:

    > db.test.find({"x": {"elemMatch": {"$gt": 10, "$lt": 20}}})
    >
    

      不会和{"x": 15}相匹配了,因为只查询数组了。

    如果当前查询的字段上创建过索引可以使用min,max将查询条件遍历的索引范围限制为"$gt","$lt"的值:

    > db.test.find({"x": {"$gt": 10, "$lt": 20}}).min({"x": 10}).max({"x": 20})
    {"x": 15}
    

      

    查询内嵌文档

    查询内嵌文档

    简单的查询

    >db.people.find({"name.first": "Joe", "name.last": "Schmoe"})
    

      但是当文档结构变得更加复杂之后,内嵌文档的匹配需要一些技巧。例如假设有博客文章若干,要找到Joe发表的5分以上的评论。博客文章的结构如下例所示:

    > db.blog.find()
    {
        "content": "...",
        "comments": [
            {
                 "author": "joe",
                 "score": 3,
                 "comment": "nice post"
            },
           {
                 "author": "mary",
                 "score": 5,
                 "comment": "terrible post"
            }
        ]
    }

    直接用 > db.blog.find({"comments": {"author": "joe", "score": {"$gte": 5}}}) 也不行因为符合author条件的评论和score条件的评论可能不是同一条评论。也就是说,会返回刚才显示的那个文档,因为“author”: joe 在第一条评论中匹配了,"score": 6在第二条评论中匹配了

    要准确的指定一组条件,而不必指定每个键,就需要使用"elemMatch".。

    > db.blog.find("comments": {"$elemMatch": {"author": "joe", "score": {"$gte": 5}}})
    

      

    $where

    可以使用任何的JavaScript代码

    > db.foo.find({"$where": function() {
        ....
    }})    
    

      如果函数返回true文档就作为结果集的一部分返回;如果为false就不返回。

    limit skip sort

    限制返回结果的数量,忽略一定数量的结果以及排序,所有这些选项一定要在查询被发送到服务器之前指定。

    先限制结果数量,可在find后使用limit函数,例如只返回3个结果,可以这样:

    > db.c.find().limit(3)
    

      要是匹配的结果不到三个,则返回匹配的数量。limit指定的是数量上限而非下限。

    skip和limit相似

    >db.c.find().skip(3)
    

      上面的操作会略过前三个匹配的文档,然后返回余下的文档。如果集合里面能匹配的文档少于3个,则不会返回任何文档。

    sort 接受一个对象作为参数,这个对象是一组键/值对,键对应文档的键名,只代表排序的方向。排序方向可以是1(升序)或者是-1(降序)。如果指定了多个键,则按照这些键被指定的顺序逐个排序,例如按照"username"升序及"age"降序排序,可以这样写:

    >db.c.find().sort({"username": 1, "age": -1})
    

      这三个方法可以组合使用。

    对键的排序是有优先级的,顺序如下:

    1. 最小值
    2. null
    3. 数字(整型,长整型,双精度)
    4. 字符串
    5. 对象/文档
    6. 数组
    7. 二进制数据
    8. 对象ID
    9. 布尔型
    10. 日期型
    11. 时间戳
    12. 正则表达式
    13. 最大值
  • 相关阅读:
    python经典算法面试题1.5:如何找出单链表中的倒数第K个元素
    python经典面试算法题1.4:如何对链表进行重新排序
    巧妙利用引用,将数组转换成树形数组
    设计模式学习笔记
    Vue-cli项目部署到Nginx
    设计模式
    设计模式
    springboot原理
    类加载机制-深入理解jvm
    JVM内存调优原则及几种JVM内存调优方法
  • 原文地址:https://www.cnblogs.com/Stay-J/p/10822146.html
Copyright © 2011-2022 走看看