Increase value of a field inside an array of element - mongodb

I have documents that has the following structure in my collection:
{
"_id": "1234566780",
"arrayField": [
{
"id": "1",
"count": 3
},
{
"id": "2",
"count": 5
}
]
}
I want to have a method in my CollectionRepository that increments the value of the field count by giving to the method the collection id and the field id of the elements in the arrayField.
I have the feeling that it is not possible to do it with Spring Data autogenerated queries by method names.
So I'm trying to do it with the #Query annotation, but I have no idea on how to do it, since I need to select the collection based on _id, then select the field inside the array based on id, and then increase the count by one. Any hints?

Sorry for the delay, here is a solution to your problem.
You can update a specific element of an array using arrayFilter update option. The MongoDB update operation would look like:
db.collection.update(
{"_id":"1234566780"},
{$inc:{"arrayField.$[elem].count":1}},
{arrayFilters:[{"elem.id":"2"}]}
Admiting you are using MongoDB server and MongoDB java Driver version 3.6 or greater you could use:
public void updateCollection () {
MongoCollection<Document> collection = mongoTemplate.getDb().getCollection("collection");
Bson query = new Document("_id", "1234566780");
Bson update = Updates.inc("arrayField.$[elem].count",1);
UpdateOptions updateOptions = new UpdateOptions().arrayFilters(Arrays.asList( Filters.eq("elem.id","2")));
collection.updateOne(query, update, updateOptions);
}
Indeed Spring data method signature is not likely to generate this update. What I suggest you is to extend a CustomCollectionRepository interface with your CollectionRepository. You could define this update method signature in this interface and implement it in a CustomCollectionRepositoryImpl class. Please find the part of the documentation that mention this solution.

Related

Mongodb use foreach on query and update results its results

I have a mongo collection where a field is supposed to point to another document's id in the same collection, but instead it is pointing to its "number". I need to perform an update on them but I'm having some problems on forming the query. Could you help me?
The structure of the document is like this:
{
"_id": "269410e2-cebf-40f1-a81f-fdce34185cdc",
"number": 1471,
"alternativeLocationId": "9871",
"locationType": "DUMMY"
},
{
"_id": "2945b24a-b82f-45a9-ad06-a884379b5597",
"number": 9871,
"locationType": "MAIN"
}
So as asked, I'd need to make document with number 1471 "alternativeLocationId" to be "2945b24a-b82f-45a9-ad06-a884379b5597" instead of 9871 (Note that the referenced documents are not locationType "DUMMY" nor have this alternativeLocationId field).
The query I've done so far goes like this, but when executed its not doing any changes:
db.location.find({alternativeLocationId: {$exists:true}}).forEach(
function (loc) {
var correctLocation = db.location.findOne({number: loc.alternativeLocationId});
db.location.update(
{_id: loc._id},
{$set: {alternativeLocationId: correctLocation._id} }
);
}
);
As mentioned by user20042973 the issue was the type mismatch between alternativeLocationId and locationNumber, after converting the value to int when looking for it, it works perfectly.

MongoDB: Updating element in array always results in first element updated

TL;DR : Updating element in array always results in first element updated and results differ based on property name of key used in find params
Playground : https://mongoplayground.net/p/-4kGZnxa-WA
I want to update an object in a array and I am using 2 of the object properties to find the object then using $set operator with array.$.updateProperty to update the object
Here is the working playground link of what I want to do:
https://mongoplayground.net/p/dswt8vuzJMc
But I cant reproduce the same when I change a single property name (both in database as well as find parameter) , from the above example I changed property foo to trackID but then only the first element in array is always updated
Playground : https://mongoplayground.net/p/-4kGZnxa-WA
It seems weird as I assumed the property name shouldn't matter as long as it used the same in find params too and its not a keyword like _id
Your update is very close. You need to use "$elemMatch" to identify the specific array position where both conditions match.
N.B.: $ will only update the first matching array element. If you want to update all array elements, use $[], and if you want to update all matching array elements, using "arrayFilters" with $[<indentifier>] is convenient.
db.collection.update({
"_id": ObjectId("62f11e22d99c79532de6ff7f"),
"jobs": {
"$elemMatch": {
"trackID": 0,
"name": "kaisen_track-0_h264_1080p"
}
}
},
{
"$set": {
"jobs.$.status": "Done"
}
})
Try it on mongoplayground.net.

how can I make the "updated" of mongodb stop when updating a field of a nested array?

I have a database like this:
{
"universe":"comics",
"saga":[
{
"name":"x-men",
"characters":[
{
"character":"wolverine",
"picture":"618035022351.png"
},
{
"character":"wolverine",
"picture":"618035022352.png"
}
]
}
]
},
{
"universe":"dc",
"saga":[
{
"name":"spiderman",
"characters":[
{
"character":"venom",
"picture":"618035022353.png"
}
]
}
]
}
And with this code, I update the field where name: wolverine:
db.getCollection('collection').findOneAndUpdate(
{
"universe": "comics"
},
{
$set: {
"saga.$[outer].characters.$[inner].character": "lobezno",
"saga.$[outer].characters.$[inner].picture": "618035022354.png"
}
},
/*{
"saga.characters": 1
},*/
{
"arrayFilters": [
{
"outer.name": "x-men"
},
{
"inner.character": "wolverine"
}
],
"multi":false
}
)
I want to just update the first object where there is a match, and stop it.
For example, if I have an array of 100,000 elements and the object where the match is, is in the tenth position, he will update that record, but he will continue going through the entire array and this seems ineffective to me even though he already did the update.
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
How can I do it?
Update using arrayFilters conditions
I don't think it will find and update through loop, and It does not matter if collection have 100,000 sub documents, because here is nice explanation in $[<identifier>] and has mentioned:
The $[<identifier>] to define an identifier to update only those array elements that match the corresponding filter document in the arrayFilters
In the update document, use the $[<identifier>] filtered positional operator to define an identifier, which you then reference in the array filter documents. But make sure you cannot have an array filter document for an identifier if the identifier is not included in the update document.
Update using _id
Your point,
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
MongoDB will certainly use the _id index. Here is the nice answer on question MongoDB Update Query Performance, from this you will get an better idea on above point
Update using indexed fields
You can create index according to your query section of update command, Here MongoDB Indexes and Indexing Strategies has explained why index is important,
In your example, lets see with examples:
Example 1: If document have 2 sub documents and when you update and check with explain("executionStats"), assume it will take 1 second to update,
quick use Mongo Playground (this platform will not support update query)
Example 2: If document have 1000 sub documents and when you update and check with explain("executionStats"), might be it will take more then 1 second,
If provide index on fields (universe, saga.characters.character and saga.characters.picture) then definitely it will take less time then usual without index, main benefit of index it will direct point to indexed fields.
quick use Mongo Playground (this platform will not support update query)
Create Index for your fields
db.maxData.createIndex({
"universe": 1,
"saga.characters.character": 1,
"saga.characters.picture": 1
})
For more experiment use above 2 examples data with index and without index and check executionStats you will get more clarity.

In MongoDB, method to keep the previous value of a field in a different field while updating an object?

Say I have an object with field state, I want to update this field, while keeping the previous value of state in previous_state field. First, I have tried to make an update with unset-rename-set:
collection.update(query, {$unset: {previous_state: ""}, $rename: {state: "previous_state"}, $set: {state: value}})
no surprise it did not work. After reading:
Update MongoDB field using value of another field
MongoDB update: Generate new field based on existing field, or update in place
Update field with another field's value in the document
I am nearly convinced that I do not have a solution to perform this in a single query. So the question is what is the best practice to do it?
There are various ways to do it, depending on the version of MongoDB, and they are described in this answer from another thread: https://stackoverflow.com/a/37280419/5538923 .
For MongoDB 3.4+, for example, there is this query that can be put in MongoSH:
db.collection.aggregate(
[
{ "$addFields": {
"previous_state": { "$concat": [ "$state" ] },
"state": { "$concat": [ "$state", " customly modified" ] }
}},
{ "$out": "collection" }
])
Also note that this query works only when the MongoDB instance is not sharded. When sharded (e.g., often the case in Microsoft Azure CosmosDB), the method described in that answer for MongoDB 3.2+ works, or alternatively put a new collection as destination (in the $out close), and then import the data in the original collection, after removing all the data there.
One solution (if you've got onlty one writer) could be to trigger your update in two steps:
> var previousvalue = collection.findOne(query).state;
> collection.update(query, {$set: {"previous_state": previousvalue, "state": newstatevalue}});

spring data mongo - mongotemplate count with query hint

The mongo docs specify that you can specify a query hint for count queries using the following syntax:
db.orders.find(
{ ord_dt: { $gt: new Date('01/01/2012') }, status: "D" }
).hint( { status: 1 } ).count()
Can you do this using the mongo template? I have a Query object and am calling the withHint method. I then call mongoTemplate.count(query); However, I'm pretty sure it's not using the hint, though I'm not positive.
Sure, there are a few forms of this including going down to the basic driver, but assuming using your defined classes you can do:
Date date = new DateTime(2012,1,1,0,0).toDate();
Query query = new Query();
query.addCriteria(Criteria.where("ord_dt").gte(date));
query.addCriteria(Criteria.where("status").is("D"));
query.withHint("status_1");
long count = mongoOperation.count(query, Class);
So you basically build up a Query object and use that object passed to your operation, which is .count() in this case.
The "hint" here is the name of the index as a "string" name of the index to use on the collection. Probably something like "status_1" by default, but whatever the actual name is given.