3

I would like to update a property of the last objet stored in a list in mongo. For performance reasons, I can not pop the object from the list, then update the property, and then put the objet back. I can not either change the code design as it does not depend on me. In brief am looking for a way to select the last element of a list.

The closest I came to get it working was to use arrayFilters that I found doing research on the subject (mongodb core ticket: https://jira.mongodb.org/browse/SERVER-27089):

db.getCollection("myCollection")
    .updateOne(
    {
        _id: ObjectId('638f5f7fe881c670052a9d08')
    },
    {
       $set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
    },
    {
        arrayFilters: [{'i.type': 'MyTypeFilter'}]
    }
)

I use a filter to only update the objets in theList that have their property type evaluated as MyTypeFilter.

What I am looking for is something like:

db.getCollection("maCollection")
    .updateOne(
    {
        _id: ObjectId('638f5f7fe881c670052a9d08')
    },
    {
       $set: {"theList.$[i].propertyToUpdate": 'NewValueToAssign'}
    },
    {
        arrayFilters: [{'i.index': -1}]
    }
)

I also tried using "theList.$last.propertyToUpdate" instead of "theList.$[i].propertyToUpdate" but the path is not recognized (since $last is invalid)

I could not find anything online matching my case.

Thank you for your help, have a great day

lilgallon
  • 555
  • 1
  • 7
  • 14

2 Answers2

2

You want to be using Mongo's pipelined updates, this allows us to use aggregation operators within the update body.

You do however need to consider edge cases that the previous answer does not. (null list, empty list, and list.length == 1)

Overall it looks like so:

db.collection.update({
  _id: ObjectId("638f5f7fe881c670052a9d08")
},
[
  {
    $set: {
      list: {
        $concatArrays: [
          {
            $cond: [
              {
                $gt: [
                  {
                    $size: {
                      $ifNull: [
                        "$list",
                        []
                      ]
                    }
                  },
                  1
                ]
              },
              {
                $slice: [
                  "$list",
                  0,
                  {
                    $subtract: [
                      {
                        $size: "$list"
                      },
                      1
                    ]
                  }
                ]
              },
              []
            ]
          },
          [
            {
              $mergeObjects: [
                {
                  $ifNull: [
                    {
                      $last: "$list"
                    },
                    {}
                  ]
                },
                {
                  propertyToUpdate: "NewValueToAssign"
                }
              ]
            }
          ]
        ]
      }
    }
  }
])

Mongo Playground

Tom Slabbaert
  • 21,288
  • 10
  • 30
  • 43
  • Thank you for your answer! It works indeed, but when the list is empty or null, it creates an object with the only property "propertyToUpdate". It is an issue when deserializing the object: the other properties will be missing. The correct way to handle an empty or null list would be to do nothing: leave the list empty if it was empty or null if it was null – lilgallon Dec 08 '22 at 08:18
  • 1
    You can do this in multiple ways, here is a quick [example](https://mongoplayground.net/p/LeL9syHUiMm) by adding an additional check. – Tom Slabbaert Dec 08 '22 at 08:23
  • Indeed, that's perfect, thank you for your help ! – lilgallon Dec 08 '22 at 08:25
1

One option is to use update with pipeline:

db.collection.update(
  {_id: ObjectId("638f5f7fe881c670052a9d08")},
  [
    {$set: {
      theList: {
        $concatArrays: [
          {$slice: ["$theList", 0, {$subtract: [{$size: "$theList"}, 1]}]},
          [{$mergeObjects: [{$last: "$theList"}, {propertyToUpdate: "NewValueToAssign"}]}]
        ]
      }
    }}
  ]
)

See how it works on the playground example

nimrod serok
  • 14,151
  • 2
  • 11
  • 33
  • Thank you for your answer! The only issue here is that it does not work when the list is empty (it works if the list is null though by not doing anything). But when I need to update the last element, I know that the list is not empty, so it works in my case – lilgallon Dec 08 '22 at 08:14