2

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!

5
  • Could you expand on what you mean by order here? What would you expect the before and after to look like in your example? Commented Aug 4, 2024 at 7:25
  • 1
    do you have a specific reason to use addToSet. do you need to add unique names. otherwise something like this maybe mongoplayground.net/p/DPPIVXdKN7- Commented Aug 4, 2024 at 7:25
  • 2
    or with 1 based indxing mongoplayground.net/p/MxBMKg6Ecg6 because the size gives the size before the new value is added Commented Aug 4, 2024 at 7:35
  • @AdamBurke added clarification Commented Aug 5, 2024 at 0:04
  • @cmgchess the data in the array needs to be unique, but your example works beautifully with a minor update to the query: { _id: 1, "images.name": {$ne: "new image name"}} Thank you! Commented Aug 5, 2024 at 0:10

2 Answers 2

2

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

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

1 Comment

Thank you! Between your wonderful answer and @cmgchess, it looks like there are a few viable options here. Much appreciated!
0

Note that you already keep your image details in an array, so they already have an order defined by the position in the array. So if the motivation for the order attribute is to query the array index later, there are other ways to do that, and it may be redundant.

For example, the $unwind operator optionally returns an array index. You could query this instead of a custom field which needs to be separately maintained every time the images field is updated.

2 Comments

The motivation is to be able to update the order atomically.
Well, there's going to be a motivation behind that motivation. Presumably something reads that field at some point. But sharing it is of course up to you.

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.