MongoDB conditionally $addToSet sub-document in array by specific field - mongodb

Is there a way to conditionally $addToSet based on a specific key field in a subdocument on an array?
Here's an example of what I mean - given the collection produced by the following sample bootstrap;
cls
db.so.remove();
db.so.insert({
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples"
}
]
});
n defines a unique document key. I only want one entry with the same n value in the array at any one time. So I want to be able to update the pfms array using n so that I end up with just this;
{
"Name": "fruitBowl",
"pfms" : [
{
"n" : "apples",
"mState": 1111234
}
]
}
Here's where I am at the moment;
db.so.update({
"Name": "fruitBowl",
},{
// not allowed to do this of course
// "$pull": {
// "pfms": { n: "apples" },
// },
"$addToSet": {
"pfms": {
"$each": [
{
"n": "apples",
"mState": 1111234
}
]
}
}
}
)
Unfortunately, this adds another array element;
db.so.find().toArray();
[
{
"Name" : "fruitBowl",
"_id" : ObjectId("53ecfef5baca2b1079b0f97c"),
"pfms" : [
{
"n" : "apples"
},
{
"n" : "apples",
"mState" : 1111234
}
]
}
]
I need to effectively upsert the apples document matching on n as the unique identifier and just set mState whether or not an entry already exists. It's a shame I can't do a $pull and $addToSet in the same document (I tried).
What I really need here is dictionary semantics, but that's not an option right now, nor is breaking out the document - can anyone come up with another way?
FWIW - the existing format is a result of language/driver serialization, I didn't choose it exactly.
further
I've gotten a little further in the case where I know the array element already exists I can do this;
db.so.update({
"Name": "fruitBowl",
"pfms.n": "apples",
},{
$set: {
"pfms.$.mState": 1111234,
},
}
)
But of course that only works;
for a single array element
as long as I know it exists
The first limitation isn't a disaster, but if I can't effectively upsert or combine $addToSet with the previous $set (which of course I can't) then it the only workarounds I can think of for now mean two DB round-trips.

The $addToSet operator of course requires that the "whole" document being "added to the set" is in fact unique, so you cannot change "part" of the document or otherwise consider it to be a "partial match".
You stumbled on to your best approach using $pull to remove any element with the "key" field that would result in "duplicates", but of course you cannot modify the same path in different update operators like that.
So the closest thing you will get is issuing separate operations but also doing that with the "Bulk Operations API" which is introduced with MongoDB 2.6. This allows both to be sent to the server at the same time for the closest thing to a "contiguous" operations list you will get:
var bulk = db.so.initializeOrderedBulkOp();
bulk.find({ "Name": "fruitBowl", "pfms.n": "apples": }).updateOne({
"$pull": { "pfms": { "n": "apples" } }
});
bulk.find({ "Name": "fruitBowl" }).updateOne({
"$push": { "pfms": { "n": "apples", "state": 1111234 } }
})
bulk.execute();
That pretty much is your best approach if it is not possible or practical to move the elements to another collection and rely on "upserts" and $set in order to have the same functionality but on a collection rather than array.

I have faced the exact same scenario. I was inserting and removing likes from a post.
What I did is, using mongoose findOneAndUpdate function (which is similar to update or findAndModify function in mongodb).
The key concept is
Insert when the field is not present
Delete when the field is present
The insert is
findOneAndUpdate({ _id: theId, 'likes.userId': { $ne: theUserId }},
{ $push: { likes: { userId: theUserId, createdAt: new Date() }}},
{ 'new': true }, function(err, post) { // do the needful });
The delete is
findOneAndUpdate({ _id: theId, 'likes.userId': theUserId},
{ $pull: { likes: { userId: theUserId }}},
{ 'new': true }, function(err, post) { // do the needful });
This makes the whole operation atomic and there are no duplicates with respect to the userId field.
I hope this helpes. If you have any query, feel free to ask.

As far as I know MongoDB now (from v 4.2) allows to use aggregation pipelines for updates.
More or less elegant way to make it work (according to the question) looks like the following:
db.runCommand({
update: "your-collection-name",
updates: [
{
q: {},
u: {
$set: {
"pfms.$[elem]": {
"n":"apples",
"mState": NumberInt(1111234)
}
}
},
arrayFilters: [
{
"elem.n": {
$eq: "apples"
}
}
],
multi: true
}
]
})

In my scenario, The data need to be init when not existed, and update the field If existed, and the data will not be deleted. If the datas have these states, you might want to try the following method.
// Mongoose, but mostly same as mongodb
// Update the tag to user, If there existed one.
const user = await UserModel.findOneAndUpdate(
{
user: userId,
'tags.name': tag_name,
},
{
$set: {
'tags.$.description': tag_description,
},
}
)
.lean()
.exec();
// Add a default tag to user
if (user == null) {
await UserModel.findOneAndUpdate(
{
user: userId,
},
{
$push: {
tags: new Tag({
name: tag_name,
description: tag_description,
}),
},
}
);
}
This is the most clean and fast method in the scenario.

As a business analyst , I had the same problem and hopefully I have a solution to this after hours of investigation.
// The customer document:
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK"
}
]
}
// The problem : I want to insert dateField "01.01.2016" to customer documents where customerCodes subdocument has a document with code "YK" but does not have dateField. The final document must be as follows :
{
"id" : "1212",
"customerCodes" : [
{
"code" : "I"
},
{
"code" : "YK" ,
"dateField" : "01.01.2016"
}
]
}
// The solution : the solution code is in three steps :
// PART 1 - Find the customers with customerCodes "YK" but without dateField
// PART 2 - Find the index of the subdocument with "YK" in customerCodes list.
// PART 3 - Insert the value into the document
// Here is the code
// PART 1
var myCursor = db.customers.find({ customerCodes:{$elemMatch:{code:"YK", dateField:{ $exists:false} }}});
// PART 2
myCursor.forEach(function(customer){
if(customer.customerCodes != null )
{
var size = customer.customerCodes.length;
if( size > 0 )
{
var iFoundTheIndexOfSubDocument= -1;
var index = 0;
customer.customerCodes.forEach( function(clazz)
{
if( clazz.code == "YK" && clazz.changeDate == null )
{
iFoundTheIndexOfSubDocument = index;
}
index++;
})
// PART 3
// What happens here is : If i found the indice of the
// "YK" subdocument, I create "updates" document which
// corresponds to the new data to be inserted`
//
if( iFoundTheIndexOfSubDocument != -1 )
{
var toSet = "customerCodes."+ iFoundTheIndexOfSubDocument +".dateField";
var updates = {};
updates[toSet] = "01.01.2016";
db.customers.update({ "id" : customer.id } , { $set: updates });
// This statement is actually interpreted like this :
// db.customers.update({ "id" : "1212" } ,{ $set: customerCodes.0.dateField : "01.01.2016" });
}
}
}
});
Have a nice day !

Related

Find and Replace NumberLong type field in mongodb

I am trying to update some NumberLong type value in one of my collection with some other NumberLong value. Below is my collection structure :
{
"_id" : ObjectId("55e57337d4c6cf80e68b1fe3"),
"tenantId" : NumberLong(7),
"refId" : NumberLong(20),
"refIdentifierName" : "resourceInstanceId",
"config" : {
"contract" : {
"contractLength" : "12 months",
"startDate" : "2015-01-21T09:36:39+00:00",
"billingFrequency" : "monthly",
"margin" : 9,
"endDate" : "2016-01-21T09:36:39+00:00"
}
}
Lets say here i am trying to update all tenantId with value 7 to 8.
This is the script i am using :
var cursor = db.resourceInstanceConfiguration.find();
while (cursor.hasNext()) {
var x = cursor.next();
x['tenantId'] = x['tenantId'].replace('7','8');
db.resourceInstanceConfiguration.update({_id : x._id}, x);
}
Getting error : TypeError: Object NumberLong(6) has no method 'replace'
You can do achieve this by using the following query:
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
db.collectionName.update({<field>:<value>},{$set:{<field>:<New Value>}},<upsert>,<multi>);
Seems some people were very quick to upvote another answer without realizing it cannot possibly work, nor is it a very good explanation.
The two problems here are that you basically need to "loop" the results in order to read the values you want to change and then write them back. This is probably best handled with "Bulk" operations and best done under the shell as a one of script.
The "next" problem is the BSON type information here is "sticky" and that you cannot simply change the value to an plain double in code and write it back. You need to actually "remove" the field completely from the document "before" you can write back a converted value.
Again the whole process lends well to bulk operations as follows:
var bulk = db.resourceInstanceConfiguration.initializeOrderedBulkOp(),
count = 0;
db.junk.find({
"$or": [
{ "tenantId": { "$type": 18 } },
{ "refId": { "$type": 18 } }
]
}).forEach(function(doc) {
print(doc.a.valueOf());
bulk.find({ "_id": doc._id }).updateOne({
"$unset": { "tenantId": "", "refId": "" }
});
bulk.find({ "_id": doc._id }).updateOne({
"$set": {
"tenantId": doc.tenantId.valueOf(),
"refId": doc.tenantId.valueOf()
}
});
count += 2;
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.resourceInstanceConfiguration.initializeOrderedBulkOp();
}
});
if ( count % 1000 != 0 )
bulk.execute();
So you essentially $unset first to remove all trace for the BSON data for the field and the $set with the new value.
All values will then be a standard "double" or BSON type 1 as also confirmed by the $type operator.

Query by two params with $and in mongoose?

I have a user model like this:
user : {
myArmy : {
mySoldiers : [
{
positioned : false,
soldierInfo : {
_id : s99212
}
},
{
positioned : true,
soldierInfo : {
_id : s99112
}
}
]
}
},
user : {
myArmy : {
mySoldiers : [
{
positioned : true,
soldierInfo : {
_id : s99212
}
},
{
positioned : false,
soldierInfo : {
_id : s99112
}
}
]
}
}
...
I have a query that i want to do to return the user(s) who have soldier id s99212 positioned (true): (could be thousands of those, and i need to read and retrieve them all)
This is my faulty query with mongoose:
var soldierId = s99212;
stream = User.find({
$and: [
{'myArmy.mySoldier.positioned': {$ne: null}},
{'myArmy.mySoldier.soldierInfo._id': soldierId}
]
}).lean(true).stream();
Nothing is returned by this query, should there be another way to do this $and stuff?
How exactly am i suppose to use $elemMatch if at all, should it be instead of the find? (If it worth to mention, i want to return the complete user object, not just parts of it...)
Tried this, crashed my app:
stream = User.find({
'$elemMatch': [
{'myArmy.mySoldiers.pos': {$ne: null}},
{'myArmy.mySoldiers.soldierInfo._id': soldierId}
]
}).lean(true).stream();
I know i have a small syntax problem, where is it?
Very simple and well documented. The $elemMatch operator acts as a "query in itself", where it applies "it's conditions" to members of the array specified:
var query = User.find({
"myArmy.mySoldiers": {
"$elemMatch": {
"positioned": { "$ne": null },
"soldierInfo._id": soldierId
}
}
});
Therefore for a document to "match" then the conditions specfied under $elemMatch must be present and valid for the "same" array element. Array on "left", arguments on "right".
Other "dot notation" forms only ever test that the values match "some element" in the array, and not necessarily the same one.

MongoDB: Too many positional (i.e. '$') elements found in path

I just upgraded to Mongo 2.6.1 and one update statement that was working before is not returning an error. The update statement is:
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$.comments.$.name': 'joe'
}},
{ multi: true }
)
The error I get is:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 2,
"errmsg" : "Too many positional (i.e. '$') elements found in path 'answers.$.comments.$.createUsername'"
}
})
When I update an element just one level deep instead of two (i.e. answers.$.name instead of answers.$.comments.$.name), it works fine. If I downgrade my mongo instance below 2.6, it also works fine.
You CAN do this, you just need Mongo 3.6! Instead of redesigning your database, you could use the Array Filters feature in Mongo 3.6, which can be found here:
https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters
The beauty of this is that you can bind all matches in an array to a variable, and then reference that variable later. Here is the prime example from the link above:
Use arrayFilters.
MongoDB 3.5.12 extends all update modifiers to apply to all array
elements or all array elements that match a predicate, specified in a
new update option arrayFilters. This syntax also supports nested array
elements.
Let us assume a scenario-
"access": {
"projects": [{
"projectId": ObjectId(...),
"milestones": [{
"milestoneId": ObjectId(...),
"pulses": [{
"pulseId": ObjectId(...)
}]
}]
}]
}
Now if you want to add a pulse to a milestone which exists inside a project
db.users.updateOne({
"_id": ObjectId(userId)
}, {
"$push": {
"access.projects.$[i].milestones.$[j].pulses": ObjectId(pulseId)
}
}, {
arrayFilters: [{
"i.projectId": ObjectId(projectId)
}, {
"j.milestoneId": ObjectId(milestoneId)
}]
})
For PyMongo, use arrayFilters like this-
db.users.update_one({
"_id": ObjectId(userId)
}, {
"$push": {
"access.projects.$[i].milestones.$[j].pulses": ObjectId(pulseId)
}
}, array_filters = [{
"i.projectId": ObjectId(projectId)
}, {
"j.milestoneId": ObjectId(milestoneId)
}])
Also,
Each array filter must be a predicate over a document with a single
field name. Each array filter must be used in the update expression,
and each array filter identifier $[] must have a corresponding
array filter. must begin with a lowercase letter and not contain
any special characters. There must not be two array filters with the
same field name.
https://jira.mongodb.org/browse/SERVER-831
The positional operator can be used only once in a query. This is a limitation, there is an open ticket for improvement: https://jira.mongodb.org/browse/SERVER-831
As mentioned; more than one positional elements not supported for now. You may update with mongodb cursor.forEach() method.
db.post
.find({"answers.comments.name": "jeff"})
.forEach(function(post) {
if (post.answers) {
post.answers.forEach(function(answer) {
if (answer.comments) {
answer.comments.forEach(function(comment) {
if (comment.name === "jeff") {
comment.name = "joe";
}
});
}
});
db.post.save(post);
}
});
db.post.update(
{ 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$[i].comments.$.name': 'joe'
}},
{arrayFilters: [ { "i.comments.name": { $eq: 'jeff' } } ]}
)
check path after answers for get key path right
I have faced the same issue for the as array inside Array update require much performance impact. So, mongo db doest not support it. Redesign your database as shown in the given link below.
https://pythonolyk.wordpress.com/2016/01/17/mongodb-update-nested-array-using-positional-operator/
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$.comments.$.name': 'joe'
}},
{ multi: true }
)
Answer is
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.0.comments.1.name': 'joe'
}},
{ multi: true }
)

Merge changeset documents in a query

I have recorded changes from an information system in a mongo database. Every time a set of values are set or changed, a record is saved in the mongo database.
The change collection is in the following form:
{ "user_id": 1, "timestamp": { "date" : "2010-09-22 09:28:02", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldA": "valueA", "fieldB": "valueB", "fieldC": "valueC" } }
{ "user_id": 1, "timestamp": { "date" : "2010-09-24 19:01:52", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldA": "new_valueA", "fieldB": null, "fieldD": "valueD" } }
{ "user_id": 1, "timestamp": { "date" : "2010-10-01 11:11:02", "timezone_type" : 3, "timezone" : "Europe/Paris" } }, "changes: { "fieldD": "new_valueD" } }
Of course there are thousands of records per user with different attributes which represent millions of records. What I want to do is to see a user status at a given time. By example, the user_id 1 at 2010-09-30 would be
fieldA: new_valueA
fieldC: valueC
fieldD: valueD
This means I need to flatten all the changes prior to a given date for a given user into a single record. Can I do that directly in mongo ?
Edit: I am using the 2.0 version of mongodb hence cannot benefit from the aggregation framework.
Edit: It sounds I have found the answer to my question.
var mapTimeAndChangesByUserId = function() {
var key = this.user_id;
var value = { timestamp: this.timestamp.date, changes: this.changes };
emit(key, value);
}
var reduceMergeChanges = function(user_id, changeset) {
var mergeFunction = function(a, b) { for (var attr in b) a[attr] = b[attr]; };
var result = {};
changeset.forEach(function(e) { mergeFunction(result, e.changes); });
return { timestamp: changeset.pop().timestamp, changes: result };
}
The reduce function merges the changes in the order they come and returns the result.
db.user_change.mapReduce(
mapTimeAndChangesByUserId,
reduceMergeChanges,
{
out: { inline: 1 },
query: { user_id: 1, "timestamp.date": { $lt: "2010-09-30" } },
sort: { "timestamp.date": 1 }
});
'results' : [
"_id": 1,
"value": {
"timestamp": "2010-09-24 19:01:52",
"changes": {
"fieldA": "new_valueA",
"fieldB": null,
"fieldC": "valueC",
"fieldD": "valueD"
}
}
]
Which is fine to me.
You could write a MR to do this.
Since the fields are a lot like tags you can modify a nice cookbook example of counting tags here: http://cookbook.mongodb.org/patterns/count_tags/ of course instead of counting you want the latest value applied (assumption since this is not clear in your question) for that field.
So lets get our map function:
map = function() {
if (!this.changes) {
// If there were not changes for some reason lets bail this record
return;
}
// We iterate the changes
for (index in this.changes) {
emit(index /* We emit the field name */, this.changes[index] /* We emit the field value */);
}
}
And now for our reduce:
reduce = function(values){
// This part is dependant upon your input query. If you add a sort of
// date (ts) DESC then you will prolly want the first index (0) not the last as
// gathered here by values.length
return values[values.length];
}
And this will output a single document per field change of the type:
{
_id: your_field_ie_fieldA,
value: whoop
}
You can then iterate the end of the (most likely) in line output and, bam, you have your changes.
This is of course one way of dong it and is not designed to be run completely in line to your app, however that all depends on the size of the data your working on; it could be run very close.
I am unsure whether the group and distinct can run on this but it looks like it might: http://docs.mongodb.org/manual/reference/method/db.collection.group/#db-collection-group however I should note that group is basically a MR wrapper but you could do something like (untested just like the MR above):
db.col.group( {
key: { 'changes.fieldA': 1, // the rest of the fields },
cond: { 'timestamp.date': { $gt: new Date( '01/01/2012' ) } },
reduce: function ( curr, result ) { },
initial: { }
} )
But it does require you to define the keys instead of just iterating them programmably (maybe a better way).

In mongoDb, how do you remove an array element by its index?

In the following example, assume the document is in the db.people collection.
How to remove the 3rd element of the interests array by it's index?
{
"_id" : ObjectId("4d1cb5de451600000000497a"),
"name" : "dannie",
"interests" : [
"guitar",
"programming",
"gadgets",
"reading"
]
}
This is my current solution:
var interests = db.people.findOne({"name":"dannie"}).interests;
interests.splice(2,1)
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});
Is there a more direct way?
There is no straight way of pulling/removing by array index. In fact, this is an open issue http://jira.mongodb.org/browse/SERVER-1014 , you may vote for it.
The workaround is using $unset and then $pull:
db.lists.update({}, {$unset : {"interests.3" : 1 }})
db.lists.update({}, {$pull : {"interests" : null}})
Update: as mentioned in some of the comments this approach is not atomic and can cause some race conditions if other clients read and/or write between the two operations. If we need the operation to be atomic, we could:
Read the document from the database
Update the document and remove the item in the array
Replace the document in the database. To ensure the document has not changed since we read it, we can use the update if current pattern described in the mongo docs
You can use $pull modifier of update operation for removing a particular element in an array. In case you provided a query will look like this:
db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})
Also, you may consider using $pullAll for removing all occurrences. More about this on the official documentation page - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull
This doesn't use index as a criteria for removing an element, but still might help in cases similar to yours. IMO, using indexes for addressing elements inside an array is not very reliable since mongodb isn't consistent on an elements order as fas as I know.
in Mongodb 4.2 you can do this:
db.example.update({}, [
{$set: {field: {
$concatArrays: [
{$slice: ["$field", P]},
{$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
]
}}}
]);
P is the index of element you want to remove from array.
If you want to remove from P till end:
db.example.update({}, [
{ $set: { field: { $slice: ["$field", 1] } } },
]);
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to update an array by removing an element at a given index:
// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
{ "name": "dannie" },
[{ $set:
{ "interests":
{ $function: {
body: function(interests) { interests.splice(2, 1); return interests; },
args: ["$interests"],
lang: "js"
}}
}
}]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify. The function here simply consists in using splice to remove 1 element at index 2.
args, which contains the fields from the record that the body function takes as parameter. In our case "$interests".
lang, which is the language in which the body function is written. Only js is currently available.
Rather than using the unset (as in the accepted answer), I solve this by setting the field to a unique value (i.e. not NULL) and then immediately pulling that value. A little safer from an asynch perspective. Here is the code:
var update = {};
var key = "ToBePulled_"+ new Date().toString();
update['feedback.'+index] = key;
Venues.update(venueId, {$set: update});
return Venues.update(venueId, {$pull: {feedback: key}});
Hopefully mongo will address this, perhaps by extending the $position modifier to support $pull as well as $push.
I would recommend using a GUID (I tend to use ObjectID) field, or an auto-incrementing field for each sub-document in the array.
With this GUID it is easy to issue a $pull and be sure that the correct one will be pulled. Same goes for other array operations.
For people who are searching an answer using mongoose with nodejs. This is how I do it.
exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my other field in my db
Test.findOneAndUpdate({ tCodigo: codTest },
{
'$unset': {
[inPregunta]: 1, // put the field with []
[inOpciones]: 1,
[inTipo]: 1
}
}).then(()=>{
Test.findOneAndUpdate({ tCodigo: codTest }, {
'$pull': {
'tPreguntas.0.pregunta': null,
'tPreguntas.0.opciones': null,
'tPreguntas.0.tipo': null
}
}).then(testModificado => {
if (!testModificado) {
res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
} else {
res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
}
})}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
}
I can rewrite this answer if it dont understand very well, but I think is okay.
Hope this help you, I lost a lot of time facing this issue.
It is little bit late but some may find it useful who are using robo3t-
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
interests: "guitar" // you can change value to
}
},
{ multi: true }
);
If you have values something like -
property: [
{
"key" : "key1",
"value" : "value 1"
},
{
"key" : "key2",
"value" : "value 2"
},
{
"key" : "key3",
"value" : "value 3"
}
]
and you want to delete a record where the key is key3 then you can use something -
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
property: { key: "key3"} // you can change value to
}
},
{ multi: true }
);
The same goes for the nested property.
this can be done using $pop operator,
db.getCollection('collection_name').updateOne( {}, {$pop: {"path_to_array_object":1}})