使用 MongoDB 引用

MongoDBMongoDBBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本实验中,你将学习如何使用 MongoDB 的引用来设计数据关系并管理父子文档之间的关系。你将从一个简单的图书馆管理系统开始,包含书籍和作者,然后探索如何链接父子文档、更新子文档引用、查询父子关系以及维护引用完整性。本实验涵盖了 MongoDB 中文档引用的关键原则,并通过实际示例帮助你有效理解和应用这些概念。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL mongodb(("MongoDB")) -.-> mongodb/QueryOperationsGroup(["Query Operations"]) mongodb(("MongoDB")) -.-> mongodb/ArrayandEmbeddedDocumentsGroup(["Array and Embedded Documents"]) mongodb(("MongoDB")) -.-> mongodb/AggregationOperationsGroup(["Aggregation Operations"]) mongodb(("MongoDB")) -.-> mongodb/BasicOperationsGroup(["Basic Operations"]) mongodb(("MongoDB")) -.-> mongodb/RelationshipsGroup(["Relationships"]) mongodb(("MongoDB")) -.-> mongodb/IndexingGroup(["Indexing"]) mongodb/BasicOperationsGroup -.-> mongodb/start_mongodb_shell("Start MongoDB Shell") mongodb/BasicOperationsGroup -.-> mongodb/update_document("Update Document") mongodb/BasicOperationsGroup -.-> mongodb/bulk_update_documents("Bulk Update Documents") mongodb/QueryOperationsGroup -.-> mongodb/query_with_conditions("Query with Conditions") mongodb/ArrayandEmbeddedDocumentsGroup -.-> mongodb/query_embedded_documents("Query Embedded Documents") mongodb/IndexingGroup -.-> mongodb/create_index("Create Index") mongodb/AggregationOperationsGroup -.-> mongodb/aggregate_group_totals("Aggregate Group Totals") mongodb/RelationshipsGroup -.-> mongodb/create_document_references("Create Document References") mongodb/RelationshipsGroup -.-> mongodb/link_related_documents("Link Related Documents") subgraph Lab Skills mongodb/start_mongodb_shell -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/update_document -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/bulk_update_documents -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/query_with_conditions -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/query_embedded_documents -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/create_index -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/aggregate_group_totals -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/create_document_references -.-> lab-422099{{"使用 MongoDB 引用"}} mongodb/link_related_documents -.-> lab-422099{{"使用 MongoDB 引用"}} end

设计数据关系

在这一步骤中,我们将通过创建一个链接文档的实际示例,探索如何在 MongoDB 中设计数据关系。我们将为一个简单的图书馆管理系统建模,包含书籍和作者。

理解 MongoDB 中的文档引用

MongoDB 提供了两种主要方式来创建文档之间的关系:

  1. 嵌入式文档(Embedded Documents)
  2. 文档引用(Document References)

对于我们的图书馆系统,我们将使用文档引用来演示如何在集合之间链接相关数据。

首先,启动 MongoDB shell:

mongosh

接下来,创建我们的数据库和集合:

use library_database

db.authors.insertOne({
    _id: ObjectId("author1"),
    name: "Jane Austen",
    nationality: "British",
    birthYear: 1775
})

db.books.insertOne({
    title: "Pride and Prejudice",
    author_id: ObjectId("author1"),
    published: 1813,
    genre: "Classic Literature"
})

解析引用

让我们分析一下我们做了什么:

  • 我们创建了一个 authors 集合,其中包含一个唯一的 _id
  • 我们创建了一个 books 集合,通过 author_id 引用作者
  • author_id 字段包含对应作者文档的 _id

为了验证我们的引用,可以查询文档:

db.authors.findOne({ name: "Jane Austen" })
db.books.findOne({ title: "Pride and Prejudice" })
示例输出
{
  _id: ObjectId("author1"),
  name: 'Jane Austen',
  nationality: 'British',
  birthYear: 1775
}

{
  _id: ObjectId(...),
  title: 'Pride and Prejudice',
  author_id: ObjectId("author1"),
  published: 1813,
  genre: 'Classic Literature'
}

文档引用的关键原则

  • 使用 _id 创建文档之间的链接
  • 保持引用简单且一致
  • 当数据量较大或频繁变化时,选择引用
  • 规范化数据以减少重复

链接父子文档

在这一步骤中,我们将通过创建更复杂的父子文档关系来扩展我们的图书馆数据库。我们将演示如何将多本书链接到一位作者,并有效管理这些关系。

为作者添加多本书

让我们继续使用现有的数据库,并为 Jane Austen 添加更多书籍:

db.books.insertMany([
    {
        title: "Sense and Sensibility",
        author_id: ObjectId("author1"),
        published: 1811,
        genre: "Classic Literature"
    },
    {
        title: "Emma",
        author_id: ObjectId("author1"),
        published: 1815,
        genre: "Classic Literature"
    }
])

查询相关文档

要查找 Jane Austen 的所有书籍,我们可以使用 author_id

db.books.find({ author_id: ObjectId("author1") })
示例输出
[
  {
    _id: ObjectId(...),
    title: 'Pride and Prejudice',
    author_id: ObjectId("author1"),
    published: 1813,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId(...),
    title: 'Sense and Sensibility',
    author_id: ObjectId("author1"),
    published: 1811,
    genre: 'Classic Literature'
  },
  {
    _id: ObjectId(...),
    title: 'Emma',
    author_id: ObjectId("author1"),
    published: 1815,
    genre: 'Classic Literature'
  }
]

统计某位作者的书籍数量

我们还可以统计特定作者的书籍数量:

db.books.countDocuments({ author_id: ObjectId("author1") })
示例输出
3

使用聚合框架进行高级查询

让我们使用聚合框架来获取更详细的信息:

db.books.aggregate([
    { $match: { author_id: ObjectId("author1") } },
    { $group: {
        _id: "$author_id",
        totalBooks: { $sum: 1 },
        earliestPublished: { $min: "$published" },
        latestPublished: { $max: "$published" }
    }}
])
示例输出
[
  {
    _id: ObjectId("author1"),
    totalBooks: 3,
    earliestPublished: 1811,
    latestPublished: 1815
  }
]

更新子文档引用

在这一步骤中,我们将学习如何在 MongoDB 中更新父子文档之间的引用。我们将探索不同的技术来修改文档引用并维护数据完整性。

添加新作者并更新书籍引用

首先,让我们向数据库中添加另一位作者:

db.authors.insertOne({
    _id: ObjectId("author2"),
    name: "Charles Dickens",
    nationality: "British",
    birthYear: 1812
})

更新单本书的作者引用

让我们更新一本书以更改其作者引用:

db.books.updateOne(
    { title: "Emma" },
    { $set: { author_id: ObjectId("author2") } }
)

验证更新

检查更新后的书籍的作者引用:

db.books.findOne({ title: "Emma" })
示例输出
{
  _id: ObjectId(...),
  title: 'Emma',
  author_id: ObjectId("author2"),
  published: 1815,
  genre: 'Classic Literature'
}

批量更新引用

我们还可以一次性更新多个文档:

db.books.updateMany(
    { author_id: ObjectId("author1") },
    { $set: { genre: "Romantic Novel" } }
)

检查多个文档的更新

验证类型更新:

db.books.find({ author_id: ObjectId("author1") })
示例输出
[
  {
    _id: ObjectId(...),
    title: 'Pride and Prejudice',
    author_id: ObjectId("author1"),
    published: 1813,
    genre: 'Romantic Novel'
  },
  {
    _id: ObjectId(...),
    title: 'Sense and Sensibility',
    author_id: ObjectId("author1"),
    published: 1811,
    genre: 'Romantic Novel'
  }
]

Upsert:更新或插入

我们可以使用 upsert 选项来更新文档,如果文档不存在则创建它:

db.books.updateOne(
    { title: "Oliver Twist" },
    { $set: {
        author_id: ObjectId("author2"),
        published: 1837,
        genre: "Historical Fiction"
    }},
    { upsert: true }
)

查询父子文档

在这一步骤中,我们将探索高级查询技术,以在 MongoDB 中检索父子集合之间的相关文档。我们将演示如何有效地查找和连接相关数据。

基本过滤查询

首先,查找特定作者的书籍:

db.books.find({ author_id: ObjectId("author1") })

多条件过滤

使用多个过滤器查询书籍:

db.books.find({
    author_id: ObjectId("author1"),
    published: { $gt: 1812 }
})

此查询查找 Jane Austen 在 1812 年后出版的书籍。

复杂查询的聚合管道

使用聚合框架连接作者和书籍信息:

db.books.aggregate([
    { $lookup: {
        from: "authors",
        localField: "author_id",
        foreignField: "_id",
        as: "author_details"
    }},
    { $match: {
        "author_details.nationality": "British"
    }},
    { $project: {
        title: 1,
        published: 1,
        "author_name": "$author_details.name"
    }}
])
示例输出
[
  {
    _id: ObjectId(...),
    title: 'Pride and Prejudice',
    published: 1813,
    author_name: ['Jane Austen']
  },
  {
    _id: ObjectId(...),
    title: 'Emma',
    published: 1815,
    author_name: ['Charles Dickens']
  }
]

排序和限制结果

按出版年份查询并排序书籍:

db.books.find()
    .sort({ published: 1 })
    .limit(2)

此操作检索最早出版的两本书。

使用正则表达式进行高级过滤

查找标题包含特定单词的书籍:

db.books.find({
    title: { $regex: /Sense/, $options: 'i' }
})

$options: 'i' 使搜索不区分大小写。

统计相关文档

统计每位作者的书籍数量:

db.books.aggregate([
    { $group: {
        _id: "$author_id",
        book_count: { $sum: 1 }
    }},
    { $lookup: {
        from: "authors",
        localField: "_id",
        foreignField: "_id",
        as: "author_info"
    }},
    { $project: {
        author_name: "$author_info.name",
        book_count: 1
    }}
])
示例输出
[
  {
    _id: ObjectId("author1"),
    author_name: ['Jane Austen'],
    book_count: 2
  },
  {
    _id: ObjectId("author2"),
    author_name: ['Charles Dickens'],
    book_count: 2
  }
]

维护引用

在这最后一步中,我们将探讨在 MongoDB 中维护文档引用的策略,重点关注数据完整性、清理以及管理集合之间关系的最佳实践。

处理孤立引用

有时,当父文档被删除时,引用可能会变得过时。让我们演示如何管理这种情况:

创建测试作者和书籍

db.authors.insertOne({
    _id: ObjectId("author3"),
    name: "Mark Twain",
    nationality: "American",
    birthYear: 1835
})

db.books.insertOne({
    title: "The Adventures of Tom Sawyer",
    author_id: ObjectId("author3"),
    published: 1876,
    genre: "Classic Literature"
})

模拟引用清理

删除一位作者并检查孤立的书籍:

db.authors.deleteOne({ _id: ObjectId("author3") })

## 查找具有不存在作者引用的书籍
db.books.find({
    author_id: { $nin: db.authors.distinct("_id") }
})

实现引用管理

创建一个函数来清理孤立的引用:

db.books.deleteMany({
    author_id: { $nin: db.authors.distinct("_id") }
})

防止无效引用

在插入文档时使用验证规则:

db.createCollection("books", {
   validator: {
      $jsonSchema: {
         bsonType: "object",
         required: ["title", "author_id"],
         properties: {
            title: {
               bsonType: "string",
               description: "must be a string and is required"
            },
            author_id: {
               bsonType: "objectId",
               description: "must be a valid author reference"
            }
         }
      }
   }
})

为性能创建引用索引

在引用字段上创建索引:

db.books.createIndex({ author_id: 1 })

检查索引创建

db.books.getIndexes()
示例输出
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { author_id: 1 }, name: 'author_id_1' }
]

引用管理的最佳实践

  1. 在插入前始终验证引用
  2. 在引用字段上使用索引
  3. 实现清理机制
  4. 对于复杂操作,考虑使用数据库事务

演示引用验证

## 由于无效的 author_id,此操作将失败
db.books.insertOne({
    title: "Invalid Book",
    author_id: ObjectId("invalid_id")
})

总结

在本实验中,我们学习了如何使用文档引用在 MongoDB 中设计数据关系。我们创建了一个图书馆管理系统的实际示例,模拟了书籍和作者的关系。我们探讨了在 MongoDB 中创建关系的两种主要方式:嵌入式文档和文档引用。对于我们的图书馆系统,我们选择使用文档引用来跨集合链接相关数据。我们学习了文档引用的关键原则,例如使用 _id 创建链接、保持引用简单一致,以及在数据量大或频繁变化时选择引用。我们还通过创建更复杂的父子文档关系扩展了图书馆数据库。