MongoDB: Replace object in array - node.js

I am trying to replace/update a whole object in an array to it's latest values, but I cannot get it to work.
Db looks like this: (Note: there is only 1 main object in this collection)
{
"_id": {...},
"something that doesnt matter": {...},
"var1": {
"var2": [{...}, {...}, {...}, {...}, {...}],
"var3": [{...}, {...}, {...}, {...}, {...}]
},
"something that doesnt matter": {...}
}
I need to update a certain object from array var2, I have the object ID or there is a custom ID in the object that I can also get it with (id == updatedObject.id)
This worked but I cannot get it to work with a custom array id
await db.collection("collectionName").findOneAndUpdate(
{"var1.var2": { $exists: true }},
{ $set: { "var1.var2.1": updatedObject } }
);
I have the ID of the object already in the array on the db, but idk how to update it from var1.var2.ID,
so basically what I need is { $set: { "var1.var2.**ID**": updatedObject } } but I cant seem to find out how to get it to work.
Cause I dont want to update the whole array, and I also dont want to update a single variable in the object. I need to update the whole object.
Thank you in advance for your replies.

Have you tried as below
await db.collection("collectionName").findOneAndUpdate(
{
"var1.var2.id": id // id value (or any matching field) of object inside array you want to update
},
{
$set: {
"var1.var2.$": updatedObject // Update with new object
}
}
);
Hope this official mongodb documentation helps better for your requirement.

Sorry, I'm not able to comment but the above answer is almost correct except that you have to filter by var1.var2._id instead of var1.var2.id because mongodb default ID field is _id

Related

Mongoose: create multiple documents if filter finds none

I'm trying to create multiple documents based on a filter: if document not found => create it.
After searching a bit I found that the correct way to do so is by using updateMany and setting upsert: true (docs).
This made somewhat sense from the documentation's example but as I understand it the filter modifier would be used for the newly-created document. As in the example:
try {
db.inspectors.updateMany(
{ "Sector" : { $gt : 4 }, "inspector" : "R. Coltrane" },
{ $set: { "Patrolling" : false } },
{ upsert: true }
);
} catch (e) {
print(e);
}
"Inspector" : "R. Coltrane" would be inserted to the newly-created document.
But what if my setOnInsert modifier contains the same field as the one in the filter?
What my code looks like:
//first find the already-created tags
await tagModel.find({"tagName": tags}).select('tagName -_id').exec()
.then(async (result: Tag[])=>{
//create the new tags
const newTags = tags.map((tag: any)=>new tagModel({tagName: tag}));
//now insert only the new tags, filtering out the already-created tags ("result")
await tagModel.updateMany(
{"tagName": result},
{$setOnInsert: newTags} ,
{upsert: true},
(err:any, res:any)=>{
...
At first, result is an empty Array ([]). What is created in my MongoDB database is a new Tag document, but its tagName is the result object. Meaning, it looks like:
{
"_id": {
"$oid": "61659c92c6267fe11963b236"
},
"tagName": {
"$in": []
}
}
So essentially my question is, what am I suppose to do in this case where my update modifier should replace my filter query? Perhaps it's just something bad in my code that makes the updateMany function to malfunction? Or should I replace it with a different function?

Push Array Items to Array type Column in mongoDb

This is a Controller in which I'm trying to catch multiple candidates id(ObjectId) and try to store it in the database in the array Candidates. But data is not getting pushed in Candidates column of Array type.
routes.post('/Job/:id',checkAuthenticated,function(req,res){
var candidates=req.body.candidate;
console.log(candidates);
Job.update({_id:req.params.id},{$push:{Appliedby : req.user.username}},{$push:{Candidates:{$each:
candidates}}}
});
Console screens output
[ '5eb257119f2b2f0b4883558b', '5eb2ae1cff3ae7106019ad7e' ] //candidates
you have to do all the update operations ($set, $push, $pull, ...) in one object, and this object should be the second argument passed to the update method after the filter object
{$push:{Appliedby : req.user.username}},{$push:{Candidates:{$each: candidates}}
this will update the Appliedby array only, as the third object in update is reserved for the options (like upsert, new, ....)
you have to do something like that
{ $push: { Appliedby: req.user.username, Candidates: { $each: candidates } } }
then the whole query should be something like that
routes.post('/Job/:id', checkAuthenticated, function (req, res) {
var candidates = req.body.candidate;
console.log(candidates);
Job.update(
{ _id: req.params.id }, // filter part
{ $push: { Appliedby: req.user.username, Candidates: { $each: candidates } } } // update part in one object
)
});
this could do the trick I guess, hope it helps

Mongoose FindOneandUpdate - Only update Specific fields leave others alone

Maybe I'm not wording this properly but hopefully I can find some help with this. I've been playing around with mongoose schema's and boy its doing my head in with this issue..
I want to update a document on a collection under a specific ID, I've got all that working however. I'm running into an issue where since I never supplied values for others I dont want updated it makes them either all Null or deletes them if omitUndefined is turned on.
router.put('/api/playerinfo/:player_steamID/main_server', passport.authenticate('basic', {session: false}),
function(req, res){
const params = {
main_server: {
game_stats:{
kills: req.body.main_server.game_stats.kills,
deaths: req.body.main_server.game_stats.deaths,
}
}
};
PlayerInfo.findOneAndUpdate({player_steamID: req.body.player_steamID }, {$set: params}, { upsert: true, new: true }).then(playerinfo =>{
res.json(playerinfo)
});
});
For example:
Current Stored information
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 3,
"deaths":0
}
}
}
Then if we used the above code to modify the fields.. But we did not supply a death value for that field, it would null out the bottom or remove it entirely if omit is true.
Sending postman update with the following:
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 34
}
}
}
Updated Information
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 34,
"deaths": null
}
}
}
What I would like to know is the best way to modify multiple fields without it "changing" the last value. if a field is missing. Is that possible?
IE: If i only supply kills value and leave json update for deaths blank it would retain the old value from deaths.
Thanks.
-- Fixed.
created a object inside $set then to update old values without replacing you need to wrap them in quotes. 'main_server.game_stats.kills' : req.body.....kills etc.
Use $set to update only certain fields.
PlayerInfo.findOneAndUpdate({steamID: req.body.steamID },{ $set: {'yourcoll.$.someField': 'Value'} }, { upsert: true, new: true }).then(playerinfo =>{
res.json(playerinfo)
});
Documentation:
https://docs.mongodb.com/manual/reference/operator/update/set/
Fixed. created a object inside $set then to update old values without replacing you need to wrap them in quotes. 'main_server.game_stats.kills' : req.body.....kills etc.
I also did a for loop in the object and assigned props to handle multiple values or single if passed through from json.
You can try just put part of model, which contains specific field for update
_userModelRepository.updateUser( { email: req.user.email }, { specificField: specificValue});
Here's schema implementation
updateUser = function(query, params){
return _userModelRepository.findOneAndUpdate(query, params, function(err, result) {
if (err) {
throw err;
}
});
}
What I did was
retrieve the original value in hidden input field using Ejs something like:
<input type="hidden" name="nameOf_thefield" value="<%= variable %>" />
Pass the value req.body.nameOf_thefield to the variable which you wish not to change

I want to update sub document in array

I now use mongooses to pull and pull subdocuments to the array, and now I want to change the contents of the detail field of that subdocument with the _id of the subdocument.
{
subDocument: [{
_id: ObjectId('123'),
detail: 'I want update this part'
}]
}
I tried to use the $set method as shown below but it did not work as expected.
Model.findByIdAndUpdate(uid, { $Set: {subDocument: {_id: _id}}});
Looking at the for statement as shown below is likely to have a bad effect on performance. So I want to avoid this method.
const data = findById(uid);
for(...) {
if(data.subDocument[i]._id==_id) {
data.subDocument[i].detail = detail
}
}
Can you tell me some mongodb queries that I can implement?
And, Is it not better to use the 'for(;;)' statement shown above than to search using mongodb's query?
This should work:
Model.findOneAndUpdate({"subdocument._id": uid},
{
$set: {
"subdocument.$.detail ": "detail here"
}
},
).exec(function(err, doc) {
//code
});
To find subdocument by id, I am using something like this :
var subDocument = data.subDocument.id(subDocumentId);
if (subDocument) {
// Do some stuff
}
else {
// No subDocument found
}
Hope it helps.

how to remove object in array by index mongodb / mongoose [duplicate]

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}})

Resources