MongoDB权威指南之查询

前言

ruby 复制代码
上一章节介绍了增删改,本章使用单独一章来介绍查,主要涵盖以下几个方面:使用 $ 条件进行范围查询、数据集包含查询、不等式查询,以及其他一些查询;查询会返回一个数据库游标,其只会在需要时才惰性地进行批量返回;有很多可以针对游标执行的元操作,包括跳过一定数量的结果、限定返回结果的数量,以及对结果进行排序。

find简介

使用find 方法来进行查询,空的查询文档({})会匹配集合中的所有内容。如果 find 没有给定查询文档,则默认为 {}。

stylus 复制代码
> db.c.find()
> // 返回集合C中的所有文档

当开始向查询文档中添加键--值对时,就意味着限定了查询条件。 可以在查询文档中加入多个键--值对,以将多个查询条件组合在一起,这样的查询条件会被解释为"条件 1 AND 条件 2 AND...AND 条件 N "。

stylus 复制代码
> db.users.find({"username" : "joe", "age" : 27})

指定要返回的键

有时候并不需要返回文档中的所有键--值对。遇到这种情况时,可以通过 find(或者 findOne)的第二个参数来指定需要的键。这样做既可以:

  1. 节省网络传输的数据量。
  2. 也可以减少客户端解码文档的时间和内存消耗。
stylus 复制代码
> db.users.find({}, {"username" : 1, "email" : 1})
{
    "_id" : ObjectId("4ba0f0dfd22aa494fd523620"),
    "username" : "joe",
    "email" : "joe@example.com"
}

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

也可以用第二个参数来剔除查询结果中的某些键--值对。同样可以将 "_id" 键从返回结果中剔除。

stylus 复制代码
> db.users.find({}, {"username" : 1, "_id" : 0})
{
    "username" : "joe"
}

限制

查询在使用上是有一些限制的。传递给数据库的查询文档的值必须是常量。

查询条件

复制代码
查询不仅能像上一节描述的那样进行精确匹配,还可以匹配更加复杂的条件,比如范围、OR 子句以及取反。

查询条件

"lt"、"lte"、"gt" 、"gte" 和 "$ne" 都属于比较运算符,分别对应 <、<=、> 和>=、!=。可以将它们组合使用以查找一个范围内的值。

例如,要查询 18 到 30 岁的用户,可以进行如下操作:

stylus 复制代码
> db.users.find({"age" : {"$gte" : 18, "$lte" : 30}})

OR查询

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

如果一个键需要与多个值进行匹配,那么可以将一个条件数组与 "$in" 一起使用。

stylus 复制代码
> db.raffle.find({"ticket_no" : {"$in" : [725, 542, 390]}})

与 "in" 相反的是 "nin",此运算符会返回与数组中所有条件都不匹配的文档。如果想返回所有没有中奖的人,可以这样进行查询:

stylus 复制代码
> db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})

如果想找到满足多个条件中部分或全部条件的文档可以使用$or。

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

对于普通的 AND 类型查询,我们总是希望尽可能用最少的参数来限定结果的范围。OR 类型的查询则相反:如果第一个参数能够匹配尽可能多的文档,则其效率最高。

$not

"$not" 是一个元条件运算符:可以用于任何其他条件之上。也就是取反。

特定类型的查询

如第 2 章所述,MongoDB 在一个文档中可以使用多种类型的数据,其中一些类型在查询时会有特别的行为。

null

null 的行为有一些特别。它可以与自身匹配,null即会匹配为值为null的文档也会匹配不存在这个条件。

如果仅想匹配键值为 null 的文档,则需要检查该键的值是否为 null,并且通过"$exists" 条件确认该键已存在。

正则表达式

"$regex" 可以在查询中为字符串的模式匹配提供正则表达式功能。这里不扩展了,使用的是 Perl 兼容的正则表达式,反正也记不住

查询数组

查询数组元素的方式与查询标量值相同。

**" a l l " ∗ ∗ 如果需要通过多个元素来匹配数组,那么可以使用 " all"** 如果需要通过多个元素来匹配数组,那么可以使用 " all"∗∗如果需要通过多个元素来匹配数组,那么可以使用"all"。这允许你匹配一个元素列表。

stylus 复制代码
> db.food.find({fruit : {$all : ["apple", "banana"]}})
{"_id" : 1, "fruit" : ["apple", "banana", "peach"]}
{"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}

也可以使用整个数组进行精确匹配。不过,精确匹配无法匹配上元素丢失、多余或顺序不同 的文档。

如果想在数组中查询特定位置的元素,可以使用 key.index 语法来指定下标。

stylus 复制代码
> db.food.find({"fruit.2" : "peach"})

"$size"

"$size" 条件运算符对于查询数组来说非常有用,可以用它查询特定长度的数组,如下所示。

stylus 复制代码
> db.food.find({"fruit" : {"$size" : 3}})

" s i z e " 并不能与另一个 size" 并不能与另一个 size"并不能与另一个 条件运算符(如"$gt")组合使用。

"$slice"

正如本章前面提到的,find 的第二个参数是可选的,可以指定需要返回的键。这个特别的 "$slice" 运算符可以返回一个数组键中元素的子集。

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

除非特别指定,否则在使用 "$slice" 时会返回文档中的所有键。这与其他的键指定符不同,后者不会返回未指定的键。

返回一个匹配的数组元素

有时我们希望返回与查询条件匹配的任意数组元素。这时可以使用 $ 运算符来返回匹配的元素。

stylus 复制代码
> db.blog.posts.find({
    "comments.name": "bob"
},
{
    "comments.$": 1
})
{
    "_id": ObjectId("4b2d75476cc613d5ee930164"),
    "comments": [
        {
            "name": "bob",
            "email": "bob@example.com",
            "content": "good post."
        }
    ]
}

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

文档中的标量(非数组元素)必须与查询条件中的每一条子句相匹配。如果使用{"x" : {" g t " : 10 , " gt" : 10, " gt":10,"lt" : 20}} 进行查询,那么 "x" 必须同时满足大于 10 且小于20。然而,如果文档中的 "x" 字段是一个数组,那么当 "x" 键的某一个元素与查询条件的任意一条语句相匹配(查询条件中的每条语句可以匹配不同的数组元素)时,此文档也会被返回。

可以使用 "elemMatch" 强制 MongoDB 将这两个子句与单个数组元素进行比较。不过,这里有一个问题,"elemMatch" 不会匹配非数组元素:

stylus 复制代码
> db.test.find({"x" : {"$elemMatch" : {"$gt" : 10, "$lt" : 20}}}) > 
> // 没有结果

如果在要查询的字段上有索引(参见第 5 章),那么可以使用 min 和 max 将查询条件遍历的索引范围限制为 "gt" 和 "lt" 的值。

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

但是,只有在要查询的字段上存在索引时,才能使用 min 和 max,并且必须将索引的所有字段传递给 min 和 max。

查询内嵌文档

查询内嵌文档的方法有两种:查询整个文档或针对其单个键--值对进行查询。

如果可能,最好只针对内嵌文档的特定键进行查询。这样,即使数据模式变了,也不会导致所有查询因为需要精确匹配而无法使用。可以使用点表示法对内嵌文档的键进行查询:

stylus 复制代码
> db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"})

这种点表示法是查询文档和其他文档类型的主要区别。查询文档可以包含点,表示"进入内嵌文档内部"的意思。点表示法也是待插入文档不能包含 . 字符的原因。当人们试图将 URL 保存为键时,常常会遇到这种限制。解决这个问题的一种方法是在插入前或者提取后始终执行全局替换,用点字符替换 URL 中不合法的字符。

要正确指定一组条件而无须指定每个键,请使用 "$elemMatch"。这种模糊的命名条件允许你在查询条件中部分指定匹配数组中的单个内嵌文档。

$where查询

键--值对是一种相当有表现力的查询方式,但有些查询依然无法表示。对于无法以其他方式执行的查询,可以使用 "where" 子句,它允许你在查询中执行`任意的JavaScript代码`。这样就能在查询中做大部分事情了。为安全起见,应该严格限制或消除 "where" 子句的使用。应该禁止终端用户随意使用 "$where" 子句。

stylus 复制代码
> db.foo.find({"$where" : function () {
... for (var current in this) {
...     for (var other in this) {
...         if (current != other && this[current] == this[other]) {
...             return true;
...         }
...     }
... }
... return false;
... }});

除非绝对必要,否则不应该使用 "where" 查询:它们比常规查询慢得多。只有在没有使用其他方法进行查询时,才可以使用 "where"。

游标

数据库会使用游标返回 find 的执行结果。游标的客户端实现通常能够在很大程度上对查询的最终输出进行控制。

要遍历结果,可以在游标上使用 next 方法。可以使用 hasNext 检查是否还有其他结果。

limit、skip和sort

最常用的查询选项是限制返回结果的数量、略过一定数量的结果以及排序。所有这些选项必须在查询被发送到数据库之前指定。

比较顺序 MongoDB 对于类型的比较有一个层次结构。有时一个键的值可能有多种类型:整型和布尔型,或者字符串和 null。如果对混合类型的键进行排序,那么会有一个预定义的排序顺序。

null<数字(整型、长整型、双精度浮点型、小数型)<字符串<对象/文档<数组<二进制数据<对象 ID<布尔型<日期<时间戳<正则表达式

避免略过大量结果

使用 skip 来略过少量的文档是可以的。但对于结果非常多的情况,skip 会非常慢,因为需要先找到被略过的结果,然后再丢弃这些数据。大多数数据库会在索引中保存更多的元数据以处理 skip,但 MongoDB 目前还不支持这样做,所以应该避免略过大量的数据。通常下一次查询的条件可以基于上一次查询的结果计算出来。

不使用skip对结果进行分页

可以存储上一次查询的最后一个文档的键值作为获取下一页的查询条件。

相关推荐
lovingsoft27 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly37 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝41 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦42 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码
葡萄城技术团队2 小时前
用 SpreadJS 数据验证实现省市区三级联动
前端