Updating objects within array in MongoDB with mongoose - javascript

I'm trying to update objects within an array in a document. The objects are not subdocuments and therefore don't have a unique ID. I've tried looking at various methods to do this but it just doesn't seem to have any effect (as in, nothing happens - no errors, but the document doesn't update.
Probably easier just to show the code. First, here's an example of a document in this collection:
{
_id: 123,
//other fields go here
submissions: [
{
submission: 1,
downloaded: null, //This should be an ISODate when populated
assessor: null, //This should be an ObjectID when populated
feedbackUploaded: null, //This should also be an ISODate
path: '401Test1.doc',
assessorNotes: null //This should be a string when populated
},
{
submission: 2,
downloaded: null,
assessor: null,
feedbackUploaded: null,
path: '401Test2.doc',
assessorNotes: null
}
]
}
And now my mongoose query (within an express route):
const unit = req.body.unit; //e.g. 123
const submission = req.body.submission // e.g. 2
const date = Date.now();
Unit.update({ "_id": unit, "submissions.submission": submission }, {
$set: {
'submissions.assessorNotes': req.body.assessorComments,
'submissions.feedbackUploaded': date,
'submissions.assessor': req.body.assessor
}, function(err){
//callback
}
})
I've also tried this using $ notation in the $set command i.e. 'submissions.$.assessorNotes': req.body.assessorComments' but this doesn't seem to work either.
Where am I going wrong?!
Cheers!
Chris

Just tested this with the positional operator as mentioned above by TomG in a shell.
> db.foo.find().pretty()
{
"_id" : 123,
"submissions" : [
{
"submission" : 1,
"downloaded" : null,
"assessor" : null,
"feedbackUploaded" : null,
"path" : "401Test1.doc",
"assessorNotes" : null
},
{
"submission" : 2,
"downloaded" : null,
"assessor" : null,
"feedbackUploaded" : null,
"path" : "401Test2.doc",
"assessorNotes" : null
}
]
}
Then I ran the update command:
db.foo.update(
{ _id: 123, "submissions.submission": 2 },
{ $set: {
"submissions.$.assessorNotes": "testAssessorNotes",
"submissions.$.feedbackUploaded": new Date(),
"submissions.$.assessor": "testAssessor"
} }
)
Results are as follows:
{
"_id" : 123,
"submissions" : [
{
"submission" : 1,
"downloaded" : null,
"assessor" : null,
"feedbackUploaded" : null,
"path" : "401Test1.doc",
"assessorNotes" : null
},
{
"submission" : 2,
"downloaded" : null,
"assessor" : "testAssessor",
"feedbackUploaded" : ISODate("2016-10-26T15:33:09.573Z"),
"path" : "401Test2.doc",
"assessorNotes" : "testAssessorNotes"
}
]
}
Edit: My hunch is that the error could also be with the update in Mongoose, you could try changing both to the positional operators, as well as using findOneAndUpdate if it fits your needs instead of update.
Please try this query for Mongoose:
Unit.update(
{ "_id": unit, "submissions.submission": submission },
{ $set: {
'submissions.$.assessorNotes': req.body.assessorComments,
'submissions.$.feedbackUploaded': date,
'submissions.$.assessor': req.body.assessor
} }, function(err, result) {
//callback
}
});

Related

updateOne returns "matchedCount": 0, "modifiedCount": 0

I want to just update two fields using two criteria in updateOne method.
Let's say that I got a document like this:
{ "_id" : ObjectId("123"), "name" : "someName", "text" : "someText", "date" : "someDay"}
Then I have function that is responsible for this update:
static async updateSomething( id , someName, newText, newDate) {
try {
const updateResponse = await collection.updateOne(
{ _id: ObjectId(id), name: someName },
{ $set: { text: newText, date: newDate} },
)
console.log( id, someName, newText, newDate );
console.log(updateResponse.matchedCount);
console.log(updateResponse.modifiedCount);
return **updateResponse**
} catch (e) {
console.error(`Unable to update comment: ${e}`)
return { error: e }
}
}
Then, when I'm finally calling thi function with:
updateSomething( 123, "someName", "new text that i want", "new date that i want")
My first log confirms that data matches, but next two logs returning 0.
And nothing has been updated.
Anyone knows what is it about?
I was looking in docs, syntax looks good, I was searching answer anywhere else, found non of this case.
//actual code output from the mongo shell
//you need to remove Object ID in the command and run the below code. it returns //matchedCount and modifiedCount correctly
> db.test4.find();
{ "_id" : "123", "name" : "someName", "text" : "someText", "date" : "someDay" }
> db.test4.updateOne(
... {_id: "123", name:"someName"},
... {$set:{text:"new Text", date: "new Date"}}
... );
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.version();
4.2.6
>

Don't replace null with zero in MongoDB

I wrote a mongo shell script to insert in a new collection the result of an aggregation; my problem is that the null values from the source collection are replaced by zero; the aggregation part is:
db.getCollection("unhcr_pop_concern_flat").aggregate(
[
{
"$group" : {
"_id" : {
"country" : "$country",
"time" : "$time",
"origin": "$origin"
},
"value_fields": {
"$addToSet": {
k: "$type",
v: {$ifNull: ["$value", null]}
}
}
}
},
{ $addFields: { value_fields: { $arrayToObject: "$value_fields" } } }
],
{
"allowDiskUse" : true
}
Where is the problem? I have also used the $ifnull operator.
The query is working correctly; there was a problem in the source collections where data were changed by others!

How to add within an array information using findOneAndUpdate without deleting information that was previously contained [duplicate]

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

How to get element from collection in Meteor if it multi-dimensional array or object or both?

I have collection "groups". like this:
{
"_id" : "e9sc7ogDp8pwY2uSX",
"groupName" : "one",
"creator" : "KPi9JwvEohKJsFyL4",
"eventDate" : "",
"isEvent" : true,
"eventStatus" : "Event announced",
"user" : [
{
"id" : "xfaAjgcSpSeGdmBuv",
"username" : "1#gmail.com",
"email" : "1#gmail.com",
"order" : [ ],
"price" : [ ],
"confirm" : false,
"complete" : false,
"emailText" : ""
},
...
],
...
"buyingStatus" : false,
"emailTextConfirmOrder" : " With love, your Pizzaday!! "
}
How can I get a value of specific element? For example i need to get value of "Groups.user.confirm" of specific group and specific user.
I tried to do so in methods.js
'pizzaDay.user.confirm': function(thisGroupeId, thisUser){
return Groups.find({ _id: thisGroupeId },{"user": ""},{"id": thisUser}).confirm
},
but it returns nothing.
Even in mongo console I can get just users array using
db.groups.findOne({ _id: "e9sc7ogDp8pwY2uSX"},{"user": ""})
The whole code is github
http://github.com/sysstas/pizzaday2
Try the following query:-
db.groups.aggregate(
[
{
$match:
{
_id: thisGroupeId,
"user.id": thisUser
}
},
{
$project:
{
groupName : 1,
//Add other fields of `user` level, if want to project those as well.
user:
{
"$setDifference":
[{
"$map":
{
"input": "$user",
"as": "o",
"in":
{
$eq : ["$$o.id" , thisUser] //Updated here
}
}
},[false]
]
}
}
}
]);
The above query will give the object(s) matching the query in $match inside user array. Now you can access any field you want of that particular object.
'pizzaDay.user.confirm': function(){
return Groups.findOne({ _id: thisGroupeId }).user.confirm;
I resolved it using this:
Template.Pizzaday.helpers({
confirm: function(){
let isConfirm = Groups.findOne(
{_id: Session.get("idgroupe")}).user.filter(
function(v){
return v.id === Meteor.userId();
})[0].confirm;
return isConfirm;
},
});
But I still think that there is some much elegant way to do that.

$pull from array in document

I am trying to $pull from an array within a mongodb document.
The document has the structure:
{
"_id" : ObjectId("54ee62ef688b41ff072b934b"),
"pictures" : [
{
"url" : "...",
"_id" : ObjectId("54ee6303688b41ff072b934d")
},
{
"url" : "...",
"_id" : ObjectId("54ee6304688b41ff072b934e")
},
{
"url" : "",
"_id" : ObjectId("54ee6304688b41ff072b934f")
}
]
}
I tried the update object
var update = { $pull: { pictures: {$elemMatch: {_id:req.params.picid } } } }
db.activity.update({_id: new ObjectId(req.params.id)}, update)
which returns writeresult: 1, but the picture is never removed.
ps I am using node, hense the req.params.picid
The $pull operator acts as a query document in itself and is also considered against every element of the array so $elemMatch is not needed:
var update = {
"$pull": { "pictures": { "_id": new ObjectId(req.params.picid) } }
};
You also need to cast your "string" from request params to an ObjectId.

Categories