2

I'm trying to make a seemingly simple update to a MongoDB collection that looks like the below using Node.

Collection

{
  account_id: "ORG1",
  progress: [{week: 1, goal: 5000, raised: 2400}, {week: 2, goal:5100,  raised: 1000}]
} 

The goal is to be able to

  1. Find the correct org (this works for me)
  2. Add a value to the last array entry's "raised" value. (e.g. Initially the raised value is 1000, after my update, it will be 1000 + an incoming value).

My hacky approach would be to do a find query to get the initial value and then do an update to add my incoming value to the initial value. But I'm sure there's a simpler way.

Thanks in advance!

2
  • What is your definition to "the most recent array entry" ? Which entry is the most recent in this example, and why? Commented Jul 30, 2022 at 8:30
  • The most recent array entry is the last one in the array. I abbreviated the schema a bit, but the last one is always the newest in terms of date. Updated question for clarity. @nimrodserok Commented Jul 30, 2022 at 8:31

3 Answers 3

2

One option is using an update pipeline:

  1. Split the array into the last item and all the rest.
  2. update the last item
  3. Build the array again using $concatArrays
db.collection.update(
  {account_id: "ORG1"},
  [
    {$set: {
      lastItem: {$last: "$progress"},
      rest: {$slice: ["$progress", 0, {$subtract: [{$size: "$progress"}, 1]}]}
    }
    },
    {$set: {"lastItem.raised": {$add: ["$lastItem.raised", incomingValue]}}},
    {$set: {
      progress: {$concatArrays: ["$rest", ["$lastItem"]]},
      lastItem: "$$REMOVE",
      rest: "$$REMOVE"
    }
  }
])

See how it works on the playground example - concatArrays

Another option is using $reduce:

Here we are iterating on the array, for each item checking if it is the last one, and if so updating it:

db.collection.update(
  {account_id: "ORG1"},
  [
  {$set: {lastIndex: {$subtract: [{$size: "$progress"}, 1]}}},
  {$set: {
      lastIndex: "$$REMOVE",
      progress: {
        $reduce: {
          input: "$progress",
          initialValue: [],
          in: {
            $concatArrays: [
              "$$value",
              [
                {
                  $cond: [
                    {$eq: [{$size: "$$value"}, "$lastIndex"]},
                    {$mergeObjects: [
                       "$$this",
                        {raised: {$add: ["$$this.raised", incomingValue]}}
                      ]
                    },
                    "$$this"
                  ]
                }
              ]
            ]
          }
        }
      }
    }
  }
])

See how it works on the playground example - reduce

Sign up to request clarification or add additional context in comments.

Comments

2

One way to do it is by using "$function" in the update/aggregation pipeline.

db.collection.update({
  "account_id": "ORG1"
},
[
  {
    "$set": {
      "progress": {
        "$function": {           // your increment value goes here ⮯
          "body": "function(prog) {prog[prog.length - 1].raised += 75; return prog}",
          "args": ["$progress"],
          "lang": "js"
        }
      }
    }
  }
])

Try it on mongoplayground.net.

2 Comments

Nice one:) I wonder which answer here is the fastest. Maybe @Ahmed Haque will tell us which one works best
@nimrodserok I wonder the same. I'm a javascript noob so I just wander around developer.mozilla.org looking for helpful methods, etc. I also wonder if "$function" "body" functions are cached for possible future/repeated use.
0

I ended up finding a solution that feels a bit simpler than the solutions I've seen.

I added a field to the db called "status":

{
  account_id: "ORG1",
  progress: [{week: 1, goal: 5000, raised: 2400, status: historic}, {week: 2, goal:5100,  raised: 1000, status: current}]
} 

Then I ended up using the positional operator ($) and increment functions.

const collection = client.collection(goalCollection);

 // Search Query
const query = {
  'account_id': ORG1,
  'progress.status: "current"
};

// Update with increment
const update = {
   $inc: {"progress.$.raised": ADDITIONAL_SUM}
}


// Run the update
const result = await collection.updateOne(query, update);

Worked smoothly.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.