MongoDB
Update Documents
Updating existing data is one of the most common operations in any application — a user changes their email, a product price drops, an order status moves from shipped to delivered. MongoDB provides update_one() to modify the first matching document, update_many() to modify every matching document, and replace_one() to swap a document entirely. All three accept a filter to target the right documents and an update specification that describes the change using powerful update operators. This lesson covers every major operator and technique using the Dataplexa Store dataset.
update_one() — Modifying a Single Document
update_one() finds the first document matching the filter and applies the update. It returns a result object that reports whether a document was found and how many were modified. The update is atomic — the document either fully updates or does not update at all.
Why it exists: most application updates are targeted at one specific record — one user, one order, one product. update_one() is precise and safe — it will never accidentally modify more than one document.
# update_one() — modify the first matching document
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Update Alice's city from London to Edinburgh
result = db.users.update_one(
{"_id": "u001"}, # filter — which document
{"$set": {"city": "Edinburgh"}} # update — what to change
)
print("Matched: ", result.matched_count)
print("Modified:", result.modified_count)
# Verify the change
alice = db.users.find_one({"_id": "u001"}, {"name": 1, "city": 1, "_id": 0})
print("Alice now:", alice)Modified: 1
Alice now: {'name': 'Alice Johnson', 'city': 'Edinburgh'}
result.matched_count— number of documents that matched the filterresult.modified_count— number of documents actually changed (0 if the value was already the same)- Always use an update operator like
$set— passing a raw document without an operator replaces the entire document - The filter can match any field — not just
_id— but targeting by_idis the most precise and fastest approach
$set — Setting Field Values
$set is the most common update operator. It sets the value of one or more fields. If the field does not exist in the document, $set adds it. If it already exists, $set overwrites it. Other fields in the document are left untouched.
# $set — set one or more fields, add new fields, update nested fields
from pymongo import MongoClient
from datetime import datetime, timezone
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Set multiple fields at once
db.users.update_one(
{"_id": "u002"},
{"$set": {
"city": "Leeds",
"membership": "premium",
"updated_at": datetime.now(timezone.utc)
}}
)
bob = db.users.find_one({"_id": "u002"}, {"name": 1, "city": 1, "membership": 1, "_id": 0})
print("Bob updated:", bob)
# Add a brand new field — $set creates it if missing
db.products.update_one(
{"_id": "p001"},
{"$set": {"on_sale": True, "sale_price": 24.99}}
)
mouse = db.products.find_one({"_id": "p001"}, {"name": 1, "on_sale": 1, "sale_price": 1, "_id": 0})
print("Mouse updated:", mouse)
# Update a nested field using dot notation
# If orders had a shipping.address sub-document:
db.orders.update_one(
{"_id": "o001"},
{"$set": {"status": "archived"}}
)
order = db.orders.find_one({"_id": "o001"}, {"status": 1, "_id": 0})
print("Order o001 status:", order)Mouse updated: {'name': 'Wireless Mouse', 'on_sale': True, 'sale_price': 24.99}
Order o001 status: {'status': 'archived'}
$setwith multiple fields updates all of them in a single atomic write- Use dot notation to update a nested field:
{"$set": {"address.city": "Bristol"}} - New fields added with
$setappear only on the updated document — other documents in the collection are unchanged
$unset — Removing Fields
$unset removes a field from a document entirely. The value you assign does not matter — pass an empty string by convention. This is the equivalent of removing a column from one row in SQL — except in MongoDB it is perfectly valid and common.
# $unset — remove a field from a document
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Remove the on_sale and sale_price fields added in the previous example
db.products.update_one(
{"_id": "p001"},
{"$unset": {"on_sale": "", "sale_price": ""}}
)
mouse = db.products.find_one({"_id": "p001"})
print("on_sale field present:", "on_sale" in mouse)
print("sale_price field present:", "sale_price" in mouse)
# Remove the updated_at field from Bob's document
db.users.update_one(
{"_id": "u002"},
{"$unset": {"updated_at": ""}}
)
bob = db.users.find_one({"_id": "u002"})
print("updated_at field present:", "updated_at" in bob)sale_price field present: False
updated_at field present: False
- The value assigned in
$unsetis irrelevant —""is the conventional placeholder - Using
$unseton a field that does not exist has no effect and does not raise an error - To remove a field from every document in a collection, combine
$unsetwithupdate_many()
$inc — Incrementing and Decrementing Numbers
$inc adds a value to a numeric field atomically. Use a positive number to increment, a negative number to decrement. Because it is atomic, concurrent updates never lose a count — essential for stock levels, view counters, and scores.
# $inc — atomically increment or decrement a numeric field
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Check current stock for Wireless Mouse
before = db.products.find_one({"_id": "p001"}, {"name": 1, "stock": 1, "_id": 0})
print("Before:", before)
# A customer just bought 2 — decrement stock by 2
db.products.update_one(
{"_id": "p001"},
{"$inc": {"stock": -2}}
)
# A new shipment arrived — increment stock by 50
db.products.update_one(
{"_id": "p001"},
{"$inc": {"stock": 50}}
)
after = db.products.find_one({"_id": "p001"}, {"name": 1, "stock": 1, "_id": 0})
print("After: ", after)
# Increment a non-existent field — $inc creates it starting from 0
db.products.update_one(
{"_id": "p001"},
{"$inc": {"view_count": 1}}
)
views = db.products.find_one({"_id": "p001"}, {"view_count": 1, "_id": 0})
print("View count:", views)After: {'name': 'Wireless Mouse', 'stock': 198}
View count: {'view_count': 1}
$incis atomic — two concurrent decrements of 1 always result in a total reduction of 2, never 1- If the field does not exist,
$inccreates it and initialises it to the given value - Never use
$setfor counters — a read-then-write sequence is not atomic and causes race conditions under concurrent load
$push and $pull — Modifying Arrays
$push appends a value to an array field. $pull removes all matching values from an array. Both operate on the array atomically without fetching the document into your application first.
# $push and $pull — add and remove array elements
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Check Alice's current tags
alice = db.users.find_one({"_id": "u001"}, {"name": 1, "tags": 1, "_id": 0})
print("Alice tags before:", alice["tags"])
# $push — add a tag
db.users.update_one(
{"_id": "u001"},
{"$push": {"tags": "vip"}}
)
alice = db.users.find_one({"_id": "u001"}, {"tags": 1, "_id": 0})
print("After $push 'vip':", alice["tags"])
# $pull — remove a specific tag
db.users.update_one(
{"_id": "u001"},
{"$pull": {"tags": "newsletter"}}
)
alice = db.users.find_one({"_id": "u001"}, {"tags": 1, "_id": 0})
print("After $pull 'newsletter':", alice["tags"])
# $addToSet — like $push but only adds if the value is not already present
db.users.update_one(
{"_id": "u001"},
{"$addToSet": {"tags": "vip"}} # 'vip' already exists — no duplicate added
)
alice = db.users.find_one({"_id": "u001"}, {"tags": 1, "_id": 0})
print("After $addToSet 'vip' (already exists):", alice["tags"])After $push 'vip': ['early_adopter', 'newsletter', 'vip']
After $pull 'newsletter': ['early_adopter', 'vip']
After $addToSet 'vip' (already exists): ['early_adopter', 'vip']
$pushalways appends — it can create duplicates. Use$addToSetwhen the array should act like a set with unique values$pullremoves all occurrences of the matching value — if the value appears twice, both are removed- Use
$pushwith$eachto append multiple values at once:{"$push": {"tags": {"$each": ["a", "b"]}}}
update_many() — Modifying Multiple Documents
update_many() applies the update to every document that matches the filter. It is the right tool for bulk changes — applying a discount to all Electronics products, marking all unread notifications as read, or adding a new field to every document in a collection.
# update_many() — apply an update to all matching documents
from pymongo import MongoClient
from datetime import datetime, timezone
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Apply a 10% discount to all Electronics products
result = db.products.update_many(
{"category": "Electronics"},
{"$mul": {"price": 0.90}} # $mul multiplies the field value
)
print(f"Electronics discounted — matched: {result.matched_count}, modified: {result.modified_count}")
# Verify
electronics = db.products.find(
{"category": "Electronics"},
{"name": 1, "price": 1, "_id": 0}
)
for p in electronics:
print(f" {p['name']:25} ${p['price']:.2f}")
# Add a last_checked field to every product
result = db.products.update_many(
{}, # empty filter — matches all documents
{"$set": {"last_checked": datetime.now(timezone.utc)}}
)
print(f"\nAll products updated — modified: {result.modified_count}")Wireless Mouse $26.99
Mechanical Keyboard $80.99
USB-C Hub $44.99
Monitor 27-inch $269.99
All products updated — modified: 7
- An empty filter
{}matches every document — use with caution on large collections $mulmultiplies a field value — perfect for bulk price adjustments and percentage changesupdate_many()applies changes one document at a time internally — it is not wrapped in a transaction by default, so some documents may be updated before others if the operation is interrupted
upsert — Update or Insert
An upsert combines update and insert — if a matching document is found it is updated, if no document matches a new one is created. Pass upsert=True as an option. This eliminates the common check-then-insert pattern that creates race conditions.
# upsert — update if exists, insert if not
from pymongo import MongoClient
from datetime import datetime, timezone
client = MongoClient("mongodb://localhost:27017/")
db = client["dataplexa"]
# Upsert a user preference document — create if missing, update if present
def record_last_login(user_id):
result = db.user_sessions.update_one(
{"user_id": user_id},
{"$set": {"last_login": datetime.now(timezone.utc)},
"$setOnInsert": {"user_id": user_id, "login_count": 0},
"$inc": {"login_count": 1}},
upsert=True
)
if result.upserted_id:
print(f"New session document created for {user_id}")
else:
print(f"Existing session updated for {user_id}")
record_last_login("u001") # creates a new document
record_last_login("u001") # updates the existing document
session = db.user_sessions.find_one({"user_id": "u001"})
print("Login count:", session["login_count"])Existing session updated for u001
Login count: 2
result.upserted_idis set when a new document was created —Nonewhen an existing one was updated$setOnInsertonly runs when the operation creates a new document — fields set with it are ignored on updates- Upserts are atomic — no race condition between "check if exists" and "insert if not" is possible
Summary Table
| Operator / Method | What It Does | Example |
|---|---|---|
$set |
Set or add one or more fields | {"$set": {"city": "Rome"}} |
$unset |
Remove a field from a document | {"$unset": {"field": ""}} |
$inc |
Atomically increment or decrement | {"$inc": {"stock": -2}} |
$mul |
Multiply a field by a factor | {"$mul": {"price": 0.9}} |
$push |
Append a value to an array | {"$push": {"tags": "vip"}} |
$pull |
Remove matching values from array | {"$pull": {"tags": "old"}} |
$addToSet |
Push only if value not already present | {"$addToSet": {"tags": "vip"}} |
upsert=True |
Update if exists, insert if not | update_one(filter, update, upsert=True) |
Practice Questions
Practice 1. What is the difference between result.matched_count and result.modified_count after an update?
Practice 2. Why should you use $inc instead of $set when incrementing a counter field?
Practice 3. What is the difference between $push and $addToSet?
Practice 4. Write the update operator to remove the sale_price field from a product document.
Practice 5. What does $setOnInsert do and when does it run?
Quiz
Quiz 1. What happens if you call update_one() with a raw document instead of an update operator like $set?
Quiz 2. Which update operator multiplies a numeric field by a factor?
Quiz 3. What does result.upserted_id contain when an upsert updates an existing document?
Quiz 4. Which operator removes all occurrences of a value from an array field?
Quiz 5. What does passing an empty filter {} to update_many() do?
Next up — Delete Documents: Removing single and multiple documents safely with deleteOne() and deleteMany().