Abe
Abe

Reputation: 24008

MongoDB: How do I update a single subelement in an array, referenced by the index within the array?

I'm trying to update a single subelement contained within an array in a mongodb document. I want to reference the field using its array index (elements within the array don't have any fields that I can guarantee will be unique identifiers). Seems like this should be easy to do, but I can't figure out the syntax.

Here's what I want to do in pseudo-json.

Before:

{
  _id : ...,
  other_stuff ... ,
  my_array : [
    { ... old content A ... },
    { ... old content B ... },
    { ... old content C ... }
  ]
}

After:

{
  _id : ...,
  other_stuff ... ,
  my_array : [
    { ... old content A ... },
    { ... NEW content B ... },
    { ... old content C ... }
  ]
}

Seems like the query should be something like this:

//pseudocode
db.my_collection.update(
  {_id: ObjectId(document_id), my_array.1 : 1 },
  {my_array.$.content: NEW content B }
)

But this doesn't work. I've spent way too long searching the mongodb docs, and trying different variations on this syntax (e.g. using $slice, etc.). I can't find any clear explanation of how to accomplish this kind of update in MongoDB.

Upvotes: 138

Views: 191399

Answers (11)

Nate Anderson
Nate Anderson

Reputation: 21176

I needed an updateMany (not updateOne as shown in accepted answer), and I think it's important to call out the arrayFilters argument.

The arrayFilters argument is:

Optional. An array of filter documents that determine which array elements to modify for an update operation on an array field.

Here's the example from MongoDb documentation:

Update Elements Match arrayFilters Criteria

Create a collection students with the following documents:

db.students.insertMany( [
   { "_id" : 1, "grades" : [ 95, 92, 90 ] },
   { "_id" : 2, "grades" : [ 98, 100, 102 ] },
   { "_id" : 3, "grades" : [ 95, 110, 100 ] }
])

To update all elements that are greater than or equal to 100 in the grades array, use the filtered positional operator $[] with the arrayFilters option:

db.students.updateMany(
   { grades: { $gte: 100 } },
   { $set: { "grades.$[element]" : 100 } },
   { arrayFilters: [ { "element": { $gte: 100 } } ] }
)

After the operation, the collection contains the following documents:

{ "_id" : 1, "grades" : [ 95, 92, 90 ] }
{ "_id" : 2, "grades" : [ 98, 100, 100 ] }
{ "_id" : 3, "grades" : [ 95, 100, 100 ] }

Upvotes: 0

angabriel
angabriel

Reputation: 5048

If you have a "plain" array containing simple strings, this did the trick:

db.paintings.insertMany([
  {_id: 1, colors: ["red", "blue", "green"]},
  {_id: 2, colors: ["red", "yellow"]}

db.paintings.updateMany(
  {colors: "red"},
  {$set: {"colors.$": "magenta"}})

the positional $ operator acts as a placeholder for the first element that matches the query document Source

Upvotes: -1

Adwait Mathkari
Adwait Mathkari

Reputation: 140

If you want to update the authorName of the testimonial having _id = 60c4918d74c30165ba585c14 from the following document:

"business": {
    "ownerId": "60a5ebad7432d91b853c0277",
    "testimonials": [
        {
            "_id": "60c4912877dd5664f2201b08",
            "authorName": "user1",
            "authorBio": "User from 10 years",
            "image": "user1/img1",
            "review": "asdfiuahsdfpoiuashdpfoaspdlfkjn;alsfpuoh"
        },
        {
            "_id": "60c4918d74c30165ba585c14",
            "authorName": "user2",
            "authorBio": "User from 3 years",
            "image": "user/img1",
            "review": "asdpfuahsfljnsadfoihsf."
        }
    ],
    "createdAt": "2021-06-11T20:12:56.666Z",
    "updatedAt": "2021-06-12T11:11:56.696Z",
    
}

Then the following mongoose query works:

await BusinessModel.updateOne(
        {
            '_id': Mongoose.Types.ObjectId(businessId),
            'testimonials._id': Mongoose.Types.ObjectId('60c4918d74c30165ba585c14')
        },
        {
            $set: { 'testimonials.$.authorName' : 'new author name' } 
        }
    );

Also refer to https://docs.mongodb.com/drivers/node/fundamentals/crud/write-operations/embedded-arrays/

Upvotes: 6

Amarnathrao Sulake
Amarnathrao Sulake

Reputation: 481

When it's required to update an array element without knowing it's an actual index but having a unique identifier of the element

db.getCollection('profiles').update(
  {
    'userId':'4360a380-1540-45d9-b902-200f2d346263',
    'skills.name':'css'
  },
  {
      $set: {'skills.$.proficiencyLevel': 5}
  }, 
  {
      multi: true
  }
)

Upvotes: 10

iamcrypticcoder
iamcrypticcoder

Reputation: 2889

When it's required to update an array element without knowing it's actual index but having a unique identifier of the element:

// Modify a comment in a bucket
db.POST_COMMENT.update(
    {
        "_id": ObjectId("5ec424a1ed1af85a50855964"),
        "bucket.commentId": "5eaf258bb80a1f03cd97a3ad_lepf4f"
    },
    {
        $set: {
            "bucket.$.text": "Comment text changed",
            "bucket.$.createdDate": ISODate("2015-12-11T14:12:00.000+0000")
        }
    }
)

Here "bucket.commentId" is the unique identifier of an array element.

Upvotes: 68

Ismael Soschinski
Ismael Soschinski

Reputation: 475

A neat way to do it in Javascript, with backticks, is:

 const index = 1;

 ...  {   $set: { [`myArray.${index}.value`]: "new content"}  },  ...

Upvotes: 18

Samuel Cabral
Samuel Cabral

Reputation: 37

You can use the updateOne function of mongoDB passing the index of the element in array, if the key of old content B is "value" per example:

[
...
"value" : "old content A"
"value" : "old content B"
"value" : "old content C"
...
]

the command should be like this:

db.collection.updateOne({"_id" : "...,"},{$set: {"my_array.1.value": "NEW content B"}})

Upvotes: 2

tomaskazemekas
tomaskazemekas

Reputation: 5188

Update of an array element referenced by an index (e.g. 1 ) in Mongo Shell can also be done by directly indicating the index value:

db.my_collection.update(
    {_id : "document_id"},
    {$set : {"my_array.1.content" : "New content B"}}
)

Upvotes: 79

Doel
Doel

Reputation: 1004

In mongo style, using '$' positional operator. Check out this link for details.

db.my_collection.update(
  {_id: ObjectId(document_id), my_array.1 : 1 },
  { $set: { "my_array.$.content" : "NEW content B" } }
)

Upvotes: 73

znbwo
znbwo

Reputation: 101

db.my_collection.update(
  {_id: ObjectId(document_id), my_array : { ... old content A ... } },
  { $set: { "my_array.$.content" : "NEW content B" } }
)

Upvotes: 10

Abe
Abe

Reputation: 24008

As expected, the query is easy once you know how. Here's the syntax, in python:

db["my_collection"].update(
    { "_id": ObjectId(document_id) },
    { "$set": { 'documents.'+str(doc_index)+'.content' : new_content_B}}
)

Upvotes: 98

Related Questions