IsaacLevon
IsaacLevon

Reputation: 2580

How to update a total field after updating an element in an array?

I have a list of objects where each object has a count value. Is it possible to update a total field without $suming all the values after one of the values has changed?

For example, given the following data

{
    "elements": [
        {"id": "A", "count": 1},
        {"id": "B", "count": 2},
        {"id": "C", "count": 3},
    ],
    
    "total": 6
}

I want to update object A's count to 2 and total to 7

Upvotes: 1

Views: 437

Answers (2)

turivishal
turivishal

Reputation: 36144

There is no straight way to handle this situation, you can use update with aggregation pipeline starting from MongoDB 4.2,

  • $map to iterate loop of elements and update count when condition id match
  • update total by $sum of count
db.collection.update({
  "elements.id": "B"
},
[
  {
    $set: {
      elements: {
        $map: {
          input: "$elements",
          in: {
            id: "$$this.id",
            count: {
              $cond: [
                { $eq: ["$$this.id", "A"] },
                2,
                "$$this.count"
              ]
            }
          }
        }
      }
    }
  },
  { $set: { total: { $sum: "$elements.count" } } }
])

Playground

Upvotes: 1

amda
amda

Reputation: 354

First of all, I would stronget suggest you to use Mongoose in your project, as it will provide pretty powerfull tools to access the information contained in your MongoDB. For example, the easiest to get a dynamically updated total would be turning this field into a virtual one:

YourSchema.virtual("total").get(function () {
    this.elements.map(element => element.count).reduce((accumulator, currentValue) => accumulator + currentValue);
});

If you need the field not to be virtual, I would suggest using Mongoose's middleware YourSchema.pre("save") to update the total field each time an entry from this collection is modified.

YourSchema.pre("save", function (next) {
    this.total = this.elements.map(element => element.count).reduce((accumulator, currentValue) => accumulator + currentValue);
    next();
});

Upvotes: 1

Related Questions