zoukankan      html  css  js  c++  java
  • MongoDB 数据库 — 查询方法

    接上篇,本篇专门整理 MongoDB 查询方法。

    4 基本查询

    • 你可以在数据库中使用 find 或者 findOne 函数来执行专门的查询;
    • 你可以查询范围、集合、不等式,也可以使用 $-条件 执行更多的操作;
    • 查询结果是一个数据库游标(cursor),当需要的时候返回你需要的文档。
    • 你可以在 cursor 上执行许多元操作(metaoperations),包括 skipping 一定数量的结果,limiting 返回结果的数量,和 sorting 结果。

    4.1 find

    # find 的第一个参数指定了查询准则
    db.users.find()  #  匹配集合中的所有文档
    db.users.find({"age": 27})
    
    # 多条件查询可以通过增加更多的 key/value 对,可以解释为 *condition1* AND *condition2* AND ... AND *conditionN*
    db.users.find({"username": "joe", "age": 27)
    

    4.1.1 指定返回的键

    find(或者 findOne)的第二个参数指定返回的键,虽然 "_id" 键没有被指定,但是默认返回。也可以指定需要排除的 key/value 对。

    # "_id" 键默认返回
    > db.users.find({}, {"username": 1, "email": 1})
    
    # 结果
    {
        "_id" : ObjectId("4ba0f0dfd22aa494fd523620"),
        "username" : "joe",
        "email" : "joe@example.com"
    }
    
    # 阻止 "_id" 键返回
    > db.users.find({}, {"username": 1, "email": 1, "_id": 0})
    
    # 结果
    {
        "username" : "joe",
        "email" : "joe@example.com"
    }
    

    4.1.2 限制(Limitations)

    数据库所关心的查询文档的值必须是常量,也就是不能引用文档中其他键的值。例如,要想保持库存,有原库存 "in_stock" 和 "num_sold" 两个键,我们不能像下面这样比较两者的值:

    > db.stock.find({"in_stock" : "this.num_sold"})  // doesn't work
    

    4.2 查询准则(Criteria)

    4.2.1 查询条件

    比较操作:"($)lt","($)lte","($)gt","($)gte","($)ne"

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

    4.2.2 OR 查询

    MongoDB 中有两种 OR 查询。"($)in" 用作对一个 key 查询多个值;"($)or" 用作查询多个 keys 给定的值。

    # 单个键,一种类型的值
    > db.raffle.find({"ticket_no" : {"$in" : [725, 542, 390]}})
    
    # 单个键,多种类型的值
    > db.users.find({"user_id" : {"$in" : [12345, "joe"]})
    
    # "$nin" 
    > db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})
    
    # 多个键
    > db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
    
    # 多个键,带有条件
    > db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725, 542, 390]}}, {"winner" : true}]})
    

    4.2.3 $not

    "($)not" 是元条件句,可以用在任何条件之上。例如,取模运算符 "($)mod" 会将查询的值除以第一个给定的值,若余数等于第二个给定值,则返回该结果:

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

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

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

    "$not" 与正则表达式联合使用的时候极为有用,用来查找那些与特定模式不符的文档。

    4.2.4 条件句的规则

    比较更新修改器和查询文档,会发现以 $ 开头的键处在不同的位置。条件句是内层文档的键,而修改器是外层文档的键。可以对一个键应用多个条件,但是一个键不能对应多个修改器。

    
    > db.users.find({"age" : {"$lt" : 30, "$gt" : 20}})
    
    # 修改了年龄两次,错误
    > db.users.find({"$inc" : {"age" : 1}, "$set" : {age : 40}})
    

    但是,也有一些 元操作 可以用在外层文档:"($)and","($)or",和 "($)nor":

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

    看上去这个条件相矛盾,但是如果 x 是一个数组的话:{"x" : [0, 4]} 是符合的。

    4.3 特定类型的查询

    4.3.1 null

    null 表现起来有些奇怪,它不但匹配它自己,而且能匹配 "does not exist",所以查询一个值为 null 的键,会返回缺乏那个键的所有文档。

    > db.c.find()
    
    { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null }
    { "_id" : ObjectId("4ba0f0dfd22aa494fd523622"), "y" : 1 }
    { "_id" : ObjectId("4ba0f148d22aa494fd523623"), "y" : 2 }
    
    > db.c.find({"y" : null})
    
    { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null }
    
    > db.c.find({"z" : null})
    
    { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null } 
    { "_id" : ObjectId("4ba0f0dfd22aa494fd523622"), "y" : 1 }
    { "_id" : ObjectId("4ba0f148d22aa494fd523623"), "y" : 2 }
    

    如果我们想要找到值为 null 的键,我们可以使用 "($)exists" 检查键是 null,并且存在。如下,不幸的是,没有 "($)eq"操作,但是带有一个元素的 "($)in" 和它等价。

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

    4.3.2 正则表达式

    1. MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
    2. MongoDB 使用 Perl Compatible Regular Expression (PCRE) 库匹配正则表达式;任何在 PCRE 中可以使用的正则表达式语法都可以在 MongoDB 中使用。在使用正则表达式之前可以先在 JavaScript shell 中检查语法,看是否是你想要匹配的。
    # 文档结构
    {
       "post_text": "enjoy the mongodb articles on runoob",
       "tags": [
          "mongodb",
          "runoob"
       ]
    }
    
    # 使用正则表达式查找包含 runoob 字符串的文章
    > db.posts.find({post_text:{$regex:"runoob"}})
    
    # 以上操作还可以表示如下
    > db.posts.find({post_text:/runoob/})
    
    # 不区分大小写的正则表达式
    # 如果为 $options: "$s",表示允许点字符(dot character,即 .)匹配包括换行字符(newline characters)在内的所有字符
    > db.posts.find({post_text:{"$regex":"runoob", "$options":"$i"}})
    # 或者 
    > db.posts.find({post_text: /runoob/i}) 
    

    4.3.3 查询内嵌文档

    有两种方式查询内嵌文档:查询整个文档,或者只针对它的键/值对进行查询。

    {
        "name" : {
                "first" : "Joe",
                "last" : "Schmoe"
                },
         "age" : 45
    }
    

    可以使用如下方式进行查:

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

    但是,对于子文档的查询必须精确匹配子文档。如果 Joe 决定去添加一个中间的名字,这个查询就将不起作用,这类查询也是 order-sensitive 的,{"last" : "Schmoe", "first" : "Joe"} 将不能匹配。

    仅仅查询内嵌文档特定的键通常是一个好主意。这样的话,如果你的数据模式改变,也不会导致所有查询突然失效,因为他们不再是精确匹配。可以通过使用 dot-记号 查询内嵌的键:

    > 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" : 6,
                    "comment" : "terrible post"
                }
        ]
    }
    

    查询的方式如下:

    # 错误,内嵌的文档必须匹配整个文档,这个没有匹配 "comment" 键
    > db.blog.find({"comments" : {"author" : "joe", "score" : {"$gte" : 5}}})
    
    # 错误,因为符合 author 条件的评论和符合 score 条件的评论可能不是同一条评论
    > db.blog.find({"comments.author" : "joe", "comments.score" : {"$gte" : 5}})
    
    # 正确,"$elemMatch" 将限定条件进行分组,仅当对一个内嵌文档的多个键进行操作时才会用到
    > db.blog.find({"comments" : {"$elemMatch" : {"author" : "joe", "score" : {"$gte" : 5}}}})
    

    5 聚合(aggregation)

    将数据存储在 MongoDB 中后,我们就可以进行检索,然而,我们可能想在它上面做更多的分析工作。

    5.1 聚合框架(The aggregation framework)

    聚合框架可以在一个集合中转化(transform)混合(combine)文档。基本的,你可以通过几个创建模块(filtering, projecting, grouping, sorting, limiting, and skipping)来建立处理一批文档的管道。

    例如,如果有一个杂志文章的集合,你可能想找出谁是最多产的作者。假设每一篇文章都作为一个文档存储在 MongoDB 中,你可以通过以下几步来创建一个管道:

    # 1. 将每篇文章文档的作者映射出来
    {"$project" : {"author" : 1}}
    
    # 2. 通过名字将作者分组,统计文档的数量
    {"$group" : {"_id" : "$author", "count" : {"$sum" : 1}}}
    
    # 3. 通过文章数量,降序排列作者
    {"$sort" : {"count" : -1}}
    
    # 4. 限制前5个结果
    {"$limit" : 5}
    
    # 在 MonoDB 中,将每个操作传递给 aggregate() 函数
    > db.articles.aggregate( {"$project" : {"author" : 1}},
            {"$group" : {"_id" : "$author", "count" : {"$sum" : 1}}},
            {"$sort" : {"count" : -1}},
            {"$limit" : 5}
            )
    
    # 输出结果,返回一个结果文档数组
        {
            "result" : [ 
                {
                    "_id" : "R. L. Stine",
                    "count" : 430
                },
                {
                    "_id" : "Edgar Wallace",
                    "count" : 175
                },
                {
                    "_id" : "Nora Roberts",
                    "count" : 145
                },
                {
                    "_id" : "Erle Stanley Gardner",
                    "count" : 140
                },
                {
                    "_id" : "Agatha Christie",
                    "count" : 85 
                }
           ],
            "ok" : 1 
        }
    

    注:aggregate 框架不会写入到集合,所以所有的结果必须返回客户端。因此,aggregation 返回的数据结果限制在 16MB。

    5.2 管道操作(Pipeline Operations)

    5.2.1 $match

    ($)match 过滤文档,以致于你可以在文档子集上运行聚合操作。通常,尽可能的将 "($)match" 操作放到管道操作的前面。这样做主要有两个优点:1. 可以快速过滤掉不需要的文档(留下管道操作需要执行的文档),2. 可以在 projections 和 groupings 之前使用 indexes 查询。

    5.2.2 $project

    映射在管道中操作比在“标准的”查询语言中(find函数的第二个参数)更加强有力。

    # 映射,"_id" 总是默认返回,此处指定不返回
    > db.articles.aggregate({"$project" : {"author" : 1, "_id" : 0}})
    
    # 重命名被映射的域 "_id"
    > db.users.aggregate({"$project" : {"userId" : "$_id", "_id" : 0}})
    
    # 如果 originalFieldname 是索引,则在重命名之后就不再默认为索引了
    > db.articles.aggregate({"$project" : {"newFieldname" : "$originalFieldname"}},
           {"$sort" : {"newFieldname" : 1}})
    

    "($)fieldname" 语法被用来在 aggregation framework 中引用 fieldname 的值。比如上面例子中,"($)_id" 将会被 _id 域的内容取代。当然,如果重命名了,则就不要返回两次了,正如上例所示,当 "_id" 被重命名之后就不再返回。

    管道表达式

    最简单的 "$project" 表达式是包含、排除和域名重命名。也可以使用其它的表达式。

    数学表达式

    "($)add", "($)subtract", "($)multiply", "($)divide", "($)mod"

    # 域 "salary" 和域 "bonus" 相加
    > db.employees.aggregate(
          {
                "$project" : {
                        "totalPay" : {
                                "$add" : ["$salary", "$bonus"]
                          }
                   }
           })
    
    # "$subtract" 表达式,减掉 401k 
     > db.employees.aggregate(
            {
                "$project" : {
                        "totalPay" : {
                                "$subtract" : [{"$add" : ["$salary", "$bonus"]}, "$401k"]
                          }
                    }
            })
    

    日期表达式

    aggregation 有一个可以提取日期信息的表达式集合: "($)year", "($)month", "($)week","($)dayOfMonth", "($)dayOfWeek", "($)dayOfYear", "($)hour", "($)minute" 和 "($)second"。

    # 返回每个员工被雇佣的月
    > db.employees.aggregate(
           {
                "$project" : {
                        "hiredIn" : {"$month" : "$hireDate"}
                }
            })
    
    # 计算员工在公司工作的年数
    > db.employees.aggregate(
            {
                "$project" : {
                        "tenure" : {
                                "$subtract" : [{"$year" : new Date()}, {"$year" : "$hireDate"}] }
                         }
                  }
             }
    

    字符串表达式

    • "$substr" : [expr, startOffset, numToReturn] 返回第一个参数的子串,起始于第 startOffset 个字节,包含 numToReturn 个字节(注意,这个以字节测量,而不是字符,所以多字节编码需要小心)。

    • "$concat" : [expr1[, expr2, ..., exprN]] 连接每一个给定的字符串。

    • "$toLower" : expr 以小写的形式返回字符串。

    • "$toUpper" : expr 以大写的形式返回字符串。

    > db.employees.aggregate(
            {
                    "$project" : {
                            "email" : {
                                    "$concat" : [
                                            {"$substr" : ["$firstName", 0, 1]},
                                            ".",
                                            "$lastName",
                                            "@example.com"
                                      ] 
                                }
                        }
            })
    

    逻辑表达式

    比较表达式

    • "$cmp" : [expr1, expr2] 比较表达式 expr1 和 expr2,如果相等返回 0,如果 expr1 小于 expr2 返回负值,如果 expr2 小于 expr1 返回正值。

    • "$strcasecmp" : [string1, string2] 比较 string1 和 string2,必须为罗马字符。

    • "($)eq","($)ne", "($)gt", "($)gte", "($)lt", "($)lte" : [expr1, expr2]

    布尔表达式:

    • "$and" : [expr1[, expr2, ..., exprN]]

    • "$or" : [expr1[, expr2, ..., exprN]]

    • "$not" : expr

    控制语句:

    • "$cond" : [booleanExpr, trueExpr, falseExpr] booleanExpr 为 true 时返回 trueExpr,否则返回 falseExpr。

    • "$ifNull" : [expr, replacementExpr] 如果 expr 为空返回 replacementExpr,否则返回 expr。

    一个例子

    > db.students.aggregate(
            {
                    "$project" : {
                            "grade" : {
                                    "$cond" : [
                                            "$teachersPet",
                                            100, // if
                                            {     // else
                                                    "$add" : [
                                                            {"$multiply" : [.1, "$attendanceAvg"]},
                                                            {"$multiply" : [.3, "$quizzAvg"]},
                                                            {"$multiply" : [.6, "$testAvg"]}
                                                        ]
                                             }
                                      ]
                               }
                        }
                })
    

    5.2.3 $group

    算数操作符

    # 在多个国家销售数据的集合,计算每个国家的总收入
    > db.sales.aggregate(
            {
                    "$group" : {
                            "_id" : "$country",
                            "totalRevenue" : {"$sum" : "$revenue"}
                    }
            })
    
    # 返回每个国家的平均收入和销售的数量
    > db.sales.aggregate(
            {
                    "$group" : {
                            "_id" : "$country",
                            "totalRevenue" : {"$average" : "$revenue"},
                            "numSales" : {"$sum" : 1}
                    }
            })
    

    极端操作符(Extreme operators)

    如果你的数据已经排序好了,使用 ($)first 和 ($)last 比 ($)min 和 ($)max 更有效率。如果数据事先没有排序,则使用 ($)min 和 ($)max 比先排序然后 ($)first 和 ($)last 更有效率。

    # 在一次测验中学生分数的集合,找出每个年级的局外点
    > db.scores.aggregate(
            {
                    "$group" : {
                            "_id" : "$grade",
                            "lowestScore" : {"$min" : "$score"},
                            "highestScore" : {"$max" : "$score"}
                    }
            }
    
    # 或者
    > db.scores.aggregate(
            {
                    "$sort" : {"score" : 1}
            },
            {
                    "$group" : {
                    "_id" : "$grade",
                    "lowestScore" : {"$first" : "$score"},
                    "highestScore" : {"$last" : "$score"}
                    }
            })
    

    数组操作符(Array operators)

    • "$addToSet": expr 保持一个数组,如果 expr 不在数组中,添加它。每一个值在数组中最多出现一次,不一定按照顺序。

    • "$push": expr 不加区分的将每一个看到的值添加到数组,返回包含所有值得数组。

    5.2.4 $unwind(展开)

    unwind 将数组的每个域转化为一个单独的文档。例如,如果我们有一个有多条评论的博客,我们可以使用 unwind 将每个评论转化为自己的文档。

    > db.blog.findOne()
            {
                    "_id" : ObjectId("50eeffc4c82a5271290530be"),
                    "author" : "k",
                    "post" : "Hello, world!",
                    "comments" : [
                            {
                                    "author" : "mark",
                                    "date" : ISODate("2013-01-10T17:52:04.148Z"),
                                    "text" : "Nice post"
                            },
                            {
                                    "author" : "bill",
                                    "date" : ISODate("2013-01-10T17:52:04.148Z"),
                                    "text" : "I agree"
                            }
                    ]
    } 
    
    # unwind 
    > db.blog.aggregate({"$unwind" : "$comments"})
            {
                    "results" :
                            {
                                    "_id" : ObjectId("50eeffc4c82a5271290530be"),
                                    "author" : "k",
                                    "post" : "Hello, world!",
                                    "comments" : {
                                            "author" : "mark",
                                            "date" : ISODate("2013-01-10T17:52:04.148Z"),
                                            "text" : "Nice post"
                                    }
                            },
                            {
                                    "_id" : ObjectId("50eeffc4c82a5271290530be"),
                                    "author" : "k",
                                    "post" : "Hello, world!",
                                    "comments" : {
                                            "author" : "bill",
                                            "date" : ISODate("2013-01-10T17:52:04.148Z"),
                                            "text" : "I agree"
                                    }
                            }
                    "ok" : 1
            }
    

    5.2.5 $sort

    # 1 是 ascending,-1 是 descending
    > db.employees.aggregate(
            {
                    "$project" : {
                            "compensation" : {
                                    "$add" : ["$salary", "$bonus"]
                            },
                            "name" : 1
                    }
            },
            {
                    "$sort" : {"compensation" : -1, "name" : 1}
            }
        )
    

    5.2.6 $limit

    $limit 接收数值 n,然后返回前 n 个结果文档。

    5.2.7 $skip

    $limit 接收数值 n,然后从结果集中剔除前 n 个文档。对于标准查询,一个大的 skips 效率比较低,因为它必须找出所有匹配被 skipped 的文档,然后剔除它们。

    5.2.8 使用管道

    在使用 "($)project"、"($)group" 或者 "($)unwind" 操作之前,最好尽可能过滤出更多的文档(和更多的域)。一旦管道不使用直接来自集合中的数据,索引(index)就不再能够帮助取过滤(filter)和排序(sort)。如果可能的话,聚合管道试图为你重新排序这些操作,以便能使用索引。

    MongoDB 不允许单一聚合操作使用超过一定比例的系统内存:如果它计算得到一个聚合操作占用超过 20% 的内存,聚合就会出错。允许输出被输送到一个集合中(这样可以最小化所需内存的数量)是为将来作计划。

    参考资料

    1. MongoDB: The Definitive Guide, Second Edition

    2. MongoDB 正则表达式

    3. dateToString

  • 相关阅读:
    自己实现一个hash类的vue-router插件/vue-router底层原理实现
    XSS攻击和防护
    浏览器缓存机制介绍之http缓存-强缓存-协商缓存
    chrome控制台查看网络性能指标-TTFB_Content Download_window.performance
    vscode创建vue快捷键
    移动端布局适配方案
    node生成token
    vue组件的讨论&容易忽略的知识点
    函数防抖
    webpack-搭建项目的代码
  • 原文地址:https://www.cnblogs.com/shaocf/p/10988551.html
Copyright © 2011-2022 走看看