MongoDB One-to-Many Relationships
One-to-many relationships are among the most common data relationships you'll encounter when designing database schemas. In traditional relational databases, these relationships are typically handled through foreign keys. However, MongoDB, being a document-oriented database, provides more flexible options for modeling one-to-many relationships.
Introduction to One-to-Many Relationships
A one-to-many relationship occurs when a single entity is associated with multiple instances of another entity. For example:
- One author writes many books
- One customer places many orders
- One department has many employees
In MongoDB, there are primarily two approaches to model these relationships:
- Embedded Documents - where related data is nested within the parent document
- Document References - where related data is kept in separate collections and referenced
Let's explore both approaches with examples.
Embedded Documents Approach
Embedding documents is a natural way to model one-to-many relationships in MongoDB when the "many" side belongs exclusively to the "one" side and is typically accessed together.
When to Use Embedded Documents
- The "many" objects always appear within the context of their parent
- The number of child documents is small and has controlled growth
- Deep and frequent querying of the embedded data is required
- You need to atomically update the entire document
Example: Author and Books
Consider an author who has written several books:
// An author document with embedded books
db.authors.insertOne({
name: "J.K. Rowling",
country: "United Kingdom",
birthDate: new Date("1965-07-31"),
books: [
{
title: "Harry Potter and the Philosopher's Stone",
publishedYear: 1997,
pages: 223,
genres: ["Fantasy", "Young Adult"]
},
{
title: "Harry Potter and the Chamber of Secrets",
publishedYear: 1998,
pages: 251,
genres: ["Fantasy", "Young Adult"]
},
{
title: "The Casual Vacancy",
publishedYear: 2012,
pages: 503,
genres: ["Drama", "Fiction"]
}
]
});
Querying Embedded Documents
To find an author with a specific book:
db.authors.find({ "books.title": "The Casual Vacancy" });
To find all authors who have published books in the "Fantasy" genre:
db.authors.find({ "books.genres": "Fantasy" });
Updating Embedded Documents
Adding a new book to an author's list:
db.authors.updateOne(
{ name: "J.K. Rowling" },
{
$push: {
books: {
title: "Harry Potter and the Prisoner of Azkaban",
publishedYear: 1999,
pages: 317,
genres: ["Fantasy", "Young Adult"]
}
}
}
);
Document References Approach
For larger or more complex relationships, referencing documents across collections may be more appropriate.
When to Use Document References
- The "many" objects can exist independently of the "one" object
- The number of child documents is large or unbounded
- The child documents are frequently accessed directly without loading the parent
- The child documents are shared between multiple parent documents
- The overall document would be too large with embedded data
Example: Department and Employees
Consider a department with multiple employees:
// First, create the departments
db.departments.insertOne({
_id: ObjectId("5f8d0c91f2f2e8a2b9c6a1d3"),
name: "Engineering",
location: "Building A",
budget: 500000
});
// Then create employees that reference their department
db.employees.insertMany([
{
name: "Alice Johnson",
title: "Software Engineer",
hireDate: new Date("2019-03-15"),
department_id: ObjectId("5f8d0c91f2f2e8a2b9c6a1d3"),
skills: ["JavaScript", "MongoDB", "Node.js"]
},
{
name: "Bob Smith",
title: "Senior Developer",
hireDate: new Date("2017-11-05"),
department_id: ObjectId("5f8d0c91f2f2e8a2b9c6a1d3"),
skills: ["Python", "AWS", "System Design"]
},
{
name: "Carol Williams",
title: "DevOps Engineer",
hireDate: new Date("2020-01-12"),
department_id: ObjectId("5f8d0c91f2f2e8a2b9c6a1d3"),
skills: ["Docker", "Kubernetes", "CI/CD"]
}
]);
Querying Referenced Documents
To find all employees in a specific department:
db.employees.find({ department_id: ObjectId("5f8d0c91f2f2e8a2b9c6a1d3") });
Using Aggregation for Joins
MongoDB's aggregation framework allows you to join data across collections:
db.employees.aggregate([
{
$match: {
name: "Alice Johnson"
}
},
{
$lookup: {
from: "departments",
localField: "department_id",
foreignField: "_id",
as: "department"
}
},
{
$unwind: "$department"
}
]);
This returns the employee document with their department information included.
Real-world Application: E-commerce Platform
Let's model a more complex real-world example of an e-commerce platform where customers can place multiple orders.
Customer and Orders Model
Since orders are business-critical documents that may need to be queried independently and can grow significantly, we'll use the referenced approach:
// Create a customer
db.customers.insertOne({
_id: ObjectId("6052f9e5d67c4a8d12b5c123"),
name: "John Doe",
email: "[email protected]",
address: {
street: "123 Main St",
city: "Boston",
state: "MA",
zipCode: "02108"
},
phone: "+1-555-123-4567",
dateCreated: new Date("2020-10-15")
});
// Create multiple orders for this customer
db.orders.insertMany([
{
customer_id: ObjectId("6052f9e5d67c4a8d12b5c123"),
orderNumber: "ORD-2021-001",
orderDate: new Date("2021-01-10"),
status: "Delivered",
shippingAddress: {
street: "123 Main St",
city: "Boston",
state: "MA",
zipCode: "02108"
},
totalAmount: 125.99,
items: [
{ product: "Wireless Headphones", quantity: 1, price: 89.99 },
{ product: "Phone Case", quantity: 2, price: 18.00 }
]
},
{
customer_id: ObjectId("6052f9e5d67c4a8d12b5c123"),
orderNumber: "ORD-2021-008",
orderDate: new Date("2021-02-22"),
status: "Processing",
shippingAddress: {
street: "123 Main St",
city: "Boston",
state: "MA",
zipCode: "02108"
},
totalAmount: 349.95,
items: [
{ product: "Smart Watch", quantity: 1, price: 299.95 },
{ product: "Watch Band", quantity: 1, price: 50.00 }
]
}
]);
Notice how we've embedded the order items within the orders, while keeping orders separate from customers. This is a hybrid approach that's common in real-world applications.