0Pricing
MongoDB Academy · 课时

引用:一对多与多对多

您将在集合之间存储 ObjectId 引用,并使用 $lookup 在查询时将它们连接起来。

引用:一对多与多对多 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

What Is Referencing?

Referencing means storing an ObjectId (or another unique identifier) inside one document that points to a document in a different collection, similar to a foreign key in relational databases. Instead of nesting data, you link it. This approach is essential when the child count is large, when children are shared among multiple parents, or when children need to be queried independently.

One-to-Many With References

In a one-to-many relationship a single parent has many children. A user can have hundreds of orders—far too many to safely embed. Instead, each order document stores a userId reference pointing back to the parent user. This keeps the user document small while allowing unbounded order growth.

// users collection
db.users.insertOne({ _id: ObjectId('u1'), name: 'Alice', email: 'alice@example.com' });

// orders collection — each order references the user
db.orders.insertMany([
  { _id: ObjectId('o1'), userId: ObjectId('u1'), total: 49.99, status: 'shipped' },
  { _id: ObjectId('o2'), userId: ObjectId('u1'), total: 120.00, status: 'pending' }
]);

Many-to-Many With References

A many-to-many relationship—like students enrolled in multiple courses, and courses containing multiple students—cannot be embedded without duplication. The cleanest approach is to store an array of references on one side. For example, each student document holds an array of courseId values it is enrolled in.

db.students.insertOne({
  _id: ObjectId('s1'),
  name: 'Bob',
  enrolledCourses: [ ObjectId('c1'), ObjectId('c2'), ObjectId('c3') ]
});

db.courses.insertOne({
  _id: ObjectId('c1'),
  title: 'MongoDB Fundamentals',
  instructorId: ObjectId('i1')
});

Joining References With $lookup

To retrieve a document along with its referenced data, use the $lookup aggregation stage. It performs a left outer join between two collections. The from field names the collection to join, localField is the reference field in the current collection, and foreignField is the field to match in the joined collection.

// Fetch orders and join the user for each order
db.orders.aggregate([
  { $match: { status: 'shipped' } },
  {
    $lookup: {
      from: 'users',
      localField: 'userId',
      foreignField: '_id',
      as: 'user'
    }
  },
  { $unwind: '$user' },
  { $project: { total: 1, status: 1, 'user.name': 1 } }
]);

Child-Side vs Parent-Side References

You can store the reference on either side of the relationship. Child-side reference: the child stores the parent's ID (e.g., order.userId)—queries like 'all orders for a user' are simple range queries. Parent-side reference: the parent stores an array of child IDs (e.g., user.orderIds)—useful when you frequently retrieve all child IDs without querying child documents.

Many-to-Many $lookup Example

To resolve a many-to-many relationship—finding all courses a student is enrolled in—use $lookup with the array of IDs stored in the student document. The $in syntax inside $lookup's pipeline form lets you match multiple IDs efficiently.

db.students.aggregate([
  { $match: { name: 'Bob' } },
  {
    $lookup: {
      from: 'courses',
      localField: 'enrolledCourses',
      foreignField: '_id',
      as: 'courses'
    }
  },
  { $project: { name: 1, 'courses.title': 1 } }
]);

Referencing in Mongoose With populate()

Mongoose provides the populate() method as an abstraction over $lookup. You declare a field as a reference using type: mongoose.Schema.Types.ObjectId, ref: 'ModelName', and then call .populate('fieldName') on a query to automatically resolve the reference to the full document.

const orderSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  total: Number,
  status: String
});

// Usage: populate resolves userId to the full User document
const orders = await Order.find({ status: 'shipped' }).populate('userId', 'name email');

Indexing the Reference Field

When querying child documents by their parent reference—such as all orders for a given user—MongoDB must scan the entire collection unless the userId field is indexed. Always create an index on reference fields used in frequent queries. An index on userId turns a full collection scan into a fast index scan.

// Create an index on the reference field for fast lookups
db.orders.createIndex({ userId: 1 });

// Now this query hits the index instead of scanning all orders
db.orders.find({ userId: ObjectId('u1') });

Shared Data: The Case for References

When multiple documents share the same child—like many blog posts sharing the same author profile—embedding would duplicate the author data in every post. With referencing, only the authorId is stored in each post. If the author's name changes, one update to the authors collection propagates everywhere, whereas with embedding you'd have to update every post.

Performance Trade-offs of References

Referencing requires at least two round trips to the database (or one aggregation with $lookup) to fetch the parent and its children. This is slower than a single embedded read, but the trade-off is often worth it when children are large in number, updated independently, or shared across parents. Design choices always depend on your dominant query patterns.

Combining Embedding and Referencing

Real schemas often mix both strategies. An order document might embed the shipping address (immutable at order time) while referencing the productId for each line item (shared catalogue data). This hybrid approach co-locates data that changes together and references data that is shared or grows independently. There is no rule against mixing the two patterns in a single document.

db.orders.insertOne({
  _id: ObjectId(),
  userId: ObjectId('u1'),           // reference to user
  shippingAddress: {                // embedded snapshot
    street: '123 Maple St',
    city: 'Austin',
    zip: '78701'
  },
  items: [
    { productId: ObjectId('p1'), qty: 2, price: 19.99 }  // reference to product
  ]
});

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: referencing stores ObjectId links across collections, $lookup and populate() resolve references at query time, and referencing is preferred when child count is large, data is shared, or children need independent updates. Next up we explore the unbounded array anti-pattern—when embedding goes wrong.

常见问题解答

「引用:一对多与多对多」课时是免费的吗?

是的 — 「引用:一对多与多对多」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「引用:一对多与多对多」这节课中我会学到什么?

您将在集合之间存储 ObjectId 引用,并使用 $lookup 在查询时将它们连接起来。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「引用:一对多与多对多」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 嵌入:一对少关系
  2. 引用:一对多与多对多
  3. 无界数组反模式
  4. 模式设计决策框架
← 返回 MongoDB Academy