Arrays are everywhere in document models, and their matching rules surprise most newcomers.
When a field is an array, a plain filter matches if any element matches: { tags: "sale" } finds documents whose tags array contains "sale". A filter value that is an array, { tags: ["a", "b"] }, requires the exact array in that order. Use $all: ["a", "b"] for "contains both, any order" and $size: 2 for an exact length.
Arrays of subdocuments need care. { "items.sku": "PEN", "items.qty": { $gt: 5 } } can be satisfied by different elements: one item is PEN, another has qty 9. To require one element that satisfies all conditions, use $elemMatch: { items: { $elemMatch: { sku: "PEN", qty: { $gt: 5 } } } }.
Updating elements in place:
$(positional) updates the first element matched by the filter:{ $set: { "items.$.qty": 0 } },$[]updates every element:{ $inc: { "items.$[].qty": 1 } },$[name]witharrayFiltersupdates only elements matching a condition.
$push accepts modifiers: { $push: { scores: { $each: [7, 9], $sort: -1, $slice: 3 } } } appends, sorts and keeps the top three, a neat way to maintain capped lists.
Unbounded arrays that grow forever (all comments ever, every log line) are an anti-pattern; they bloat the document toward the 16 MB limit.
Example
db.carts.insertOne({ _id: 1, items: [ { sku: "PEN", qty: 2 }, { sku: "PAD", qty: 9 } ] })
db.carts.countDocuments({ "items.sku": "PEN", "items.qty": { $gt: 5 } }) // 1 (different elements!)
db.carts.countDocuments({ items: { $elemMatch: { sku: "PEN", qty: { $gt: 5 } } } }) // 0
// set qty of the PAD element only
db.carts.updateOne({ _id: 1, "items.sku": "PAD" }, { $set: { "items.$.qty": 1 } })
// add 10 to every element with qty < 5
db.carts.updateOne(
{ _id: 1 },
{ $inc: { "items.$[low].qty": 10 } },
{ arrayFilters: [ { "low.qty": { $lt: 5 } } ] }
)
db.carts.findOne({ _id: 1 }).items
// [ { sku: 'PEN', qty: 12 }, { sku: 'PAD', qty: 11 } ]Remember
- { arr: x } matches if any element equals x
- Use $elemMatch when several conditions must hold on the same element
- $ updates the first matched element, $[] all, $[id] those matching arrayFilters
- Avoid arrays that grow without bound
Watch out for
- Combining conditions on "items.a" and "items.b" and expecting them to apply to the same element.