MongoDB Embedded Documents and Arrays, With Query Examples
For MongoDB beginners who can insert and find documents, and want to store and query data nested inside them.
On this page
An embedded document is a document stored as the value of a field, and an array field stores a list of values or documents. A relational database keeps such data in a second table and joins it back. MongoDB keeps it inside the parent document, so one read returns all of it.
A document can hold both shapes, in any mix:
{
field: value,
document: { field: value, field: value },
values: [ value, value ],
documents: [ { field: value }, { field: value } ]
}
documentis an embedded document, with fields of its own.valuesis an array of values, such as strings or numbers.documentsis an array of embedded documents.- A query reaches inside with dot notation:
"document.field", in quotes. A number picks an array element by position, from 0:"values.0".
The examples use one collection of three passengers:
db.passengers.insertMany([
{
name: "Alice",
address: { city: "Paris", country: "FR" },
flights: ["AF12", "AF15"],
bags: [{ item: "backpack", kg: 8 }, { item: "trolley", kg: 18 }]
},
{
name: "Bob",
address: { city: "Lyon", country: "FR" },
flights: ["AF15"],
bags: [{ item: "duffel", kg: 10 }]
},
{
name: "Carol",
address: { city: "Boston", country: "US" },
flights: ["DL40", "AF12"],
bags: [{ item: "backpack", kg: 8 }, { item: "suitcase", kg: 25 }]
}
])
Dot notation follows a path from the top of the document down:
The queries end with a projection such as { _id: 0, name: 1 }, so mongosh prints only the fields that matter.
Dot notation
Dot notation reads a field inside an embedded document. Write the path from the outer field to the inner one, in quotes.
db.passengers.find({ "address.country": "FR" }, { _id: 0, name: 1, address: 1 })
The query returns Alice and Bob, each with the whole embedded address, because their addresses hold FR and Carol's holds US. MongoDB's page on querying embedded documents describes this match on a nested field. The quotes are needed because a key with a dot in it is not a valid JavaScript name.
Arrays of values
A condition on an array field matches when any element equals the value.
db.passengers.find({ flights: "AF12" }, { _id: 0, name: 1, flights: 1 })
The query returns Alice and Carol. AF12 is Alice's first flight and Carol's second, and an array matches when at least one element equals the value. Bob flies only AF15.
Arrays of embedded documents
Dot notation also works through an array of documents. The condition matches when any element has that value in that field.
db.passengers.find({ "bags.item": "backpack" }, { _id: 0, name: 1, "bags.item": 1 })
The query returns Alice and Carol, because each carries a backpack, and one matching element is enough. Each comes back with the items of all its bags, the trolley and the suitcase included, because the projection keeps the whole array.
$elemMatch
Two conditions on one array can be met by two different elements. $elemMatch makes a single element meet all of them. First the query without it:
db.passengers.find({ "bags.kg": { $gt: 10, $lt: 20 } }, { _id: 0, name: 1 })
This query returns Alice and Carol. Then the same two conditions inside $elemMatch:
db.passengers.find({ bags: { $elemMatch: { kg: { $gt: 10, $lt: 20 } } } }, { _id: 0, name: 1 })
This query returns Alice alone, because her trolley of 18 is over 10 and under 20 on its own. Carol's suitcase of 25 is over 10 and her backpack of 8 is under 20, so only the first query counts her. The manual's page on arrays of documents shows the same split.
$push
$push adds an element to the end of an array, and leaves the rest of the document as it was.
db.passengers.updateOne({ name: "Bob" }, { $push: { bags: { item: "skis", kg: 12 } } })
db.passengers.find({ name: "Bob" }, { _id: 0, name: 1, bags: 1 })
The update changes Bob's document only, and the find returns it with two bags. The duffel stays first, and the skis become the second element, because $push appends to the end of the array. $pull does the reverse: { $pull: { bags: { item: "skis" } } } removes every bag that matches.
The positional $ operator
$ in an update path stands for the first array element that the query matched. Use it to change one element without knowing its position.
db.passengers.updateOne(
{ name: "Carol", "bags.item": "suitcase" },
{ $set: { "bags.$.kg": 23 } }
)
db.passengers.find({ name: "Carol" }, { _id: 0, name: 1, bags: 1 })
The filter matched Carol's suitcase, the second element, so bags.$.kg changes only its weight, from 25 to 23. Her backpack still weighs 8, because the positional $ stands for the first element that matched.
What goes wrong
A whole embedded document in a query
A query can match a whole embedded document instead of one field. Then the fields and their order have to be exactly the same:
db.passengers.find({ address: { city: "Paris", country: "FR" } }, { _id: 0, name: 1 })
The query returns Alice, the only passenger whose address is exactly that document. Swap the two fields to { country: "FR", city: "Paris" }, and the query returns no document. Dot notation on each field, { "address.city": "Paris", "address.country": "FR" }, matches in any order.
Two arrays in one index
An index on an array field is a multikey index, with one entry for each distinct value in the array. A compound index can include at most one array field per document:
db.passengers.createIndex({ flights: 1, "bags.item": 1 })
Both flights and bags are arrays, so MongoDB refuses the index with a "cannot index parallel arrays" error that names the two fields. Index each array on its own instead, such as { "bags.item": 1 }.
Embed or reference
Embed data that belongs to one parent, is read with it, and stays small, such as an address or a few bags. Data that many documents share, or that keeps growing, goes in its own collection: a document can't exceed 16 mebibytes. The next lesson, embed versus reference, works through the choice.
Embedded documents and arrays on a DbSchema diagram
DbSchema connects to MongoDB and draws each collection on a diagram. It infers the fields and their types from a sample of documents, by default 100 from each end of the collection.
This diagram shows the shop database of DbSchema's documentation, with the nested address of each customer and the items array of each order. On the passengers collection, address shows as an object and bags as an array of objects in the same way.
This field list approximates the sampled documents, and MongoDB enforces none of it. Edit Collection shows Inferred ( no validation ) for a collection without a validator.
Where a collection has a $jsonSchema validator, DbSchema reads the fields from it instead. Editing the collection in DbSchema writes the validator to the database and to the model file. Virtual relations, drawn by dragging one field onto another, link collections in the model file only.
The Data Explorer follows those relations from a document to its related documents, level after level. The HTML5 documentation shows the diagram as a vector image, with collection and field comments as mouse-over tooltips.
Download DbSchema at https://dbschema.com/download.html, connect it to your MongoDB database, and open your collections on the diagram. Connecting and the diagram are in the free Community Edition. The Data Explorer across relations and the HTML5 documentation are in DbSchema Pro.
See your MongoDB collections as a diagram
DbSchema connects to MongoDB, infers the fields from a sample of documents, and shows embedded documents and arrays as nested fields on the diagram. Connecting and the diagram are in the free Community Edition.