本教程向你介紹了MongoDB資料庫。你將發現如何安裝該軟體、運算元據,並將資料設計技術應用於你自己的應用程式。
所有的例子都是使用MongoDB 5開發的,但大多數都可以在以前或以後的版本中使用。程式碼可以直接輸入到客戶端應用程式或MongoDB shell(mongo或mongosh),以查詢和更新資料庫
- 什麼是MongoDB?
- MongoDB的要素
- 如何安裝MongoDB
- 如何訪問你的MongoDB資料庫
- 如何在MongoDB中插入新檔案
- 簡單的MongoDB查詢
- 在MongoDB中使用遊標
- 如何在MongoDB中建立索引
- 如何管理MongoDB的索引
- 使用MongoDB的資料驗證模式
- 如何更新MongoDB中的現有檔案
- 如何刪除MongoDB中的檔案
- 在MongoDB中使用聚合操作
- 如何執行批量MongoDB操作
什麼是MongoDB?
MongoDB是一個開源的NoSQL資料庫。NoSQL意味著該資料庫不像傳統的SQL資料庫那樣使用關係表。
有一系列的NoSQL資料庫型別,但MongoDB將資料儲存在被稱為文件的類似JavaScript的物件中,其內容看起來像這樣:
{ _id: "123", name: "Craig" }
雖然MongoDB已經成為基於JavaScript的框架Node.js的代名詞,但大多數框架、語言和執行時都有MongoDB官方資料庫驅動,包括Node.js、PHP和Python。你也可以選擇像Mongoose這樣的庫,提供更高層次的抽象或物件關係對映(ORM)功能。
與SQL表不同,你可以在MongoDB中儲存的內容沒有結構限制。資料模式不被強制執行。你可以在你喜歡的地方儲存任何你喜歡的東西。這使得MongoDB成為更多有機的–或混亂的–資料結構的理想選擇。
考慮一個聯絡地址簿。個人往往有多個電話號碼。你可以在一個SQL表中定義三個電話欄位,但這對一些聯絡人來說太多,對另一些人來說太少。最終,你將需要一個單獨的電話表,這就增加了複雜性。
在MongoDB中,這些電話號碼可以被定義為同一個文件中無限的物件陣列。
{ _id: "123", name: "Craig", telephone: [ { home: "0123456789" }, { work: "9876543210" }, { cell: "3141592654" } ] }
請注意,MongoDB使用類似的JavaScript物件符號進行資料更新和查詢,如果你習慣於SQL,這可能會帶來一些挑戰。
MongoDB的要素
在我們進一步討論之前,讓我們看一下MongoDB的特點。我們將在本文中使用這些詞彙。
- Document。資料儲存中的一個單獨的物件,類似於SQL資料庫表中的記錄或行。
- Field。檔案中的一個單一資料項,如姓名或電話號碼,類似於SQL欄位或表列。
- Collection。一組類似的檔案,類似於一個SQL表。雖然你可以把所有的檔案放到一個集合中,但通常把它們分成特定的型別更實用。在一個聯絡人地址簿中,你可以有一個人的集合和一個公司的集合。
- Database。一個相關資料的集合,與SQL資料庫的含義相同。模式。一個模式定義了資料
- Schema。在SQL資料庫中,你必須在儲存資料之前定義帶有相關欄位和型別的表定義。這在MongoDB中是沒有必要的,儘管它可以建立一個模式,在檔案被新增到集合之前對其進行驗證。
- Index。一種用於提高查詢效能的資料結構,與SQL索引的含義相同。
- Primary Key。每個文件的唯一識別符號。MongoDB會自動為集合中的每個文件新增一個唯一的、有索引的_id欄位。
- Denormalization。在SQL資料庫中,”規範化 “是一種用於組織資料和消除重複的技術。在MongoDB中,鼓勵 “去規範化”。你積極地重複資料,一個單一的檔案可以包含所有需要的資訊。
- JOINs。SQL提供了一個JOIN操作符,因此可以在一次查詢中從多個規範化的表中檢索資料。在MongoDB中,直到3.6版本才可以進行連線,而且限制仍然存在。這也是為什麼資料應該被反規範化為自包含檔案的另一個原因。
- Transactions。當一個更新改變了一個文件上的兩個或更多的值時,MongoDB確保它們全部成
功或全部失敗。跨越兩個或多個文件的更新必須被包裹在一個事務中。MongoDB從4.0
版本開始支援交易,但需要一個多伺服器副本集或分片叢集。下面的安裝示例使用的是
單臺伺服器,因此不可能有事務。
如何安裝MongoDB
在你的本地機器上使用MongoDB,你有三種選擇。我們將引導你瞭解每個選項。
1. 使用Docker(推薦)。
Docker是一個軟體管理工具,可以在幾分鐘內安裝、配置和執行MongoDB或任何其他應用程式。
安裝Docker和Docker Compose,然後建立一個專案資料夾,其中有一個名為dockercompose.yml的檔案,包含以下內容(注意,縮略號是必不可少的)。
version: '3' services: mongodb: image: mongo:5 environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=pass - MONGO_INITDB_DATABASE=mongodemo container_name: mongodb volumes: - dbdata:/data/db ports: - "27017:27017" adminer: image: dehy/adminer container_name: adminer depends_on: - mongodb ports: - "8080:80" volumes: dbdata:
從命令列訪問該資料夾並執行。
docker-compose up
最新版本的MongoDB 5將被下載並啟動。這在第一次啟動時需要幾分鐘的時間,但隨後的執行會快很多。
請注意:
- 一個MongoDB管理員賬戶被定義為ID “root “和密碼 “pass”。
- 資料在重啟之間被儲存在一個名為dbdata的Docker卷中。
- 還提供了Adminer資料庫客戶端。
您可以使用任何MongoDB資料庫客戶端,使用ID “root “和密碼 “pass “連線到localhost:27017。或者,你可以訪問Adminer,網址是http://localhost:8080/,用以下憑證登入。
- System: MongoDB (alpha)
- Server: host.docker.internal
- Username: root
- Password: pass
資訊
伺服器host.docker.internal可以在執行Docker Desktop的Mac和Windows裝置上工作。Linux使用者應該使用裝置的網路IP地址,而不是localhost(Adminer將其解析為自己的Docker容器)。
Adminer登陸
Adminer允許你檢查集合和檔案。然而,要注意的是,集合被稱為 “表”。
Adminer集合檢視
要執行命令,你可以使用MongoDB Shell (mongosh
) 或傳統的 mongo
命令列REPL(讀取評估列印迴圈)環境。
訪問Docker MongoDB容器的bash shell。
docker exec -it mongodb bash
然後用ID和密碼啟動MongoDB shell:
mongosh -u root -p pass
(如果你願意,可以使用傳統的 mongo
命令。)然後你可以釋出
MongoDB的命令,比如下面這些。
show dbs;
— – 顯示所有資料庫use mongodemo;
— 使用一個特定的資料庫show collections;
— 列出資料庫中的集合db.person.find();
— 列出一個集合中的所有文件exit;
— 退出/關閉shell
通過在專案目錄下執行以下命令關閉MongoDB:
docker-compose down
2. 使用雲供應商(無需安裝)。
你可以使用一個託管的MongoDB例項,所以不需要在本地安裝任何東西。網際網路連線是必不可少的,響應速度將取決於主機和你的頻寬。大多數服務將收取每月和/或兆位元組的使用費。
主機通常會提供詳細資訊,因此你可以使用MongoDB客戶端軟體遠端管理資料庫
3. 在本地安裝MongoDB
MongoDB可以在Linux、Windows或Mac OS上安裝和配置。有兩個版本可供選擇:
- 一個商業的企業版
- 一個開源的社羣版(在本教程中使用)。
MongoDB的安裝頁面提供了各種作業系統的說明。一般來說:
- Linux版本使用軟體包管理器進行安裝,如Ubuntu上的apt
- Mac OS版本使用brew進行安裝。
- Windows版本使用.msi安裝程式進行安裝
請務必仔細按照說明進行操作,這樣你的安裝才會成功!
如何訪問你的MongoDB資料庫
現在你的MongoDB資料庫已經安裝完畢,現在是時候學習如何管理它了。讓我們討論一下,為了訪問和使用你的資料庫,你需要做什麼。
1. 安裝一個MongoDB客戶端
管理資料庫需要一個MongoDB客戶端應用程式。如果你使用的是雲端或本地安裝,我們建議你安裝命令列mongosh MongoDB Shell。
Adminer是一個基於Web的資料庫客戶端,它支援MongoDB,儘管目前它僅限於檢查集合。Adminer可作為一個單一的PHP指令碼下載,但如果你使用Docker安裝,它就已經設定好了。
GUI客戶端應用程式為更新和檢查資料提供了一個更好的介面。有幾種選擇,包括免費和跨平臺的MongoDB Compass:
MongoDB Compass
Studio 3T,另一個GUI競爭者,提供了一個商業應用程式,免費授予有限的功能。
Studio 3T
你可以通過使用以下任何一種工具來訪問你的MongoDB資料庫。
- 機器網路名稱、URL或IP地址(本地安裝的是localhost)。
- MongoDB的埠(預設為27017)。
- 一個使用者ID和一個password。一個根使用者通常是在安裝時定義的。
2. 設定和儲存資料庫訪問憑證
根管理員對所有資料庫都有不受限制的訪問權。一般來說,你應該使用一個具有特定許可權的自定義使用者來限制訪問並提高安全性。
例如,以下命令建立了一個名為myuser的使用者,其密碼為mypass,他對mydb資料庫有讀和寫的許可權。
use mydb; db.createUser({ user: "myuser", pwd: "mypass", roles: [ { role: "readWrite", db: "mydb" } ] });
如何在MongoDB中插入新檔案
在插入你的第一個文件之前,不需要定義一個資料庫或集合。使用任何MongoDB客戶端,簡單地切換到一個名為mongodemo的資料庫:
use mongodemo;
然後在一個新的人員集合中插入一個單一的檔案:
db.person.insertOne( { name: 'Abdul', company: 'Alpha Inc', telephone: [ { home: '0123456789' }, { work: '9876543210' } ] } );
通過執行查詢來檢視檔案,以返回人物集合的所有結果:
db.person.find({});
其結果將是這樣的:
{ "_id" : ObjectId("62442429854636a03f6b8534"), name: 'Abdul', company: 'Alpha Inc', telephone: [ { home: '0123456789' }, { work: '9876543210' } ] }
如何插入多個檔案
你可以通過向insertMany()傳遞一個陣列來向一個集合中插入多個文件。下面的程式碼建立了額外的人物文件和一個新的公司集合:
db.person.insertMany([ { name: 'Brian', company: 'Beta Inc' }, { name: 'Claire', company: 'Gamma Inc', telephone: [ { cell: '3141592654' } ] }, { name: 'Dawn', company: 'Alpha Inc' }, { name: 'Esther', company: 'Beta Inc', telephone: [ { home: '001122334455' } ] }, { name: 'George', company: 'Gamma Inc' }, { name: 'Henry', company: 'Alpha Inc', telephone: [ { work: '012301230123' }, { cell: '161803398875' } ] }, ]); db.company.insertMany([ { name: 'Alpha Inc', base: 'US' }, { name: 'Beta Inc', base: 'US' }, { name: 'Gamma Inc', base: 'GB' }, ]);
_id從何而來?
MongoDB自動為集合中的每個文件分配一個_id。這是一個ObjectID- 一個BSON(二進位制Javascript物件符號)值,其中包含:
- 建立時的Unix epoch(秒)(4個位元組)
- Aa 5個位元組的機器/程序ID
- 一個3位元組的計數器,從一個隨機值開始
這是該文件的主鍵。這個24個字元的十六進位制值在資料庫中的所有文件中保證是唯一的,而且一旦插入就不能改變。
MongoDB還提供了一個getTimeStamp()函式,因此你可以獲得文件的建立日期/時間,而不需要明確設定一個值。另外,你可以在建立文件時定義你自己獨特的_id值。
資料去規範化
上面插入的記錄將每個使用者的公司設定為一個字串,如 “Alpha Inc”。在規範化的SQL資料庫中不建議這樣做:
- 這很容易出錯:一個使用者被分配到 “阿爾法公司”,而另一個則是 “阿爾法公司”(附加句號字元)。他們被視為不同的公司。
- 更新一個公司名稱可能意味著更新許多記錄。
SQL的解決方案是建立一個公司表,並使用其主鍵(可能是一個整數)將一個公司與一個人聯絡起來。無論公司名稱如何變化,主鍵都會保持不變,資料庫可以執行規則以保證資料的完整性。
在MongoDB中鼓勵去規範化。你應該積極地重複資料,一個單一的檔案可以包含它所需要的所有資訊。這有幾個優點:
- 檔案是自成一體的,更容易閱讀–不需要參考其他文集。
- 寫入效能可以比SQL資料庫快,因為執行的資料完整性規則較少。
- 分片–或將資料分佈在多臺機器上–變得更容易,因為不需要引用其他集合中的資料。
簡單的MongoDB查詢
你可以通過使用一個空的find()來列出一個集合中的所有文件,比如person。
db.person.find({})
count()方法返回文件的數量(在我們的例子中,這個數字將是7)。
db.person.find({}).count();
sort()方法按你喜歡的任何順序返回檔案,如按姓名的反字母順序。
db.person.find({}).sort({ name: -1 });
你也可以限制返回的檔案數量,例如,找到前三個名字:
db.person.find({}).sort({ name: 1 }).limit(2);
你可以通過定義一個或多個欄位的查詢來搜尋特定的記錄,例如,定位所有名字被設定為 “Claire”的人的檔案。
db.person.find({ name: 'Claire' });
也支援邏輯運算子,如$and、$or、$not、$gt(大於)、$lt(小於)和$ne(不等於),例如,定位所有公司為 “Alpha Inc “或 “Beta Inc “的個人檔案。
db.person.find({ $or: [ { company: 'Alpha Inc' }, { company: 'Beta Inc' } ] });
在這個例子的資料庫中,同樣的結果可以用$nin(不在)來提取所有公司不是 “Gamma Inc “的文件。
db.person.find({ company: { $nin: ['Gamma Inc'] } });
find()方法中的第二個值物件設定了一個投影,該投影定義了返回的欄位。在這個例子中,只有名字被返回(注意,除非明確關閉,否則文件_id總是被返回)。
db.person.find( { name:'Claire' }, { _id:0, name:1 } );
其結果是:
{ "name" : "Claire" }
通過$elemMatch查詢,可以找到一個陣列中的專案,比如電話陣列中有工作專案的所有檔案。同樣的$elemMatch可以在投影中使用,只顯示工作編號:
db.person.find( { telephone: { $elemMatch: { work: { $exists: true }} } }, { _id: 0, name:1, telephone: { $elemMatch: { work: { $exists: true }}} } );
其結果是:
{ "name" : "Abdul", "telephone" : [ { "work" : "9876543210" } ] }, { "name" : "Henry", "telephone" : [ { "work" : "012301230123" } ] }
在MongoDB中使用遊標
大多數資料庫驅動程式允許將查詢結果以陣列或類似的資料結構形式返回。然而,如果該集合包含成千上萬的檔案,這可能導致記憶體問題。
像大多數SQL資料庫一樣,MongoDB支援遊標的概念。遊標允許應用程式在進入下一個專案或放棄搜尋之前一次讀取查詢結果。
遊標也可以從MongoDB shell中使用。
let myCursor = db.person.find( {} ); while ( myCursor.hasNext() ) { print(tojson( myCursor.next() )); }
如何在MongoDB中建立索引
這個人名集合目前有7個檔案,所以任何查詢都不會有計算成本。然而,想象一下你有一百萬個有姓名和電子郵件地址的聯絡人。聯絡人可能是按姓名排序的,但電子郵件地址會在一個似乎是隨機順序。
如果你需要通過電子郵件查詢一個聯絡人,資料庫將不得不搜尋多達一百萬個專案才能找到匹配。在電子郵件欄位上新增索引可以建立一個查詢 “表”,其中電子郵件按字母順序儲存。資料庫現在可以使用更有效的搜尋演算法來定位正確的人。
隨著文件數量的增加,索引變得至關重要。一般來說,你應該對任何可能在查詢中被引用的欄位應用一個索引。你可以對每個欄位都應用索引,但要注意這將減慢資料更新的速度,並增加所需的磁碟空間,因為重新編制索引成為必要。
MongoDB提供了一系列的索引型別。
單一欄位索引
大多數索引將應用於單個欄位,例如,以升序字母順序對姓名欄位進行索引:
db.person.createIndex({ name: 1 });
使用-1會顛倒順序。這在我們的例子中沒有什麼好處,但如果你有一個日期欄位,最近的事件優先,這可能很實用。
在例子mongodemo資料庫中,還有三個索引是有用的:
db.person.createIndex( { company: 1 } ); db.company.createIndex( { name: 1 } ); db.company.createIndex( { base: 1 } );
多個欄位的複合索引
在一個索引中可以指定兩個或多個欄位,例如
db.person.createIndex( { name: 1, company: 1 } );
當一個欄位在搜尋查詢中經常與另一個欄位一起使用時,這可能是有用的。
陣列或物件元素的多鍵索引
檔案可能很複雜,往往需要對結構中更深的欄位進行索引,如工作電話:
db.products.createIndex( { 'telephone.work': 1 } );
萬用字元索引
萬用字元可以對文件中的每一個欄位進行索引。這在較小和較簡單的檔案上通常是實用的,這些檔案可以用各種方式進行查詢:
db.company.createIndex( { '$**': 1 } );
全文索引
文字索引允許你建立類似搜尋引擎的查詢,可以檢查所有字串欄位的文字並按相關性排序。你可以將文字索引限制在特定欄位:
db.person.createIndex( { name: "text", company: "text" } );
…或者在所有字串欄位上建立一個文字索引:
db.person.createIndex( { "$**": "text" } );
$text運算子允許你搜尋這個索引,比如找到所有引用 “Gamma “的檔案:
db.person.find({ $text: { $search: 'Gamma' } });
請注意,全文搜尋一般需要五個或更多的字元才能返回有用的結果。
其他指數型別
MongoDB提供了其他幾種專門的索引型別:
- hashed index
- 2d index — 二維平面上的點
- 2dsphere index — 類地球體上的幾何圖形
如何管理MongoDB的索引
在一個集合上定義的索引可以用以下方法檢查:
db.person.getIndexes();
這將返回一個陣列的結果,如:
[ { "v" : 2.0, "key" : { "_id" : 1.0 }, "name" : "_id_" }, { "v" : 2.0, "key" : { "company" : 1.0 }, "name" : "company_1" }, { "v" : 2.0, "key" : { "name" : 1.0 }, "name" : "name_1" } ]
key “定義了欄位和順序,而 “name “是該索引的唯一識別符號–例如 “company_1 “是公司欄位的索引。
索引的有效性可以通過給任何查詢新增一個.explain() 方法來檢查,例如:
db.person.find({ name:'Claire' }).explain();
這將返回一個大的資料集,但 “winningPlan “物件顯示了查詢中使用的 “indexName”:
"winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "name" : 1.0 }, "indexName" : "name_1", } }
如果有必要,你可以通過引用一個索引的名稱來放棄它:
db.person.dropIndex( 'name_1' );
或使用索引規範檔案:
db.person.dropIndex({ name: 1 });
.dropIndexes()方法允許你在一條命令中放棄一個以上的索引。
使用MongoDB的資料驗證模式
與SQL不同,資料定義模式在MongoDB中是不需要的。你可以在任何時候向任何集合中的任何文件釋出任何資料。
這提供了相當大的自由。然而,有時你可能要堅持遵守規則。例如,除非一個檔案包含一個名字,否則就不可能將其插入到人的集合中。
驗證規則可以使用$jsonSchema物件來指定,該物件定義了一個所需專案的陣列和每個驗證欄位的屬性。人的集合已經被建立,但是你仍然可以定義一個模式,指定一個名字字串是必須的:
db.runCommand({ collMod: 'person', validator: { $jsonSchema: { required: [ 'name' ], properties: { name: { bsonType: 'string', description: 'name string required' } } } } });
試著插入一個沒有名字的人的檔案:
db.person.insertOne({ company: 'Alpha Inc' });
…命令就會失敗:
{ "index" : 0.0, "code" : 121.0, "errmsg" : "Document failed validation", "op" : { "_id" : ObjectId("624591771658cd08f8290401"), "company" : "Alpha Inc" } }
如果你在使用一個集合之前建立了該集合,也可以定義模式。下面的命令實現了與上述相同的規則。
db.createCollection('person', { validator: { $jsonSchema: { required: [ 'name' ], properties: { name: { bsonType: 'string', description: 'name string required' } } } } });
這個更復雜的例子建立了一個使用者集合,驗證了必須提供姓名、電子郵件地址和至少一個電話號碼:
db.createCollection('users', { validator: { $jsonSchema: { required: [ 'name', 'email', 'telephone' ], properties: { name: { bsonType: 'string', description: 'name string required' }, email: { bsonType: 'string', pattern: '^.+\@.+$', description: 'valid email required' }, telephone: { bsonType: 'array', minItems: 1, description: 'at least one telephone number required' } } } } });
如何更新MongoDB中的現有檔案
MongoDB提供了幾種更新方法,包括 updateOne()
, updateMany()
和replaceOne()
。這些都是通過。
- 一個過濾器物件,用於定位要更新的檔案
- 一個更新物件–或一個更新物件的陣列–描述要改變的資料 一個可選的選項物件。
- 最有用的屬性是upsert,它可以在沒有發現的情況下插入一個新的文件。
下面的例子更新了姓名被設定為 “Henry “的人的檔案。它刪除了工作電話,增加了家庭電話,並設定了一個新的出生日期。
db.person.updateOne( { name: 'Henry' }, [ { $unset: [ 'telephone.work' ] }, { $set: { 'birthdate': new ISODate('1980-01-01'), 'telephone': [ { 'home': '789789789' } ] } } ] );
接下來的例子更新了姓名被設定為 “Ian “的人名文件。這個名字目前並不存在,但將upsert設定為 “true “就可以建立它。
db.person.updateOne( { name: 'Ian' }, { $set: { company: 'Beta Inc' } }, { upsert: true } );
你可以在任何時候執行查詢命令來檢查資料更新。
如何刪除MongoDB中的檔案
上面的更新例子使用$unset從名字為 “Henry “的文件中刪除工作電話。要刪除整個文件,你可以使用幾個刪除方法中的一個,包括deleteOne()
, deleteMany()
和remove()
(可以刪除一個或多個)。
為Ian新建立的檔案可以用適當的過濾器刪除。
db.person.deleteOne({ name: 'Ian' });
在MongoDB中使用聚合操作
聚合是強大的,但可能難以理解。它定義了一個系列–或管道- 在一個陣列中的操作。該管道的每個階段都會執行一個操作,如過濾、分組、計算或修改一組文件。一個階段也可以使用類似SQL JOIN的$lookup操作。得到的檔案被傳遞給管道的下一個階段,以便在必要時進行進一步處理。
聚合最好用一個例子來說明。我們將逐步建立一個查詢,返回在美國某機構工作的人的姓名、公司和工作電話(如果有的話)。
第一個操作是執行一個$match來過濾美國的公司。
db.company.aggregate([ { $match: { base: 'US' } } ]);
這樣的返回:
{ "_id" : ObjectId("62442429854636a03f6b853b"), "name" : "Alpha Inc", "base" : "US" } { "_id" : ObjectId("62442429854636a03f6b853c"), "name" : "Beta Inc", "base" : "US" }
然後我們可以新增一個新的$lookup管道操作符,將公司名稱(localField)與人(from)集合中的公司(foreignField)進行匹配。輸出結果將作為一個僱員陣列附加到每個公司的檔案中:
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } } ]);
結果是這樣的:
{ "_id" : ObjectId("62442429854636a03f6b853b"), "name" : "Alpha Inc", "base" : "US", "employee" : [ { "_id" : ObjectId("62442429854636a03f6b8534"), "name" : "Abdul", "company" : "Alpha Inc", "telephone" : [ { "home" : "0123456789" }, { "work" : "9876543210" } ] }, { "_id" : ObjectId("62442429854636a03f6b8537"), "name" : "Dawn", "company" : "Alpha Inc" }, { "_id" : ObjectId("62442429854636a03f6b853a"), "name" : "Henry", "company" : "Alpha Inc", "telephone" : [ { "home" : "789789789" } ], } ] } { "_id" : ObjectId("62442429854636a03f6b853c"), "name" : "Beta Inc", "base" : "US", "employee" : [ { "_id" : ObjectId("62442429854636a03f6b8535"), "name" : "Brian", "company" : "Beta Inc" }, { "_id" : ObjectId("62442429854636a03f6b8538"), "name" : "Esther", "company" : "Beta Inc", "telephone" : [ { "home" : "001122334455" } ] } ] }
現在,一個$project(投射)操作可以刪除所有的,但僱員陣列除外。隨後的$unwind操作可以破壞陣列,獲得獨立的僱員檔案。
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } }, { $project: { _id: 0, employee: 1 } }, { $unwind: '$employee' } ]);
其結果是:
{ "employee" : { "_id" : ObjectId("62442429854636a03f6b8534"), "name" : "Abdul", "company" : "Alpha Inc", "telephone" : [ { "home" : "0123456789" }, { "work" : "9876543210" } ] } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8537"), "name" : "Dawn", "company" : "Alpha Inc" } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b853a"), "name" : "Henry", "company" : "Alpha Inc", "telephone" : [ { "home" : "789789789" } ] } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8535"), "name" : "Brian", "company" : "Beta Inc" } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8538"), "name" : "Esther", "company" : "Beta Inc", "telephone" : [ { "home" : "001122334455" } ] } }
最後,$replaceRoot操作被用來格式化每個檔案,所以只返回個人的名字、公司和工作電話。接下來是一個$sort操作,以升序的名字順序輸出檔案。完整的聚合查詢:
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } }, { $project: { _id: 0, employee: 1 } }, { $unwind: '$employee' }, { $replaceRoot: { newRoot: { $mergeObjects: [ { name: "$employee.name", company: '$employee.company', work: { $first: '$employee.telephone.work' } }, "$name" ] } } }, { $sort: { name: 1 } } ]);
其結果是:
{ "name" : "Abdul", "company" : "Alpha Inc", "work" : "9876543210" } { "name" : "Brian", "company" : "Beta Inc", } { "name" : "Dawn", "company" : "Alpha Inc", } { "name" : "Esther", "company" : "Beta Inc" } { "name" : "Henry", "company" : "Alpha Inc" }
還有其他方法可以實現這一結果,但關鍵是MongoDB可以完成大部分的工作。很少有必要在你的應用程式程式碼中直接讀取文件和運算元據。
如何執行批量MongoDB操作
預設情況下,MongoDB可以處理1,000個併發操作。在使用mongosh時,這不太可能是一個問題,但如果應用程式對單個記錄進行一系列的資料操作,就會達到這個限制。Node.js應用程式尤其有問題,因為它們可以迅速發出一系列非同步請求,而不必等到它們完成。
為了規避這個問題,MongoDB提供了一個批量操作API,它接受任何數量的更新,這些更新可以按順序或按任何順序執行。
這裡有一個Node.js的虛擬碼例子。
// reference the mycollection collection const bulk = db.collection('mycollection').initializeUnorderedBulkOp(); // make any number of data changes bulk.insertOne(...); bulk.insertMany(...) bulk.updateOne(...); bulk.deleteOne(...); // etc... bulk.execute();
最後一條語句有效地發出了一個MongoDB請求,所以你有較少的機會達到1000個操作的限制。
小結
MongoDB為內容管理系統、地址簿和社交網路等應用提供了一個靈活的儲存,在這些應用中,嚴格的資料結構過於僵硬,難以定義。資料寫入速度快,在多個伺服器上分片變得更容易。
使用MongoDB資料庫編寫應用程式也可以是一種解放。它可以在任何時候將任何資料儲存在任何文件的集合中。當你使用敏捷方法學開發一個原型或最小可行產品時,這一點尤其實用,因為需求會隨著時間的推移而變化。
也就是說,複雜的查詢可能是一個挑戰,當你從SQL世界遷移過來的時候,反規範化的概念很難接受。
MongoDB不太適合有嚴格交易要求的應用程式,因為資料的完整性是至關重要的,例如銀行、會計和庫存控制系統。這些有可識別的資料欄位,應在開始編碼前設計好。
在這兩個極端之間有很多應用型別,所以選擇一個合適的資料庫變得更加困難。幸運的是,包括MongoDB在內的NoSQL資料庫已經開始採用類似SQL的選項,包括JOIN和事務。
相反,SQL資料庫,如MySQL和PostgreSQL現在提供類似NoSQL的JSON資料欄位。他們也可能值得你注意,但像往常一樣,最終的選擇權在你手中。
評論留言