Documents, collections and queries
The document model, dot notation into nested data, and the query and update operators you will use every day.
The document model
A MongoDB database holds collections, and a collection holds BSON documents — ordered key/value maps that may nest other documents and arrays. There is no fixed column list: two documents in one collection can have different fields, which is why the schema lives in your application code as much as in the database.
use shop;
db.orders.insertOne({
_id: ObjectId("66e0a1f2c3b4d5e6f7a8b9c0"),
customer: { id: 42, name: "Ada", country: "GB" },
items: [
{ sku: "LAMP-01", qty: 2, unitPrice: NumberDecimal("19.90") },
{ sku: "CABLE-3", qty: 1, unitPrice: NumberDecimal("4.50") }
],
status: "paid",
tags: ["priority", "wholesale"],
placedAt: ISODate("2026-09-14T09:12:00Z")
});_idis mandatory and unique per collection; anObjectIdis generated client-side if you do not supply one.- Field order inside a document is preserved, but the order of documents in a collection is not — always sort explicitly.
- BSON types are distinct from JavaScript types.
NumberDecimalpreserves exact decimals; a plain number becomes adoublewhen it has a fraction. - Storing a date as a string breaks range queries and sorting, because string comparison is lexicographic.
💡
Schema flexibility is a choice, not a licence. Add a
$jsonSchema validator to a collection when the shape matters — it turns a class of corrupt writes into rejected writes.Querying
// equality on a nested field and an array element
db.orders.find({ "customer.country": "GB", tags: "priority" })
// operators
db.orders.find({
status: { $in: ["paid", "shipped"] },
"items.qty": { $gte: 2 },
placedAt: { $gte: ISODate("2026-01-01") }
})
// elements of an array must match together
db.orders.find({ items: { $elemMatch: { sku: "LAMP-01", qty: { $gt: 1 } } } })
// projection plus sorting and paging
db.orders.find({ status: "paid" }, { "customer.name": 1, total: 1, _id: 0 })
.sort({ placedAt: -1 })
.limit(20);| Operator | Meaning |
|---|---|
$eq $ne $gt $gte $lt $lte | Comparison; $gt/$lt only compare within one BSON type |
$in $nin | Match any / none of a list |
$exists | Field present or absent — distinct from being null |
$all $size | Array contains all values / has this length |
$elemMatch | Several conditions must hold on the same array element |
$and $or $nor $not | Logical composition |
$regex | Pattern match; anchored patterns can use an index |
Without $elemMatch, conditions on array fields can each be satisfied by different elements, so { "items.sku": "LAMP-01", "items.qty": 5 } matches a document where no single item is both.
Updating without clobbering
db.orders.updateOne(
{ _id: id },
{
$set: { status: "shipped", "customer.country": "US" },
$inc: { retries: 1 },
$unset: { holdReason: "" },
$push: { history: { at: new Date(), by: "worker" } },
$addToSet: { tags: "export" }
},
{ upsert: false }
);
db.orders.updateMany({ status: "new" }, { $set: { status: "queued" } });
const before = db.orders.findOneAndUpdate(
{ _id: id, status: "queued" },
{ $set: { status: "running" } },
{ returnDocument: "before" } // fetch-and-claim in one round trip
);- Update operators change named fields; a replacement document overwrites everything else it does not mention.
updateManywith a broad filter is the document equivalent of a missingWHERE— run it asfindfirst.findOneAndUpdateis atomic, which makes it the right primitive for claiming a job without a separate transaction.$pushon an unbounded array eventually hits the 16 MB document limit; use the bucket pattern for time-series style growth.
FAQ
When should I embed and when should I reference?
Embed when the child is always read with the parent and is bounded in size — order line items, an address. Reference when the child is large, unbounded, or shared between parents, so it has its own collection and its own indexes.
Does MongoDB have joins?
$lookup performs a left outer join inside the aggregation pipeline. It works, but each one costs a lookup per input document, so a design that avoids them stays faster.Related
The aggregation pipeline Indexes and schema design patterns
Last refreshed 2026-09-18.