$inc vs. $setOnInsert in MongoCollection.findOneAndUpdate() - mongodb

I'm trying to implement a 'server-side counter-versioned' item in mongodb and trying to do following /* using Java API */
Document dbDoc = dbCollection.findOneAndUpdate(
new Document("_id", "meta"),
new Document("$inc", new Document("version", 1))
.append("$setOnInsert", new Document("version", 0)),
new FindOneAndUpdateOptions().upsert(true)
.returnDocument(ReturnDocument.AFTER));
Assumed logic is simple: if there is no record in database - start counting from zero (and with a whole fresh new object), otherwise - increment counter.
Code sample fails with: 'Cannot update 'version' and 'version' at the same time'
My assumption is that in 'upsert' mode mongo should only use "$setOnInsert" when no matching item is found - but it works in some other way.
Is it possible to implement such operation in one atomic mongoDB call?
PS: MongoDB documentation regarding findOneAndUpdate() and upsert() is fuzzy - at least I cannot get why this error arizes from their description.
Also there is similar question here - findAndModify fails with error: "Cannot update 'field1' and 'field1' at the same time - accepted, but again with no clear reasoning.

You can just remove the update operator of $setOnInsert as this will be set to the value specified in the $inc operator if the document does not exist
https://docs.mongodb.com/v3.2/reference/operator/update/inc/#behavior
If the field does not exist, $inc creates the field and sets the field to the specified value.
Example from the mongo shell:
> db.dropDatabase()
{ "ok" : 1 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 1 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 2 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 3 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 4 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 5 }
If you need to set a given version on the first initial insert, then mongodb does not support any operators to support this atomically, however the follow would be safe and is a common workaround:
> db.dropDatabase()
{ "dropped" : "test", "ok" : 1 }
> function updateMeta(){
... function update(){
... return db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {returnNewDocument: true});
... }
...
... var result = update();
...
... if(result === null){
... db.test.insert({_id: "meta", version: -10});
... result = update();
... }
...
... return result;
... }
>
> updateMeta()
{ "_id" : "meta", "version" : -9 }
> updateMeta()
{ "_id" : "meta", "version" : -8 }
> updateMeta()
{ "_id" : "meta", "version" : -7 }
> updateMeta()
{ "_id" : "meta", "version" : -6 }
> updateMeta()
{ "_id" : "meta", "version" : -5 }
>

Related

Query to update showing 0 records updated mongo

Am using below query in db.getcolletion
db.getCollection('publickdata').find({"mediatorProf" : "ctvdl Without audieo",
"ProfileName":"p2_max_dls",ProfileDetails:
{ $elemMatch: { Name: "psMaxdetail", Value: "Mozilla 13" } }})
I need to update Value: Mozilla 13 to Value: Mozilla 12
Am using below query:
db.collection.update( // or updateMany directly, removing the flag for 'multi'
{"elemMatch.Value":"Mozilla 13"},
{$set:{"elemMatch.$[].Value":"Mozilla 12"}}, // notice the empty brackets after '$' opearor
false,
true
)
output showing: Updated 0 records provide suggestions.
document is:
/* 1 */
{
"_id" : ObjectId("5e31a24faa0f99e"),
"lastUpdateDate" : ISODate("2020-02-29T16:18:39.664Z"),
"mediatorProf" : "ctvdl Without audieo",
"ProfileName" : "p2_max_dls",
"ProfileDetails" : [
{
"Name" : "psMaxdetail",
"Value" : "Mozilla 13"
},
{
"Name" : "iptvSupported",
"Value" : "N"
}
]
}
Use $[identifier] along with arrayFilters.
db.publickdata.updateMany(
{
"mediatorProf": "ctvdl Without audieo",
"ProfileName": "p2_max_dls"
},
{
$set:{
"ProfileDetails.$[elem].Value": "Mozilla 12"
},
$unset:{
"ProfileDetails.$[elem].AttributeValue": 1
}
},
{
multi: true,
arrayFilters: [{"elem.Name": "psMaxdetail", "elem.Value": "Mozilla 13"}]
}
);

Empty result try find element in array Mongodb v4 (broke compatibility)

why second command return empty result? Its work in mongodb 3 version after update to 4 version broking...
// Command #1:
db.c.find({
AlbumId: HexData(3, "ca79977db39b1640a45e13dc5a06cb48"),
})
// Result:
{ "_id" : ObjectId("5e30aaadfc253f6d211aefb1"), "AlbumId" : BinData(3,"ynmXfbObFkCkXhPcWgbLSA=="), "Items" : [ ] }
// Command #2:
db.c.find({
AlbumId: HexData(3, "ca79977db39b1640a45e13dc5a06cb48"),
Items: { "$elemMatch" : { "field" : { "$ne" : HexData(3, "ad326a5404856c4e9fb4aafa6f0430e6") }}}
})

Where does MongoDB store function added via console?

I created auto-incrementing sequence field via counters collection and getNextSequence() function (absolutely like in docs)
According another document JavaScript functions are stored in a special system collection named system.js
But there is no such collection at my database (a least db.system.js.find() shows empty result):
> db.dropDatabase();
{ "dropped" : "mongopa", "ok" : 1 }
> version()
3.2.5
> db.counters.insert({_id: "userid", seq: 0 })
WriteResult({ "nInserted" : 1 })
> db.counters.find()
{ "_id" : "userid", "seq" : 0 }
> function getNextSequence(name) {
... var ret = db.counters.findAndModify(
... {
... query: { _id: name },
... update: { $inc: { seq: 1 } },
... new: true
... }
... );
...
... return ret.seq;
... }
> db.system.js.find()
> show collections
counters
> db.users.insert({"login":"demo","user_id":getNextSequence("userid"),"password":"demo"})
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("574ff1c7436a1b4f9c6f47b9"), "login" : "demo", "user_id" : 1, "password" : "demo" }
> db.users.insert({"login":"demo2","user_id":getNextSequence("userid"),"password":"demo2"})
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("574ff1c7436a1b4f9c6f47b9"), "login" : "demo", "user_id" : 1, "password" : "demo" }
{ "_id" : ObjectId("574ff1d6436a1b4f9c6f47ba"), "login" : "demo2", "user_id" : 2, "password" : "demo2" }
>
So where does the getNextSequence function really stored?
When you define the function as,
function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
It is merely defined for that particular session and is not available to you once the session ends. Hence, its not saved anywhere.
To make the function re-usable across the sessions, you need to explicitly save the function is system.js by using,
db.system.js.save(
{
_id: "getNextSequence",
value: function(name){var ret = db.counters.findAndModify({
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
});
return ret.seq;}
})
Once you have saved the function, you can cross-check it by,
db.system.js.find()
You need to call this
db.loadServerScripts();
across the sessions. It loads all the scripts saved in system.js collection.
For details, please check here.

mongodb update multi not working

I'm tying to update a field named company, here is my query
user_collection.update({"campaing" => "mediacom"}, {"campaing" => "flatiron"}, {"multi" => true})
but the problem is the update affects only one document not all documents from collection and "multi" flag is set to true
Building on what paul referenced in the docs, you want to use a $set to change a field value:
user_collection.update({ "campaing" : "mediacom" }, { "$set" : { "campaing" : "flatiron" } }, { "multi" : true })
Updating with a document means "replace the matched document with this document":
> db.test.drop()
> db.test.insert({ "_id" : 0, "a" : 1, "b" : 0 })
> db.test.update({ "_id" : 0 }, { "b" : 1 })
> db.test.findOne()
{ "_id" : 0, "b" : 1 }
From the mongoDB documentation:
"If the document contains only field:value expressions, then update() cannot update multiple documents."
http://docs.mongodb.org/manual/reference/method/db.collection.update/
You can try updateMulti function

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"
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.
db.collection.update({
_id: ObjectId("<id>"))
},
{timestamp: <newTimestamp>, data: <data>},
{upsert: true})
This operation would update an existing document if it meets the condition that it exists and that the existing timestamp is less than the newTimestamp; otherwise will insert a new document.