MongoDB 数据库

您所在的位置:网站首页 mongodb不等于多个值查询 MongoDB 数据库

MongoDB 数据库

2023-07-02 22:01| 来源: 网络整理| 查看: 265

接上篇,本篇专门整理 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" : "[email protected]" } # 阻止 "_id" 键返回 > db.users.find({}, {"username": 1, "email": 1, "_id": 0}) # 结果 { "username" : "joe", "email" : "[email protected]" } 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 正则表达式 MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。 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% 的内存,聚合就会出错。允许输出被输送到一个集合中(这样可以最小化所需内存的数量)是为将来作计划。

参考资料

MongoDB: The Definitive Guide, Second Edition

MongoDB 正则表达式

dateToString



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3