Skip to content

Arrays & Nested Documents

Positional Operators

$, $[] and $[<identifier>] for updating the element you actually matched.

IntermediateAbout 6 minutes

You matched an item. Now change that item. Three tools: `$` (the first match of the query), `$[]` (every element), `$[<id>]` with arrayFilters (every element that matches a filter, not necessarily the query).

db.orders.updateOne(
  { "items.name": "USB-C Cable" },
  { $inc: { "items.$.quantity": 1 } }
)
$ — first array element that matched the query
{ items: [
  { name: "Mechanical Keyboard", quantity: 1, unitPrice: 4500 },
  { name: "USB-C Cable", quantity: 3, unitPrice: 200 }
] }
Cable quantity 2 → 3. Keyboard untouched.

$ is only the first match, and the query must have matched that array. It cannot see arrayFilters. Two cables in items and you only bump the first.

// 10% off every line
db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c90011") },
  { $mul: { "items.$[].unitPrice": 0.9 } }
)

db.orders.updateOne(
  { _id: ObjectId("64a1b2c3d4e5f6a7b8c90011") },
  { $inc: { "items.$[line].quantity": 1 } },
  { arrayFilters: [{ "line.name": "USB-C Cable" }] }
)
$[] — all elements. $[i] — those that match arrayFilters.
  • `$` — one element, the first that the query matched. Simple, easy to get wrong with duplicates.
  • `$[]` — every element. 'Mark all items shipped' inside one order.
  • `$[line]` + `arrayFilters` — every element that matches line.…. This is what you want for 'all cables' or 'unitPrice > 1000'.

Interview question

How do you update one object inside an array without replacing the array?

Think about it first.