Course outline

MongoDB / Basic: document foundations / Lesson 5

Arrays and embedded-document queries

Match the intended array element and preserve update scope. Distinguish separate matching elements, positional updates and array expansion in reports.

About 4 min readContent reviewed
On this page

Lesson goals

  • Use $elemMatch when predicates belong to one element.
  • Explain array projection and filtered positional updates.

Several predicates may match different elements

A booking lines array can contain several SKU and quantity pairs. A filter on lines.sku and lines.qty without $elemMatch may satisfy those predicates through different elements of the same array. If the business asks for one PEN line with quantity at least five, bind both requirements inside one $elemMatch. This is a semantic correctness issue before it is an indexing question. Create a fixture where one element matches SKU and another matches quantity so the difference cannot hide behind convenient sample data.

Require one qualifying line

Assume booking 1 has PEN quantity two and PAD quantity ten, while booking 2 has PEN quantity six. The query returns only booking 2 because its one PEN element satisfies both conditions. The projection returns the identifier alone, so matching an array does not automatically mean only matching elements are returned. If the response needs a filtered array, use the appropriate projection or aggregation expression and document whether it selects the first match or all qualifying elements.

javascript Illustrative mongosh; results are reasoned, not measured.
db.getSiblingDB('quay_lab').bookings.find(
  { lines: { $elemMatch: { sku: 'PEN', qty: { $gte: 5 } } } },
  { _id: 1 }
);
Expected output
{ _id: 2 }

The result depends on the two stated synthetic bookings; separate elements cannot satisfy this $elemMatch.

Path matching differs from whole-document equality

A dot path such as destination.city asks about a nested field. Matching an entire embedded document as a literal requires the stored structure and field order to meet MongoDBโ€™s documented equality semantics, which is often more restrictive than a field-level question. Prefer specific paths when only one fact matters. Arrays of scalars and arrays of documents also have different useful operators. Do not treat every bracketed value as an SQL IN list or assume an exact-array equality means set membership without order.

Constrain document and element update scopes

The filtered positional identifier line updates all matching PEN elements inside the selected training booking. The outer filter selects booking 101; arrayFilters selects elements, so these are two separate scopes. $inc preserves arithmetic update intent rather than replacing a quantity calculated from an earlier application read. If repeated SKUs are forbidden, enforce that business rule separately; this update intentionally changes every matching PEN element. No actual mutation is performed here, and a transaction is not needed merely because several fields in one document change.

javascript Illustrative mongosh; results are reasoned, not measured.
db.getSiblingDB('quay_lab').bookings.updateOne(
  { _id: 101 },
  { $inc: { 'lines.$[line].qty': 1 } },
  { arrayFilters: [{ 'line.sku': 'PEN' }] }
);

With one PEN line initially qty 2, the expected new quantity is 3. Multiple matching PEN lines would all increment.

Array correctness affects index and report behavior

An index over an array field becomes multikey and has restrictions that matter for compound indexes and covered queries. $unwind in an aggregation expands one document into several rows, changing report grain and potentially multiplying later joins. Empty, missing and null arrays need an explicit preserve policy. Keep arrays bounded and avoid using one hot parent document as an unlimited event log. Review correctness with duplicate elements, empty arrays and elements missing a field before optimizing the plan.

Common errors and fixes

A booking matches although no single line satisfies both rules

Why it happens
Dot-path predicates matched different array elements.
How to fix it
Use $elemMatch for the shared element requirement.
Prevent it next time
Include split-match elements in fixtures.

An array update changes more elements than expected

Why it happens
Filtered positional syntax matched every qualifying element.
How to fix it
Define the intended element identity and precise array filter.
Prevent it next time
Test duplicate keys within arrays and document update scope.

An embedded-object equality misses a nested fact

Why it happens
Whole-object comparison was used for a path-level question.
How to fix it
Query the specific nested field unless full equality is intended.
Prevent it next time
Distinguish path, element and whole-array semantics in review.

Check your understanding

1. One line is PEN qty 2 and another is PAD qty 10. Does a PEN-and-qty>=5 question match?

Show worked solution

It should not match when both requirements describe one line. A naive pair of dot predicates can nevertheless match through separate elements, so use lines: { $elemMatch: { sku: "PEN", qty: { $gte: 5 } } }. Keep that counterexample in tests because a fixture containing only one line would not expose the mistake.

2. A booking has two PEN lines. What does the shown $[line] update do, and what if only one line should change?

Show worked solution

It increments both matching quantities. If one specific line should change, give line elements stable identifiers and filter that intended identifier, with any required SKU or state precondition. Do not assume array position is permanent across edits or that the first matching SKU uniquely identifies a business line. Document the intended cardinality and verify the postcondition.

Key takeaways

  • $elemMatch binds predicates to one array element.
  • Document selection, element selection and report grain need separate checks.

Official references