0

i have the following simplified Scheme:

var restsSchema = new Schema({
    name: String
    menu: [mongoose.Schema.Types.Mixed]
});

My document can look like:

{
     name: "Sandwiches & More",
     menu: [
                {id:1,name:"Tona Sandwich",price: 10, soldCounter:0},
                {id:2,name:"Salami Sandwich",price: 10, soldCounter:0},
                {id:3,name:"Cheese Sandwich",price: 10, soldCounter:0}
     ]
}

The collection rests is indexed with:

db.rests.createIndex( { "menu.id": 1} , { unique: true })

Lets say i have this array of ids [1,3] and based on that i need to increment the soldCounter by 1 of menu items with ids=1 or 3.

What will be the must efficient way of doing so?

thanks for the helpers!

EDIT: I have used the following solution:

db.model('rests').update({ _id: restid,'menu.id': {$in: ids}}, {$inc: {'menu.$.soldCounter': 1}}, {multi: true},function(err) {
        if(err)
            console.log("Error while updating sold counters: " + err.message);
    });

where ids is an array of integers with ids of menu items. restid is the id of the specific document we want to edit in the collection.

For some reason only the first id in the ids array is being updated.

TBE
  • 1,002
  • 1
  • 11
  • 32
  • Have you already implemented something that you deem not efficient enough? – Antoine Jan 26 '16 at 08:52
  • yes, i updated the entire rest model which is pretty big in its unsimplified form. I found the rest using `find()` and in the callback i looked for the wanted soldCounters in a loop, incremented them and saved the model. Highly not efficient and can also cause some data integrity issues. – TBE Jan 26 '16 at 08:54

2 Answers2

2

There is a way of doing multiple updates, here it is: Just make sure you have the indexes in the array you want to update.

var update = { $inc: {} };
for (var i = 0; i < indexes.length; ++i) {
  update.$inc[`menu.${indexes[i]}.soldCounter`] = 1;
}
Rests.update({ _id: restid }, update, function(error) {
  // ...
});
TBE
  • 1,002
  • 1
  • 11
  • 32
  • Sure, but you need to have the indices beforehand. You have to be absolutely certain the array will not be modified, or you'll have data integrity issues. – Antoine Jan 26 '16 at 12:24
  • If you are a good programmer, then you always know the indices completely not caring if the array was changed. It all depends on whether you prepared your data correctly. – TBE Jan 27 '16 at 10:11
  • 1
    What if the data was modified by another request, handled by another server of your distributed system, between the moment you computed your indices and the one you apply the update? Doesn't matter how good a programmer you are. – Antoine Jan 27 '16 at 10:32
  • Its not possible in my test case, but you are absolutely right. One who wants to use this solution will need to make sure he won't get any data integrity issues. – TBE Jan 27 '16 at 12:51
1

it seems not possible to update multiple subdocuments at once (see this answer). So a find & save seems to be the only solution.

Rest.findById(restId).then(function(rest){
  var menus = rest.menu.filter(function(x){
    return menuIds.indexOf(x.id) != -1;
  });
  for (var menu of menus){
    menu.soldCounter++;
  }
  rest.save();
});

In the end it's only one find and one save requests.

Community
  • 1
  • 1
Antoine
  • 5,055
  • 11
  • 54
  • 82
  • That is a nice solution, but what if i only have the `restid` and not the entire model? is it still possible to use your solution with a small change to include the `restid`? – TBE Jan 26 '16 at 09:30
  • Never mind, i figure it out :) `db.model('rests').update({ _id: restid,'menu.id': {$in: ids}}, {$inc: {'menu.$.soldCounter': 1}}, {multi: true})` Thanks for the help! – TBE Jan 26 '16 at 09:38
  • for some reason, the only `soldCounter` that is being updated is the first `id` in the given array. Any idea why? I have updated my question to include my findings – TBE Jan 26 '16 at 09:48
  • Please see my answer – TBE Jan 26 '16 at 11:42