Chapter 04 · Query Operators, Arrays, Nested Fields, Null/Missing Semantics, and Expressions
Dot Notation, Embedded-Document Matching, Arrays, $elemMatch, and Positional Semantics
Make nested and array predicates logically exact before optimizing them: whole-document equality, dot notation, same-element binding, array positions, and multikey evidence.
Learning outcomes
AtlasMart stores product dimensions as embedded documents and
market offers as arrays of embedded documents. A query that
sounds simple—“find products with an EU offer that is in
stock”—can be logically wrong if the region condition is
satisfied by one array element and the stock condition by
another. This lesson builds the exact mental model for
dot notation, whole-subdocument equality, array
matching, $elemMatch, and query-time array
positions before later chapters add update-position operators.
Contrast exact embedded-document equality with dot-notation predicates on nested fields.
Explain how scalar and dotted predicates interact with arrays and why independent array predicates can cross elements.
Use $elemMatch when multiple conditions must be true for one array element.
Distinguish zero-based array-position queries from later update positional operators such as $, $[], and $[identifier].
Pair correctness checks with multikey-index explain evidence instead of assuming an index makes a logically wrong predicate correct.
Mandatory labs use a disposable loopback-only standalone
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
with dedicated AtlasMart query fixtures and explicit reset
commands. Driver examples pin pymongo==4.17.0.
The standalone is intentionally unauthenticated only for these
short-lived local exercises; do not publish it beyond
127.0.0.1. The labs create only local
secondary/multikey indexes needed to expose query plans. Read
concern, read preference, replication, and sharding are not
varied in this chapter because the goal is query semantics and
indexability.
Docker, mongod, mongosh, and PyMongo are not available in this generation environment. Commands were checked against current official MongoDB Server and PyMongo documentation, but product commands were not executed here. Expected-output blocks describe stable fields and relationships to verify; they are not fabricated captured transcripts.
1. Whole embedded-document equality is an exact structural match
An embedded document is a BSON document stored
as the value of a field in another document. MongoDB can compare
that value as a whole. When the query says
{specs:{width:10,height:20}}, the embedded value
must match that document exactly, including field names, values,
field order, and the absence of extra fields. That makes
whole-document equality much stricter than many application
developers expect.
use atlasmartdb.query_products.drop()db.query_products.insertMany([ {_id:"p-exact", specs:{width:10,height:20}, tags:["camera","travel"], offers:[{region:"EU",stock:7,priceCents:11900},{region:"US",stock:0,priceCents:10900}]}, {_id:"p-reordered", specs:{height:20,width:10}, tags:["camera","sale"], offers:[{region:"EU",stock:0,priceCents:9900},{region:"US",stock:8,priceCents:10900}]}, {_id:"p-extra", specs:{width:10,height:20,unit:"cm"}, tags:["accessory"], offers:[{region:"EU",stock:3,priceCents:12900}]}])printjson({ exact: db.query_products.find({specs:{width:10,height:20}},{_id:1}).sort({_id:1}).toArray(), dotted: db.query_products.find({"specs.width":10,"specs.height":20},{_id:1}).sort({_id:1}).toArray()})
The expected distinction is deliberate: the exact query matches
only p-exact; the dotted query matches all three
because each document has the two requested nested fields
regardless of embedded field order or the extra
unit field. This is why whole-subdocument equality
is fragile when an embedded schema evolves. If the application
cares about individual fields, query the fields.
2. Array predicates can be satisfied by different elements
An array field introduces multikey semantics: a
predicate on an array path can match an element rather than the
array as one scalar value. For a scalar array,
{tags:"camera"} matches documents whose
tags array contains that string. For an array of
embedded documents, a dotted path traverses every element that
contains the nested field.
The important trap is that separate dotted predicates do not
automatically mean “the same element.” In
p-reordered, the EU offer has stock zero while a US
offer has stock eight. The filter below can still match because
each condition is independently true somewhere in the array.
const independent = { "offers.region":"EU", "offers.stock":{$gt:0}};const sameElement = { offers:{$elemMatch:{region:"EU",stock:{$gt:0}}}};printjson({ independent: db.query_products.find(independent,{_id:1}).sort({_id:1}).toArray(), elemMatch: db.query_products.find(sameElement,{_id:1}).sort({_id:1}).toArray()})
The independent filter should include p-reordered;
the $elemMatch filter should not. That difference
is a correctness property, not a performance optimization.
Adding an index to the wrong predicate can make a wrong answer
arrive faster.
3. $elemMatch binds multiple conditions to one array element
$elemMatch selects documents when at least one
array element satisfies all predicates inside the operator. It
is therefore the natural expression of AtlasMart's business
requirement “one EU offer must itself be in stock.” The operator
applies to one array value; it does not mean all array elements
must match.
db.query_products.createIndex({"offers.region":1,"offers.stock":1})const q1={"offers.region":"EU","offers.stock":{$gt:0}};const q2={offers:{$elemMatch:{region:"EU",stock:{$gt:0}}}};for (const [name,q] of [["independent",q1],["elemMatch",q2]]) { const e=db.query_products.explain("executionStats").find(q,{_id:1}); printjson({ name, nReturned:e.executionStats.nReturned, totalKeysExamined:e.executionStats.totalKeysExamined, totalDocsExamined:e.executionStats.totalDocsExamined, winningPlan:e.queryPlanner.winningPlan });}
The exact winning-plan tree can differ between query engines and
versions, so compare the stable execution statistics and inspect
the actual plan rather than memorizing one stage diagram. A
compound multikey index can support fields from one array of
embedded documents, and $elemMatch lets MongoDB
reason that the compound constraints belong to a single array
element. Later indexing chapters will study multikey bounds in
depth.
4. Positional query semantics are zero-based—and are not the update $ operator
Dot notation can address a specific zero-based array position.
The predicate {"offers.0.region":"EU"} asks whether
the first offer has region EU. This can be useful when array
position itself has stable business meaning, but it is brittle
when order is incidental. If AtlasMart reorders offers by price,
“position zero” changes even though the set of offers does not.
printjson({ firstOfferEU: db.query_products.find({"offers.0.region":"EU"},{_id:1,"offers.0":1}).sort({_id:1}).toArray(), anyOfferEU: db.query_products.find({"offers.region":"EU"},{_id:1}).sort({_id:1}).toArray()})
Chapter 05 teaches the update positional operators
$, $[], and
$[identifier]. In this lesson, “positional” means
query-time array index paths such as
offers.0.region and the same-element binding
provided by $elemMatch.
When position is not a domain invariant, query by value or element structure. An array whose order matters should document why it matters and how writers preserve that order.
5. AtlasMart nested/array query lab
docker rm -f atlasmart-mongo-ch04-l1docker run --name atlasmart-mongo-ch04-l1 -p 127.0.0.1:27032:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimdocker logs atlasmart-mongo-ch04-l1 --tail 25
mongosh "mongodb://127.0.0.1:27032/atlasmart?directConnection=true" --quiet --eval 'db.query_products.drop();db.query_products.insertMany([ {_id:"p-exact",specs:{width:10,height:20},offers:[{region:"EU",stock:7},{region:"US",stock:0}]}, {_id:"p-reordered",specs:{height:20,width:10},offers:[{region:"EU",stock:0},{region:"US",stock:8}]}, {_id:"p-extra",specs:{width:10,height:20,unit:"cm"},offers:[{region:"EU",stock:3}]}]);db.query_products.createIndex({"offers.region":1,"offers.stock":1});const exact={specs:{width:10,height:20}};const dotted={"specs.width":10,"specs.height":20};const independent={"offers.region":"EU","offers.stock":{$gt:0}};const same={offers:{$elemMatch:{region:"EU",stock:{$gt:0}}}};for (const [name,q] of [["exact",exact],["dotted",dotted],["independent",independent],["same",same]]) { const docs=db.query_products.find(q,{_id:1}).sort({_id:1}).toArray(); const e=db.query_products.explain("executionStats").find(q,{_id:1}); printjson({name,docs,nReturned:e.executionStats.nReturned,keys:e.executionStats.totalKeysExamined,docsExamined:e.executionStats.totalDocsExamined});}'
Verification checklist
- The whole embedded-document query matches only the exact field order/shape fixture.
- The dotted nested-field query matches all documents containing the requested nested values.
- The independent array predicates include the cross-element false positive.
-
$elemMatchremoves that false positive by requiring one offer to satisfy both conditions. - The explain output is inspected for returned/keys/docs counts; no fixed stage name is assumed.
-
The only destructive operation is dropping the disposable
atlasmart.query_productsfixture.
Check your understanding
- Why can {"offers.region":"EU","offers.stock":{$gt:0}} match a product with no in-stock EU offer?
- What does $elemMatch change?
- Why is {specs:{width:10,height:20}} more brittle than dotted predicates?
- Does an index repair incorrect array semantics?
- When is an array-index query such as offers.0.region appropriate?
Review the answers
Each dotted predicate can be satisfied by a different array element. The query does not bind the conditions to one offer.
It requires at least one array element to satisfy every condition inside the $elemMatch document.
Whole embedded-document equality requires exact structure and field order, so extra fields or reordered fields can change the match.
No. Indexes affect access paths and performance; the filter still needs to express the business condition correctly.
Only when the element position itself has stable domain meaning and writers preserve that order.
docker rm -f atlasmart-mongo-ch04-l1
The next lesson broadens the predicate vocabulary to membership, array cardinality, existence, type, regex, and expression-based filtering.
Authoritative references
- MongoDB release notes — Official current stable server series and patch notes.
- MongoDB 8.3 release notes — Official 8.3 patch history; 8.3.8 is the latest released patch at review time.
- MongoDB query documents — Official find/query behavior and cursor semantics.
- MongoDB query optimization — Official selectivity, index, and explain guidance.
- PyMongo query documents — Official Python driver query-filter behavior.
- Query on embedded/nested documents — Exact embedded-document equality and dot-notation query behavior.
- Query an array of embedded documents — Array-of-document matching and $elemMatch semantics.
- Query an array — Array containment, multiple criteria, $elemMatch, and zero-based positional paths.
- Multikey indexes — Multikey index behavior, compound restrictions, $elemMatch coverage caveats, and index bounds.