一、find简介

1、查询所有
>db.test.find()
>db.test.find({})
2、根据键值对
>db.test.find({“key”:value})
3、根据多个键查询,且关系为AND[且的关系]
>db.test.find({“key”:value,“key1”:value1})
4、指定返回的键
>db.test.find({},{“key”:value})
其中value为0代表不返回,value为1代表字段会返回
5、查询限制
传递给数据库的查询文档的值必须是常量。不能是变量,如以下实例即为错误的:
>db.test.find({“key”:“this.key1”})

二、查询条件

1、 比较运算符
“$lt”、“$lte”、“$gt” 和 “$gte” 都属于比较运算符,分别对应<、<=、> 和 >=。
1.1、要查询 18 到 30 岁的用户
>db.test.find({“age”: { “$gte”:18,“$lt”:30 } })
1.2、要查找在 2022 年 1月 1 日前注册的用户
>startTime=new Date(“01/01/2022”)
>db.test.find({“createTime”: {“$lt”:startTime}})
1.3、$ne 文档键值不等于某个特定值
>db.test.find({“name”:{“$ne”:“zhangsan”}})
2、OR查询
MongoDB 中有两种方式可以进行 OR 查询。“ i n " 可 以 用 来 查 询 一 个 键 的 多 个 值 。 " in" 可以用来查询一个键的多个值。" in""or” 则更通用一些,可以在多个键中查询任意的给定值。
>db.test.find({“id”:{“$in”:[1,2,3,4]}})
>db.test.find({“id”:{“$nin”:[1,2,3,4]}})
>db.test.find({“$or”:[{“id”:1},{“name”:“zhangsan”}}]})
>db.test.find({“$or”:[{“id”:{“$in”:[1,2,3,4]}},{“name”:“zhangsan”}}]})
3、“$not” 是一个元条件运算符:可以用于任何其他条件之上。
如查询会返回 “id” 为 1、6、11、16 等值的数据
>db.test.find({“id”:{“$mod”:[5,1]}})
>db.test.find({“id”:{“$not”:{“$mod”:[5,1]}}})

特定类型的查询

1、null
假设test集合中没有age字段,相当于查询所有
>db.test.find({“age”:null})
2、如果仅想匹配键值为 null 的文档,则需要检查该键的值是否为null,并且通过 “$exists” 条件确认该键已存在。
>db.test.find({“name”:{“$eq”:null,“$exists”:true}})
3、正则表达式
“$regex” 可以在查询中为字符串的模式匹配提供正则表达式功能。
如果要查找所有用户名为 Zhang或 zhang 的用户,那么可以使用正则表达式进行不区分大小写的匹配:
>db.test.find({“name”:{“$regex”:/zhang/i}})
希望匹配如“zhangs”这样的键,那么可以改进一下刚刚的正则表达式
>db.test.find({“name”:{“$regex”:/zhangs?/i}})
注: MongoDB 可以利用索引来查询前缀正则表达式(如/^zhang/)。索引不能用于不区分大小写的搜索(/^zhangs/i)。当正则表达式以插入符号(^)或左锚点(\A)开头时,它就是“前缀表达式”。如果正则表达式使用了区分大小写的查询,那么当字段存在索引时,则可以对索引中的值进行匹配。如果它也是一个前缀表达式,那么可以将搜索限制在由该索引的前缀所形成的范围内的值。

正则表达式也可以匹配自身。虽然很少有人会将正则表达式插入数据库中,但是如果你这么做了,那么它也可以匹配到自身。
>db.test.insert({“name”:/zhang/})
>db.test.find({“name”:/zhang/})
4、数组查询
1)、匹配文档
>db.test.insertOne({“address”:[“addr1”,“addr2”,“addr3”]})
>db.test.find({“address”:“addr1”})
2)、同时包含,且跟顺序无关
>db.test.find({“address”:{“$all”:[“addr1”,“addr2”]}})
3)、精确匹配且顺序必须一致
>db.test.find({“address”:[“addr1”,“addr2”,“addr3”]})
4)、根据下标查询,数组下标都是从0开始
>db.test.find({“address.2”:“addr2”})
5)、查询特定长度的数组
>db.test.find({“address”:{“$size”:3}})
“$size” 并不能与另一个$ 条件运算符(如 “$gt”)组合使用,但这种查询可以通过在文档中添加一个 “size” 键的方式来实现。
>db.test.update(query,{“$push”:{“$address”:“addr3”},{“$inc”:{“size”:1}}})
后续查询就可以通过size来查询,如:
>db.test.find({“size”:{“$gt”:3}})
6)、“$slice” 运算符可以返回一个数组键中元素的子集。
如,返回前10条数据:
>db.test.find(query,{“address”:{“$slice”:10}})
返回后10条记录:
>db.test.find(query,{“address”:{“$slice”:-10}})
指定偏移量和返回的元素数量来获取数组中间的结果:
>db.test.find(query,{“address”:{“$slice”:[23,10]}})
这个操作会略过前 23 个元素,返回第 24~33 个元素。如果数组中的元素少于 33 个,则会返回尽可能多的元素。
7)、返回与查询条件匹配的任意数组元素,使用 $运算符来返回匹配的元素
db.test.find({“name”:“zhangsan”},{“address.$”:1})
这种方式只会返回每个文档中第一个匹配的元素:如果zhangsan 在资料中留下了多地址,那么只有 “comments” 数组中的第一个会被返回。
8)、数组与范围查询的相互作用
比如目前有如下文档
{“x”:5}
{“x”:15}
{“x”:25}
{“x”:[5,25]}
>db.test.find({“x”:{“$gt”:10,“$lt”:20}})
{“x”:15}
>db.test.find({“x”:{“$gt”:10,“$”:25}})
{“x”:15}
{“x”:[5,25]}
5 和 25 都不在 10 和 20 之间,但由于 25 与查询条件中的第一个子句(“x” 的值大于 10)相匹配,5 与查询条件中的第二个子句(“x” 的值小于 20)相匹配,因此这个文档会被返回。
明显与预期结果不一致,所以可以使用 “$elemMatch” 强制 MongoDB 将这两个子句与单个数组元素进行比较。不过,这里有一个问题,“$elemMatch” 不会匹配非数组元素:
>db.test.find({“x”:{“$elemMatch”:{“$gt”:10,“$lt”:20}}})
此时是没有结果返回的
9)、如果在要查询的字段上有索引,那么可以使用min 和 max 将查询条件遍历的索引范围限制为 “$gt” 和 “$lt"的值:
>db.test.find({“x”:{”$gt":10,“$lt”:20}}).min({“x”:10}).max({“x”:20})
现在,这条查询语句只会遍历值在 10 和 20 之间的索引,不会与值为 5 和 25 的这两个条目进行比较。但是,只有在要查询的字段上存在索引时,才能使用 min 和 max,并且必须将索引的所有字段传递给 min 和 max。
在查询可能包含数组的文档的范围时,使用 min 和 max 通常是一个好主意。在整个索引范围内对数组使用 “$gt”/“$lt” 进行查询是非常低效的。它基本上接受任何值,因此会搜索每个索引项,而不仅仅是索引范围内的值。
5、查询内嵌文档
查询姓名为zhang san的人,精确匹配
>db.test.find(“name”:{“first”:“zhang”,“last”:“san”})
针对特定的键进行查询
>db.test.find({“name.first”:“zhang”})
这种点表示法是查询文档和其他文档类型的主要区别。查询文档可以包含点,表示“进入内嵌文档内部”的意思。点表示法也是待插入文档不能包含 . 字符的原因。当试图将 URL 保存为键时,常常会遇到这种限制。解决这个问题的一种方法是在插入前或者提取后始终执行全局替换,用点字符替换 URL 中不合法的字符。
6、内嵌文档中的数组查询
userInfos是一个数组,包含多个userinfo
>db.test.find({“userinfos”:{“$elemMatch”:{“name”:“zhangsan”,“age”:{“$gte”:6}}}})
要正确指定一组条件而无须指定每个键,请使用"$elemMatch",“$elemMatch” 允许你将限定条件进行“分组”。仅当需要对一个内嵌文档的多个键进行操作时才会用到它。

$where查询

键–值对是一种相当有表现力的查询方式,但有些查询依然无法表示。对于无法以其他方式执行的查询,可以使用 “$where” 子句,它允许你在查询中执行任意的 JavaScript 代码。这样就能在查询中做大部分事情了。为安全起见,应该严格限制或消除"$where" 子句的使用。应该禁止终端用户随意使用 “$where"子句。
使用 “$where” 最常见的情况是比较文档中两个键的值。假设有如下文档:
>db.test.insertOne({“name”:“zhangsan”,“age”:10})
>db.test.insertOne({“name”:“lisi”,“age”:20,“height”:20})
返回任意两个字段相等的文档
>db.test.find({”$where":function(){
for(var current in this){
for(var other in this){
if(current!=other&&this[current]==this[other]){
return true;
}
}
}
return false;
}})
如果函数返回 true,文档就作为结果集的一部分返回;如果函数返回 false,文档就不返回。
除非绝对必要,否则不应该使用 “$where” 查询:它们比常规查询慢得多。每个文档都必须从 BSON 转换为 JavaScript 对象,然后通过 “$where” 表达式运行。此外,“$where” 也无法使用索引。因此,只有在没有使用其他方法进行查询时,才可以使用 “$where”。可以先使用其他查询进行过滤,然后再使用"$where" 子句,这样组合使用可以降低性能损失。如果可能,应该使用索引来基于非 $where 子句进行过滤,而 "$where"表达式仅用于对结果进行进一步微调。MongoDB 3.6 增加了$expr 运算符,它允许在 MongoDB 查询语句中使用聚合表达式。因为它不需要执行 JavaScript,所以速度比 $where 快,建议尽可能使用此运算符作为替代。
进行复杂查询的另一种方法是使用聚合工具.

游标

1、数据库会使用游标返回 find 的执行结果。游标的客户端实现通常能够在很大程度上对查询的最终输出进行控制。你可以限制结果的数量,跳过一些结果,按任意方向的任意键组合对结果进行排序,以及执行许多其他功能强大的操作。
>var cursor = db.test.find();
>while(cursor.hasNext()){
obj = cursor.next();
//执行业务逻辑
}
还可以迭代游标
>cursor.forEach(function(x){
print(x.name);
})
调用 find 时,shell 并不会立即查询数据库,而是等到真正开始请求结果时才发送查询,这样可以在执行之前给查询附加额外的选项。cursor 对象的大多数方法会返回游标本身,这样就可以按照任意顺序将选项链接起来了。
var cursor = db.test.find();只是构造查询,查询还没有真正执行,cursor.hasNext()调用时才会执行,这时,查询会被发往服务器端。shell 会立刻获取前 100 个结果或者前 4MB 的数据(两者之中较小者),这样下次调用 next或者 hasNext 时就不必再次连接服务器端去获取结果了。在客户端遍历完第一组结果后,shell 会再次连接数据库,使用getMore 请求更多的结果。getMore 请求包含一个游标的标识符,它会向数据库询问是否还有更多的结果,如果有则返回下一批结果。这个过程会一直持续,直到游标耗尽或者结果被全部返回。
2、limit、skip和sort
只返回 3 个结果
>db.test.find().limit(3)
略过前 3 个匹配的文档
>db.test.find().skip(3)
sort排序方向可以是 1(升序)或 -1(降序)
>db.test.find().sort({“name”:1,“age”:-1})
3、比较顺序
MongoDB 对于类型的比较有一个层次结构。有时一个键的值可能有多种类型:整型和布尔型,或者字符串和 null。如果对混合类型的键进行排序,那么会有一个预定义的排序顺序。从最小值到最大值,顺序如下。
最小值
null
数字(整型、长整型、双精度浮点型、小数型)
字符串
对象/文档
数组
二进制数据
对象 ID
布尔型
日期
时间戳
正则表达式
最大值
4、避免略过大量结果
使用 skip 来略过少量的文档是可以的。但对于结果非常多的情况,skip 会非常慢,因为需要先找到被略过的结果,然后再丢弃这些数据。大多数数据库会在索引中保存更多的元数据以处理 skip,但 MongoDB 目前还不支持这样做,所以应该避免略过大量的数据。通常下一次查询的条件可以基于上一次查询的结果计算出来。
不使用skip对结果进行分页
>var page=db.test.find().sort({“date”:-1}).limit(100)
然后,假设日期是唯一的,可以使用最后一个文档的 “date” 值作为获取下一页的查询条件:
var latest = null;
//显示第一页
while(page.hasNext()){
latest = page.next();
display(latest);
}
//获取第二页
var page2 = db.test.find({“date”:{“$lt”:latest.date}})
page2.sort({“date”:-1}).limit(100)
5、查找一个随机文档
如果是以下方式,有可能会略过很多文档,性能不佳
>db.test.find().skip(random).limit(1)
所以需要一些提前规划,但如果你知道要在集合中查找随机元素,那么有一种高效得多的方法可以执行此操作。诀窍就是在插入文档时为每个文档添加一个额外的随机键。如果正在使用shell,那么可以使用 Math.random() 函数(这会产生 0 和 1 之间的一个随机数):
>random=Math.random();
>db.test.insertOne({“name”:“zhang”,“random”:Math.random()})
>db.test.find({“random”:{“$gt”:random}})
6、游标生命周期
游标包括两个部分:面向客户端的游标和由客户端游标所表示的数据库游标。我们用的基本都是客户端游标:
1)、在服务器端,游标会占用内存和资源。一旦游标遍历完结果之后,或者客户端发送一条消息要求终止,数据库就可以释放它正在使用的资源。释放这些资源可以让数据库将其用于其他用途,这是非常有益的,因此要确保可以尽快(在合理的范围内)释放游标。
2)、还有一些情况可能导致游标终止以及随后的清理。首先,当游标遍历完匹配的结果时,它会清除自身。其次,当游标超出客户端的作用域时,驱动程序会向数据库发送一条特殊的消息,让数据库知道它可以“杀死”该游标。最后,即使用户没有遍历完所有结果而且游标仍在作用域内,如果 10 分钟没有被使用的话,数据库游标也将自动“销毁”。这样,如果客户端崩溃或者出错,MongoDB 就不需要维护上千个被打开的游标了。
3)、这种“超时销毁”的机制通常是用户所期望的:很少有用户愿意花几分钟坐在那里等待结果。然而,有时可能的确需要一个游标维持很长时间。在这种情况下,许多驱动程序实现了一个称为 immortal 的函数,或者类似的机制,它告诉数据库不要让游标超时。如果关闭了游标超时,则必须遍历完所有结果或主动将其销毁以确保游标被关闭。否则,它会一直占用数据库的资源,直到服务器重新启动。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐