Update Operators and Upsert
Introduction
Updates modify existing documents without replacing the whole record unless you choose to. MongoDB provides updateOne, updateMany, and replaceOne, plus operators like $set, $inc, and $push. This chapter covers partial updates, array changes, upsert, and atomic findOneAndUpdate.
Prerequisites
- Query Operators — filters
hello_demo.postssample data
updateOne and updateMany
Filter + update document:
// Set fields — $set required for partial update
db.posts.updateOne(
{ slug: "mongodb-basics" },
{ $set: { published: true, updatedAt: new Date() } }
)
// Update all matching
db.posts.updateMany(
{ published: false },
{ $set: { status: "archived" } }
)Return document (requires returnDocument in drivers; mongosh shows):
const result = db.posts.updateOne(
{ slug: "draft-notes" },
{ $set: { published: true } }
)
// result.matchedCount, result.modifiedCountWarning
Always Use Update Operators
updateOne(filter, { published: true }) replaces the entire document structure incorrectly in older patterns. Always use $set, $inc, etc.
Common Update Operators
Array Update Operators
$push with $each, $slice, $sort caps array size (subset pattern preview).
replaceOne
Replace entire document except _id:
db.posts.replaceOne(
{ slug: "draft-notes" },
{
title: "Rewritten Draft",
slug: "draft-notes",
published: false,
body: "New content only.",
createdAt: new Date()
}
)Dropped fields not in replacement are removed—prefer $set for partial edits.
Upsert
Insert if no match:
db.posts.updateOne(
{ slug: "new-post" },
{
$set: {
title: "New Post",
published: false,
createdAt: new Date()
}
},
{ upsert: true }
)upsertedId in result when insert occurred.
Use upsert for idempotent "create or update" by business key (slug, sku).
findOneAndUpdate
Atomic read-modify-write:
db.posts.findOneAndUpdate(
{ slug: "mongodb-basics" },
{ $inc: { viewCount: 1 } },
{ returnDocument: "after" }
)Options:
| Option | Use |
|---|---|
returnDocument: "after" | Return updated doc |
upsert: true | Create if missing |
projection | Limit returned fields |
Related: findOneAndReplace, findOneAndDelete.
Update vs SQL
| SQL | MongoDB |
|---|---|
UPDATE posts SET views = views + 1 WHERE slug = 'x' | updateOne({ slug }, { $inc: { views: 1 } }) |
INSERT ... ON DUPLICATE KEY UPDATE | updateOne(..., { upsert: true }) |
Transactions for multi-doc updates — chapter 14.
Common Mistakes
| Mistake | Result | Fix |
|---|---|---|
Missing $set | Document corruption | Operator form |
updateMany without filter | All docs changed | Always narrow filter in prod |
$push unbounded | 16 MB risk | $slice, separate collection |
| Race on read-then-write | Lost updates | findOneAndUpdate or transaction |
Post-Chapter Checklist
-
$set,$inc,$unseton posts -
$push/$pullon array fields -
upsert: truecreates missing document -
findOneAndUpdatereturns updated document
FAQ
modifiedCount 0 but matched?
Document already had target values—no change written.
Update _id?
Generally forbidden. Insert new doc and delete old if needed.
Array filters?
arrayFilters option for positional updates on matched array elements—advanced.
Bulk updates?
bulkWrite in drivers — PyMongo.
Soft delete?
$set: { deletedAt: new Date() } and filter { deletedAt: { $exists: false } } in queries.