MongoDB Add Array Element with Virtual Order
Asked Answered
A

2

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!

Anatropous answered 4/8 at 7:19 Comment(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?Brazee
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-Gwynethgwynne
or with 1 based indxing mongoplayground.net/p/MxBMKg6Ecg6 because the size gives the size before the new value is addedGwynethgwynne
@AdamBurke added clarificationAnatropous
@Gwynethgwynne 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!Anatropous
B
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

Binni answered 4/8 at 8:0 Comment(1)
Thank you! Between your wonderful answer and @cmgchess, it looks like there are a few viable options here. Much appreciated!Anatropous
B
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.

Brazee answered 5/8 at 1:54 Comment(2)
The motivation is to be able to update the order atomically.Anatropous
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.Brazee

© 2022 - 2024 — McMap. All rights reserved.