Querying and updating arrays

MongoDB · Practical · 12 min

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] with arrayFilters updates 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.

Check yourself

  1. Which filter finds documents where tags contains both "red" and "big", in any order?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.mongodb.com/docs/manual/tutorial/query-arrays/