分享到

简介

使用 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() 方法返回一个包含一些额外有用信息的文档。它确认写入已得到集群的确认,并且它包含分配给文档的 ObjectID,因为我们没有提供一个。

使用 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

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