简介
在使用 MongoDB 时,您大部分时间都会以某种方式管理文档。无论是创建新文档并将其添加到集合中、检索文档、更新数据还是清理过期项,文档都处于 MongoDB 模型的核心。
在本指南中,我们将介绍 MongoDB 文档是什么,然后介绍您可能需要了解的常见操作,以管理以文档为中心的环境。
如果您正在使用 MongoDB,请查看 Prisma 的 MongoDB 连接器!您可以使用 Prisma Client 自信地管理生产 MongoDB 数据库。
要开始使用 MongoDB 和 Prisma,请查看我们的 从头开始指南 或如何添加到现有项目。
MongoDB 文档是什么?
在 MongoDB 中,数据库和集合中的所有数据都存储在文档中。由于集合默认不指定必需的模式,集合中的文档可以包含任意复杂的结构,并且不需要与同级文档使用的格式匹配。这提供了令人难以置信的灵活性,并允许模式随着应用程序需求的变化而有机地发展。
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,因为我们没有提供一个。
使用 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 or 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 }})
