mongodbmongodb-queryaggregation-framework

MongoDB Add Array Element with Virtual Order


Does anyone know how to add an array element to a mongodb array and set a "virtual" order to the size of the array, all in an atomic operation?

So, something like:

db.users.updateOne(
    { _id: 1},
    { $addToSet: { images: {name: 'new image', order: "size of the array"} } }
)

The idea is to always have the last array element added have an order that is last.

Update:

Data before op:

{
  _id: 1, 
  images: [
    { name: 'some name', order: 0 }
  ]
}

Data after op:

{
  _id: 1, 
  images: [
    { name: 'some name', order: 0 }, 
    { name: 'new image', order: 1 }
  ]
}

Update 2:

In case anyone is interested, to update the order atomically, you can do something like this (build this dynamically, of course):

db.collection.update({
  _id: 1
},
{
  $set: {
    "images.$[elem].order": 4,
    "images.$[elem2].order": 3
  }
},
{
  arrayFilters: [
    {
      "elem.name": {
        $eq: "some name"
      }
    },
    {
      "elem2.name": {
        $eq: "new image"
      }
    }
  ]
})

Thank you!


Solution

  • Assuming your order field would be an integer field, you can use $let to compute the next order number and use $concatArrays to append it to the end of the array.

    Some scenarios:

    1. images already has data in it: $max will get the largest order number, $add 1 to it.
    2. images is an empty array: the $max result will fallback to 0 due to $ifNull
    3. images.order is not perfectly 0-th ordered: it may have some gaps in between, or not starting from 0. It will be handled by $max and $add 1 logic.
    4. images is a null field or does not exists: it will be safeguarded by $ifNull and fall back to an empty array.
    db.collection.update({},
    [
      {
        "$set": {
          "images": {
            "$let": {
              "vars": {
                "seq": {
                  "$ifNull": [
                    {
                      "$add": [
                        {
                          "$max": "$images.order"
                        },
                        1
                      ]
                    },
                    0
                  ]
                }
              },
              in: {
                "$concatArrays": [
                  {
                    "$ifNull": [
                      "$images",
                      []
                    ]
                  },
                  [
                    //your payload here
                    {
                      "name": "new image",
                      "order": "$$seq"
                    }
                  ]
                ]
              }
            }
          }
        }
      }
    ],
    {
      multi: true
    })
    

    Mongo Playground