15
votes

I am trying to use upsert in MongoDB to update a single field in a document if found OR insert a whole new document with lots of fields. The problem is that it appears to me that MongoDB either replaces every field or inserts a subset of fields in its upsert operation, i.e. it can not insert more fields than it actually wants to update.

What I want to do is the following:

  • I query for a single unique value
  • If a document already exists, only a timestamp value (lets call it 'lastseen') is updated to a new value
  • If a document does not exists, I will add it with a long list of different key/value pairs that should remain static for the remainder of its lifespan.

Lets illustrate:

This example would from my understanding update the 'lastseen' date if 'name' is found, but if 'name' is not found it would only insert 'name' + 'lastseen'.

db.somecollection.update({name: "some name"},{ $set: {"lastseen": "2012-12-28"}}, {upsert:true})

If I added more fields (key/value pairs) to the second argument and drop the $set, then every field would be replaced on update, but would have the desired effect on insert. Is there anything like $insert or similar to perform operations only when inserting?

So it seems to me that I can only get one of the following:

  • The correct update behavior, but would insert a document with only a subset of the desired fields if document does not exist
  • The correct insert behavior, but would then overwrite all existing fields if document already exists

Are my understanding correct? If so, is this possible to solve with a single operation?

2
Is there a reason why these key-value pairs cannot be used in the find clause of the upsert?Sammaye
The reason is that there is only one field that I know the value of at that point. I do not want the query to return 0 hits if the exact combo of fields do not match. The only field I know the value of will decide whether its a new insert or a update.agnsaft

2 Answers

36
votes

MongoDB 2.4 has $setOnInsert

db.somecollection.update(
    {name: "some name"},
    {
        $set: {
            "lastseen": "2012-12-28"
        },
        $setOnInsert: {
            "firstseen": <TIMESTAMP>  # set on insert, not on update
        }
    },
    {upsert:true}
)
8
votes

There is a feature request for this ( https://jira.mongodb.org/browse/SERVER-340 ) which is resolved in 2.3. Odd releases are actually dev releases so this will be in the 2.4 stable.

So there is no real way in the current stable versions to do this yet. I am afraid the only method is to actually do 3 conditional queries atm: 1 to check the row, then a if to either insert or update.

I suppose if you had real problems with lock here you could do this function with sole JS but that's evil however it would lock this update to a single thread.