Embedded Docs and Arrays
The true power of MongoDB lies in its ability to model complex, hierarchical data without shredding it into flat tables. This chapter explores Embedded Documents and Arrays, the building blocks of high-performance document models.
1. First Principles: Data Locality
In a relational database, retrieving a User and their Addresses requires joining two tables. This means the database engine might need to perform random I/O to seek two different locations on disk (or pages in memory).
In MongoDB, if the addresses are embedded in the user document, they are stored contiguously on disk. Reading the User reads the Addresses in a single sequential I/O operation. This Data Locality is key to MongoDB’s read performance.
Interactive: Data Locality vs Joins
Visualize the disk head movement (IOPS) required to fetch a User and their Orders.
2. Embedded Documents
You can nest documents inside other documents. This is perfect for “Contains” relationships (e.g., A User contains a Profile).
Dot Notation
To access fields inside an embedded document, use Dot Notation.
[!NOTE] When using dot notation, you must enclose the path in quotes (e.g.,
"profile.bio").
Code Examples
Java
// Find users in "New York"
collection.find(eq("address.city", "New York"));
// Update the bio
collection.updateOne(
eq("_id", userId),
set("profile.bio", "Software Architect")
);
Go
// Find users in "New York"
filter := bson.D{{"address.city", "New York"}}
cursor, err := coll.Find(ctx, filter)
// Update the bio
update := bson.D{{"$set", bson.D{{"profile.bio", "Software Architect"}}}}
_, err := coll.UpdateOne(ctx, bson.D{{"_id", userId}}, update)
3. Working with Arrays
Arrays allow you to store lists of values or objects. This replaces the “Link Table” (Many-to-Many) or “Child Table” (One-to-Many) patterns in SQL.
Array Operators
$push: Appends a value to an array.$addToSet: Appends a value only if it doesn’t exist (Set behavior).$pull: Removes all instances of a value.$pop: Removes the first or last element.
Advanced Querying: $elemMatch
When querying an array of objects, simple equality checks might not be enough.
Scenario: Find documents where the grades array contains a grade with score > 90 AND subject = "Math".
// ❌ WRONG: Finds a grade > 90 (maybe English) AND a grade = "Math" (maybe score 60)
db.students.find({ "grades.score": { $gt: 90 }, "grades.subject": "Math" })
// ✅ CORRECT: Finds a SINGLE element matching BOTH criteria
db.students.find({
grades: { $elemMatch: { score: { $gt: 90 }, subject: "Math" } }
})
Interactive: $elemMatch Visualizer
See why simple dot notation fails for specific array element matching.
Document: Student
{
"grades": [
{ "subject": "Math", "score": 80 }, // Index 0
{ "subject": "English", "score": 95 } // Index 1
]
}
Code Examples
Java
// Add a tag "sale" if not present
collection.updateOne(
eq("_id", prodId),
addToSet("tags", "sale")
);
// Remove "outdated" tag
collection.updateOne(
eq("_id", prodId),
pull("tags", "outdated")
);
Go
// Add a tag "sale" if not present
update := bson.D{{"$addToSet", bson.D{{"tags", "sale"}}}}
_, err := coll.UpdateOne(ctx, bson.D{{"_id", prodId}}, update)
// Remove "outdated" tag
update = bson.D{{"$pull", bson.D{{"tags", "outdated"}}}}
_, err = coll.UpdateOne(ctx, bson.D{{"_id", prodId}}, update)
4. Interactive: The Document Builder
Construct complex documents to understand nesting.
Construct Document
{
"name": "User 1",
"address": {},
"tags": []
}