Finding a nested document and pushing a document into a nested document - mongodb

So i started learning mongoDB and moongoose today. I have the following Schema:
{
username: {
type : String,
required : true,
unique: true,
trim : true
},
routines : {
type: [
{
_id : mongoose.Schema.Types.ObjectId,
name : String,
todos : [
{
_id : mongoose.Schema.Types.ObjectId,
todo : String,
isCompleted : Boolean
}
]
}
],
}
}
for example:
{
"username": "John",
"routines": [
{
"_id" : 1234, //just for an example assume it to be as 1234
"name" : "Trip plan",
"todos" : [
{
"_id": 1213123,
"todo": "book flight",
"isCompleted" : "false"
}....
]
}......
]
}
what i want to do is, first i would select a document from collection, by using username, then in routines(Array of objects), i want to select a particular routine by id, which would be given by user in request body, now in the selected routine, i want to push into todos array.
For above example, suppose, selecting document with username john, then selecting an object from routines array with _id as 1234, then to its todos i will add a todo.
I have spent almost a day searching about how to do this, while doing this i learnt concepts like arrayFilters, projections. But still couldn't get how to do. Also i read many answers in Stack Overflow, i couldn't grasp much out of them.
PS:I am very new to MongoDB and mongoose, Chances that my question is very silly, and might not be good as per stack overflow standards, I apologize for the same.

You were on the right track, you indeed want to use arrayFilter to achieve this.
Here is a quick example:
// the user you want.
let user = user;
await db.routines.updateOne(
{
username: user.username
},
{
$addToSet: {
"routines.$[elem].todos": newTodo
}
},
{arrayFilters: [{'elem._id': "1234"}]}
);

Related

how to update model from a list of documents to an array in mongodb

We have a MongoDB instance with a collection of users that is something like this:
{
"Username": "Amin-AMD",
"FriendsList": [
{
"UserId": "5e076f4b19e8cd000162c962",
"NickName": "Amin-Mobile",
"ClanName": null,
"ClanId": null,
"Level": NumberInt(1),
"ActiveCosmeticItems": [
"hair0",
"skin0",
"eye0",
"mouth0",
"daub0",
"acc"
],
"IsOnline": false
},
{
"UserId": "5e08a4a119e8cd000167929e",
"NickName": "saeed",
"ClanName": null,
"ClanId": null,
"Level": NumberInt(7),
"ActiveCosmeticItems": [
"hair5",
"skin2",
"eye2",
"mouth10",
"daub0",
"acc0"
],
"IsOnline": false
}
]
}
As shown above, I have embedded a list of Friends in our User's collection. but for a reason, we need to change this model to reference Friends. So I need to write a query to replace the whole FriendModel with just a UserId.
I have reached to this query but it throws an exception.
db.Users.updateMany({ "FriendsList" : { $ne : [] }}, { $set : { "FriendsList.$" : "FriendsList.$.UserId" }})
In fact, for each friend, I just need the UserId so the new FriendsList will be an array and it should be something like this:
{
"Username": "Amin-AMD",
"FriendsList": [
"5e076f4b19e8cd000162c962",
"5e08a4a119e8cd000167929e"
]
}
MongoDB version: 4.2.1
You have to do it in two steps :
Update existing data in DB :
As you can use aggregation pipeline in updates starting MongoDB version 4.2.
Query to update data :
db.collection.updateMany({ "FriendsList" : { $ne : [] }},[{$set :{FriendsList: '$FriendsList.UserId'}}])
But if your FriendsList.UserId are strings better convert them to ObjectId() as like below :
db.colleciton.updateMany({ "FriendsList" : { $ne : [] }},[{$set :{FriendsList: { $map: { input: '$FriendsList.UserId', in: {$toObjectId: '$$this'}} }}}])
Update existing mongoose model to restrict future writes on DB :
Mongoose Model :
So FriendsList will be an array of ObjectId()'s which will be referred to another new schema via ref field.
FriendsList:[{
type: mongoose.Schema.Types.ObjectId,
ref: "FriendList" /** 'FriendList' will be a mongoose schema refers to a collection */
}]
Ref : mongoose-populate
Try using User model
FriendsList:{
type: mongoose.Schema.Types.ObjectId,
ref: "FriendList"
}

How is findById() + save() different from update() in MongoDB

While trying to update a MongoDB document using Mongoose, can I use a findById() with a save() in the callback, or should I stick with traditional update methods such as findByIdAndModify, findOneAndModify, update(), etc.? Say I want to update the name field of the following document (please see a more elaborate example in the edit at the end, which motivated my question):
{
"_id": ObjectId("123"),
"name": "Development"
}
(Mongoose model name for the collection is Category)
I could do this:
Category.update({ "_id" : "123" }, { "name" : "Software Development" }, { new: true })
or I could do this:
Category.findById("123", function(err, category) {
if (err) throw err;
category.name = "Software Development";
category.save();
});
For more elaborate examples, it feels easier to manipulate a JavaScript object that can simply be saved, as opposed to devising a relatively complex update document for the .update() operation. Am I missing something fundamentally important?
Edited 7/21/2016 Responding to the comment from #Cameron, I think a better example is warranted:
{
"_id": ObjectId("123"),
"roles": [{
"roleId": ObjectId("1234"),
"name": "Leader"
}, {
"roleId": ObjectId("1235"),
"name": "Moderator"
}, {
"roleId": ObjectId("1236"),
"name": "Arbitrator"
}]
}
What I am trying to do is remove some roles as well as add some roles in the roles array of sub-documents in a single operation. To add role sub-documents, $push can be used and to remove role sub-documents, $pull is used. But if I did something like this:
Person.update({
"_id": "123"
}, {
$pull : {
"roles" : {
"roleId" : {
$in : [ "1235", "1236" ]
}
}
},
$push : {
"roles" : {
$each: [{
"roleId" : ObjectId("1237"),
"name" : "Developer"
}]
}
}
}
When I try to execute this, I get the error Cannot update 'roles' and 'roles' at the same time, of course. That's when I felt it is easier to find a document, manipulate it any way I want and then save it. In that scenario, I don't know if there is really any other choice for updating the document.
I typically like to use findById() when I am performing more elaborate updates and don't think you are missing anything fundamentally important.
However one method to be aware of in mongoose is findByIdAndUpdate(), this issues a mongodb findAndModify update command and would allow you to perform your first example with the following code: Category.findByIdAndUpdate("123", function(err, savedDoc) {...}).

query for multiple value using single query [duplicate]

How to achieve below SQL in MongoShell?
Select TableA.* from TableA where TableA.FieldB in (select TableB.FieldValue from TableB)
Mongo doc gives some example of
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
I want that array be dynamically from another query. Is it possible?
Extending my question
I have a collection of bot names
bots collection
{
"_id" : ObjectId("53266697c294991f57c36e42"),
"name" : "teoma"
}
I have a collection of user traffic, in that traffic collection, I have a field useragent
userTraffic Collection
{
"_id" : ObjectId("5325ee6efb91c0161cbe7b2c"),
"hosttype" : "http",
"useragent" : "Mediapartners-Google",
"is_crawler" : false,
"City" : "Mountain View",
"State" : "CA",
"Country" : "United States"
}
I want to select all user traffic documents where its useragent contains any name of bot collection
This is what I have come up with
var botArray = db.bots.find({},{name:1, _id:0}).toArray()
db.Sessions.find({
useragent: {$in: botArray}
},{
ipaddress:1
})
Here i believe it is doing equals to comparison, but I want it to do like %% comparison
Once I get the result, I want to do an update to that result set as is_crawler= true
Tried something like this, isn't helpful
db.bots.find().forEach( function(myBot) {
db.Sessions.find({
useragent: /myBot.name/
},{
ipaddress:1
})
});
Another way of looping through the records, but no match found.
var bots = db.bots.find( {
$query: {},
$orderby:{
name:1}
});
while( bots.hasNext()) {
var bot = bots.next();
//print(bot.name);
var botName = bot.name.toLowerCase();
print(botName);
db.Sessions.find({
useragent: /botName/,
is_crawler:false
},{
start_date:1,
ipaddress:1,
useragent:1,
City:1,
State:1,
Country:1,
is_crawler:1,
_id:0
})
}
Not in a single query it isn't.
There is nothing wrong with getting the results from a query and feeding that in as your in condition.
var list = db.collectionA.find({},{ "_id": 0, "field": 1 }).toArray();
results = db.collectionB.find({ "newfield": { "$in": list } });
But your actual purpose is not clear, as using SQL queries alone as the only example of what you want to achieve are generally not a good guide to answer the question. The main cause of this is that you probably should be modelling differently than as you do in relational. Otherwise, why use MongoDB at all?
I would suggest reading the documentation section on Data Modelling which shows several examples of how to approach common modelling cases.
Considering that information, then perhaps you can reconsider what you are modelling, and if you then have specific questions to other problems there, then feel free to ask your questions here.
Finally this is how I could accomplish it.
// Get a array with values for name field
var botArray = db.bots.find({},{name:1}).toArray();
// loop through another collection
db.Sessions.find().forEach(function(sess){
if(sess.is_crawler == false){ // check a condition
// loop in the above array
botArray.forEach(function(b){
//check if exists in the array
if(String(sess.useragent).toUpperCase().indexOf(b.name.toUpperCase()) > -1){
db.Sessions.update({ _id : sess._id} // find by _id
,{
is_crawler : true // set a update value
},
{
upsert:false // do update only
})
}
});
}
});

Query by two params with $and in mongoose?

I have a user model like this:
user : {
myArmy : {
mySoldiers : [
{
positioned : false,
soldierInfo : {
_id : s99212
}
},
{
positioned : true,
soldierInfo : {
_id : s99112
}
}
]
}
},
user : {
myArmy : {
mySoldiers : [
{
positioned : true,
soldierInfo : {
_id : s99212
}
},
{
positioned : false,
soldierInfo : {
_id : s99112
}
}
]
}
}
...
I have a query that i want to do to return the user(s) who have soldier id s99212 positioned (true): (could be thousands of those, and i need to read and retrieve them all)
This is my faulty query with mongoose:
var soldierId = s99212;
stream = User.find({
$and: [
{'myArmy.mySoldier.positioned': {$ne: null}},
{'myArmy.mySoldier.soldierInfo._id': soldierId}
]
}).lean(true).stream();
Nothing is returned by this query, should there be another way to do this $and stuff?
How exactly am i suppose to use $elemMatch if at all, should it be instead of the find? (If it worth to mention, i want to return the complete user object, not just parts of it...)
Tried this, crashed my app:
stream = User.find({
'$elemMatch': [
{'myArmy.mySoldiers.pos': {$ne: null}},
{'myArmy.mySoldiers.soldierInfo._id': soldierId}
]
}).lean(true).stream();
I know i have a small syntax problem, where is it?
Very simple and well documented. The $elemMatch operator acts as a "query in itself", where it applies "it's conditions" to members of the array specified:
var query = User.find({
"myArmy.mySoldiers": {
"$elemMatch": {
"positioned": { "$ne": null },
"soldierInfo._id": soldierId
}
}
});
Therefore for a document to "match" then the conditions specfied under $elemMatch must be present and valid for the "same" array element. Array on "left", arguments on "right".
Other "dot notation" forms only ever test that the values match "some element" in the array, and not necessarily the same one.

MongoDB select all where field value in a query list

How to achieve below SQL in MongoShell?
Select TableA.* from TableA where TableA.FieldB in (select TableB.FieldValue from TableB)
Mongo doc gives some example of
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
I want that array be dynamically from another query. Is it possible?
Extending my question
I have a collection of bot names
bots collection
{
"_id" : ObjectId("53266697c294991f57c36e42"),
"name" : "teoma"
}
I have a collection of user traffic, in that traffic collection, I have a field useragent
userTraffic Collection
{
"_id" : ObjectId("5325ee6efb91c0161cbe7b2c"),
"hosttype" : "http",
"useragent" : "Mediapartners-Google",
"is_crawler" : false,
"City" : "Mountain View",
"State" : "CA",
"Country" : "United States"
}
I want to select all user traffic documents where its useragent contains any name of bot collection
This is what I have come up with
var botArray = db.bots.find({},{name:1, _id:0}).toArray()
db.Sessions.find({
useragent: {$in: botArray}
},{
ipaddress:1
})
Here i believe it is doing equals to comparison, but I want it to do like %% comparison
Once I get the result, I want to do an update to that result set as is_crawler= true
Tried something like this, isn't helpful
db.bots.find().forEach( function(myBot) {
db.Sessions.find({
useragent: /myBot.name/
},{
ipaddress:1
})
});
Another way of looping through the records, but no match found.
var bots = db.bots.find( {
$query: {},
$orderby:{
name:1}
});
while( bots.hasNext()) {
var bot = bots.next();
//print(bot.name);
var botName = bot.name.toLowerCase();
print(botName);
db.Sessions.find({
useragent: /botName/,
is_crawler:false
},{
start_date:1,
ipaddress:1,
useragent:1,
City:1,
State:1,
Country:1,
is_crawler:1,
_id:0
})
}
Not in a single query it isn't.
There is nothing wrong with getting the results from a query and feeding that in as your in condition.
var list = db.collectionA.find({},{ "_id": 0, "field": 1 }).toArray();
results = db.collectionB.find({ "newfield": { "$in": list } });
But your actual purpose is not clear, as using SQL queries alone as the only example of what you want to achieve are generally not a good guide to answer the question. The main cause of this is that you probably should be modelling differently than as you do in relational. Otherwise, why use MongoDB at all?
I would suggest reading the documentation section on Data Modelling which shows several examples of how to approach common modelling cases.
Considering that information, then perhaps you can reconsider what you are modelling, and if you then have specific questions to other problems there, then feel free to ask your questions here.
Finally this is how I could accomplish it.
// Get a array with values for name field
var botArray = db.bots.find({},{name:1}).toArray();
// loop through another collection
db.Sessions.find().forEach(function(sess){
if(sess.is_crawler == false){ // check a condition
// loop in the above array
botArray.forEach(function(b){
//check if exists in the array
if(String(sess.useragent).toUpperCase().indexOf(b.name.toUpperCase()) > -1){
db.Sessions.update({ _id : sess._id} // find by _id
,{
is_crawler : true // set a update value
},
{
upsert:false // do update only
})
}
});
}
});