Updating nested array inside array mongodb [duplicate] - arrays

This question already has answers here:
Updating a Nested Array with MongoDB
(2 answers)
Closed 3 years ago.
I have the following mongodb document structure:
[
{
"_id": "04",
"name": "test service 4",
"id": "04",
"version": "0.0.1",
"title": "testing",
"description": "test",
"protocol": "test",
"operations": [
{
"_id": "99",
"oName": "test op 52222222222",
"sid": "04",
"name": "test op 52222222222",
"oid": "99",
"description": "testing",
"returntype": "test",
"parameters": [
{
"oName": "Param1",
"name": "Param1",
"pid": "011",
"type": "582",
"description": "testing",
"value": ""
},
{
"oName": "Param2",
"name": "Param2",
"pid": "012",
"type": "58222",
"description": "testing",
"value": ""
}
]
}
]
}
]
I have been able to use $elemMatch in order to update fields in operations, but when I try to do the same thing (modified) for parameters it does not seem to work. I was wondering what other approach should I look into trying in order to be able to successfully update fields in a specific parameter, looking it up by its pid.
The update code I currently have and does not work looks like this:
var oid = req.params.operations;
var pid = req.params.parameters;
collection.update({"parameters":{"$elemMatch": {"pid": pid}}},{"$set": {"parameters.$.name":req.body.name, "parameters.$.description": req.body.description,"parameters.$.oName": req.body.oName,"parameters.$.type": req.body.type} }, function(err, result) {
if (err) {
console.log('Error updating service: ' + err);
res.send({'error':'An error has occurred'});
} else {
// console.log('' + result + ' document(s) updated');
res.send(result);
}
});

MongoDB 3.6 and newer
With MongoDB 3.6 and above comes a new feature that allows you to update nested arrays by using the positional filtered $\[<identifier>\] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:
const { oid, pid } = req.params;
const { name, oName, description, type } = req.body;
collection.update(
{
"_id": 1,
"operations": {
"$elemMatch": {
oid, "parameters.pid": pid
}
}
},
{ "$set": {
"operations.$[outer].parameters.$[inner].name": name,
"operations.$[outer].parameters.$[inner].description": description,
"operations.$[outer].parameters.$[inner].oName": oName,
"operations.$[outer].parameters.$[inner].type": type
} },
{ "arrayFilters": [
{ "outer.oid": oid },
{ "inner.pid": pid }
] }, (err, result) => {
if (err) {
console.log('Error updating service: ' + err);
res.send({'error':'An error has occurred'});
} else {
// console.log('' + result + ' document(s) updated');
res.send(result);
}
});
For MongoDB 3.4 and older:
As #wdberkeley mentioned in his comment:
MongoDB doesn't support matching into more than one level of an array.
Consider altering your document model so each document represents an
operation, with information common to a set of operations duplicated
in the operation documents.
I concur with the above and would recommend redesigning your schema as MongoDB engine does not support multiple positional operators ( See Multiple use of the positional $ operator to update nested arrays)
However, if you know the index of the operations array that has the parameters object to be updated beforehand then the update query will be:
db.collection.update(
{
"_id" : "04",
"operations.parameters.pid": "011"
},
{
"$set": {
"operations.0.parameters.$.name": "foo",
"operations.0.parameters.$.description": "bar",
"operations.0.parameters.$.type": "foo"
}
}
)
EDIT:
If you would like to create the $set conditions on the fly i.e. something which would help you get the indexes for the objects and then modify accordingly, then consider using MapReduce.
Currently this seems to be not possible using the aggregation framework. There is an unresolved open JIRA issue linked to it. However, a workaround is possible with MapReduce. The basic idea with MapReduce is that it uses JavaScript as its query language but this tends to be fairly slower than the aggregation framework and should not be used for real-time data analysis.
In your MapReduce operation, you need to define a couple of steps i.e. the mapping step (which maps an operation into every document in the collection, and the operation can either do nothing or emit some object with keys and projected values) and reducing step (which takes the list of emitted values and reduces it to a single element).
For the map step, you ideally would want to get for every document in the collection, the index for each operations array field and another key that contains the $set keys.
Your reduce step would be a function (which does nothing) simply defined as var reduce = function() {};
The final step in your MapReduce operation will then create a separate collection operations that contains the emitted operations array object along with a field with the $set conditions. This collection can be updated periodically when you run the MapReduce operation on the original collection.
Altogether, this MapReduce method would look like:
var map = function(){
for(var i = 0; i < this.operations.length; i++){
emit(
{
"_id": this._id,
"index": i
},
{
"index": i,
"operations": this.operations[i],
"update": {
"name": "operations." + i.toString() + ".parameters.$.name",
"description": "operations." + i.toString() + ".parameters.$.description",
"type": "operations." + i.toString() + ".parameters.$.type"
}
}
);
}
};
var reduce = function(){};
db.collection.mapReduce(
map,
reduce,
{
"out": {
"replace": "operations"
}
}
);
Querying the output collection operations from the MapReduce operation will typically give you the result:
db.operations.findOne()
Output:
{
"_id" : {
"_id" : "03",
"index" : 0
},
"value" : {
"index" : 0,
"operations" : {
"_id" : "96",
"oName" : "test op 52222222222",
"sid" : "04",
"name" : "test op 52222222222",
"oid" : "99",
"description" : "testing",
"returntype" : "test",
"parameters" : [
{
"oName" : "Param1",
"name" : "foo",
"pid" : "011",
"type" : "foo",
"description" : "bar",
"value" : ""
},
{
"oName" : "Param2",
"name" : "Param2",
"pid" : "012",
"type" : "58222",
"description" : "testing",
"value" : ""
}
]
},
"update" : {
"name" : "operations.0.parameters.$.name",
"description" : "operations.0.parameters.$.description",
"type" : "operations.0.parameters.$.type"
}
}
}
You can then use the cursor from the db.operations.find() method to iterate over and update your collection accordingly:
var oid = req.params.operations;
var pid = req.params.parameters;
var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid });
// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
var doc = cur.next();
var update = { "$set": {} };
// set the update query object
update["$set"][doc.value.update.name] = req.body.name;
update["$set"][doc.value.update.description] = req.body.description;
update["$set"][doc.value.update.type] = req.body.type;
db.collection.update(
{
"_id" : oid,
"operations.parameters.pid": pid
},
update
);
};

If it is data that is changed frequently, you should flatten the structure and separate the data that changes a lot from that which does not.
If it is data that does not change often, and the entire data object is not massive, just modify the object client-side, and update the entire object.

We will try to find index of outer array(i) and inner array(j)and then update
collection.findById(04)
.then(result =>{
for(let i = 0; i<result.operations.length; i++){
if(result.operation[i]._id == "99"){
let parameters = result.operations[i].parameters;`enter code here`
for(let j = 0; j<parameters.length; j++){
if(parameters[j].pid == "011"){
console.log("i", i);
console.log("j", j);
let data = {}
data["operations." + i + ".parameters." + j + ".oName"] = updateoName
data["operations." + i + ".parameters." + j + ".name"] = updatename
data["operations." + i + ".parameters." + j + ".pid"] = updatepid
data["operations." + i + ".parameters." + j + ".description"] = updatedescription
data["operations." + i + ".parameters." + j + ".value"] = updatevalue
console.log(data)
collection.update({
"_id": "04"
},{
$set: data
})
.then(dbModel => res.json(dbModel))
}
}
}
}
})

Starting with mongo version 3.6 you can use the $[] in Conjunction with $[] to Update Nested Arrays
Update Nested Arrays in Conjunction with $[]
The $[] filtered positional operator, in conjunction with
all $[] positional operator can be used to update nested arrays.
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#position-nested-arrays-filtered

Related

Pouchdb view returning empty result

Good morning,
I'm currently working with Couchdb and Pouchdb and I'm having a problem with one query on Pouchdb side.
I have a database with different documents setup like this:
{
"_id": "fd87b66087503d760fa501fa49029f94",
"_rev": "1-e2be19d447c98d624c2c8492eaf0a3f4",
"type": "product",
"name": "Blanc de Morgex et de la Salle Brut Extreme 2014",
"category": "Wine",
"subcategory": null,
"zone": "Italy",
"nation": "Valle d'Aosta",
"province": "Morgex, AO",
"cellar": "Cave Mont Blanc",
"price": 30,
"structure": null,
"year": 2014,
"mescita": null,
"tag": null
}
The query I wrote should return the available years of products that match some filters. This is the query, with reduce : _count:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year != null) {
emit(doc.year , 1);
}
}
If I try it with Postman adding the group = true parameter everything works and the result is something like:
{
"rows": [
{
"key": 2004,
"value": 2
},
{
"key": 2006,
"value": 2
},
{
"key": 2008,
"value": 2
}
]
}
The problem is when i run this view with Pouchdb with the following code which return a JSON with an empty array:
wine_db.query('wine_list/years', {reduce: '_count', key : "Bollicine", group : true, group_level: 2}).then(function(doc) {
years_list = doc;
console.log('getting year list');
console.log(doc);
}).catch(function(err){
console.log(err);
});
I've tried to play a little with the parameters of the function and even changing the function to return just a list of all the years, but nope.
I can't find the problem neither a different solution so I'm open to every suggestion you can have.
Another solution (group result)
Working on the indications and on the solution suggested by #user3405291 I finally found a way to group the results by year.
Since the emit function return a complex key ['CATEGORY', YEAR] I can use the startkey and endkey parameters to query the result just for a section of the index returned keeping this way the reduce function enable to group the result.
In the end the view function is:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.category, doc.year], doc.year );
}
}
And the Pouchdb query:
wine_db.query('wine_list/years',
{
startkey : ['CATEGORY'],
endkey : ['CATEGORY', {}],
group: true
}
).then(function (doc) {
years_list = doc;
console.log(years_list);
}).catch(function (err) {
console.log(err);
});
The result, where value is the total number of elements with that index:
{
"rows": [
{
"key": [
"Bollicine",
2004
],
"value": 2
},
{
"key": [
"Bollicine",
2006
],
"value": 2
},
{
"key": [
"Bollicine",
2008
],
"value": 2
}
]
}
In your view map function you emit the year as the key/index:
emit(doc.year , 1);
Now, I'm not sure why your are doing your query with a key like {key : "Bollicine"}:
wine_db.query('wine_list/years', {key : "Bollicine"})
.then(res=>{console.log(res)})
Of course you would get an empty response, because your view is actually indexing your docs according to year. I think you might want to do a query with a key like: {key : "2014"}
UPDATE
Based on your comments, I feel like you need to find docs based on both year and category. I'm not sure if I understand what you want, but this may help you: change your view map function like this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.year, doc.category] , 1);
}
}
The above view will index your docs according to both year and category. You then query your view like this:
wine_db.query('wine_list/years', {key : ['2014', 'Bollicine']})
.then(res=>{console.log(res)})
The above query will give you all the docs with year field equal to 2014 and category field equal to Bollicine.
Second Update
your code works, but I just get the result for the year 2014. What I'm trying to accomplish is to get all the available years given a specific category
One solution is this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit(doc.category, doc.year);
}
}
The above view will index your docs according to category as key and will return the year as value. Therefore you can query like this:
wine_db.query('wine_list/years', {key : 'Bollicine'})
.then(res=>{console.log(res)})
You should get a response like this, by which you have all the available years for Bollicine category:
{
"total_rows": 400,
"offset": 0,
"rows": [
{
"key": "Bollicine",
"value": "2014"
},
{
"key": "Bollicine",
"value": "2015"
},
{
"key": "Bollicine",
"value": "2018"
}
]
}

Remove element from array in mongoose

i am working on nodejs/express
I want to delete element in array in mongoose
i have document in this format
{
"_id" : ObjectId("5a2e19223e50551504b316c0"),
"username" : "nikhil",
"email" : "nikhil#itradicals.com",
"password" : "$2a$10$f3NvrTywIezlzKcZLWSU0O98gn6Mc.Q8B0ZNEDG2F66f4rwyo65Yu",
"companyname" : "itr",
"role" : "vendor",
"verify" : "true",
"createddate" : ISODate("2017-12-10T18:30:00.000+0000"),
"account" : [
{
"Region" : [
"North America",
"Africa",
"Asia"
]
},
{
"Category" : [
"Group 1",
"Group 2",
"Group 3"
]
}
]
}
i want to remove only Region field using mongoose query i m getting
data dyanamically so user can delete any array either it is Region or
Category. i m getting array name which i want to delete in parameter
"field".
module.exports.setupAccount = function(id,field,callback){
var query = { _id: id};
User.findOneAndUpdate(query,{ $set: { account.Region:[] }}, callback);
}
i am new to nodejs and mongoose
thanks in advance
If you don't want to keep the empty document after removing the field you can simply $pull the entire element from the array:
module.exports.setupAccount = function(id,field,callback){
var query = { _id: id };
User.findOneAndUpdate(query, { $pull: { "account": { field: { $exists: true } } } }, callback);
}
You can use below function.
Use computed property names to dynamically pass the field to unset and query expression.
Query to verify for array existence followed by $unset to remove array.
$ positional operator to dynamically locate array based on query criteria. You can hardcode the array index if you know the array position.
module.exports.setupAccount = function(id, field, callback){
var query = { "_id": id, ['account.' + field]:{"$exists":true}};
var unset = {"$unset":{['account.$.' + field]:""}};
User.findOneAndUpdate(query, unset, callback);
}

Updating Nested Array Mongoose

I am working on an express js application where I need to update a nested array.
1) Schema :
//Creating a mongoose schema
var userSchema = mongoose.Schema({
_id: {type: String, required:true},
name: String,
sensors: [{
sensor_name: {type: String, required:true},
measurements: [{time: String}]
}] });
2)
Here is the code snippet and explanation is below:
router.route('/sensors_update/:_id/:sensor_name/')
.post(function (req, res) {
User.findOneAndUpdate({_id:req.body._id}, {$push: {"sensors" :
{"sensor_name" : req.body.sensor_name , "measurements.0.time": req.body.time } } },
{new:true},function(err, newSensor) {
if (err)
res.send(err);
res.send(newSensor)
}); });
I am able to successfully update a value to the measurements array using the findOneAndUpdate with push technique but I'm failing when I try to add multiple measurements to the sensors array.
Here is current json I get if I get when I post a second measurement to the sensors array :
{
"_id": "Manasa",
"name": "Manasa Sub",
"__v": 0,
"sensors": [
{
"sensor_name": "ras",
"_id": "57da0a4bf3884d1fb2234c74",
"measurements": [
{
"time": "8:00"
}
]
},
{
"sensor_name": "ras",
"_id": "57da0a68f3884d1fb2234c75",
"measurements": [
{
"time": "9:00"
}
]
}]}
But the right format I want is posting multiple measurements with the sensors array like this :
Right JSON format would be :
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
}
],
"measurements" : [
{
"time" : "9:00"
}
]
}],
"__v" : 0 }
Please suggest some ideas regarding this. Thanks in advance.
You might want to rethink your data model. As it is currently, you cannot accomplish what you want. The sensors field refers to an array. In the ideal document format that you have provided, you have a single object inside that array. Then inside that object, you have two fields with the exact same key. In a JSON object, or mongo document in this context, you can't have duplicate keys within the same object.
It's not clear exactly what you're looking for here, but perhaps it would be best to go for something like this:
{
"_id" : "Manasa",
"name" : "Manasa Sub",
"sensors" : [
{
"sensor_name" : "ras",
"_id" : ObjectId("57da0a4bf3884d1fb2234c74"),
"measurements" : [
{
"time" : "8:00"
},
{
"time" : "9:00"
}
]
},
{
// next sensor in the sensors array with similar format
"_id": "",
"name": "",
"measurements": []
}],
}
If this is what you want, then you can try this:
User.findOneAndUpdate(
{ _id:req.body._id "sensors.sensor_name": req.body.sensor_name },
{ $push: { "sensors.0.measurements": { "time": req.body.time } } }
);
And as a side note, if you're only ever going to store a single string in each object in the measurements array, you might want to just store the actual values instead of the whole object { time: "value" }. You might find the data easier to handle this way.
Instead of hardcoding the index of the array it is possible to use identifier and positional operator $.
Example:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74")}]
);
You may notice than instead of getting a first element of the array I specified which element of the sensors array I would like to update by providing its ObjectId.
Note that arrayFilters are passed as the third argument to the update query as an option.
You could now make "outer._id" dynamic by passing the ObjectId of the sensor like so: {"outer._id": req.body.sensorId}
In general, with the use of identifier, you can get to even deeper nested array elements by following the same procedure and adding more filters.
If there was a third level nesting you could then do something like:
User.findOneAndUpdate(
{ _id: "Manasa" },
{ $push: { "sensors.$[outer].measurements.$[inner].example": { "time": req.body.time } } }
{ "arrayFilters:" [{"outer._id": ObjectId("57da0a4bf3884d1fb2234c74"), {"inner._id": ObjectId("57da0a4bf3884d1fb2234c74"}}]
);
You can find more details here in the answer written by Neil Lunn.
refer ::: positional-all
--- conditions :: { other_conditions, 'array1.array2.field_to_be_checked': 'value' }
--- updateData ::: { $push : { 'array1.$[].array2.$[].array3' : 'value_to_be_pushed' } }

In Mongodb subdocument array is there any way to add a new field in each subcoument

Suppose i have a document like
{
"_id" : 5,
"rows": [
{ "id" : "aab", "value":100},
{ "id" : "aac", "value":400},
{ "id" : "abc", "value":200},
{ "id" : "xyz", "value":300}
]
}
and i need to add a new key in each sub document "status" : 1 and result should be look like
{
"_id" : 5,
"rows": [
{ "id" : "aab", "value":100, "status":1},
{ "id" : "aac", "value":400, "status":1},
{ "id" : "abc", "value":200, "status":1},
{ "id" : "xyz", "value":300, "status":1}
]
}
How can i do this by single update query?
Mongo positional operator with $elemMatch having problem;
The $ operator can update the first array element that matches multiple query criteria specified with the $elemMatch() operator.
So this case using mongo query you should update only specific matching criteria. If you set rows.aac in match then you will add status:1 in row.aac array, check query as below :
db.collectionName.update({
"_id": 5,
"rows": {
"$elemMatch": {
"id": "abc"
}
}
}, {
$set: {
"rows.$.status": 1
}
}, true, false) // here you insert new field so upsert true
mongo update showing how upsert and multi works.
But still you want to updated all documents then you should use some programming code or some script. Below code update all data using cursor forEach :
db.collectionName.find().forEach(function(data) {
for (var ii = 0; ii < data.rows.length; ii++) {
db.collectionName.update({
"_id": data._id,
"rows.id": data.rows[ii].id
}, {
"$set": {
"rows.$.status": 1
}
}, true, false);
}
})
If your documents size more then better way to use mongo bulk update below code shows how to updated using mongo bulk :
var bulk = db.collectionName.initializeOrderedBulkOp();
var counter = 0;
db.collectionName.find().forEach(function(data) {
for (var ii = 0; ii < data.rows.length; ii++) {
var updatedDocument = {
"$set": {}
};
var setStatus = "rows." + ii + ".status";
updatedDocument["$set"][setStatus] = 101;
// queue the update
bulk.find({
"_id": data._id
}).update(updatedDocument);
counter++;
// re-initialize every 1000 update statements
if (counter % 1000 == 0) {
bulk.execute();
bulk = db.collectionName.initializeOrderedBulkOp();
}
}
});
// Add the rest in the queue
if (counter % 1000 != 0)
bulk.execute();

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.
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}}
)
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"}}
)
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" } }
)
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.
A neat way to do it in Javascript, with backticks, is:
const index = 1;
... { $set: { [`myArray.${index}.value`]: "new content"} }, ...
db.my_collection.update(
{_id: ObjectId(document_id), my_array : { ... old content A ... } },
{ $set: { "my_array.$.content" : "NEW content B" } }
)
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
}
)
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/
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"}})
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

Resources