Pawel Veselov
Pawel Veselov

Reputation: 4225

mongodb: upserting: only set value if document is being inserted

Considering a simple mongo document structure:

{ _id, firstTime, lastTime }

The client needs to insert a document with a known ID, or update an existing document. The 'lastTime' should always be set to some latest time. For the 'firstTime', if a document is being inserted, then the 'firstTime' should be set to current time. However, if the document is already created, then 'firstTime' remain unchanged. I would like to do it purely with upserts (to avoid look ups).

I've crawled the http://www.mongodb.org/display/DOCS/Updating, but I just don't see how that particular operation can be done.

I don't believe this is something unreasonable, there are $push and $addToSet operations that effectively do that on array fields, just nothing that would do the same on simple fields. It's like there should be something like $setIf operation.

Upvotes: 21

Views: 9233

Answers (4)

Andrei Petrik
Andrei Petrik

Reputation: 1181

If you will trigger the following code 2 subsequent times, it will first set both firstVisit and lastVisit on document insert (and will return upsertedId in the response) and on the second it will only update lastVisit (and will return modifiedCount: 1).

Tested with Mongo 4.0.5 though I believe should be working with older versions.

db.collection.updateOne(
  {_id: 1}, 
  { 
    $set: { 
      lastVisit: Date.now() 
    }, 
    $setOnInsert: {
      firstVisit: Date.now()
    }
  }, 
  { upsert: true }
);

Upvotes: 1

domguinard
domguinard

Reputation: 923

I ran into the exact same problem and there was no simple solution for <2.4 however since 2.4 the $setOnInsert operator let's you do exactly that.

db.collection.update( <query>,
                      { $setOnInsert: { "firstTime": <TIMESTAMP> } },
                      { upsert: true }
                    )

See the 2.4 release notes of setOnInsert for more info.

Upvotes: 34

jeffh
jeffh

Reputation: 11

I ran into a very similar problem when attempting to upsert documents based on existing content--maybe this solution will work for you also:

Try removing the _id attribute from your record and only use it in the query portion of your update (you'll have to translate from pymongo speak...)

myid = doc.get('_id')
del doc['_id']
mycollection.update({'_id':myid}, {'$set':doc}, upsert=True)

Upvotes: 1

stbrody
stbrody

Reputation: 1846

There's no way to do this with just one upsert. You'd have to do it as 2 operations - first try to insert the document, if it already exists the insert will fail due to duplicate key violation on the _id index. Then you do an update operation to set the lastTime to now.

Upvotes: -2

Related Questions