Mango DBLesson 16 – Update Documents | Dataplexa

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)
Matched: 1
Modified: 1
Alice now: {'name': 'Alice Johnson', 'city': 'Edinburgh'}
  • result.matched_count — number of documents that matched the filter
  • result.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 _id is 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)
Bob updated: {'name': 'Bob Smith', 'city': 'Leeds', 'membership': 'premium'}
Mouse updated: {'name': 'Wireless Mouse', 'on_sale': True, 'sale_price': 24.99}
Order o001 status: {'status': 'archived'}
  • $set with 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 $set appear 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)
on_sale field present: False
sale_price field present: False
updated_at field present: False
  • The value assigned in $unset is irrelevant — "" is the conventional placeholder
  • Using $unset on 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 $unset with update_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)
Before: {'name': 'Wireless Mouse', 'stock': 150}
After: {'name': 'Wireless Mouse', 'stock': 198}
View count: {'view_count': 1}
  • $inc is atomic — two concurrent decrements of 1 always result in a total reduction of 2, never 1
  • If the field does not exist, $inc creates it and initialises it to the given value
  • Never use $set for 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"])
Alice tags before: ['early_adopter', 'newsletter']
After $push 'vip': ['early_adopter', 'newsletter', 'vip']
After $pull 'newsletter': ['early_adopter', 'vip']
After $addToSet 'vip' (already exists): ['early_adopter', 'vip']
  • $push always appends — it can create duplicates. Use $addToSet when the array should act like a set with unique values
  • $pull removes all occurrences of the matching value — if the value appears twice, both are removed
  • Use $push with $each to 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}")
Electronics discounted — matched: 4, modified: 4
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
  • $mul multiplies a field value — perfect for bulk price adjustments and percentage changes
  • update_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"])
New session document created for u001
Existing session updated for u001
Login count: 2
  • result.upserted_id is set when a new document was created — None when an existing one was updated
  • $setOnInsert only 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().