Remove a single object from an array of objects in a single document of a collection - node.js

I have a problem with which I am struggling for quite some time.Suppose my document is like this
{"owner":"princu7", "books":[{"name":"the alchemist"}, {"name":"the alchemist"}, {"name":"the alchemist"}].
Now what do I do if I have to just remove one single element from the books array based on the matching of the name?I did it like this
var bookName="the alchemist";
var obj={"name":bookName}
db.collection("colName").update({"owner":"princu7"}, {$pull:{books:obj}}, {multi:false})
But the problems is that it removes all the entries in the array which have the name matching to "the alchemist". What I wanted was this
{"owner":"princu7", "books":[{"name":"the alchemist"}, {"name":"the alchemist"}
But what I got was this
{"owner":"princu7", "books":[]}
Upon reading the documentation, it says that pull removes all the instances that match the required condition so maybe that's why it's removing all other entries of the array which match the condition.So what should I do here.Thanks for reading.Really appreciate your help.

See this issue:
https://jira.mongodb.org/browse/SERVER-1014
You cannot achieve what you are trying to do in a single update. A way would be to modify that record in your application & save the changes.

You could use collection.updateOne() with upsert set to true to re-write the record in place. The idea is you get the original document, modify it in your app logic, then re-apply it to the database after removing the element from the array.
function upsert(collection, query, json) {
var col = db.collection(collection);
col.updateOne(query
, { $set : json }
, { upsert : true }
, function (err, result) {
if(err) {
log('error = ', err);
} else {
// no error, call the next function
}
}
);
};

By design, mongodb's $pull operator removes from an existing array all instances of a value or values that match the specified condition.Therefore it will remove all matching {"name":"the alchemist"} elements from the array.
I guess I would use $pop, which only removes the first matching element.
db.collection("colName")
.update({"owner": "princu7"}
, {$pop: {"books": {$match: {"name": "the alchemist"}}}}
, {multi:false}
)

Related

Appending objects to array field using $push and $each returns null (Mongoose)

Background:
I have an array (A) of new objects that need to be added to an array field in Mongoose. if successful it should print to screen the newly updated object but it prints null. I checked the database and confirmed it also does not update at all. I followed the docs to use the $push and $each modifiers here:
https://www.mongodb.com/docs/manual/reference/operator/update/each/
Desired Behaviour:
I would like each object in (A) to be added to the Array field, not (A) itself. Upon success, it should print to screen the newly updated object.
Attempted Approach:
let identifier={customer:{external_id:'12345'}}
let array = [{value:30},{value:30}]
User.findOneAndUpdate(identifier,{$push:{points:{$each:array}}},{new:true})
.then((result)=>{
console.log(result)
})
Attempted Resolutions:
I tested if the issue was with the identifier parameter, but it works fine when the update parameter does not have $each (i.e. it pushes the whole array (A) into the array field)
I thought about using $addToSet like in the solution below, but as you can see in the sample code above, I want to push all objects even if they are not unique:
Mongoose - Push objects into nested array
use dot "." notation for embedded field
let filter={'customer.external_id':'12345'}
let array = [{value:30},{value:30}];
let update = { $push: { points: { $each: array } } };
User.findOneAndUpdate(filter,update,{new: true})
.then((result)=>{
console.log(result)
})
It turns out the IDs did not match. There was no issue with the code. Sorry guys.

Mongoose, Nodejs - replace many documents in one I/O?

I have an array of objects and I want to store them in a collection using only one I/O operation if it's possible. If any document already exists in the collection I want to replace it, or insert it otherwise.
These are the solutions that I found, but doesn't work exactly as I want:
insertMany(): this doesn't replace the document that already exists, but throws exception instead (This is what I found in the Mongodb documentation, but I don't know if it's the same as mongoose).
update() or ‎updateMany() with upsert = true: this doesn't help me as well, because here I have to do the same updates to all the to stored documents.
‎There is no replaceMany() in mongodb or mongoose.
Is there anyone how knows any optimal way to do replaceMany using mongoose and node.js
There is bulkWrite (https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/), which makes it possible to execute multiple operations at once. In your case, you can use it to perform multiple replaceOne operations with upsert. The code below shows how you can do it with Mongoose:
// Assuming *data* is an array of documents that you want to insert (or replace)
const bulkData = data.map(item => (
{
replaceOne: {
upsert: true,
filter: {
// Filter specification. You must provide a field that
// identifies *item*
},
replacement: item
}
}
));
db.bulkWrite(bulkData);
You need to query like this:
db.getCollection('hotspot').update({
/Your Condition/
}, {
$set: {
"New Key": "Value"
}
}, {
multi: true,
upsert: true
});
It fulfils your requirements..!!!

MongoDB & Mongoose: How do I get the index of the removed item when using pull?

I have to remove an item from an array of subschemas in a document.
SubSchema = new mongoose.Schema({...})
MySchema = new mongoose.Schema({someArray: [SubSchema]})
(...)
mydoc.somearray.pull(req.body.submodel_id);
However, I need the index of the element that has been removed to notify all connected clients.
Is there an elegant solution to this, or do I have to use _.findIndex or something like that? (I imagine that to have worse performance since it unnecessarily iterates the array twice)
Not sure if an elegant solution exists for this as MongoDB has no way of returning the index of the array element
being pulled within an update operation. One approach (though I would consider it a dirty hack) would be to get the original
array after the update operation and get the removed element index using Array.indexOf() within the update callback.
Consider the following update operation using findOneAndUpdate() to get the update document:
var submodel_id = req.body.submodel_id,
query = { "someArray": submodel_id };
Model.findOneAndUpdate(
query,
{ "$pull": { "someArray": submodel_id } },
{ "new": false },
function(err, doc) {
var removedIndex = doc.someArray.indexOf(submodel_id);
console.log(removedIndex);
}
);

Updating values in array field if criteria matches else inserting MongoDB

I have seen multiple instances of the question i'm asking but none seems to solve my problem.
I have a collection of documents with schema:
{
_id:someId,
brands:[
{_id:1,name:Nike,products:[]},
{_id:2,name:Reebok,products:[]}
]
}
what i want to do is update the array field if any of the array element matches else insert a new object in the brands array.
So far i have come across this:
var args = {
'query': {_id:someId,"brands._id":someBrandId,}
, 'update': {
"$set": {
'brands.$.products':req.tempStore.products
}
},
, 'upsert':true
};
and then i'm using the nodejs driver and used the following:
db.companies.findAndModify(args,function(err,document){
if(err) return next(err);
//some other logic here
})
On executing the above if it finds the object exists inside the brands array it successfully updates it but if it doesn't it gives the following error:
Cannot apply the positional operator without a corresponding query field containing an array
i don't have any clue how to add a new value in the array field if it doesn't exists.

$push in nested field works wonky in mongoose?

I have a schema that's something like this
firstfield :{
secondfield:{
thirdfield : [ObjectId]
}
}
and i do something like this
models.mymodel.update({_id:ObjectId(logId)}, {$push: {"firstfield.secondfield.thirdfield" : ObjectId(userId)}}, function(err, result) {
console.dir(result);
})
Here's the problem. When i give it an existing logId to search for... after it's done, it deletes the whole row.. and creates another one, somehow managing to increment SOME fields (let's assume i have name field set as : mylog_6 (the current row i want to modify) and the maximum is 105.. it will delete that row and create one and it will set the name as mylog_106.
What gives? :)

Resources