1. 前言
在這篇博文中,我們將學習如何查詢mongoDB中的數據。當我們把數據存儲在mongoDB以后,我們需要把數據查詢出來。畢竟CRUD操作中,查詢操作在我們系統中是我們應用比較頻繁的操作。我們需要應對不同的業務需求,構造合適的查詢條件去查詢我們想要的數據。我們需要去學習mongoDB給我們提供了哪些查詢相關的語法和功能。在這里,我們使用mongodb自帶的mongo shell(mongo shell是一個javascript環境的mongodb客戶端,支持js語法)來學習。
2. 准備
在開始之前,我們需要准備一下實驗用的數據:

// 啟動mongo shell客戶端 $ mongo // 在這里我們使用test數據庫,如果沒有這個數據庫,會自動創建 > use test // 在users collection中插入6條用戶數據 > db.users.insertMany( [ { _id: 1, name: "sue", age: 19, type: 1, status: "P", favorites: { artist: "Picasso", food: "pizza" }, finished: [ 17, 3 ], badges: [ "blue", "black" ], points: [ { points: 85, bonus: 20 }, { points: 85, bonus: 10 } ] }, { _id: 2, name: "bob", age: 42, type: 1, status: "A", favorites: { artist: "Miro", food: "meringue" }, finished: [ 11, 25 ], badges: [ "green" ], points: [ { points: 85, bonus: 20 }, { points: 64, bonus: 12 } ] }, { _id: 3, name: "ahn", age: 22, type: 2, status: "A", favorites: { artist: "Cassatt", food: "cake" }, finished: [ 6 ], badges: [ "blue", "red" ], points: [ { points: 81, bonus: 8 }, { points: 55, bonus: 20 } ] }, { _id: 4, name: "xi", age: 34, type: 2, status: "D", favorites: { artist: "Chagall", food: "chocolate" }, finished: [ 5, 11 ], badges: [ "red", "black" ], points: [ { points: 53, bonus: 15 }, { points: 51, bonus: 15 } ] }, { _id: 5, name: "xyz", age: 23, type: 2, status: "D", favorites: { artist: "Noguchi", food: "nougat" }, finished: [ 14, 6 ], badges: [ "orange" ], points: [ { points: 71, bonus: 20 } ] }, { _id: 6, name: "abc", age: 43, type: 1, status: "A", favorites: { food: "pizza", artist: "Picasso" }, finished: [ 18, 12 ], badges: [ "black", "blue" ], points: [ { points: 78, bonus: 8 }, { points: 57, bonus: 7 } ] } ] )
3. 基本查詢
MongoDB提供了db.collection.find()方法來執行查詢操作。find方法接受兩個參數:一個查詢條件,一個是投影的字段。這兩個參數都不是必須的,如果省略了查詢條件,則默認列出collection中的所有文檔。
db.users.find() // 這個和上面的語句是等價的 db.users.find({})
3.1 等值查詢
通過find()方法來執行等值查詢的時候,可以通過{<field>:<value>}的方式來指定查詢條件,這個條件表示在collection中查詢滿足field的值為value的所有文檔。假設我們需要查找所有status是'A'的用戶,我們可以這么查詢:
db.users.find({status: 'A'})
查詢結果
{ "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] }
{ "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
3.2 操作符查詢
mongodb支持使用操作符來構造查詢條件,比如比較操作符,如$gt,$lt等。使用操作符的查詢條件通過{<field>: {<operator>:<value>}}來表示。假設我們要查詢age超過22的用戶,可以這么做:
db.users.find({age: {$gt: 22}})
查詢結果
{ "_id" : 2, "name" : "bob", "age" : 42, "type" : 1, "status" : "A", "favorites" : { "artist" : "Miro", "food" : "meringue" }, "finished" : [ 11, 25 ], "badges" : [ "green" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 64, "bonus" : 12 } ] } { "_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] } { "_id" : 5, "name" : "xyz", "age" : 23, "type" : 2, "status" : "D", "favorites" : { "artist" : "Noguchi", "food" : "nougat" }, "finished" : [ 14, 6 ], "badges" : [ "orange" ], "points" : [ { "points" : 71, "bonus" : 20 } ] } { "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
3.3 and關系
mongodb支持的and關系查詢很簡單,當一個查詢條件中包含多個<field:value>對的時候,這些條件之間的關系就是and關系。所以and關系的查詢可以這樣表示{<field1>:<value1>, <field2>:<value2>, ...,<fieldN>:<valueN>}。假設我們要查詢滿足條件:status是'D',並且age大於23的用戶,我們可以這么查詢:
db.users.find({status: 'D', age: {$gt: 23}})
查詢結果
"_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] }
3.4 or關系
mongodb通過操作符"$or"來支持or關系的查詢。or關系的查詢條件可以這么構造:{$or: [{<field1>:<value1>}, {<field2>:<value2>},..., {<fieldN>:<valueN>}]}。比如我們想查詢status是'A' 或者age大於23的用戶,我們可以這么做:
db.users.find({$or: [{status: 'A'}, {age: {$gt: 23}}]})
查詢條件
{ "_id" : 2, "name" : "bob", "age" : 42, "type" : 1, "status" : "A", "favorites" : { "artist" : "Miro", "food" : "meringue" }, "finished" : [ 11, 25 ], "badges" : [ "green" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 64, "bonus" : 12 } ] } { "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] } { "_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] } { "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
4. 嵌套文檔查詢
如果在文檔中的一個字段上的值也是一個文檔,當我們需要查詢這個文檔中的子文檔中的值得時候,就涉及到嵌套文檔的查詢。MongoDB支持對嵌套文檔的查詢。
4.1 匹配整個子文檔
匹配整個子文檔可以認為也是一種等值查詢,只不過這次的值是一個子文檔。所以查詢條件就是這樣的{<field>:<sub document>}。假設我們要查詢favorites這個字段上,artist為'Picasso',food為'pizza'的用戶,我們可以這樣做:
db.users.find( { favorites: { artist: "Picasso", food: "pizza" } } )
查詢結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] }
4.2 匹配子文檔中的字段
mongodb支持匹配子文檔中的字段,通過點(.)符號來表示子文檔中的字段,比如我們在favorites字段對應的子文檔中的artist字段上做查詢操作,可以通過favorites.artist來表示這個子文檔中的artist字段
db.users.find( { "favorites.artist": "Picasso" } )
查詢結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] }
{ "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
5. 在數組上查詢
如果文檔中的字段的值是數組類型,mongodb支持對數組類型的字段構造查詢條件。
5.1 匹配整個數組
mongodb對整個數組進行匹配的時候,和匹配整個嵌套文檔的方式類似。只要在條件中給出整個數組就可以了,就像這樣{<field>:<value>},只不過這里的<value>是需要匹配的整個數組。假設我們需要查找badges的值為"['blue', 'black']"的用戶,我們可以這樣做
db.users.find( { badges: [ "blue", "black" ] } )
查詢結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] }
5.2 查找數組中的一個元素
除了對數組類型的字段做完全匹配的查詢,我們也可以把數組的元素作為查詢條件,主要數組中包含了這個元素,那么都會被匹配到。比如我們需要查找badges的數組中包含了'black'元素的所有用戶,我們可以這樣做
db.users.find( { badges: "black" } )
查詢結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] } { "_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] } { "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
可以看到,這三個用戶的badges字段中的值中,都有‘black’元素在數組中。
5.3 匹配數組中指定下標的元素
mongodb支持把數組中指定下標的元素作為查詢條件來構造查詢語句,通過類似於引用子文檔的點號(.)方式來說引用指定下標的元素。比如badges字段中數組的第一個元素,可以表示成:badges.0,類似的,第N個就是badges.N。假設我們想查找滿足badges數組中的第一個元素是'black'的用戶,我們可以這樣做:
db.users.find( { "badges.0": "black" } )
查詢結果
{ "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
查詢結果看到,只有一個滿足條件的用戶。雖然別的用戶的badges字段也有'black'字段,但是由於需要查找數組的第一個元素是'black'的用戶,所以只有一個用戶是滿足條件的。
5.4 對數組元素指定多個查詢條件
多個條件之間的and關系
mongodb提供了一個"$elemMatch"操作符,這個操作符的作用是對數組中的元素進行多條件匹配,只要數組中至少一個元素滿足指定的條件,那么就表示匹配成功,也就是說,'$elemMatch'操作符指定的條件之間是"與"的關系。來看例子,假設我們要找到滿足finished字段中的數組元素的值大於15,小於20,我們可以這樣做:
db.users.find( { finished: { $elemMatch: { $gt: 15, $lt: 20 } } } )
查詢結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] }
{ "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
我們可以看到,“$elemMatch”操作符的用法,是用指定的多個條件來匹配數組中的每一個值,只要數組中至少有一個值滿足我們指定的條件,就表示匹配成功。
多條件之間的or關系
mongodb在數組匹配的時候,可以返回滿足指定條件的結果的並集。通俗的講,指定的條件之間是“或”的關系,即只要數組中的任何一個元素滿足多個查詢條件中的任何一個,那么就認為這個文檔被匹配上了。比如,我們如果這樣指定查詢條件
db.users.find( { finished: { $gt: 15, $lt: 20 } } )
我們先來看下查詢的結果
{ "_id" : 1, "name" : "sue", "age" : 19, "type" : 1, "status" : "P", "favorites" : { "artist" : "Picasso", "food" : "pizza" }, "finished" : [ 17, 3 ], "badges" : [ "blue", "black" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 85, "bonus" : 10 } ] } { "_id" : 2, "name" : "bob", "age" : 42, "type" : 1, "status" : "A", "favorites" : { "artist" : "Miro", "food" : "meringue" }, "finished" : [ 11, 25 ], "badges" : [ "green" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 64, "bonus" : 12 } ] } { "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
這個查詢條件和上面的元素匹配的查詢條件相比,查詢的結果我們看到,中間的記錄中,finished的值是[11, 25]的也滿足查詢條件。這是因為{finished: {$gt: 15, $lt: 20}}這個條件,表示數組中的任何一個元素,只要滿足大於15或者小於20,都被認為是滿足查詢條件的,指定的條件之間是“或”的關系。而通過"$elemMatch"操作符指定的查詢條件,條件之間的關系是“與”的關系。所以不難理解,值為[11, 25]的那個記錄之所以被匹配,是因為它滿足25是大於條件中的15的,而11是小於條件中的20的,所以自然就滿足條件了。
5.5 數組中包含子文檔的查詢
當數組中包含子文檔的時候,也可以為這些子文檔中的字段構造查詢條件。
使用數組下標定位到具體的子文檔
這種方式的查詢條件,是同時利用數組的下標表示和文檔中字段的點號(.)表示法來指定數組中子文檔中的字段。講的有點繞,我們來看具體的例子。
db.users.find( { 'points.0.points': { $lte: 55 } } )
這條查詢語句的意思,表示我們要查詢points字段中,它包含的數組中下標為0的子文檔中的points字段的值,滿足條件{$lte: 55},也就是這個子文檔中的points字段的值小於等於55。查詢結果如下:
{"_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] }
匹配任何一個滿足條件的子文檔
如果我們省略了數組的下標,那么查詢條件就變成了這樣
db.users.find( { 'points.points': { $lte: 55 } } )
它表示查詢points數組中任何一個子文檔,只要子文檔中的points字段的值滿足條件{$lte: 55}就可以了。
查詢結果:
{ "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] }
{ "_id" : 4, "name" : "xi", "age" : 34, "type" : 2, "status" : "D", "favorites" : { "artist" : "Chagall", "food" : "chocolate" }, "finished" : [ 5, 11 ], "badges" : [ "red", "black" ], "points" : [ { "points" : 53, "bonus" : 15 }, { "points" : 51, "bonus" : 15 } ] }
可以看到,查詢結果中包含了所有滿足條件的記錄。
多個條件之間的and關系
利用上面提到的數組的多條件“and”關系匹配的方法,也可以用來為數組中的子文檔指定多個匹配條件。查詢條件也是利用了"$elemMatch"操作符:
db.users.find( { points: { $elemMatch: { points: { $lte: 70 }, bonus: 20 } } } )
可以看到,我們在對points數組中的子文檔指定多個匹配匹配條件的時候,和上面提到的對數組中元素指定多個匹配條件的方式類似。只不過,上面是對整數指定匹配條件,這里是換成了對子文檔指定匹配條件,而且子文檔中的字段可以直接引用,不用采用點(.)號的方式引用。
查詢結果
{ "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] }
多個條件之間的or關系
和對數組中元素指定多個or關系的查詢條件一樣,對數組中的子文檔指定多個or關系的查詢條件的語句,在形式上類似。來看具體的例子:
db.users.find( { "points.points": { $lte: 70 }, "points.bonus": 20 } )
這里使用點(.)號來引用數組中的子文檔的字段,其中兩個條件之間是“或”的關系,語句的意思大體是這樣的:查詢滿足points數組中,任何一個子文檔的points字段的值不小於70或者任何一個子文檔的bonus字段的值為20的記錄。查詢結果是這樣的:
{ "_id" : 2, "name" : "bob", "age" : 42, "type" : 1, "status" : "A", "favorites" : { "artist" : "Miro", "food" : "meringue" }, "finished" : [ 11, 25 ], "badges" : [ "green" ], "points" : [ { "points" : 85, "bonus" : 20 }, { "points" : 64, "bonus" : 12 } ] }
{ "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] }
如果我們對數組的下標做了指定,那么可以在上面的多個“or”條件之上,再加一個數組元素的位置限定。比如:
db.users.find({'points.0.points': {$gte: 70}, 'points.0.bonus': 8})
這里使用了上面提到的,在數組中指定下標的方式來指定數組中的某一個元素。
查詢結果
{ "_id" : 3, "name" : "ahn", "age" : 22, "type" : 2, "status" : "A", "favorites" : { "artist" : "Cassatt", "food" : "cake" }, "finished" : [ 6 ], "badges" : [ "blue", "red" ], "points" : [ { "points" : 81, "bonus" : 8 }, { "points" : 55, "bonus" : 20 } ] }
{ "_id" : 6, "name" : "abc", "age" : 43, "type" : 1, "status" : "A", "favorites" : { "food" : "pizza", "artist" : "Picasso" }, "finished" : [ 18, 12 ], "badges" : [ "black", "blue" ], "points" : [ { "points" : 78, "bonus" : 8 }, { "points" : 57, "bonus" : 7 } ] }
6. null值和字段存在性檢查
mongodb中,字段的值為null和字段缺失對於不同的查詢條件中,會有不同的處理方式。
首先我們先添加兩條分別是字段值為null和字段缺失的實驗數據:
db.users.insert( [ { "_id" : 900, "name" : null }, { "_id" : 901 } ] )
查詢值是否為null或者值字段值是否存在,可以使用等值查詢來判斷。以{<field>:null}的方式判斷的時候,字段的值為null和字段不存在這兩種情況被認為是相同的,比如我們這樣查詢:
db.users.find({name: null})
查詢的結果
{ "_id" : 900, "name" : null } { "_id" : 901 }
可以看到,當使用等值查詢的時候,會把字段不存在和字段值為null的記錄都查詢到,但是有時候我們需要區分這兩種情況,這個時候,我們就需要用別的方法來進行查詢了。
首先,對於字段缺失的情況,我們可以使用"$exists"操作符來判斷字段是否存在。
db.users.find( { name : { $exists: false } } )
查詢結果
{ "_id" : 901 }
可以看到,"$exists"操作符對待null和字段缺失的情況是不同的,只要字段存在,即使字段的值為null,$exists操作符也認為字段是存在的。
字段的存在性可以通過“$exists”操作符來判斷,那么判斷字段是否為null的也是有方法,這就需要通過字段值類型來解決了。
db.users.find( { name : { $type: 10 } } )
查詢結果
{ "_id" : 900, "name" : null }
我們可以看到,在查詢語句中,使用了"$type"操作符,"$type"操作符的作用是判斷字段的值得類型。而null值得類型在BSON中為10,所以利用這一點來判斷字段是否為null。
7. 總結
到這里,我們已經差不多講完了mongo中有關查詢語句的語法。從對簡單值的查詢,到對嵌套子文檔的查詢,再到數組的查詢,最后到數組和子文檔嵌套的復雜查詢。以及如何對多個查詢條件做“or”操作和“and”操作。希望這篇文章對各位有一些幫助吧。
講了那么多,光看沒用,還是要多動手實踐,自己動手去敲一遍代碼才會加深印象,如果能在項目中的業務場景中需要用到這些查詢,那么就再好不過了。因為這樣才會讓這些知識更好地被吸收,不然就會掉入學了忘,忘了學的深淵之中。這其實也是寫博客的好處,學了新的知識,即使不能馬上用到工作中,通過一篇博客來鞏固加深印象,雖然沒在實際項目中使用產生的效果好,也是也是有一定的效果的。