Mongo update specific subdoc - mongodb

I have the following mongo entry:
et = {
languages: [{
code: String,
title: String,
tools: [{
description: String,
mds: [ObjectId]
}],
}]
//some more stuff
}
I now need to update this object and add an new ObjectId to the mds array. I need to specify the language element via the code element and the tools entry via the description parameter.
So far I came up with the following update method with which I can update some element of the correct language entry:
ETs.find({
'_id':mdAttributes.etID,
'languages':{'$elemMatch':{'code':mdAttributes.language}}
},{
'$set':{
'languages.$.title':'update2.jpg'
}
});
However I do not know how add an query for the correct tool.
So what my set should make should be something like this:
ETs.find({
'_id':mdAttributes.etID,
'languages':{'$elemMatch':{'code':mdAttributes.language}}
},{
'$set':{
'languages.$.tools.$.mds': ["newId"]
}
});
Is there a way to achieve this in mongo?

Short answer, no. The positional operator doesn't currently work with nested arrays (https://jira.mongodb.org/browse/server-831).
You can do it nonetheless by setting the whole tools array entry (like you do on your first example, but for the tools array instead of the title field).

Related

Mongoose findOne not working as expected on nested records

I've got a collection in MongoDB whose simplified version looks like this:
Dealers = [{
Id: 123,
Name: 'Someone',
Email: 'someone#somewhere.com',
Vehicles: [
{
Id: 1234,
Make: 'Honda',
Model: 'Civic'
},
{
Id: 2345,
Make: 'Ford',
Model: 'Focus'
},
{
Id: 3456,
Make: 'Ford',
Model: 'KA'
}
]
}]
And my Mongoose Model looks a bit like this:
const vehicle_model = mongoose.Schema({
Id: {
Type: Number
},
Email: {
Type: String
},
Vehicles: [{
Id: {
Type: Number
},
Make: {
Type: String
},
Model: {
Type: String
}
}]
})
Note the Ids are not MongoDB Ids, just distinct numbers.
I try doing something like this:
const response = await vehicle_model.findOne({ 'Id': 123, 'Vehicles.Id': 1234 })
But when I do:
console.log(response.Vehicles.length)
It's returned all the Vehicles nested records instead on the one I'm after.
What am I doing wrong?
Thanks.
This question is asked very frequently. Indeed someone asked a related question here just 18 minutes before this one.
When query the database you are requesting that it identify and return matching documents to the client. That is a separate action entirely than asking for it to transform the shape of those documents before they are sent back to the client.
In MongoDB, the latter operation (transforming the shape of the document) is usually referred to as "Projection". Simple projections, specifically just returning a subset of the fields, can be done directly in find() (and similar) operations. Most drivers and the shell use the second argument to the method as the projection specification, see here in the documentation.
Your particular case is a little more complicated because you are looking to trim off some of the values in the array. There is a dedicated page in the documentation titled Project Fields to Return from Query which goes into more detail about different situations. Indeed near the bottom is a section titled Project Specific Array Elements in the Returned Array which describes your situation more directly. In it is where they describe usage of the positional $ operator. You can use that as a starting place as follows:
db.collection.find({
"Id": 123,
"Vehicles.Id": 1234
},
{
"Vehicles.$": 1
})
Playground demonstration here.
If you need something more complex, then you would have to start exploring usage of the $elemMatch (projection) operator (not the query variant) or, as #nimrod serok mentions in the comments, using the $filter aggregation operator in an aggregation pipeline. The last option here is certainly the most expressive and flexible, but also the most verbose.

Mongoose findOneAndUpdate an array within an array within a document

I'm trying to update an array that sits inside another array in a document. The schema is like this:
const projectSchema = new mongoose.Schema({
stakeholders: [{
stakeholderTitle: {
type: String,
},
...
subgroup: [{
subgroupTitle: {
type: String
},
subgroupPercent: {
type: Number,
}
}]
}],
and I'm trying to update the 'subgroup' array. I have got the query to work on its parent (the stakeholder array) with the positional $ operator, using the answer to this question I asked previously. So my query looks like this.....
await db.findOneAndUpdate({ find by the id }, { "stakeholders.$.stakeholderTitle": req.body.stakeholderTitle, ... "stakeholders.$.subgroup": req.body.subgroup })
However, this query doesn't work for the 'stakeholders subgroup' array, and makes it null. Looking through the mongo docs for the positional operator it states that 'The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value', which I guess might be my problem.
So how can I do this with a findOneAndUpdate query?
From what I see is you have to specify the object you want to update inside the subgroup array. Try this - (i.e I'm updating the subgroupTitle of the subgroup array);
await db.findOneAndUpdate(
{
_id: userId,
"stakeholders.stakeholderTitle": req.body.stakeholderTitle,
"stakeholders.stakeholderTitle.subgroup.subgroupTitle": req.body.subgroupTitle
},
{$set: {
"stakeholders.stakeholderTitle.subgroup.$.subgroupPercent": somePercentValue,
}
},
);
Also note, it's only the array that you find that you can update. It might not be exactly what you want, but its a step closer

How to update in nested schema in mongodb?

I am working on meteor. I am trying to make blog with commmenting system.For post insertion my schema is this:
Posts.insert({
post:post,
author:username,
date:date,
createdAt:new Date(),
comment:{
commentBy:'',
commentMsg:''
},
userId:Meteor.userId()
})
At first the comment section will be blank. when user comment on particular post then comment section is filled.
I am trying to update this schema:
Posts.update(thisPost,{$addToSet:{comment.commentedBy:Name,comment.commentMsg:post}});
But its not working.How to do it right way??
for entering multiple comments the field should be an array.
comments:[{
Commentedby:String,
CommentedMsh:string
}]
You can either use $addtoset or $push,
db.update({_id:post._id},{$push:{comments:newComment}})
OR
db.update({_id:post._id}, { $addToSet: { comments : comment });
I think you need to put quotes around any dot references, like this:
Posts.update(thisPost,{$addToSet:{"comment.commentedBy":Name,"comment.commentMsg":post}});
The reason is because if you don't, your compiler will try and do object references locally, which is not what you want, if you pass it as a string, then Mongo will do it as expected.
There are two issues that I could find in your provided information.
First is that the comment property should be an array/collection, so you should initiate it as following (renamed to comments):
Posts.insert({
...
comments:[],
...
})
And second, and most important to answer you question, according to the documentation the first parameter on update should be either a selector, a ObjectID or a string id.
https://docs.meteor.com/api/collections.html#Mongo-Collection-update
So your update should look like this:
var comment = { commentBy: 'user-id', commentMsg: 'le-message' }
Posts.update(post._id, { $addToSet: { comments : comment });

Removing Non-Collection Embedded Document via Mongo Shell

So I've got an application that uses a lot of embedded documents, which is fine. However I've noticed that some embedded documents aren't displayed when you show collections in Mongo shell.
Normally this isn't an issue, but whilst dicking with setting embedded documents, I accidentally added an empty entry to one of the entries. I'd normally do something like this to remove the entry db.collection.remove({_id: ObjectId('<OBJECT_ID>')}), but since some of these aren't actual collections I'm unable to do it like this.
I'm also not sure how I'd splice out this successfully while actually removing the document. I could splice it out of the entry, but I have a feeling it would leave that embedded document floating around somewhere in the DB.
Any ideas how to do this?
To give you an idea what I'm talking about, an example of the entry:
entry = {
_id: ObjectId('blah blah blah'),
name: {
first: 'example',
last: 'city'
},
log : [
{
_id: ObjectId('some id'),
action: 'whatever',
someField: 'etc.'
},
{
_id: ObjectId('another id')
},
{
_id: ObjectId('yet another id'),
action: 'who cares',
someField: 'data'
}
]
}
If you want to remove one of the log entries, then you want the $pull operator.
The format would be something like:
db.collection.update({_id:<id-of-document-to-update>},
{$pull:{"log._id":<id-of-log-entry-to-remove>"}}
)
This says, find document with certain _id and remove from log array an entry with certain sub_id.

Update records within arrays

I've just started to look at meteor and I'm stuck trying to update a record within an array within a record. Say i have a document that looks something like this:
Users:
{
_id: "somerandom",
name: "name1",
items, [
{
name: "item1",
data: "somedata",
...
},
{
name: "item2",
data: "somedata",
...
}
],
...
},
...
And I want to update the data of items 'item1'. I can't find anything on how to do this in meteor, but from what I have read about MongoDB I think that the correct way would be something like
Users.update({_id: userId, "items.name": 'item1}, {$set: {"items.$.data": newData}});
but since the MongoDB used in Meteor doesn't support $ it won't work, on the other hand I might be way of how this should be done.
Does anyone have a solution for this?
Use Meteor.methods() and implement your database operation on the server side and call it using Meteor.call() from the client. This feature is not yet implemented in Meteor and it is documented here.
You can read more in the discussion on this issue raised on Github, here.
I found a workaround at meteor's github https://github.com/meteor/meteor/blob/master/examples/parties/model.js
The idea is to first get the index of the array-item you want to change and then use that index instead of $. So in this case it would be something like
user = Users.findOne(userId);
index = _.indexOf(_.pluck(user.items, 'name'), 'item1');
modifier = {$set: {}};
modifier.$set["items." + index + ".data"] = newData;
Users.update(userId, modifier);