引言
使用 MongoDB 時,你大部分時間都會以某種方式管理文件。無論是建立新文件並將其新增到集合中、檢索文件、更新資料還是清除過期項,文件都是 MongoDB 模型的核心。
在本指南中,我們將介紹什麼是 MongoDB 文件,然後講解管理以文件為中心的環境時可能需要了解的常見操作。
如果你正在使用 MongoDB,請檢視 Prisma 的 MongoDB 聯結器!你可以使用 Prisma Client 自信地管理生產環境的 MongoDB 資料庫。
要開始使用 MongoDB 和 Prisma,請檢視我們的從零開始指南,或瞭解如何新增到現有專案。
什麼是 MongoDB 文件?
在 MongoDB 中,資料庫和集合中的所有資料都以文件形式儲存。由於集合預設不指定必需的模式(schema),因此集合中的文件可以包含任意複雜的結構,並且不必與同級文件使用的格式匹配。這提供了令人難以置信的靈活性,並允許模式隨著應用程式需求的變化而有機地發展。
MongoDB 文件本身使用 BSON 資料序列化格式,這是 JSON JavaScript 物件表示法的二進位制表示。這提供了一種有組織的結構,具有定義的資料型別,可以進行程式設計查詢和操作。
BSON 文件由一對花括號({})表示,其中包含鍵值對。在 BSON 中,這些資料對被稱為“欄位”和“值”。欄位在前,由字串表示。值可以是任何有效的BSON 資料型別。冒號(:)將欄位與其值分開。逗號用於分隔每個欄位和值對。
例如,這是一個 MongoDB 可以理解的有效 BSON 文件
{_id: 80380,vehicle_type: "car",mileage: 7377.80,color: "blue",markets: ["US","UK"],options: {transmission: "automatic",num_doors: 4,power_windows: true}}
在這裡,我們可以看到很多型別
_id是一個整數vehicle_type和color是字串mileage是一個浮點數markets是一個字串陣列options包含一個巢狀文件,其值由字串、整數和布林值組成
由於這種靈活性,文件是儲存資料相當靈活的媒介。可以輕鬆新增新欄位,文件可以相互巢狀,並且結構複雜性與儲存的資料精確匹配。
如何建立新文件
要建立新文件,請切換到要儲存建立文件的資料庫。在本文中,我們將使用一個名為 school 的資料庫進行演示
use school
你還需要選擇要插入文件的集合。與資料庫一樣,你無需顯式建立要插入文件的集合。當第一批資料寫入時,MongoDB 會自動建立它。對於本例,我們將使用一個名為 students 的集合。
現在你知道文件將儲存在哪裡了,你可以使用以下方法之一插入新文件。
使用 insert() 方法
insert() 方法允許你將一個或多個文件插入到其被呼叫的集合中。
要插入單個文件,請在集合上呼叫該方法並將文件傳遞給它。在這裡,我們為名為 Ashley 的學生插入一個新文件
db.students.insert({first_name: "Ashley",last_name: "Jenkins",dob: new Date("January 08, 2003"),grade_level: 8})
WriteResult({ "nInserted" : 1 })
如果你想同時插入多個文件,請傳遞一個文件陣列,而不是將單個文件傳遞給 insert()。我們可以為名為 Brian 和 Leah 的學生新增兩個新文件
db.students.insert([{first_name: "Brian",last_name: "McMantis",dob: new Date("September 18, 2010"),grade_level: 2},{first_name: "Leah",last_name: "Drake",dob: new Date("October 03, 2009")}])
BulkWriteResult({"writeErrors" : [ ],"writeConcernErrors" : [ ],"nInserted" : 2,"nUpserted" : 0,"nMatched" : 0,"nModified" : 0,"nRemoved" : 0,"upserted" : [ ]})
由於我們執行了批次寫入操作,因此我們的返回值是 BulkWriteResult 而不是我們之前看到的 WriteResult 物件。
雖然 insert() 方法很靈活,但它在許多 MongoDB 驅動程式中已被棄用,取而代之的是以下兩種方法。
使用 insertOne() 方法
insertOne() 方法可用於插入單個文件。與 insert() 方法不同,它一次只能插入一個文件,這使得其行為更具可預測性。
語法與你使用 insert() 新增單個文件時相同。我們可以新增另一名名為 Naomi 的學生
db.students.insertOne({first_name: "Naomi",last_name: "Pyani"})
{"acknowledged" : true,"insertedId" : ObjectId("60e877914655cbf49ff7cb86")}
與 insert() 不同,insertOne() 方法返回一個包含一些額外有用資訊的文件。它確認寫入已由叢集確認,並且由於我們未提供物件 ID,它包含了分配給該文件的物件 ID。
使用 insertMany() 方法
為了應對你希望一次性插入多個文件的場景,現在推薦使用 insertMany() 方法。就像使用 insert() 插入多個文件時一樣,insertMany() 接受一個文件陣列。
我們可以新增三名新學生,分別是 Jasmine、Michael 和 Toni
db.students.insertMany([{first_name: "Jasmine",last_name: "Took",dob: new Date("April 11, 2011")},{first_name: "Michael",last_name: "Rodgers",dob: new Date("February 25, 2008"),grade_level: 6},{first_name: "Toni",last_name: "Fowler"}])
{"acknowledged" : true,"insertedIds" : [ObjectId("60e8792d4655cbf49ff7cb87"),ObjectId("60e8792d4655cbf49ff7cb88"),ObjectId("60e8792d4655cbf49ff7cb89")]}
與 insertOne() 一樣,insertMany() 返回一個文件,該文件確認寫入並提供一個包含已分配給插入文件的 ID 的陣列。
如何查詢現有文件
查詢文件是一個相當廣泛的話題,值得單獨撰寫一篇文章。你可以在我們的 MongoDB 資料查詢指南中找到有關如何構建查詢以檢索不同型別文件的詳細資訊。
雖然細節最好留待上面連結的文章中,但我們至少可以介紹 MongoDB 提供的查詢文件的方法。從 MongoDB 獲取文件的主要方式是在相關集合上呼叫 find() 方法。
例如,要從 students 集合中獲取所有文件,你可以不帶任何引數地呼叫 find()
db.students.find()
{ "_id" : ObjectId("60e8743b4655cbf49ff7cb83"), "first_name" : "Ashley", "last_name" : "Jenkins", "dob" : ISODate("2003-01-08T00:00:00Z"), "grade_level" : 8 }{ "_id" : ObjectId("60e875d54655cbf49ff7cb84"), "first_name" : "Brian", "last_name" : "McMantis", "dob" : ISODate("2010-09-18T00:00:00Z"), "grade_level" : 2 }{ "_id" : ObjectId("60e875d54655cbf49ff7cb85"), "first_name" : "Leah", "last_name" : "Drake", "dob" : ISODate("2009-10-03T00:00:00Z") }{ "_id" : ObjectId("60e877914655cbf49ff7cb86"), "first_name" : "Naomi", "last_name" : "Pyani" }{ "_id" : ObjectId("60e8792d4655cbf49ff7cb87"), "first_name" : "Jasmine", "last_name" : "Took", "dob" : ISODate("2011-04-11T00:00:00Z") }{ "_id" : ObjectId("60e8792d4655cbf49ff7cb88"), "first_name" : "Michael", "last_name" : "Rodgers", "dob" : ISODate("2008-02-25T00:00:00Z"), "grade_level" : 6 }{ "_id" : ObjectId("60e8792d4655cbf49ff7cb89"), "first_name" : "Toni", "last_name" : "Fowler" }
為了使輸出更具可讀性,你還可以在 find() 之後連結 pretty() 方法
db.<collection>.find().pretty()
{"_id" : ObjectId("60e8743b4655cbf49ff7cb83"),"first_name" : "Ashley","last_name" : "Jenkins","dob" : ISODate("2003-01-08T00:00:00Z"),"grade_level" : 8}{"_id" : ObjectId("60e875d54655cbf49ff7cb84"),"first_name" : "Brian","last_name" : "McMantis","dob" : ISODate("2010-09-18T00:00:00Z"),"grade_level" : 2}{"_id" : ObjectId("60e875d54655cbf49ff7cb85"),"first_name" : "Leah","last_name" : "Drake","dob" : ISODate("2009-10-03T00:00:00Z")}{"_id" : ObjectId("60e877914655cbf49ff7cb86"),"first_name" : "Naomi","last_name" : "Pyani"}{"_id" : ObjectId("60e8792d4655cbf49ff7cb87"),"first_name" : "Jasmine","last_name" : "Took","dob" : ISODate("2011-04-11T00:00:00Z")}{"_id" : ObjectId("60e8792d4655cbf49ff7cb88"),"first_name" : "Michael","last_name" : "Rodgers","dob" : ISODate("2008-02-25T00:00:00Z"),"grade_level" : 6}{"_id" : ObjectId("60e8792d4655cbf49ff7cb89"),"first_name" : "Toni","last_name" : "Fowler"}
你可以看到每個文件都添加了一個 _id 欄位。MongoDB 要求集合中的每個文件都有一個唯一的 _id。如果你在建立物件時未提供,它將為你新增一個。你可以使用此 ID 可靠地檢索單個物件
db.students.find({_id : ObjectId("60e8792d4655cbf49ff7cb89")})
{ "_id" : ObjectId("60e8792d4655cbf49ff7cb89"), "first_name" : "Toni", "last_name" : "Fowler" }
你可以透過上面連結的文章瞭解更多關於各種資料查詢方式的資訊。
如何更新現有文件
資料庫的許多或大多數用例都要求你能夠修改資料庫中的現有資料。欄位可能需要更新以反映新值,或者你可能需要在現有文件中追加額外資訊(當其可用時)。
MongoDB 使用一些相關方法來更新現有文件
updateOne():根據提供的過濾器更新集合中的單個文件。updateMany():更新集合中匹配提供的多個文件。replaceOne():根據提供的過濾器替換集合中的整個文件。
我們將介紹如何使用這些不同型別的方法來執行不同型別的更新。
更新運算子
在我們檢視每種更新文件的方法之前,我們應該先了解一些可用的更新運算子。
$currentDate:將欄位的值設定為當前日期,可以是日期型別或時間戳型別。- 語法:
{ $currentDate: { <field>: <type>, ... } }
- 語法:
$inc:將欄位的值增加一個設定量。- 語法:
{ $inc: { <field>: <amount>, ... } }
- 語法:
$min:如果指定值小於當前值,則更新欄位的值。- 語法:
{ $min: { <field>: <value>, ... } }
- 語法:
$max:如果指定值大於當前值,則更新欄位的值。- 語法:
{ $max: { <field>: <value>, ... } }
- 語法:
$mul:將欄位的值乘以給定數字來更新它。- 語法:
{ $mul: { <field>: <value>, ... } }
- 語法:
$rename:將欄位名稱重新命名為新識別符號。- 語法:
{ $rename: { <field>: <new_name>, ... } }
- 語法:
$set:用給定值替換欄位的值。- 語法:
{ $set: { <field>: value, ... } }
- 語法:
$setOnInsert:在 upsert 操作期間,如果正在建立新文件,則設定欄位的值;否則不執行任何操作。- 語法:
{ $setOnInsert: { <field>: <value>, ... } }
- 語法:
$unset:從文件中刪除欄位。- 語法:
{ $unset: { <field>: "", ... } }
- 語法:
$:滿足查詢的第一個陣列元素的佔位符。- 語法:
{ <update_operator>: {<array>.$: <value> } }
- 語法:
$[]:滿足查詢的所有陣列元素的佔位符。- 語法:
{ <update_operator>: { <array>.$[]: <value> } }
- 語法:
$addToSet:向陣列新增值,除非這些值已存在。- 語法:
{ $addToSet: { <field>: <value>, ... } }
- 語法:
$pop:刪除陣列的第一個或最後一個元素。- 語法:
{ $pop: { <field>: (-1 或 1), ... } }
- 語法:
$pull:刪除陣列中所有匹配條件的元素。- 語法:
{ $pull: { <field>: <condition>, ... } }
- 語法:
$push:向陣列追加一個值。- 語法:
{ $push: { <field>: <value>, ... } }
- 語法:
$pullAll:從陣列中刪除所有指定元素。- 語法:
{ $pullAll: { <field>: [ <value>, ... ], ...} }
- 語法:
$each:修改$addToSet和$push運算子,使它們新增陣列的每個元素,而不是將整個陣列作為一個單一元素新增。- 語法:
{ <update_operator>: { <field>: { $each: [ <value>, ... ] }, ... } }
- 語法:
$position:與$each結合使用,指定$push運算子應插入的位置。- 語法:
{ $push: { <field>: { $each: [ <value>, ... ], $position: <num> } } }
- 語法:
$slice:與$each和$push結合使用,限制陣列中元素的總數。- 語法:
{ $push: { <field>: { $each: [ <value>, ... ], $slice: <num> } } }
- 語法:
$sort:與$each和$push結合使用,對陣列元素進行排序。- 語法:
{ $push: { <field>: { $each: [ <value>, ... ], $sort: <sort_order> } } }
- 語法:
這些各種更新運算子允許你以不同方式更新文件的各個欄位。
更新集合中的單個文件
MongoDB 的 updateOne() 方法用於更新集合中的單個文件。該方法接受兩個必需引數以及一個指定可選引數的文件。
第一個引數是一個文件,它指定將用於選擇文件的過濾條件。由於 updateOne() 方法在集合中最多修改一個文件,因此將使用滿足過濾條件的第一個文件。
第二個引數指定應執行的更新操作。上面給出的更新操作可以在此處指定,以更改匹配文件的內容。
第三個引數是包含各種選項的文件,用於修改方法的行為。最重要的潛在值是
upsert:如果過濾器不匹配任何現有文件,則插入新文件,將操作轉變為 upsert 過程。collation:一個定義應適用於操作的特定語言規則的文件。
例如,我們可以更新單個學生記錄,我們透過 _id 欄位進行過濾,以確保我們定位到正確的文件。我們可以將 grade_level 設定為新值
db.students.updateOne({ _id: ObjectId("60e8792d4655cbf49ff7cb89") },{ $set: { grade_level: 3 } })
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
更新集合中的多個文件
MongoDB 的 updateMany() 方法與 updateOne() 方法類似,但它會更新所有匹配給定過濾器的文件,而不是在第一個匹配後停止。
updateMany() 的語法與 updateOne() 的語法完全相同,因此唯一的區別是操作的範圍。
例如,如果我們想將 teachers 集合文件中 subjects 陣列中所有“composition”的例項更改為“writing”,我們可以使用類似以下程式碼:
db.teachers.updateMany({ subject: "composition" },{ $set: { "subjects.$": "writing" } })
{ "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }
如果你檢查文件,所有“composition”的例項都應該已經被“writing”替換
db.teachers.find()
{ "_id" : ObjectId("60eddca65eb74f5c676f3baa"), "first_name" : "Nancy", "last_name" : "Smith", "subjects" : [ "vocabulary", "pronunciation" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bab"), "first_name" : "Ronald", "last_name" : "Taft", "subjects" : [ "literature", "grammar", "writing" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bac"), "first_name" : "Casey", "last_name" : "Meyers", "subjects" : [ "literature", "writing", "grammar" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bad"), "first_name" : "Rebecca", "last_name" : "Carrie", "subjects" : [ "grammar", "literature" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bae"), "first_name" : "Sophie", "last_name" : "Daggs", "subjects" : [ "literature", "writing", "grammar", "vocabulary", "pronunciation" ] }
替換文件
replaceOne() 方法與 updateOne() 方法類似,但它替換整個文件而不是更新單個欄位。語法與前兩個命令相同。
例如,如果 Nancy Smith 離開你的學校,你用一位名叫 Clara Newman 的文學老師替換她,你可以輸入以下內容
db.teachers.replaceOne({$and: [{ first_name: "Nancy" },{ last_name: "Smith" }]},{first_name: "Clara",last_name: "Newman",subjects: [ "literature" ]})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
你可以看到匹配的文件已被刪除,並且指定的文件已將其替換
db.teachers.find()
{ "_id" : ObjectId("60eddca65eb74f5c676f3baa"), "first_name" : "Clara", "last_name" : "Newman", "subjects" : [ "literature" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bab"), "first_name" : "Ronald", "last_name" : "Taft", "subjects" : [ "literature", "grammar", "writing" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bac"), "first_name" : "Casey", "last_name" : "Meyers", "subjects" : [ "literature", "writing", "grammar" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bad"), "first_name" : "Rebecca", "last_name" : "Carrie", "subjects" : [ "grammar", "literature" ] }{ "_id" : ObjectId("60eddca65eb74f5c676f3bae"), "first_name" : "Sophie", "last_name" : "Daggs", "subjects" : [ "literature", "writing", "grammar", "vocabulary", "pronunciation" ] }
如何刪除文件
從集合中刪除文件也是文件生命週期的一部分。要刪除文件,你可以使用 deleteOne() 或 deleteMany() 方法。它們的語法相同,唯一的區別是它們操作的文件數量。
在大多數情況下,使用這些方法刪除文件所需要做的就是提供一個過濾器文件,該文件指定你希望如何選擇要刪除的文件。deleteOne() 方法最多刪除一個文件(無論過濾器產生多少匹配),而 deleteMany() 方法刪除所有匹配過濾條件的文件。
例如,要刪除單個學生,你可以提供一個 _id 來顯式匹配他們
db.students.deleteOne({_id: ObjectId("60e8792d4655cbf49ff7cb87")})
{ "acknowledged" : true, "deletedCount" : 1 }
如果我們想刪除任何沒有分配年級的學生,我們可以改用 deleteMany() 方法
db.students.deleteMany({grade_level: { $eq: null }})
{ "acknowledged" : true, "deletedCount" : 2 }
如果我們檢查,應該會看到所有剩餘的學生都已分配了年級
db.students.find()
{ "_id" : ObjectId("60e8743b4655cbf49ff7cb83"), "first_name" : "Ashley", "last_name" : "Jenkins", "dob" : ISODate("2003-01-08T00:00:00Z"), "grade_level" : 8 }{ "_id" : ObjectId("60e875d54655cbf49ff7cb84"), "first_name" : "Brian", "last_name" : "McMantis", "dob" : ISODate("2010-09-18T00:00:00Z"), "grade_level" : 2 }{ "_id" : ObjectId("60e8792d4655cbf49ff7cb88"), "first_name" : "Michael", "last_name" : "Rodgers", "dob" : ISODate("2008-02-25T00:00:00Z"), "grade_level" : 6 }{ "_id" : ObjectId("60e8792d4655cbf49ff7cb89"), "first_name" : "Toni", "last_name" : "Fowler", "grade_level" : 3 }
總結
學習如何建立、查詢、更新和刪除文件,將為你提供日常有效管理 MongoDB 文件所需的技能。熟悉各種文件和集合方法以及允許你匹配和修改資訊的運算子,將使你能夠表達資料庫系統可以理解的複雜想法。
如果你正在使用 MongoDB,請檢視 Prisma 的 MongoDB 聯結器!你可以使用 Prisma Client 自信地管理生產環境的 MongoDB 資料庫。
要開始使用 MongoDB 和 Prisma,請檢視我們的從零開始指南,或瞭解如何新增到現有專案。
常見問題
MongoDB 中的嵌入式(或巢狀)文件是指包含另一個文件的文件。
以下是一個嵌入式文件的示例,其中 address(由額外的花括號表示為子文件)可以透過 user 記錄訪問。
db.user.findOne({_id: 111111}){_id: 111111,email: “email@example.com”,name: {given: “Jane”, family: “Han”},address: {street: “111 Elm Street”,city: “Springfield”,state: “Ohio”,country: “US”,zip: “00000”,}}
MongoDB 中的最大文件大小為 16 兆位元組。
此限制有助於確保單個文件不會佔用過多的 RAM,或在傳輸過程中佔用過多的頻寬。
為了儲存大於 16MB 的文件,MongoDB 提供了 GridFS API。
要刪除文件,你可以使用 deleteOne() 或 deleteMany() 方法。它們的語法相同,唯一的區別是它們操作的文件數量。
要刪除單個文件,刪除具有特定 _id 的文件的基本語法如下所示
db.students.deleteOne({_id: ObjectId("60e8792d4655cbf49ff7cb87")})
要刪除許多匹配特定條件的文件,語法也類似
db.students.deleteMany({grade_level: { $eq: null }})
