MongoDB - Query to fetch all related documents with same relation - mongodb

Let's say I have a student document that looks something like that:
{
"_id": 1,
"full_name": "John doe",
"gpa": 87,
"class_id": 17
}
My input is one student_id and i need to fetch all of the students from the same class.
I can of course first fetch the student by it's id and then fetch all of the related students by the class_id, but that would take 2 queries.
The question is, can I do it using only one query that returns me an array of all the class students (including the student with the student_id I have as an input)?

First you can group by class_id, so all students are divided into class. Then match the student_id to get all students of that class.
db.collection.aggregate([
{
$group: {
_id: "$class_id",
students: {
$push: "$$ROOT"
}
}
},
{
$match: {
"students._id": 1
}
}
])
Working Mongo playground

Related

How to update fields in a MongoDB collection if certain conditions met between two collections?

What am I doing?
So I am trying to update two fields in my MongoDB collection. The collection name is mydata and looks like this:
{
id: 123,
name: "John",
class: "A-100",
class_id: "", <-- Need to update this field,
class_type: "", <-- Need to update this field
}
What do I want to do?
I have another collection that is older, but it contains two fields that I need that I do not have in my current collection. But they both have the id field that corresponds. This is how it looks like the other collection:
{
id: 123,
name: "John",
class: "A-100",
class_id: 235, <-- Field That I need,
class_type: "Math" <-- Field That I need
}
What have I done so far?
I started an aggregate function that starts with a $lookup then $unwind then $match then $project. Looks like this:
db.mydata.aggregate([
{
$lookup: {
from: "old_collection",
localField: "id",
foreignField: "id",
as: "newData"
}
},
{
$unwind: "newData"
},
{
$match: {"class": "A-100"}
},
{
$project: {
_id: 0,
"id": "$newData.id",
"class_id": "$newData.class_id",
"class_type": "$newData.class_type"
}
},
Need help here to update mydata collection in the
fields that I pointed in the top
])
In summary
What I am trying to do is: If two objects from different collections have the same Id then pick the keys from the second object and update the keys in the first object.
Is there a way to do that in MongoDB?
Thanks.

Mongo DB - How to create a dynamic field based on the presence of element in array?

I have a problem in Mongo for which I am not getting any clue to resolve it efficiently.
Say I have a 'Course' collection something like this (index is created on the 'studentIds' field):
{
"courseId": 1,
"name": "Mathematics",
"studentIds": [1,3,5]
...
...
}
{
"courseId": 2,
"name": "Physics",
"studentIds": [2,3,5]
...
...
}
I am trying to write a query which would return records in the below format:
Say student 1 is querying the courses, he is enrolled for courseId 1, so the 'enrolled' is true, but student 1 not enrolled for courseId 2 and so the 'enrolled' is false:
{
"courseId": 1,
"name": "Mathematics",
"enrolled": true
}
{
"courseId": 2,
"name": "Physics",
"enrolled": false
}
Only solution I can think of is have two queries, first query to find all course IDs the student is enrolled in and while running through the cursor on the courses in the second query, add 'enrolled' field based on the existence of the courseId in the result of the first query, but looking for a way to achieve this in a single query.
Thanks.
You just need $in operator:
let studentId = 1;
db.collection.aggregate([
{
$project: {
courseId: 1,
name: 1,
enrolled: { $in: [ studentId, "$studentIds" ] }
}
}
])
Mongo Playground

Mongoose Query to Find Unique Values

In my MongoDB backend I want to create an endpoint that returns all the unique values for a property called department. Now, if I were doing this in IntelliShell I would just do something like:
db.staffmembers.distinct( "department" )
This will return an array of all the values for department.
But how do you return all unique values within a Mongoose find() query like this one?
Staffmember.find({ name: 'john', age: { $gte: 18 }});
In other words, what would the syntax look like if I want to use a find() like above, to return all unique values for department within the "staffmembers" collection?
You can use .aggregate() and pass your condition into $match stage and then use $addToSet within $group to get unique values.
let result = await Staffmember.aggregate([
{ $match: { name: 'john', age: { $gte: 18 }} },
{ $group: { _id: null, departments: { $addToSet: "$department" } } }
]);
We can use find and distinct like this for the above scenario. Aggregate might be a little overkill. I have tried both the solutions below.
Staffmember.find({name: 'john', age: {$gte: 18}}).distinct('department',
function(error, departments) {
// departments is an array of all unique department names
}
);
Staffmember.distinct('department', {name:'john', age:{ $gte: 18 }},
function(error, departments) {
// departments is an array of all unique department names
}
);
This link just nails it with all different possibilities:
How do I query for distinct values in Mongoose?

Mongodb Aggregate function with reference to another collection

I have two collections
Persons
{
"name": "Tom",
"car_id": "55b73e3e8ead0e220d8b45f3"
}
...
Cars
{
"model": "BMW",
"car_id": "55b73e3e8ead0e220d8b45f3"
}
...
How can i do a query such that i can get the following results (for example)
BMW : 2
Toyota: 3
Below is my aggregate function. I can get it the data out, however it does the car _id instead of the model name.
db.persons.aggregate([
{
$group: {
_id: {
car_Id: "$car_id",
},
carsCount: {$sum: 1}
},
},
]);
Appreciate any assistance.
you can aggregate internally in one collection with one query. I would suggest to keep aggregated data like that or similar:
{
car_id: 273645jhg2f52345hj3564jh6
sum: 4
}
And replace ID with name when you will need to expose data to the user.

Can I use populate before aggregate in mongoose?

I have two models, one is user
userSchema = new Schema({
userID: String,
age: Number
});
and the other is the score recorded several times everyday for all users
ScoreSchema = new Schema({
userID: {type: String, ref: 'User'},
score: Number,
created_date = Date,
....
})
I would like to do some query/calculation on the score for some users meeting specific requirement, say I would like to calculate the average of score for all users greater than 20 day by day.
My thought is that firstly do the populate on Scores to populate user's ages and then do the aggregate after that.
Something like
Score.
populate('userID','age').
aggregate([
{$match: {'userID.age': {$gt: 20}}},
{$group: ...},
{$group: ...}
], function(err, data){});
Is it Ok to use populate before aggregate? Or I first find all the userID meeting the requirement and save them in a array and then use $in to match the score document?
No you cannot call .populate() before .aggregate(), and there is a very good reason why you cannot. But there are different approaches you can take.
The .populate() method works "client side" where the underlying code actually performs additional queries ( or more accurately an $in query ) to "lookup" the specified element(s) from the referenced collection.
In contrast .aggregate() is a "server side" operation, so you basically cannot manipulate content "client side", and then have that data available to the aggregation pipeline stages later. It all needs to be present in the collection you are operating on.
A better approach here is available with MongoDB 3.2 and later, via the $lookup aggregation pipeline operation. Also probably best to handle from the User collection in this case in order to narrow down the selection:
User.aggregate(
[
// Filter first
{ "$match": {
"age": { "$gt": 20 }
}},
// Then join
{ "$lookup": {
"from": "scores",
"localField": "userID",
"foriegnField": "userID",
"as": "score"
}},
// More stages
],
function(err,results) {
}
)
This is basically going to include a new field "score" within the User object as an "array" of items that matched on "lookup" to the other collection:
{
"userID": "abc",
"age": 21,
"score": [{
"userID": "abc",
"score": 42,
// other fields
}]
}
The result is always an array, as the general expected usage is a "left join" of a possible "one to many" relationship. If no result is matched then it is just an empty array.
To use the content, just work with an array in any way. For instance, you can use the $arrayElemAt operator in order to just get the single first element of the array in any future operations. And then you can just use the content like any normal embedded field:
{ "$project": {
"userID": 1,
"age": 1,
"score": { "$arrayElemAt": [ "$score", 0 ] }
}}
If you don't have MongoDB 3.2 available, then your other option to process a query limited by the relations of another collection is to first get the results from that collection and then use $in to filter on the second:
// Match the user collection
User.find({ "age": { "$gt": 20 } },function(err,users) {
// Get id list
userList = users.map(function(user) {
return user.userID;
});
Score.aggregate(
[
// use the id list to select items
{ "$match": {
"userId": { "$in": userList }
}},
// more stages
],
function(err,results) {
}
);
});
So by getting the list of valid users from the other collection to the client and then feeding that to the other collection in a query is the onyl way to get this to happen in earlier releases.