引言
使用 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 }})