MongoDB/Mongoose Update Embeded Array By Element Number - mongodb

I'm novice to MongoDB/Mongoose. I have a bounded array for a user. How would I query for firstName: bob and update pet[0] array?
When reading examples, the query is always on the embeded document array itself, as each element value would be unique in a whole collection. Since this is not the case in my situation, I need to query on the outside document(like firstName) and then update the element by element number.
For example:
UserModel.update(
{ firstName: "bob" },
{'$set': {'pets.$.
//not going to work

You can try this one
UserModel.findOne({ firstName: "bob" }, function(err, user) {
user.pets[0] = // new pets is here.
user.save(function(err) {...});

Related

Mongoose Populate Returning ObjectIds and Empty Arrays

I have an Collection Model that has a property of items that holds an array of Item Models.
const CollectionSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
items : [{type : mongoose.Schema.Types.ObjectId, ref: 'Item'}]
});
I tried to populate the items array in order to get the objectId's properties, but the items array would return back empty. (The code below is how I populated the items array. I first found the collection I was looking for by the _id using the req.body.search. I then ran .populate("items") in order to populate the items array. What I got back was an empty items array.)
userRouter.post('/iList', passport.authenticate('jwt', {session: false}), (req, res) => {
Collection.findById({_id : req.body.search}).populate("items").exec((err, document) => {
if(err)
res.json(err)
else
res.json({item: document})
})
});
I know my items array isn't empty since I can check on mongoDB that it is full.
The image of my mongoDB collection with an items array that isn't empty.
The weird thing is that if I put "collections" into the .populate params, my Items array does return with stuff, but it only returns the ObjectIDs and not actual object properties. I am confused to why .populate("items") isn't working.
If you are using findById then why are you specifying {_id: req.body.search}. If your req.body.search is a type of mongoose ObjectId string then you can directly use findById(req.body.search) instead of that. Also you can use simply the projection. Second argument in find calls are projections
If you are trying get items array only then why don't you try this query:-
Collection.findById(req.body.search, {items: 1}).then((result) => {
console.log('Items are :-\n', result);
}, (err) => {
console.log(err);
})
1 means include and 0 means exclude. So items will be present in output, also _id is default in output. In case you want to exclude _id then you can change second parameter to this -> {items: 1, _id: 0}
Never mind. The issue was when I pushed the Item Models via mongoose, I forgot to do items.save() which meant the items array held nothing.

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

MongoDB - how to only update field if field does not exist

How can I update a mongo document with the following requirements:
Find a document by email property:
If the document exists:
If both retrieved and new document have property A, keep property A (the retrieved one).
If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
If the document doesn't exist
Insert the new document.
The findOneAndUpdate seems not to convey the both 3 of the requirements. Thanks.
My recommendation is to go the following path:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
}
},
{ upsert: true }
);
Check upsert documentation:
https://docs.mongodb.com/manual/reference/method/db.collection.update/#upsert-option
Lets go through your requirements now:
3. If the document doesn't exist, insert the new document.
Yes, it will insert new document to collection if it doesnt find the document by email. Resulting document will be combination of find condition + $set + autogenerated _id, so it will look something like this:
{
_id: ObjectId(...)
email: 'someguy#email.com'
name: "some guy",
username: someguy,
tel: '1234'
}
2. If retrieved document property A is null or undefined or doesn't exist, update using property A of the new object.
All properties provided in $set will unconditionally be persisted in the database, which also covers your requirement of updating null/undefined values
3. If both retrieved and new document have property A, keep property A (the retrieved one).
If both newly provided A and database A are the same, we dont have a problem.
If As are different, dont you want to store the new A value?
If you are afraid of nulls/undefined values, you can omit them before providing object to $set.
What is the use-case for you not wanting to update database property with newly provided value?
One use-case i can see is that you want to pass createdAt in case you are creating new record, but dont want to update that value for existing records.
If thats the case, and you know those properties in advance, you can use $setOnInsert update operator. https://docs.mongodb.com/manual/reference/operator/update/#id1
So your update query can look like this:
db.getCollection('<some-collection>').update(
{ email: 'someguy#email.com' },
{
$set: {
name: "some guy",
username: someguy,
tel: '1234'
},
$setOnInsert: {
createdAt: new Date(),
updatedAt: new Date()
}
},
{ upsert: true }
);
I hope this helps!
You need not retrieve the document for updating the property A. You can use the update API of mongo to do so. Please find the psuedo code below:
db.<collection>.update({
"$or": [
{ "PropertyA": { "$exists": false } },
{ "PropertyA": null }
]
}, {$set: {"PropertyA": "NewValue"}});
The above code is for one property, but I think you can figure out how to scale it up.
Hope this helps !!

Pushing into sub document array inside by element

I have mongo db collection that looks like this :
({
_id:id ,
createdAt: new Date(),
name:name,
friends : [{name:"tommy",children:[{name:"sarah",age:12}]}],
dogs : [{}]
});
I would like to be able to insert a new element in the friends array if the name doesnt exist and a children element to that new array.
If i'm adding a new friend named john with a child nathaly , i'd like the output to be
({
_id:id ,
createdAt: new Date(),
name:name,
friends : [{name:"tommy",children:[{name:"sarah",age:12}]},{name:"john",children:[{name:"natahly",age:20}]}],
dogs : [{}]
});
If friend tommy already exist i would like just the children to be added to the children array and the output to now be
({
_id:id ,
createdAt: new Date(),
name:name,
friends : [{name:"tommy",children:[{name:"sarah",age:12},{name:"newchild",age:99}]},{name:"john",children:[{name:"natahly",age:20}]}],
dogs : [{}]
});
I've tried so many things already it's impossible to list.
currently i'm trying a mix of everything
// if `friends` is `[]`, push the empty children firstly through addToSet
var bulk = Directory.rawCollection().initializeUnorderedBulkOp();
bulk.find({_id: id, 'friends.name': {$exists: false}}).updateOne(
{$addToSet: {friends: {name: name_var, children: []}});
Meteor.wrapAsync(bulk.execute)();
// then Since previous step create the children array for said friend only if it doesn't already exist i'm just trying to update said friend with the new child
Directory.update(
{ _id: id,"friends.name": name_var },
{ $push: { "friends.$.children": {name:"sarah",age:12}
}}})
(i've also tried with "friends.children" instead of "friends.$.children" but i just get a different error)
And this should cover my three case but i'm having issues and i'm wondering if i'm going the right way. Any of you guys have any idea because this should at least work to add the children but it doesn't..
Errors : MongoError: The field 'friends.0.children' must be an array but is of type Object in document {_id: "0"} # when friends.$.children"
MongoError: cannot use the part (Friends of Friends.childrens) to traverse the element when friends.children"
One way of doing it would be to,
Initialize the object you want to insert/update
The object can have multiple children inserted at once.
var obj = {name:"tommy",children:[{name:"ds",age:12},{name:"two",age:12}]};
Initialize the Bulk API.
The order of operations does not matter here.
var bulk = db.t.initializeUnorderedBulkOp();
Find and update only the children records that already have a sub document for the name.
use the $addToSet operator to maintain unique records, and $each to add more than one children at a time.
bulk.find({"friends.name":obj.name})
.update({$addToSet:{"friends.$.children":{$each:obj.children}}});
Find and update those which do not have a sub document.
$push the entire object if a document doesn't have one.
bulk.find({"friends.name":{$ne:obj.name}})
.update({$push:{"friends":obj}});
bulk.execute();

How can I query for a subdocument full of objects in Mongo?

So I have a document with an unknown number of objects in it, each with 2 properties. It's a collection of friend lists, and I'm trying to confirm if someone has a friend with a certain username before I allow a user to send a request. I'm keeping the list of friends in a subdocument, like this:
>>all the _id and other properties<<, "ownerFriends":[{"friendId":"an id goes here", "friendUsername": "username"}, {"friendId":"another id", "friendUsername":"username2"}]
I'm trying to do a query that will return username2 if given that as input, but I don't know how to do that with dot notation because I think you need to know the specific property to look for, and these are heterodox amounts of friend objects in the ownerFriends property.
If you want to select the ownerFriend object that has username as the friendUserName you can use the following selector (assuming your collection is called Friends):
Friends.find({
"ownerFriends.friendUsername": "username2"
}, {
fields: { "ownerFriends.$": 1}
});
You can find a detailed explanation of how to query an array of objects based on a property here:
http://www.curtismlarson.com/blog/2015/08/08/meteor-mongodb-array-property-selector/
In summary you have an object that contains keys, one of whose values is an array of objects. You can perform queries on the arrays using $elemMatch In your case:
MyCollection.find({ ownerFriends: { $elemMatch: { friendUsername: searchString }}});
Although I think you'll need to also query on the current user's _id. Not knowing the details of your collection, I can only speculate with:
MyCollection.find({ userId: Meteor.userId(), ownerFriends: { $elemMatch: { friendUsername: searchString }}});