分享到

简介

当使用 MongoDB 时,您的大部分时间将花费在以某种方式管理文档上。 无论您是创建新文档并将其添加到集合、检索文档、更新数据还是修剪过时的项目,文档都是 MongoDB 模型的中心。

在本指南中,我们将介绍什么是 MongoDB 文档,然后介绍您可能需要了解的常见操作,以管理以文档为中心的环境

什么是 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_typecolor 是字符串
  • 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 中的嵌入式或嵌套文档是一个在其内部包含另一个文档的文档。

以下是嵌入式文档的示例,其中 address—用额外的花括号表示为子文档—可以使用 user 记录进行访问。

db.user.findOne({_id: 111111})
{
_id: 111111,
email: “[email protected]”,
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 }
})

要修改 MongoDB 中的文档,有一些相关的更新方法

具体来说,对于将新数据附加到文档,您可以使用 $addToSet 更新运算符。 此运算符将值添加到文档的数组中,除非它们已存在。

在 MongoDB 中,没有明确比较一个文档与另一个文档的特定方法。 可以通过配置一个查询来完成,该查询使用运算符比较任何文档字段之间的相等性。

也可以通过配置聚合管道来完成比较。 此方法允许您创建阶段,这些阶段

  • 将来自多个文档的值分组在一起
  • 对分组数据执行操作以返回单个结果
  • 分析随时间推移的数据变化
关于作者
Justin Ellingwood

Justin Ellingwood

自 2013 年以来,Justin 一直在撰写关于数据库、Linux、基础设施和开发人员工具的文章。 他目前与妻子和两只兔子住在柏林。 他通常不必以第三人称写作,这对所有相关方来说都是一种解脱。