Chapter 04 · Query Operators, Arrays, Nested Fields, Null/Missing Semantics, and Expressions

Null vs Missing Fields: Query Semantics, Index Behavior, and Data-Quality Consequences

Separate explicit null, missing, and present non-null values so migrations, indexes, analytics, and application contracts do not silently collapse distinct states.

Beginner100–125 minutesNull/missing classification + explain labMongoDB Community Server 8.3.8 · mongosh 2.10.0 · PyMongo 4.17.0Last reviewed: September 2026

Learning outcomes

AtlasMart has three different business states for a promotion code: a legacy record where the field was never written, a record where the application explicitly stored null to mean “known to have no code,” and a record containing a real string. A filter that treats those states as identical can corrupt migration metrics, send the wrong notifications, or create incorrect sparse/partial-index assumptions. MongoDB's query language gives you the tools to distinguish them—but only if you ask the precise question.

01

Explain why {field:null} matches both explicit null and missing fields in modern MongoDB.

02

Use $type:"null", $exists:false, and $ne:null to distinguish explicit null, missing, and present non-null values.

03

Use the aggregation $type expression to surface the special string "missing" for absent fields.

04

Explain the MongoDB 8.0 compatibility change for deprecated BSON undefined values.

05

Connect null semantics to index coverage, sparse/partial-index design, schema contracts, and data-quality decisions.

Chapter 04 reproducible baseline

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.

Generation-time execution note

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. Equality to null means “explicit null or missing,” not “only BSON Null”

The query {promoCode:null} intentionally matches two cases: a document where promoCode exists and is BSON Null, and a document where promoCode does not exist. This is convenient when an application truly treats both states as “no value,” but dangerous when field presence itself carries meaning.

mongosh · four different null/presence questions
db.query_products.drop()db.query_products.insertMany([ {_id:"explicit-null",promoCode:null}, {_id:"missing"}, {_id:"string",promoCode:"SAVE10"}, {_id:"empty-string",promoCode:""}, {_id:"number",promoCode:10}])printjson({ equalityNull: db.query_products.find({promoCode:null},{_id:1,promoCode:1}).sort({_id:1}).toArray(), explicitNullOnly: db.query_products.find({promoCode:{$type:"null"}},{_id:1,promoCode:1}).sort({_id:1}).toArray(), missingOnly: db.query_products.find({promoCode:{$exists:false}},{_id:1}).sort({_id:1}).toArray(), presentNonNull: db.query_products.find({promoCode:{$ne:null}},{_id:1,promoCode:1}).sort({_id:1}).toArray()})

The expected sets are intentionally different. $type:"null" asks for the BSON Null type specifically. $exists:false asks for absence. $ne:null matches documents where the field exists and has a non-null value. Empty string and zero-like numeric values are not null; do not collapse them in application serializers.

2. Aggregation $type can reveal “missing” as data-quality evidence

The query $type operator filters by type. The aggregation $type expression returns a type-name string. When its field-path argument is absent, the expression returns "missing". This makes it useful in audits where you want to classify the dataset rather than just filter one category.

mongosh · classify explicit types and missing fields
printjson(db.query_products.aggregate([ {$project:{_id:1,promoCode:1,promoType:{$type:"$promoCode"}}}, {$sort:{_id:1}}]).toArray())

That output can drive a migration report: how many records are missing because they predate the field, how many intentionally store null, and how many have unexpected legacy types. The report should inform a schema-evolution decision, not become an excuse to tolerate arbitrary types forever.

3. MongoDB 8.0 changed deprecated undefined/null comparison behavior

BSON undefined is deprecated. Before MongoDB 8.0, equality comparisons to null could also match undefined values or array elements. Starting in MongoDB 8.0, equality-to-null no longer matches deprecated undefined values. The modern rule taught in this course is therefore: null equality covers BSON Null and missing fields, but not deprecated undefined.

Why this version note matters

A migration script copied from an older MongoDB blog post can classify legacy undefined data differently on MongoDB 8.x. If your dataset may contain deprecated BSON undefined values, use the official migration guidance and inspect real BSON types before rewriting them.

The lab does not create new undefined values because teaching a deprecated representation as a normal fixture would encourage the wrong pattern. The lesson records the compatibility boundary so learners can diagnose old datasets without reproducing obsolete writes.

4. Null predicates and indexes: “uses an index” is not the same as “covered”

A normal single-field index can participate in null-related queries, but MongoDB's query-optimization documentation states that a query containing a predicate equal to null cannot be a covered query. A covered query returns results entirely from index entries without examining collection documents. Because null equality includes missing-field semantics, the engine may need document evidence beyond a simple index key.

mongosh · inspect null-related index behavior rather than assuming coverage
db.query_products.createIndex({promoCode:1})for (const [label,q] of [["null-or-missing",{promoCode:null}],["explicit-null",{promoCode:{$type:"null"}}],["non-null",{promoCode:{$ne:null}}]]) { const e=db.query_products.explain("executionStats").find(q,{_id:0,promoCode:1}); printjson({label,nReturned:e.executionStats.nReturned,keys:e.executionStats.totalKeysExamined,  docs:e.executionStats.totalDocsExamined,winningPlan:e.queryPlanner.winningPlan});}

The exact plan can vary with dataset size, selectivity, engine, and cached plans. The production lesson is to inspect nReturned, keys/documents examined, and the winning plan for your workload. Sparse and partial indexes can encode presence/type assumptions, but they also change which documents are represented; later index lessons treat those designs explicitly.

5. AtlasMart null/missing data-quality lab

shell · start disposable MongoDB on 127.0.0.1:27034
docker rm -f atlasmart-mongo-ch04-l3docker run --name atlasmart-mongo-ch04-l3 -p 127.0.0.1:27034:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimdocker logs atlasmart-mongo-ch04-l3 --tail 25
shell · classify null, missing, and non-null states
mongosh "mongodb://127.0.0.1:27034/atlasmart?directConnection=true" --quiet --eval 'db.query_products.drop();db.query_products.insertMany([ {_id:"explicit-null",promoCode:null}, {_id:"missing"}, {_id:"string",promoCode:"SAVE10"}, {_id:"empty-string",promoCode:""}, {_id:"number",promoCode:10}]);db.query_products.createIndex({promoCode:1});const sets={ nullOrMissing:db.query_products.find({promoCode:null},{_id:1}).sort({_id:1}).toArray(), explicitNull:db.query_products.find({promoCode:{$type:"null"}},{_id:1}).sort({_id:1}).toArray(), missing:db.query_products.find({promoCode:{$exists:false}},{_id:1}).sort({_id:1}).toArray(), nonNull:db.query_products.find({promoCode:{$ne:null}},{_id:1}).sort({_id:1}).toArray(), types:db.query_products.aggregate([{$project:{_id:1,t:{$type:"$promoCode"}}},{$sort:{_id:1}}]).toArray()};printjson(sets);const e=db.query_products.explain("executionStats").find({promoCode:null},{_id:0,promoCode:1});printjson({nReturned:e.executionStats.nReturned,keys:e.executionStats.totalKeysExamined,docs:e.executionStats.totalDocsExamined});' 

Verification checklist

  • {promoCode:null} returns both the explicit-null and missing fixtures.
  • $type:"null" returns only the explicit BSON Null fixture.
  • $exists:false returns only the missing-field fixture.
  • $ne:null includes empty string and numeric values because they are present and non-null.
  • The aggregation type report prints missing for the absent field.
  • The explain output is treated as environment-specific evidence; the lesson does not claim a universal fixed stage tree.

Check your understanding

  1. Why does {promoCode:null} match a document with no promoCode field?
  2. How do you match only explicit BSON Null?
  3. What does $exists:true say about a null field?
  4. What changed in MongoDB 8.0 regarding deprecated undefined?
  5. Why should missing and null meanings be documented at the application-contract level?
Review the answers

MongoDB equality-to-null semantics intentionally include documents where the field is missing.

Use a type predicate such as {promoCode:{$type:"null"}}.

The field exists, so it matches even though its value is null.

Equality comparisons to null no longer match deprecated BSON undefined values or undefined array elements.

They may represent different business states—unknown/not-yet-written versus explicitly known to have no value—and migrations, analytics, and indexes can depend on that distinction.

bash · cleanup/reset
docker rm -f atlasmart-mongo-ch04-l3

The next lesson uses $expr when the predicate itself must compute or compare values from the document.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.