Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional upsert in MongoDB

I've answered this question on LinkedIn and I thought it's something useful and interesting to share. The question was:

"Suppose we have documents like {_id: ..., data: ..., timestamp: ...}.

Is there any way to write update criteria which will satisfy following rules:

1 If there is no documents with following _id then insert this document;

2 If there is exists document with following _id then

2.1 If new timestamp greater then stored timestamp then update data;

2.2 Otherwise do nothing"

like image 842
yǝsʞǝla Avatar asked Feb 08 '14 17:02

yǝsʞǝla


People also ask

Does MongoDB have Upsert?

Here in MongoDB, the upsert option is a Boolean value. Suppose the value is true and the documents match the specified query filter. In that case, the applied update operation will update the documents. If the value is true and no documents match the condition, this option inserts a new document into the collection.

How does the Upsert option work MongoDB?

Or in other words, upsert is a combination of update and insert (update + insert = upsert). If the value of this option is set to true and the document or documents found that match the specified query, then the update operation will update the matched document or documents.

How do I Upsert an array in MongoDB?

Hey You can do by using array filters option. You can refer here https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-arrayfilters. This would be very useful, but in my hands it only works if the identifiers are the same in the update object and the array filter, e.g.: db. collection.

Is there an Upsert option in the MongoDB insert command?

insert() provides no upsert possibility.


1 Answers

Solution below should do the trick, you just need to ignore dup key errors. Example is given in Mongo shell:

> var lastUpdateTime = ISODate("2013-09-10")
> var newUpdateTime = ISODate("2013-09-12")
>
> lastUpdateTime
ISODate("2013-09-10T00:00:00Z")
> newUpdateTime
ISODate("2013-09-12T00:00:00Z")
>
> var id = new ObjectId()
> id
ObjectId("52310502f3bf4823f81e7fc9")
>
> // collection is empty, first update will do insert:
> db.testcol.update(
... {"_id" : id, "ts" : { $lt : lastUpdateTime } },
... { $set: { ts: lastUpdateTime, data: 123 } },
... { upsert: true, multi: false }
... );
>
> db.testcol.find()
{ "_id" : ObjectId("52310502f3bf4823f81e7fc9"), "data" : 123, "ts" : ISODate("2013-09-10T00:00:00Z") }
>
> // try one more time to check that nothing happens (due to error):
> db.testcol.update(
... {"_id" : id, "ts" : { $lt : lastUpdateTime } },
... { $set: { ts: lastUpdateTime, data: 123 } },
... { upsert: true, multi: false }
... );
E11000 duplicate key error index: test.testcol.$_id_ dup key: { : ObjectId('52310502f3bf4823f81e7fc9') }
>
> var tooOldToUpdate = ISODate("2013-09-09")
>
> // update does not happen because query condition does not match
> // and mongo tries to insert with the same id (and fails with dup again):
> db.testcol.update(
... {"_id" : id, "ts" : { $lt : tooOldToUpdate } },
... { $set: { ts: tooOldToUpdate, data: 999 } },
... { upsert: true, multi: false }
... );
E11000 duplicate key error index: test.testcol.$_id_ dup key: { : ObjectId('52310502f3bf4823f81e7fc9') }
>
> // now query cond actually matches, so update rather than insert happens which works
> // as expected:
> db.testcol.update(
... {"_id" : id, "ts" : { $lt : newUpdateTime } },
... { $set: { ts: newUpdateTime, data: 999 } },
... { upsert: true, multi: false }
... );
>
> // check that everything worked:
> db.testcol.find()
{ "_id" : ObjectId("52310502f3bf4823f81e7fc9"), "data" : 999, "ts" : ISODate("2013-09-12T00:00:00Z") }
>

The only annoying part are those errors, but they are cheap and safe.

like image 53
yǝsʞǝla Avatar answered Sep 28 '22 00:09

yǝsʞǝla