在本文中我們將學(xué)習(xí)流式聚合操作,并深入了解語句的執(zhí)行效率。然后深入學(xué)習(xí)能夠提高數(shù)據(jù)服務(wù)可用性的復(fù)制集。接著了解 MongoDB 的水平擴(kuò)展能力,學(xué)習(xí) MongoDB 數(shù)據(jù)的備份與還原方法,并為數(shù)據(jù)服務(wù)開啟訪問控制。
基礎(chǔ)篇 一 文檔的 CRUD 操作
CRUD 操作指的是對文檔進(jìn)行 create,read,update and delete 操作,即增刪改查。文檔 CRUD 操作的內(nèi)容將分為 Create Operations, Read Operations, Update Operations, Delete Operations 和 Cursor 等 5 個(gè)部分進(jìn)行介紹。
Create Operations
創(chuàng)建操作或者插入操作會(huì)向集合添加新的文檔。之前有提到過,如果插入時(shí)集合不存在,插入操作會(huì)創(chuàng)建對應(yīng)的集合。MongoDB 提供了 3 個(gè)插入文檔的方法:
插入單個(gè)文檔
其中,db.collection.insertOne() 用于向集合插入單個(gè)文檔。而 db.collection.insertMany() 和 db.collection.insert() 可以向集合插入多個(gè)文檔。db.collection.insertOne() 示例如下:
> db.zenrust.insertOne({
... nickname: "Rust 之禪",
... name: "zenrust",
... types: "訂閱號(hào)",
... descs:"超酷人生,我用 Rust"
... })
自動(dòng)命令執(zhí)行后會(huì)返回一個(gè)結(jié)果文檔,文檔輸出如下:
{
"acknowledged" : true,
"insertedId" : ObjectId("5d157fe26fcb85935e9cb786")
}
這說明文檔插入成功。其中,acknowledged 代表本次操作的操作狀態(tài),狀態(tài)值包括 true 和 false。insertedId 即該文檔的 _id。
提示:示例中的省略號(hào)是 MongoShell 的換行標(biāo)識(shí)符。換行標(biāo)識(shí)符對命令輸入和執(zhí)行并沒有影響,所以本文也不會(huì)注重風(fēng)格的統(tǒng)一,即示例中有時(shí)會(huì)帶有換行符,有時(shí)則不帶有換行符。
插入多個(gè)文檔
db.collection.insertMany() 示例如下:
> db.zenrust.insertMany([
... {nickname: "Rust 之禪", name: "zenrust", types: "訂閱號(hào)", descs: "超酷人生,我用 Rust"},
... {nickname: "進(jìn)擊的 Coder", name: "FightingCoder", types: "訂閱號(hào)", descs: "分享爬蟲技術(shù)和機(jī)器學(xué)習(xí)方面的編程經(jīng)驗(yàn)"}
... ])
由于本次插入了 2 個(gè)文檔,所以返回的結(jié)果文檔會(huì)顯示兩個(gè) _id。返回文檔內(nèi)容如下:
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5d1582136fcb85935e9cb787"),
ObjectId("5d1582136fcb85935e9cb788")
]
}
db.collection.insert() 示例如下:
> db.zenrust.insert({title: "全面認(rèn)識(shí) RUST,掌控未來的雷電"})
示例演示的是單個(gè)文檔的插入,實(shí)際上插入多個(gè)文檔也是沒問題的。db.collection.insert() 插入單個(gè)文檔時(shí)返回的是一個(gè)帶有操作狀態(tài)的 WriteResult 對象:WriteResult({ "nInserted" : 1 }) 。其中,nInserted 表明了插入文檔的總數(shù)。但如果插入操作遇到錯(cuò)誤,那么 WriteResult 對象將包含錯(cuò)誤提示信息。
db.collection.insert() 插入多個(gè)文檔的示例如下:
> db.zenrust.insert([{nickname: "進(jìn)擊的 Coder"}, {nickname: "Rust 之禪"}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
可以看到,db.collection.insert() 插入多個(gè)文檔和插入單個(gè)文檔得到的返回結(jié)果是不同的。
Read Operations
MongoDB 提供了 db.collection.find() 方法從集合中讀取文檔。在開始練習(xí)之前,需要準(zhǔn)備用于練習(xí)的基礎(chǔ)數(shù)據(jù)。在 MongoShell 中執(zhí)行以下文檔插入操作:
> db.inven.insertMany([
{ name: "詹姆斯", number: 6, attribute: { h: 203, w: 222, p: "前鋒" }, status: "A" },
{ name: "韋德", number: 3, attribute: { h: 193, w: 220, p: "得分后衛(wèi)" }, status: "R" },
{ name: "科比", number: 24, attribute: { h: 198, w: 212, p: "得分后衛(wèi)" }, status: "R" },
{ name: "姚明", number: 11, attribute: { h: 226, w: 308, p: "中鋒" }, status: "R" },
{ name: "喬丹", number: 23, attribute: { h: 198, w: 216, p: "得分后衛(wèi)" }, status: "R" }
])
查詢文檔
將一個(gè)空位當(dāng)作為查詢過濾器參數(shù)傳遞給 db.collection.find() 方法就可以得到所有文檔,對應(yīng)示例如下:
> db.inven.find({})
或者什么都不傳,直接使用 find(),對應(yīng)示例如下:
> db.inven.find()
這等效于 SQL 中的 SELECT * FROM inven。
指定等式條件
如果要指定相等條件,可以使用 {<field1>: <value1>, ...} 這樣的過濾表達(dá)式,例如過濾出已退役球員("R" 代表退役)的查詢語句如下:
> db.inven.find({status: "R"})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "喬丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后衛(wèi)" }, "status" : "R" }
這等效于 SQL 中的 SELECT * FROM inven WHERE status = "R"。
根據(jù)嵌套文檔字段查詢
我們還可以根據(jù)嵌入式文檔中的字段進(jìn)行查詢,例如過濾出球員屬性中身高為 193 的球員,對應(yīng)示例如下:
> db.inven.find({"attribute.h": 193})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
要注意的是,訪問嵌入式文檔中的字段時(shí)使用的并不是 attribute.h,而是使用 "attribute.h"。
查詢與投影操作
查詢的情況非常復(fù)雜,MongoDB 提供了多種查詢操作符來應(yīng)對這些問題。MongoDB 提供的查詢操作符分為以下幾類:
- 比較查詢操作符
- 邏輯查詢操作符
- 元素查詢操作符
- 評估查詢操作符
- 地理空間查詢操作符
- 數(shù)組查詢操作符
- 按位查詢操作符
接下來,我們將學(xué)習(xí)每一種查詢操作符的規(guī)則和語法。
比較查詢操作符
比較是最常見的操作之一,它分為同類型比較和非同類型比較。在面對不同的 BSON 類型值時(shí),比較的并不是值的大小,而是值的類型,即按類比較。MongoDB 使用以下比較順序,順序從低到高:
- MinKey (internal type)
- Null
- Numbers (ints, longs, doubles, decimals)
- Symbol, String
- Object
- Array
- BinData
- ObjectId
- Boolean
- Date
- Timestamp
- Regular Expression
- MaxKey (internal type)
同類型比較的情況則稍微復(fù)雜一些。數(shù)字類型比較的是值的大小,例如 5 大于 3。字符串類型比較的是其值的二進(jìn)制,例如 R 大于 A 是因?yàn)?R 的二進(jìn)制值 0101 0010 大于 A 的二進(jìn)制值 0100 0001。數(shù)組的小于比較或者升序排序比較的是數(shù)組中的最小元素,大于比較或降序排序比較的是數(shù)組中的最大元素。我們可以通過一個(gè)例子來了解這些知識(shí)。準(zhǔn)備如下數(shù)據(jù):
> db.arrs.insertMany([
... {name: "James", attr: [5, 6, 7]},
... {name: "Wade", attr: [1, 7, 8]},
... {name: "Kobe", attr: [1, 9, 9]},
... {name: "Bosh", attr: [2, 9, 9]}
... ])
假設(shè)要將文檔按 name 升序排序,即字符串升序排序。對應(yīng)示例如下:
> db.arrs.find().sort({name: 1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
排序結(jié)果為 Bosh- James - Kobe - Wade,那么字符串降序排序的結(jié)果一定是 Wade - Kobe - James - Bosh。對應(yīng)示例如下:
> db.arrs.find().sort({name: -1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
現(xiàn)有:[ 5, 6, 7 ], [ 1, 7, 8 ], [ 1, 9, 9 ], [ 2, 9, 9 ] 4 個(gè)數(shù)組,上面提到,數(shù)組升序排序比較的是最小元素。4 個(gè)數(shù)組中最小的值分別是 5, 1, 1, 2,其中數(shù)組 [1, 7, 8] 和數(shù)組 [1, 9, 9] 的最小值相同,則比較第二小的值,即 7, 9。那么正確的升序排序結(jié)果因該是 [ 1, 7, 8 ], [ 1, 9, 9 ], [ 2, 9, 9 ], [5, 6, 7]。數(shù)組升序排序命令如下:
> db.arrs.find().sort({attr: 1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }
排序結(jié)果與分析結(jié)果一致。數(shù)組降序排序比較的是最大元素。4 個(gè)數(shù)組中最大的值分別是 7, 8, 9, 9,其中數(shù)組 [1, 9, 9] 和 [2, 9, 9] 的最大值和第二大的值相同,則比較第三大的值,即 1, 2。那么正確的降序排序結(jié)果應(yīng)該是 [2, 9, 9], [1, 9, 9], [1, 7, 8], [5, 6, 7]。數(shù)組降序排序命令如下:
> db.els.find().sort({attr: -1})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74fff"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df75000"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffe"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }
排序結(jié)果和分析結(jié)果并不同,這是為什么呢?難道不是按最大元素比較大小嗎?
文檔中并沒有提到,但我們可以通過例子尋找答案。準(zhǔn)備以下數(shù)據(jù):
> db.parts.insertMany([
... {name: 1, attr: [9, 9, 0, 5]},
... {name: 2, attr: [9, 9, 0, 1]},
... {name: 3, attr: [9, 0, 9, 0]},
... {name: 4, attr: [9, 8, 7, 6]},
... {name: 5, attr: [5, 2, 3, 6]},
... {name: 6, attr: [9, 0, 0, 0]},
... {name: 7, attr: [30, 0]},
... {name: 8, attr: [22, 0]},
... {name: 9, attr: [30, 5]},
... {name: 10, attr: [30, 3]}
... ])
數(shù)組降序排序示例如下:
> db.parts.find().sort({attr: -1})
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb121"), "name" : 7, "attr" : [ 30, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb123"), "name" : 9, "attr" : [ 30, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb124"), "name" : 10, "attr" : [ 30, 3 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb122"), "name" : 8, "attr" : [ 22, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11b"), "name" : 1, "attr" : [ 9, 9, 0, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11c"), "name" : 2, "attr" : [ 9, 9, 0, 1 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11d"), "name" : 3, "attr" : [ 9, 0, 9, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11e"), "name" : 4, "attr" : [ 9, 8, 7, 6 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb120"), "name" : 6, "attr" : [ 9, 0, 0, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11f"), "name" : 5, "attr" : [ 5, 2, 3, 6 ] }
數(shù)組升序排序示例如下:
> db.parts.find().sort({attr: 1})
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11b"), "name" : 1, "attr" : [ 9, 9, 0, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11c"), "name" : 2, "attr" : [ 9, 9, 0, 1 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11d"), "name" : 3, "attr" : [ 9, 0, 9, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb120"), "name" : 6, "attr" : [ 9, 0, 0, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb121"), "name" : 7, "attr" : [ 30, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb122"), "name" : 8, "attr" : [ 22, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11f"), "name" : 5, "attr" : [ 5, 2, 3, 6 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb124"), "name" : 10, "attr" : [ 30, 3 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb123"), "name" : 9, "attr" : [ 30, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11e"), "name" : 4, "attr" : [ 9, 8, 7, 6 ] }
根據(jù)以上結(jié)果,我們可以推測出排序規(guī)律:如果被比較的值相同,那么就按照插入順序(即 ObjectId)排序。降序排序比較的是最大元素,即 30 - 30 - 30 - 22 -9 - 9 - 9 - 9 - 9 - 6,其中 30 和 9 均有重復(fù)。可以發(fā)現(xiàn):
- 第一個(gè)
30對應(yīng)的name值為7,第二個(gè)30對應(yīng)的name值為9,第三個(gè)30對應(yīng)的name值為10; - 第一個(gè)
9對應(yīng)的name值為1,第二個(gè)9對應(yīng)的name值為2,第三個(gè)9對應(yīng)的name值為3,第四個(gè)9對應(yīng)的name值為4,第五個(gè)9對應(yīng)的name值為6;
升序排序比較的是最小元素,即 0 - 0 - 0 - 0 - 0 - 0 - 2 - 3 - 5 - 6,其中重復(fù)的只有 0。0 對應(yīng)的 name 值依次為 1, 2, 3, 6, 7, 8 。無論是升序排序還是降序排序,實(shí)際得到的結(jié)果與我們推測出來的規(guī)律相同,這說明我們推測出來的規(guī)律是正確的。其他類型的比較或排序規(guī)則可查閱官方文檔 comparison-sort-order 。
MongoDB 提供了一系列用于比較的比較符,它們分別是:
| 名稱 | 描述 |
|---|---|
$eq |
匹配等于指定值的值 |
$gt |
匹配大于指定值的值 |
$gte |
匹配大于或等于指定值的值 |
$in |
匹配數(shù)組中指定的任何值 |
$lt |
匹配小于指定值的值 |
$lte |
匹配小于或等于指定值的值 |
$ne |
匹配所有不等于指定值的值 |
$nin |
不匹配數(shù)組中指定的任何值 |
其中,$eq, $gte, $lt, $lte, $gt,$ne 的語法是相同的。以 $eq 為例,其語法格式如下:
{ <field>: { $eq: <value> } }
假設(shè)要匹配集合 els 中名稱為 James 的文檔,對應(yīng)示例如下:
> db.els.find({name: {$eq: "James"}})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }
這等效于 SQL 中的 SELECT * FROM els WHERE name = "James"。
$in 和 $nin 的語法相同。以 $in 為例,其格式如下:
{ field: { $in: [<value1>, <value2>, ... <valueN> ] } }
假設(shè)要過濾出集合 els 中文檔字段 attr 中包含 6 或者 9 的文檔,對應(yīng)示例如下:
> db.els.find({attr: {$in: [6, 9]}})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74fff"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df75000"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
另外,$in 和 $nin 均支持正則表達(dá)式。例如要過濾出集合 inven 中 name 字段值以 詹 或者 韋 開頭的文檔,對應(yīng)示例如下:
> db.inven.find({name: {$in: [/^詹/, /^韋/]}})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前鋒" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
反過來,過濾出集合 inven 中 name 字段值非 詹 或者非 韋 開頭的文檔。對應(yīng)示例如下:
> db.inven.find({name: {$nin: [/^詹/, /^韋/]}})
{ "_id" : ObjectId("5d200b986c39176e3a421af4"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af5"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af6"), "name" : "喬丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后衛(wèi)" }, "status" : "R" }
以上就是比較查詢操作符的相關(guān)知識(shí),更多關(guān)于比較查詢操作符的知識(shí)可查閱官方文檔 Comparison Query Operators。
邏輯查詢操作符
MongoDB 中的邏輯查詢操作符共有 4 種,它們是:
| 名稱 | 描述 |
|---|---|
$and |
匹配符合多個(gè)條件的文檔 |
$not |
匹配不符合條件的文檔 |
$nor |
匹配不符合多個(gè)條件的文檔 |
$or |
匹配符合任一條件的文檔 |
其中,$and, $nor 和 $or 語法格式相同:
{ $keyword: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
語法中的 keyword 代表 and/nor/or。而 $not 語法格式如下
{ field: { $not: { <operator-expression> } } }
$and 是隱式的,這意味著我們不必在查詢語句中表明 and 或 AND 。 假設(shè)要過濾出集合 inven 中 球衣號(hào)大于 10 的 退役球員,此時(shí)有兩個(gè)條件:球衣號(hào)大于 10,退役球員。對應(yīng)示例如下:
> db.inven.find({status: "R", number: {$gt: 10}})
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "喬丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后衛(wèi)" }, "status" : "R" }
這等效于 SQL 中的 SELECT * FROM inven WHERE status = "R" AND number > 10。當(dāng)然,也可以采用顯式寫法,對應(yīng)示例如下:
> db.inven.find({$and: [{status: "R"}, {number: {$gt: 10}}]})
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "喬丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后衛(wèi)" }, "status" : "R" }
$or, $not,$nor 均采用顯式寫法。假設(shè)要過濾出集合 inven 中 球衣號(hào)大于 10 或者 退役球員 的文檔,對應(yīng)示例如下:
> db.inven.find({$or: [{status: "R"}, {number: {$gt: 10}}]})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "喬丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后衛(wèi)" }, "status" : "R" }
這等效于 SQL 中的 SELECT * FROM inven WHERE status = "R" OR number > 10。
假設(shè)要過濾出集合 inven 中 number 不等于 11 且 number 不等于 23 的文檔,對應(yīng)示例如下:
> db.inven.find({$nor: [{number: 23}, {number: 11}]})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前鋒" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af4"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后衛(wèi)" }, "status" : "R" }
假設(shè)要過濾出集合 inven 中 number 不大于 20 的文檔,對應(yīng)示例如下:
> db.inven.find({number: {$not: {$gt: 20}}})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前鋒" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韋德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后衛(wèi)" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af5"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中鋒" }, "status" : "R" }
這個(gè)例子將比較查詢操作符和邏輯查詢操作符結(jié)合使用,實(shí)現(xiàn)了更細(xì)致的查詢。
更多關(guān)于邏輯查詢操作符的知識(shí)可查閱官方文檔 Logical Query Operators。
元素查詢操作符
MongoDB 中的元素查詢操作符只有 2 種,它們是:
| 名稱 | 描述 |
|---|---|
$exists |
匹配具有指定字段的文檔 |
$type |
匹配字段值符合類型的文檔 |
exists
在開始學(xué)習(xí) $exists 前,我們需要準(zhǔn)備以下數(shù)據(jù):
> db.elem.insertMany([
... {title: "湖人今夏交易頻繁", author: "Asyncins", date: "2019-07-01", article: "..."},
... {title: "詹姆斯現(xiàn)身 MIA-CHN 比賽現(xiàn)場", date: "2019-07-06", article: "..."},
... {title: "倫納德遲遲不肯表態(tài)", author: "Asyncins"}
... ])
$exists 語法格式如下:
{ field: { $exists: <boolean> } }
假設(shè)要過濾出集合 elem 中包含 author 字段的文檔,對應(yīng)示例如下:
> db.elem.find({author: {$exists: true}})
{ "_id" : ObjectId("5d203f1a6c39176e3a421af7"), "title" : "湖人今夏交易頻繁", "author" : "Asyncins", "date" : "2019-07-01", "article" : "..." }
{ "_id" : ObjectId("5d203f1a6c39176e3a421af9"), "title" : "倫納德遲遲不肯表態(tài)", "author" : "Asyncins" }
反過來,要過濾出集合 elem 中不包含 author 字段的文檔,對應(yīng)示例如下:
> db.elem.find({author: {$exists: false}})
{ "_id" : ObjectId("5d203f1a6c39176e3a421af8"), "title" : "詹姆斯現(xiàn)身 MIA-CHN 比賽現(xiàn)場", "date" : "2019-07-06", "article" : "..." }
type
在開始學(xué)習(xí) $type 前,我們需要準(zhǔn)備如下數(shù)據(jù):
> db.ops.insertMany([
... {title: "北京高溫持續(xù),注意避暑", weight: 5, rec: false},
... {title: "廣西持續(xù)降雨,最大降雨量 200 ml", weight: 5, rec: false},
... {title: "高考分?jǐn)?shù)線已出,高分學(xué)子增多", weight: "hot", rec: true},
... {title: "秋老虎是真是假?", weight: 3, rec: false}
... ])
$type 語法如下:
{ field: { $type: <BSON type> } }
它也支持陣列寫法:
{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }
假設(shè)要過濾出 weight 值類型為 String 的文檔,對應(yīng)示例如下:
> db.ops.find({weight: {$type: "string"}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b7"), "title" : "高考分?jǐn)?shù)線已出,高分學(xué)子增多", "weight" : "hot", "rec" : true }
同理,過濾出 weight 值類型為 Number 的文檔的對應(yīng)示例如下:
> db.ops.find({weight: {$type: "number"}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b5"), "title" : "北京高溫持續(xù),注意避暑", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b6"), "title" : "廣西持續(xù)降雨,最大降雨量 200 ml", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b8"), "title" : "秋老虎是真是假?", "weight" : 3, "rec" : false }
陣列寫法中的 BSON 類型為 or 關(guān)系,例如要過濾出 weight 值類型為 String 或者 Number 的文檔,對應(yīng)示例如下:
> db.ops.find({weight: {$type: ["string", "number"]}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b5"), "title" : "北京高溫持續(xù),注意避暑", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b6"), "title" : "廣西持續(xù)降雨,最大降雨量 200 ml", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b7"), "title" : "高考分?jǐn)?shù)線已出,高分學(xué)子增多", "weight" : "hot", "rec" : true }
{ "_id" : ObjectId("5d1838eb51b88758035de5b8"), "title" : "秋老虎是真是假?", "weight" : 3, "rec" : false }
這等效于 SELECT * FROM ops WHERE weight.type = string OR weight.type = number 這樣的 SQL 偽代碼表示。要注意的是,$type 支持所有 BSON 類型的字符串標(biāo)識(shí)符和整數(shù)標(biāo)識(shí)符,例如 String 類型的字符串標(biāo)識(shí)符 sting 及其整數(shù)標(biāo)識(shí)符 2,即 {$type: "string"} 等效于 {$type: 2}。
更多關(guān)于元素查詢操作符的知識(shí)可查閱官方文檔 Element Query Operators。
評估查詢操作符
MongoDB 中的評估查詢操作符共有 6 種,它們是:
| 名稱 | 描述 |
|---|---|
$expr |
允許在查詢語句中使用聚合表達(dá)式 |
$jsonSchema |
根據(jù)給定的 JSON 模式驗(yàn)證文檔 |
$mod |
對字段的值執(zhí)行模運(yùn)算,并選擇具有指定結(jié)果的文檔 |
$regex |
匹配與正則表達(dá)式規(guī)則相符的文檔 |
$text |
執(zhí)行文本搜索 |
$where |
匹配滿足 JavaScript 表達(dá)式的文檔 |
expr
在學(xué)習(xí) $expr 前,我們需要準(zhǔn)備以下數(shù)據(jù):
> db.acbook.insertMany([
... {_id: 1, category: "衣", 預(yù)算: 300, 開支: 600},
... {_id: 2, category: "食", 預(yù)算: 1000, 開支: 600},
... {_id: 3, category: "住", 預(yù)算: 800, 開支: 800},
... {_id: 4, category: "行", 預(yù)算: 220, 開支: 360},
... {_id: 5, category: "醫(yī)", 預(yù)算: 200, 開支: -50}
... ])
$expr 語法格式如下:
{ $expr: { <expression> } }
假設(shè)要過濾出超出預(yù)算的文檔,對應(yīng)示例如下:
> db.acbook.find({$expr: {$gt: ["$開支", "$預(yù)算"]}})
{ "_id" : 1, "category" : "衣", "預(yù)算" : 300, "開支" : 600 }
{ "_id" : 4, "category" : "行", "預(yù)算" : 220, "開支" : 360 }
示例中使用了 $gt 表達(dá)式,用于比較 開支 和 預(yù)算。我們也可以使用 $lt 表達(dá)式,對應(yīng)命令如下:
> db.acbook.find({$expr: {$lt: ["$預(yù)算", "$開支"]}})
{ "_id" : 1, "category" : "衣", "預(yù)算" : 300, "開支" : 600 }
{ "_id" : 4, "category" : "行", "預(yù)算" : 220, "開支" : 360 }
$expr 支持的表達(dá)式非常多,詳見官方文檔 Expressions。
mod
$mod 的作用是對字段的值執(zhí)行模運(yùn)算,并選擇具有指定結(jié)果的文檔。其語法格式如下:
{ field: { $mod: [ divisor, remainder ] } }
假設(shè)要過濾出集合 acbook 中滿足 mod(開支, 6) = 0 的文檔。對應(yīng)示例如下:
> db.acbook.find({開支: {$mod: [6, 0]}})
{ "_id" : 1, "category" : "衣", "預(yù)算" : 300, "開支" : 600 }
{ "_id" : 2, "category" : "食", "預(yù)算" : 1000, "開支" : 600 }
{ "_id" : 4, "category" : "行", "預(yù)算" : 220, "開支" : 360 }
即 mod(600, 6) = 0, mod(360, 6) = 0。同理,要過濾出集合 acbook 中滿足 mod(開支, 6) = 2 的文檔,對應(yīng)示例如下:
> db.acbook.find({開支: {$mod: [6, 2]}})
{ "_id" : 3, "category" : "住", "預(yù)算" : 800, "開支" : 800 }
即 mod(800, 6) = 2。要注意的是,$mod 只接受 2 個(gè)參數(shù):divisor 和 remainder。如果只傳入 1 個(gè)參數(shù),例如 db.acbook.find({開支: {$mod: [6]}}), 就會(huì)得到如下錯(cuò)誤提示:
Error: error: {
"ok" : 0,
"errmsg" : "malformed mod, not enough elements",
"code" : 2,
"codeName" : "BadValue"
}
不傳入值或傳入多個(gè)值也是不被允許的,在返回文檔中的 errmsg 處會(huì)給出對應(yīng)的提示。例如不傳入值對應(yīng)的提示為 malformed mod, not enough elements,而多個(gè)值對應(yīng)的提示為 malformed mod, too many elements。
提示:在 2.6 版本中,傳入單個(gè)值時(shí)會(huì)默認(rèn)補(bǔ)上 0,傳入多個(gè)值時(shí)會(huì)忽略多余的值。例如 db.acbook.find({開支: {$mod: [6]}}) 和 db.acbook.find({開支: {$mod: [6, 2, 3, 5]}}) 等效于 db.acbook.find({開支: {$mod: [6, 0]}})。但 4.0 版本不允許這樣做。
regex
MongoDB 提供的 $regex 讓開發(fā)者可以在查詢語句中使用正則表達(dá)式,這實(shí)在是令人驚喜。MongoDB 中的正則表達(dá)式是 PCRE,即 Perl 語言兼容的正則表達(dá)式。$regex 語法格式如下:
{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }
三種格式任選其一,特定語法的使用限制可參考 $regex vs./pattern/Syntax 。也可以用下面這種語法:
{ <field>: /pattern/<options> }
正則表達(dá)式中有一些特殊選項(xiàng)(又稱模式修正符),例如不區(qū)分大小寫或允許使用點(diǎn)字符等,MongoDB 中支持的選項(xiàng)如下:
| 選項(xiàng) | 描述 | 語法限制 |
|---|---|---|
i |
不區(qū)分大小寫字母。 | |
m |
支持多行匹配。 | |
x |
忽略空格和注釋(#),注釋以 \n 結(jié)尾。 |
必須使用 $option |
s |
允許點(diǎn)(.)字符匹配括換行符在內(nèi)的所有字符,也可以理解為允許點(diǎn)(.)字符匹配換行符后面的字符。 | 必須使用 $option |
在開始學(xué)習(xí)之前,準(zhǔn)備以下數(shù)據(jù):
> db.regexs.insertMany([
... {_id: 1, nickname: "abc123", desc: "Single Line Description."},
... {_id: 2, nickname: "abc299", desc: "First line \nSecond line"},
... {_id: 3, nickname: "xyz5566", desc: "Many spaces before line"},
... {_id: 4, nickname: "xyz8205", desc: "Multiple\nline description"}
... ])
假設(shè)要過濾出 nickname 值結(jié)尾為 299 的文檔,對應(yīng)示例如下:
> db.regexs.find({nickname: {$regex: /299$/}})
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }
這相當(dāng)于 SQL 中的模糊查詢,對應(yīng)的 SQL 語句為 SELECT * FROM regexs WHERE nickname like "%299"。接下來使用模式修正符 i 實(shí)現(xiàn)不區(qū)分大小寫的匹配,對應(yīng)示例如下:
> db.regexs.find({nickname: {$regex: /^aBc/i}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }
這個(gè)語句的作用是過濾出集合 regexs 中 nickname 字段值由 aBc 開頭的文檔,并在匹配時(shí)忽略大小寫字母。接下來我們再通過一個(gè)例子了解模式修正符 m 的用法和作用,對應(yīng)示例如下:
> db.regexs.find({desc: {$regex: /^s/, $options: "im"}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }
這個(gè)語句的作用是過濾出集合 regexs 中 desc 字段值由 s 開頭的文檔,匹配時(shí)忽略大小寫字母,并進(jìn)行多行匹配。雖然 _id 為 2 的文檔中的 desc 并不是 s 或 S 開頭,但由于使用了模式修正符 m,所以能夠匹配到 \n 符號(hào)后面的 Second。如果沒有使用模式修正符 m,那么匹配結(jié)果將會(huì)是 { "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }。
點(diǎn)字符和星號(hào)在正則表達(dá)式中是最常用的組合,MongoDB 也支持這個(gè)組合。假設(shè)要過濾出集合 regexs 中 desc 字段值由 m 開頭且 line 結(jié)尾的文檔,對應(yīng)示例如下:
> db.regexs.find({desc: {$regex: /m.*line/, $options: "is"}})
{ "_id" : 3, "nickname" : "xyz5566", "desc" : "Many spaces before line" }
{ "_id" : 4, "nickname" : "xyz8205", "desc" : "Multiple\nline description" }
如果不使用模式修正符 s,.* 組合也是可用的,但無法匹配到換行符后面的內(nèi)容,那么匹配結(jié)果將會(huì)是 { "_id" : 3, "nickname" : "xyz5566", "desc" : "Many spaces before line" }。
模式修正符 x 的描述為:“忽略空格和注釋(#),注釋以 \n 結(jié)尾”。這理解起來有些困難,但你不用擔(dān)心,只要跟著本文指引和案例,就能夠掌握模式修正符 x 的正確用法。示例如下:
> var pattern = "abc #category code\n123 #item number"
> db.regexs.find({nickname: {$regex: pattern, $options: "x"}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
正則規(guī)則為 abc #category code\n123 #item number,根據(jù)模式修正符 x 的描述,我們可以將其轉(zhuǎn)換為 abc123。即過濾出集合 regexs 中 nickname 值為 abc123 的文檔,所以執(zhí)行結(jié)果為:
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
再來看一個(gè)示例:
> var pattern = "abc #category code\n xyz#item number"
> db.regexs.find({nickname: {$regex: pattern, $options: "x"}})