Skip to content

查询

面向文档的 NoSQL 数据库解决的主要问题不是高性能的并发读写,而是在保证存储海量数据的同时具有良好的查询性能。

find 简介

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
MongoDB Enterprise > db.things.find()
{ "_id" : ObjectId("5e7cb6b5a1f939b5bef20f7a"), "x" : 3 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7e"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7f"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f80"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f81"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f82"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f83"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("5e7def2ce0fe91bb88630309"), "name" : "mongo" }
{ "_id" : ObjectId("5e7def2fe0fe91bb8863030a"), "x" : 3 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030e"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030f"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630310"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630311"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630312"), "x" : 4, "j" : 8 }

查询操作符

操作符就是对数据进行操作的符号,其表达了要对数据执行的操作。
数据库系统的每一条查询指令都有一个操作符,表示该指令应该执行什么性质的操作

条件操作符

最常用的也是最简单的操作符<<=>>=,如下面的代码所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// j 大于 5
MongoDB Enterprise > db.things.find({"j": {$gt: 5}})
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f80"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f81"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f82"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f83"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630310"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630311"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630312"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630313"), "x" : 4, "j" : 9 }
// j 小于 5
MongoDB Enterprise > db.things.find({"j": {$lt: 5}})
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7e"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030e"), "x" : 4, "j" : 4 }
// j 大于等于 7
MongoDB Enterprise > db.things.find({"j": {$gte: 7}})
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f81"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f82"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f83"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630311"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630312"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("5e7def3de0fe91bb88630313"), "x" : 4, "j" : 9 }
// j 小于等于 3
MongoDB Enterprise > db.things.find({"j": {$lte: 3}})
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030b"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030c"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030d"), "x" : 4, "j" : 3 }

如果要同时满足多个条件,可以如下的代码所示

1
2
3
4
5
6
7
MongoDB Enterprise > db.things.find({"j": {$gt: 2, $lt: 6}})
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7e"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("5e7cbc5ba1f939b5bef20f7f"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030d"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030e"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("5e7def3de0fe91bb8863030f"), "x" : 4, "j" : 5 }

对于文档的键值不等于某个特定值的情况,就要使用另外一种条件操作符"$ne"了,它表示"不等于"

1
2
3
MongoDB Enterprise > db.students.find({"age": {"$ne": 28}})
{ "_id" : ObjectId("5df18f52a6647fb1c0b8f265"), "id" : "66666", "name" : "逗比", "age" : 67, "gender" : "female" }
{ "_id" : ObjectId("5df18f5db79b47f5eb22099b"), "id" : "66666", "name" : "逗比", "age" : 67, "gender" : "female" }

"$ne"能用于所有类型的数据

OR查询

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

如果一个键需要与多个值进行匹配的花,就要用"$in"操作符,再加一个条件数组。例如,抽奖活动的中奖号码是 725、542 和 390。
要找出全部的中奖文档的话,可以构建如下查询:

1
MongoDB Enterprise > db.raffle.find({"ticket_no": {"$in": [725, 542, 390]}})

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

1
MongoDB Enterprise > db.user2s.find({"user_id": {"$in": [12345, "joe"]}})

这会匹配"user_id"等于12345的文档,也会匹配"user_id"等于"joe"的文档
要是"$in"对应的数组只有一个值,那么和直接匹配这个值效果一样。
例如, {"ticket_no": {"$in": [725]}}{"ticket_no": 725}的效果一样

"$in"相对的是"$nin""$nin"将返回与数组中所有条件都不匹配的文档。
要是想返回所有没有中奖的人,就可以用如下方法进行查询:

1
MongoDB Enterprise > db.raffle.find({"ticket_no": {"$nin": [725, 542, 390]}})

该查询会返回所有没有中奖的人

"$in"能对单个键做 OR 查询,但要是想要找到"ticket_no"725或者"winner"true的文档该怎么办呢?
对于这种情况,应该使用"$or""$or"接受一个包含所有可能条件的数组作为参数。

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

游标

数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效的控制。
可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强大的操作。

要想从 shell 中创建一个游标,首先要对集合填充一些文档,然后对其执行查询,并将结果分配给一个局部变量。
这里,先创建一个简单的集合,而后做个查询,并用 cursor 变量保存结果

1
2
3
4
5
6
MongoDB Enterprise > for (i = 0; i < 100; i++) {
... db.collection.insert({x: i})
... }
WriteResult({ "nInserted" : 1 })

MongoDB Enterprise > let cursor = db.collection.find()

这么做的好处是可以一次查看一条结果。
如果将结果放在全局变量或者就没有放到变量中,MongoDB shell 会自动迭代,自动显示最开始的若个文档。

要迭代结果,可以使用游标的next方法。也可以使用hasNext来查看游标中是否还有其他结果
典型的结果遍历如下所示:

1
2
3
4
while (cursor.hasNext()) {
    let objs = cursor.netx();
    // do stuff
}

cursor.hasNext()检查是否有后续结果存在,然后用cursor.next()获得它
游标类还实现了 JavaScript 的迭代器接口,所以可以在forEach循环中使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
MongoDB Enterprise > cursor.forEach(function(x) { print(x) })
[object BSON]
[object BSON]
[object BSON]
...

MongoDB Enterprise > cursor = db.collection.find()
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e0"), "x" : 0 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e1"), "x" : 1 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e2"), "x" : 2 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e3"), "x" : 3 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e4"), "x" : 4 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e5"), "x" : 5 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e6"), "x" : 6 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e7"), "x" : 7 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e8"), "x" : 8 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8e9"), "x" : 9 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8ea"), "x" : 10 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8eb"), "x" : 11 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8ec"), "x" : 12 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8ed"), "x" : 13 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8ee"), "x" : 14 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8ef"), "x" : 15 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8f0"), "x" : 16 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8f1"), "x" : 17 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8f2"), "x" : 18 }
{ "_id" : ObjectId("5eac2926ab4fa984d3eca8f3"), "x" : 19 }
Type "it" for more
MongoDB Enterprise > cursor.forEach(function(x) { print(x.x) })
20
21
22
23
24
25
26
...

limit、skip和sort

最常用的查询选项就是限制返回结果的数量、忽略一定数量的结果以及排序。
所有这些选项一定要在查询被发送到服务器之前指定。
要限制结果数量,可在find后使用limit函数。例如,只返回 3 个结果,可以这样

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

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

skiplimit类似

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

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

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

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

这 3 个方法可以组合使用。这对于分页非常有用。
例如,你有个在线商店,有人想搜索 mp3。若是每页返回 50 个结果,而且按照价格从高到低排序,可以这样写:

1
db.stock.find({"desc": "mp3"}).limit(50).sort({"price": -1})

点击"下一页",可以看到更多的结果,通过skip也可以非常简单地实现,只需要略过前 50 个结果就好了(已经在第一页显示了)

1
db.stock.find({"desc": "mp3"}).limit(50).skip(50).sort({"price": -1})