Find And Update in array of subdocuments - mongodb

I'm I newbie in mongodb world, and I've already spent a lot of time trying to resolve the following problem.
My collection is called 'routes' with the documents:
{
idGateway: 0,
priority: 1,
channels: [
{
id: 'chanel_1',
control: {
timestamp: ISODate("2015-09-19T16:17:12.393Z"),
qty: 3
}
},
{
id: 'chanel_2',
control: {
timestamp: ISODate("2015-09-18T16:17:12.393Z"),
qty: 5
}
}
]
},
{
idGateway: 1,
priority: 2,
channels: [
{
id: 'chanel_3',
control: {
timestamp: ISODate("2015-09-19T16:17:12.393Z"),
qty: 3
}
},
{
id: 'chanel_4',
control: {
timestamp: ISODate("2015-09-18T16:17:12.393Z"),
qty: 5
}
}
]
}
My needs are select a document and at the same time update its timestamp/qty fields (as a FindAndModify). The criteria to selection is:
Get the document with the lowest priority
From the document, select the channels' document with the MAX(timestamp)
Update timestamp and qty
And return only the channels' document (channel_1).
So, once I've selected the channel_1 I have to avoid that other process select it again befor the update. How can I do this ?
Thanks

Related

Mongodb aggregation and conditional push to array

It's been 2 days (or nights should I say) since I am trying to figure out following so would appreciate your help guys.
in mongodb I have number of orders (I will simplify documents for the case).
I want to group all documents by $campRoundId and where there are installments, push all object to installments variable.
The problem I am facing is when document.installments array is empty it pushes it to the array too.
Initial documents (same campRoundId to group by - first with no instalmments, second with 2)
[
{
_id: ObjectId("62792d8a519af6ae8cdff779"),
campRoundId: ObjectId("620a790b2cbc52006c83115a"),
installments: [],
},
{
_id: ObjectId("62792d8a519af6ae8cdff77a"),
campRoundId: ObjectId("620a790b2cbc52006c83115a"),
installments: [
{
payment: 100,
paymentStatus: false,
},
{
payment: 20,
paymentStatus: false,
},
],
},
];
my aggregation
/**
* _id: The id of the group.
* fieldN: The first field name.
*/
{
_id : "$campRoundId",
installments: {
$push: {
$cond:[
{ $gte: ["$installments.length", 1] },
"$installments",
null
]
}
}
}
I want to get rid of empty object, so if there are no installments nothing will be pushed. (dotted lines)

Updating multiple items in nested array when condition matched

I started learning MongoDB a few days ago and trying to wrap my head around update operator.
Here's the document structure I have:
db.records.insertMany([
{
name: 'first',
subrecords: [
{ id: 1, isToBeUpdated: 0, value: 1 },
{ id: 2, isToBeUpdated: 0, value: 2 },
{ id: 3, isToBeUpdated: 0, value: 3 },
{ id: 4, isToBeUpdated: 1, value: 4 }
]
},
{
name: 'second',
subrecords: [
{ id: 22, isToBeUpdated: 1, value: 5 },
{ id: 23, isToBeUpdated: 0, value: 6 },
{ id: 24, isToBeUpdated: 1, value: 7 },
{ id: 25, isToBeUpdated: 0, value: 8 }
]
}
])
I'm writing a command that is supposed to update subrecords.
My intent is to use isToBeUpdated flag — if it is set to 1 we update the value field, e.g. multiplying it by 10. We ignore items with isToBeUpdated being 0.
Here's the command I've composed:
db.records.update(
{ 'subrecords.isToBeUpdated': 1 },
{ '$mul': { 'subrecords.$.value': 10 } },
{ multi: true }
)
I expect to get the following items of subrecords array updated like this:
1. { id: 4, isToBeUpdated: 1, value: 40 }
2. { id: 22, isToBeUpdated: 1, value: 50 }
3. { id: 24, isToBeUpdated: 1, value: 70 }
BUT it just updates 2 records, instead of 3. The following record is not updated:
3. { id: 24, isToBeUpdated: 1, value: 70 }
It is the second item with isToBeUpdated being 1 located in the second document's subrecords collection — seems like the query only updates the first matching item of subrecords array in any document. And ignores other records that are subsequent to first.
I tried tweaking my command adding [] to $ (positional operator):
... { '$mul': { 'subrecords.$[].value': 10 } }, ...
But in this case literally every single item in subrecords array of any document gets affected.
Anyone knows how to write a command which loops through all records and updates each item of the nested subrecords array with isToBeUpdated field being set to 1?
Also, am I right in thinking
that { multi: true } implies that ALL record documents in records collection will be affected?
that { 'subrecords.isToBeUpdated': 1 } (query part) literally means that WE ONLY UPDATE subrecords ARRAY WHEN A DOCUMENT HAS AT LEAST ONE ITEM IN subrecords ARRAY WITH isToBeUpdated FIELD BEING SET TO 1?
The workaround turns out to be arrayFilters
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#update-all-documents-that-match-arrayfilters-in-an-array
The $[<identifier>] operator facilitates updates to arrays that contain embedded documents. To access the fields in the embedded documents, use the dot notation on the $[<identifier>].
db.records.update(
{},
{ '$mul': { 'subrecords.$[elem].value': 10 } },
{
multi: true,
arrayFilters: [{ 'elem.isToBeUpdated': 1 }]
}
)
Solved!

MongoDB: Update outer array and nested arrays in single update

Document structure in cities collection is like this
cities
{
_id: ObjectId("5e78ec62bb5b406776e92fac"),
city_name: "Mumbai",
...
...
subscriptions: [
{
_id: 1,
category: "Print Magazine",
subscribers: 183476
options: [
{
name: "Time",
subscribers: 56445
},
{
name: "The Gentlewoman",
subscribers: 9454
},
{
name: "Gourmand",
subscribers: 15564
}
...
...
]
},
{
_id: 2,
category: "RSS Feed",
subscribers: 2645873
options: [
{
name: "Finance",
subscribers: 168465
},
{
name: "Politics",
subscribers: 56945
},
{
name: "Entrepreneurship",
subscribers: 56945
},
...
...
]
}
]
}
Now when a user subscribes like below
{
cityId: 5e78ec62bb5b406776e92fac
selections: [
{
categoryId: 1,
options : ["Time", "Gourmand"]
},
{
categoryId: 2,
selected: ["Politics", "Entrepreneurship"]
}
]
}
I want to update the following in the cities document
Increment subscribers for "Print Magazine" by 1
Increment subscribers for "Time" by 1
Increment subscribers for "Gourmand" by 1
Increment subscribers for "RSS Feed" by 1
Increment subscribers for "Politics" by 1
Increment subscribers for "Entrepreneurship" by 1
So when an item is subscribed, its subscribers count is incremented by 1. And the category it falls into, its subscriber count is also incremented by 1.
I want to achieve this in a single update query. Any tips how can I do this?
Use case details
Each user's subscription details are stored in user_subscription_details collection(not listed here). subscriptions property in cities holds just the subscription summary for each city.
So I was able to it with the following query
db.cities.updateOne(
{
_id : ObjectId("5e78ec62bb5b406776e92fac")
},
{
$inc: {
"subscriptions.$[category].subscribers" : 1,
"subscriptions.$[category].options.$[option].subscribers" : 1
}
},
{ multi: true,
arrayFilters: [
{ "category._id": {$in: ["1", "2"]} },
{ "option.name": {$in: ["Time", "Gourmand", "Politics", "Entrepreneurship"]} }
]
}
)
Brief Explanation
First the document is matched with _id.
In update block we will declare the fields to be updated
"subscriptions.$[?].subscribers" : 1,
"subscriptions.$[?].options.$[?].subscribers" : 1
I have used ? here to show we don't know yet for which elements in the array we need to do these update. Which we can declare in the next block by filtering the array elements that need to be updated.
In filter block we filter array elements on some condition
{ "category._id": {$in: ["1", "2"]} }
{ "option.name": {$in: ["Time", "Gourmand", "Politics", "Entrepreneurship"]} }
First we filter the elements in the outer array by _id i.e only subscription categories whose _id is either 1 or 2.
Next, we filter the elements in the inner options array on the name field. Elements which will pass both filters will get updated.
Note: category in category._id and option in option.name can be any name. But the same name is to be used for fields path in update block.
For, Spring Boot MongoOperation translation of this query look at this answer

Update a mongo field based on another collections data

I'm looking for a way to update a field based on the sum of the data of another collection.
I tried to bring all the meals and use forEach to call the Products collection for each meal, tested if it was working, but I got a time out.
meals.find().forEach(meal => {
var products = db.Products.find(
{ sku: { $in: meal.products } },
{ _id: 1, name: 1, sku: 1, nutritional_facts: 1 }
)
printjson(products)
})
My goal was to execute something like this below to get the desired result, but I got "SyntaxError: invalid for/in left-hand side". Is not possible to use for in inside a mongo query?
db.Meals.find({}).forEach(meal => {
const nutri_facts = {};
db.Products.find({ sku: { $in: meal.products } },
{ _id: 1, name: 1, sku: 1, nutri_facts: 1 }).forEach(product => {
for (let nutriFact in product.nutri_facts) {
nutri_facts[nutriFact] =
parseFloat(nutri_facts[nutriFact]) +
parseFloat(product.nutri_facts[nutriFact]);
}
}
});
for (let nutriFact in nutri_facts) {
meal.nutri_facts[nutriFact] =
nutri_facts[nutriFact];
}
}
db.Meals.updateOne({ _id: meal._id }, meal)
});
I also had a hard time trying to figure out how to use aggregate and lookup in this case but was not successful.
Is it possible to do that?
Example - Meals Document
{
_id: ObjectId("..."),
products : ["P068","L021","L026"], //these SKUs are part of this meal
nutri_facts: {
total_fat: 5g,
calories: 100kcal
(...other properties)
}
}
For each meal I need to look for its products on 'Products' collections using 'sku' field.
Then I will sum the nutritional facts of all products to get the meal nutritional facts.
Example Products Document
{
_id: ObjectId("..."),
sku: 'A010'
nutri_facts: {
total_fat: 2g,
calories: 40kcal
(...other properties)
}
}
I know that mongo might not be the best option in this case, but the entire application is already built using it.
For each meal I need to look for its products on 'Products'
collections using 'sku' field. Then I will sum the nutritional facts
of all products to get the meal nutritional facts.
db.Meals.find( { } ).forEach( meal => {
// print(meal._id);
const nutri_facts_var = { };
db.Products.find( { sku: { $in: meal.products } }, { nutri_facts: 1 }.forEach( product => {
// printjson(product.nutri_facts);
for ( let nutriFact in product.nutri_facts ) {
let units = (product.nutri_facts[nutriFact].split(/\d+/)).pop();
// print(units)
// Checks for the existence of the field and then adds or assigns
if ( nutri_facts_var[nutriFact] ) {
nutri_facts_var[nutriFact] = parseFloat( nutri_facts_var[nutriFact] ) + parseFloat( product.nutri_facts[nutriFact] );
}
else {
nutri_facts_var[nutriFact] = parseFloat( product.nutri_facts[nutriFact] );
}
nutri_facts_var[nutriFact] = nutri_facts_var[nutriFact] + units;
}
} );
// printjson(nutri_facts_var);
db.Meals.updateOne( { _id: meal._id }, { $set: { nutri_facts: nutri_facts_var } } );
} );
NOTES:
I use the variable nutri_facts_var name ( the var suffixed) so
that we can distinguish the user defined variable names easily from
the document fields names.
{ _id: 1, name: 1, sku: 1, nutri_facts: 1 } changed to {
nutri_facts: 1 }. The _id is included by default in a
projection. The fields name and sku are not needed.
db.Meals.updateOne({ _id: meal._id }, meal) is not a correct
syntax. The update operations use Update
Operators.
The corrected code: db.Meals.updateOne( { _id: meal._id }, { $set:
{ nutri_facts: nutri_facts_v } } ). Note we are updating the
nutifacts only, not all details.
Since the individual nutrifacts are stored as strings (e.g.,
"100kcal"), during arithmetic the string parts are stripped. So, we
capture the string units (e.g., "kcal") for each nutrifact and
append it later after the arithmetic. The following code strips and
stores the units part: let units =
(product.nutri_facts[nutriFact].split(/\d+/)).pop().
Use the mongo shell methods print and printjson to print the
contents of a variable or an object respectively - for debugging
purposes.
Note the query updates the Meal collection even if the nutri_facts field is not defined in it; the $set update operator creates new fields and sets the values in case the fields do not exist.

MongoDB update outter and array-embedded document atomically

I have an object in the cart collection:
{
_id: 1,
purchased: 0,
items: [
{
item_id: 5,
count: 0
},
{
item_id: 6,
count: 0
},
]
}
And I would like to atomically increment both purchased and count of item_id: 6. I believe the correct update command is:
db.carts.update({_id: 1, "items.item_id": 6},
{
$inc: {
purchased: 1
"items.$.count": 1
}
})
This seems to work in the console but I am not sure if this is 100% correct. Can anyone spot any issues with this update command or confirm this is correct for my use case?
For scaling this to my app, suppose _id is an ObjectId and item_id is also ObjectId (ie they are unique).