How do I conditionally upsert a document in mongo?

Short of being able to do the whole thing atomically, there are two kinds of existing conditions where you want to make a change, and you can deal with each of them atomically:

  • no record for the key exists
  • a record for the key exists and its update_time is older than new_time

Update an existing record for key:

def update_if_stale(key, new_value, new_time):
    collection.update({'key': key,
                       'update_time': {'$lt': new_time}
                       },
                      {'$set': {'value': new_value,
                                'update_time': new_time
                                }
                       }
                      )

Insert if a record for key didn't exist before:

def insert_if_missing(key, new_value, new_time):
    collection.update({'key': key},
                      {'$setOnInsert': {'value': new_value,
                                        'update_time': new_time
                                        }
                       },
                      upsert=True
                      )

($setOnInsert was added in MongoDB 2.4)

You might be able to put those together to get what you need, e.g.:

def update_key(key, new_value, new_time):
    insert_if_missing(key, new_value, new_time)        
    update_if_stale(key, new_value, new_time)

However, depending on what remove/insert time scales might be possible in your system, you might need multiple calls (update/insert/update) or other shenanigans.

Aside: If you want a record missing the update_time field to be treated as a stale record to update, change {'$lt': new_time}} to {'$not': {'$gte': new_time}}


If WPCoder's response with and upsert=True isn't what you're looking for, than you may need $setOnInsert, which isn't implemented yet: https://jira.mongodb.org/browse/SERVER-340. It should be implemented for 2.4.0.


If you add a unique constraint on key, then updateOne with an update_time filter and upsert=True will

  1. insert missing records
  2. update stale records
  3. throw an error when you try to update using stale input (because the update will not match the filter condition, and the insert will fail due to the constraint)
collection.updateOne({'key': key,
                      'update_time': {'$lt': new_time}
                      },
                     {'$set': {'value': new_value,
                               'update_time': new_time
                               }
                      },
                     upsert=True
                     )

You can catch errors and check for code: 11000 (unique constraint violations) to specifically handle those cases of having tried to update to a past state.