Related
I have a collection that will store multiple copies of the same data
Is there a way in mongoose to return all the unique records based on a certain field, and additionally provide the count field with each object, while using populate to get the full document?
A simple example is something like this:
Lets say the data in my collection looks like this:
[
{
person: ObjectId("123"),
data: 'this will be random data',
_id: ObjectId("xxx")
},
{
person: ObjectId("456"),
data: 'this will be random data',
_id: ObjectId("xxx")
},
{
person: ObjectId("123"),
data: 'this will be random data',
_id: ObjectId("xxx")
}
]
Since I dont want the data field, because this data is random, and I only want a count of the unique documents, my query looks like this:
Model.find().select({'person':1})
Now I only have the person field and _id.
Now I want to populate the data, so my query looks like this:
Model.find().select({'person':1}).populate('person')
Then I get data like this:
[
{
name: 'Jim',
salary: '200'
},
{
name: 'Mike',
salary: '150'
},
{
name: 'Jim',
salary: '200'
},
]
Now what I finally want to get is something like this:
[
{
name: 'Jim',
salary: '200',
count: 2
},
{
name: 'Mike',
salary: '150',
count: 1
},
]
All the random fields have been removed, and I only get a single, populated document with a count for each unique type. in this case, the name field is used as the distinct field.
So far I could not yet get this to work while using populate. Is this possible to achieve with mongoose, or will it require some manual computation?
You might want to use a virtual populate:
Person model:
const personSchema = new mongoose.Schema(
{
name: String,
},
{
// this is needed to display the virtual in json format, like res.json()
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
personSchema.virtual("count", {
ref: "Task",
localField: "_id",
foreignField: "person",
count: true,
});
Task model:
const taskSchema = mongoose.Schema({
person: { type: mongoose.Schema.Types.ObjectId, ref: "Person" },
data: String,
});
instead of searching by task model, you can search by people model
const persons = await personModel.find().select("-_id").populate("count");
Person collection:
{
"_id": "639222fa3f87b88812e95d1d",
"name": "jonh",
"__v": 0,
},
{
"_id": "639222fffcd0793b0580302e",
"name": "joseph",
"__v": 0,
}
Tasks collection:
{
"_id": "6392231815cd8663c5671edf",
"person": "639222fffcd0793b0580302e",
"data": "some data",
},
{
"_id": "6392231a15cd8663c5671ee1",
"person": "639222fffcd0793b0580302e",
"data": "some data",
},
{
"_id": "639223337409d3e1a1f741ac",
"person": "639222fa3f87b88812e95d1d",
"data": "some data",
}
and the result of the query:
[
{
"_id": "639222fa3f87b88812e95d1d",
"name": "jonh",
"count": 1,
"id": "639222fa3f87b88812e95d1d"
},
{
"_id": "639222fffcd0793b0580302e",
"name": "joseph",
"count": 2,
"id": "639222fffcd0793b0580302e"
}
]
"id" field is added for toJSON: { virtuals: true }, you can ignore it.
I hope it helps you, here the docs: enter link description here
I am VERY close to getting what I want out of this query... but I only want SOME of the fields returned and right now it is returning all of them
NOTE: This is a refinement : I am now asking how to return only certain fields, while my similar question asks how to return the data between a start and end date
In addition, can somebody please please provide an answer using the MongoDB Playground with MY data sets so I can try it out... I can't quite figure out how to "name" the data sets so they work in the playground !
Register Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const RegisterSchema = new Schema({
userId: {type: Schema.Types.ObjectId, required: true},
accessToken: {type:String, required: true, default: null},
})
module.exports = Register = mongoose.model( 'register', RegisterSchema)
Here is some register data
[
{
"_id": "5eac9e815fc57b07f5d0d29f",
"userId": "5ea108babb65b800172b11be",
"accessToken": "111"
},
{
"_id": "5ecaeba3c7b910d3276df839",
"userId": "5e6c2dddad72870c84f8476b",
"accessToken": "222"
}
]
The next document contains data that is related to the Register schema via the accessToken
Notifications
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const NotificationSchema = new Schema({
accessToken: {type:String, required: true},
summaryId: {type:Number, required: true},
dateCreated: {type: Date, default: Date.now},
// I don't want these returned in the final results
dontWantThis1: {type:Number, required: true},
dontWantThis2: {type:Number, required: true},
})
module.exports = Notification = mongoose.model( 'notification', NotificationSchema)
Here is some notification data
[{
"_id": "5ebf0390c719e60004f42e74",
"accessToken": "111",
"summaryId": 1111,
"dontWantThis1": 61,
"dontWantThis2": 62,
"dateCreated": "2020-04-17T00:00:00.000+00:00" },
{
"_id": "6ebf0390c719e60004f42e76",
"accessToken": "222",
"summaryId": 2221,
"dontWantThis1": 71,
"dontWantThis2": 72,
"dateCreated": "2020-04-18T00:00:00.000+00:00" },
{
"_id": "6ebf0390c719e60004f42e78",
"accessToken": "111",
"summaryId": 1112,
"dontWantThis1": 611,
"dontWantThis2": 622,
"dateCreated": "2020-05-25T00:00:00.000+00:00" },
{
"_id": "6ebf0390c719e60004f42e80",
"accessToken": "222",
"summaryId": 2222,
"dontWantThis1": 711,
"dontWantThis2": 722,
"dateCreated": "2020-05-26T00:00:00.000+00:00" }
]
Works, returns data between the two dates, but
This code returns everything, including the 'dontWantThis1' and 'dontWantThis2'
NOTE
I do not want the fields prefaced with 'dontWantThis' - but that is only to show which ones I don't want... I don't literally want to exclude fields prefaced with 'dontWantThis' ..... they could be named 'foo' or 'apple' or 'dog' they are just named that way to indicate that I don't want them
// make sure the input dates are REALLY date objects
// I only want to see notifications for the month of May (in this example)
var dateStart = new Date('2020-05-01T00:00:00.000+00:00');
var dateEnd = new Date('2020-05-30T00:00:00.000+00:00');
var match = {$match: { userId: mongoose.Types.ObjectId(userId) } };
var lookup ={
$lookup:
{
from: "my_Notifications",
localField: "accessToken",
foreignField: "accessToken",
as: "notifications"
}
};
var dateCondition = { $and: [
{ $gte: [ "$$item.dateCreated", dateStart ] },
{ $lte: [ "$$item.dateCreated", dateEnd ] }
]}
var project = {
$project: {
notifications: {
$filter: {
input: "$notifications",
as: "item",
cond: dateCondition
} } }
};
var agg = [
match,
lookup,
project
];
Register.aggregate(agg)
.then( ..... )
Try 1
I thought I could do something like this, but it still returns ALL of the notification fields
var project = {
$project: {
"_id": 1,
"userId": 1,
"accessToken":1,
"count":{$size:"$notifications"},
"notifications._id":1,
"notifications.summaryId": 1,
"notifications.dateCreated":1,
notifications : {
$filter: {
input: "$notifications",
as: "item",
cond: dateCondition
},
}}
};
SOLUTION
I created another projection and added that to the pipeline:
var project2 = {
$project: {
"_id": 1,
"userId": 1,
"accessToken":1,
"count":{$size:"$notifications"},
"notifications._id":1,
"notifications.summaryId": 1,
"notifications.dateCreated":1,
"notifications.dateProcessed":1,
}
};
var agg = [
match,
lookup,
project,
project2,
];
Thanks!!
https://stackoverflow.com/users/6635464/ngshravil-py was spot on.
I created another projection:
var project2 = {
$project: {
"_id": 1,
"userId": 1,
"accessToken":1,
"count":{$size:"$notifications"},
"notifications._id":1,
"notifications.summaryId": 1,
"notifications.dateCreated":1,
"notifications.dateProcessed":1,
}
};
Then added it to my aggregation pipeline:
var agg = [
match,
lookup,
project,
project2,
];
Worked ! -- thank you https://stackoverflow.com/users/6635464/ngshravil-py
You need to convert notifications.dateCreated to ISODate, as your date is in string, by using $dateFromString and $map operator. i suggest you to do this, because I don't think that you can do date comparison with string formats. Also, make sure that dateStart and dateEnd should also be in ISODate format.
And you need two $project operator in order to achieve this. Also, I don't see any field with userAccessToken, I assume, it's accessToken. Check the below query.
db.Register.aggregate([
{
$lookup: {
from: "my_Notifications",
localField: "accessToken",
foreignField: "accessToken",
as: "notifications"
}
},
{
$project: {
"_id": 1,
"userId": 1,
"accessToken": 1,
notifications: {
$map: {
input: "$notifications",
as: "n",
in: {
"_id": "$$n._id",
"summaryId": "$$n.summaryId",
"dateCreated": {
$dateFromString: {
dateString: "$$n.dateCreated"
}
}
}
}
}
}
},
{
$project: {
"userId": 1,
"accessToken": 1,
"notifications": {
$filter: {
input: "$notifications",
as: "item",
cond: {
$and: [
{
$gte: [
"$$item.dateCreated",
ISODate("2020-05-24T00:00:00Z")
]
},
{
$lte: [
"$$item.dateCreated",
ISODate("2020-05-26T00:00:00Z")
]
}
]
}
}
}
}
},
{
$set: {
"count": {
$size: "$notifications"
}
}
}
])
MongoPlayGroundLink
I have a json object which i like to save in mongodb,
{
"user": "abc",
"week": 21,
"year": 2021
"data": [
{
"date": "01/01/21",
"present": true
},
{
"date": "02/01/21",
"present": false
}
]
}
this has to be split into 2 tables: timesheet-main and timesheet-data
timesheet-main.model will be like this:
{
"user": "abc",
"week": 21,
"year": 2021,
data: {
ref: timesheet-data.model
}
}
AND timesheet-data.model will be like this:
{
{
user: userIdFrom-timesheet-main.model,
timesheet-main-id: timesheet-main-ref-from-above-collection,
"date": "02/01/21",
"present": false
}
}
so that when I want week wise user query, i get result like this:
{
"_id": "asf5sf44sdw3sfsf",
"user": "abc",
"week": 21,
"year": 2021
"data": [
{
"_id": "Asdfsdfsdfsdfsdfsf",
"date": "01/01/21",
"present": true
},
{
"_id": "egdfgfhfghfghfgh"
"date": "02/01/21",
"present": false
}
]
}
I dont know any good or easy way of doing this, so while saving data in timesheet-main.model, after its success, i am calling another insert into timesheet-data.model, and saving data in this. But someone suggeted that there can be better way of doing this using Virtuals. I tried but no success.
Any help/suggestion is highly appreciated.
Thanks
You are looking to implement a one-to-many relationship in mongo. You could follow on this link.
As for the model:
timesheet-main.model [Parent]
{
"data": [{ type: Schema.Types.ObjectId, ref: timesheet-data.model }]
}
timesheet-data.model [Child]
{
"timesheet-main-id": { type: Schema.Types.ObjectId, ref: timesheet-main.model },
}
Consider additional fields as per choice. I am only adding fields for a one-to-many relationship.
For adding data -
const parent = new TimesheetMain({
_id: new mongoose.Types.ObjectId(),
data: []
})
const child = new TimesheetData()
child.timesheet-main-id = parent._id
child.save(err => HandlerErr)
parent.data.push(child)
parent.save(err=> HandlerErr)
I am new to mongodb, and playing around with a self-project, where user can subscribe to 3-4 different courses that are predefined. Each course is 1 hour long course everyday, and students can subscribe for either 15, 30 or more days.
App will store information of the students, the course they subscribed for (how many) days and days they were present for the course.
This is my Mongoose Schema.
var mongoose = require('mongoose');
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
}
};
var courseSchema = new mongoose.Schema({
name: String
});
var studentSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true},
phone: String,
gender: String,
age: String,
city: String,
street: String,
picture: String,
course: [courseSchema],
subscriptionDays: Number,
daysPresent: [Date]
}, schemaOptions);
module.exports = mongoose.model('Student', studentSchema);
Here, course is any of 3-4 courses, one student can subscribe one or more course at same time. subscriptionDays is the number of days they subscribe to, and daysPresent are the days they took the course.
I am not sure if this is the right schema for my project, so far I was able to do this much.
Confusions with the schema are:
When student who is subscribed to two different courses arrives to the
institute, but takes only one class (course), then I do not think this
schema supports the case, for this I thought to modify courseSchema like this,
var courseSchema = new mongoose.Schema({
name: String,
subsriptionDays: Number,
daysPresent: [Date]
});
But, after doing this I am still confused to make changes on the data, like Date has to be inserted into the documents every time student attends for the course.
Second confusion is how will I update the data inside document every day, only the data that has to be inserted on daily basis is the Date inside days.
Can I get some guidance and suggestion from Mongo Experts? TIA
I think that you are basically on the right track with your second thoughts on extending the design. I would really only expand on that by also including a "reference" to the "Course" itself as opposed to just the information embedded on the schema.
As your usage case questions, then they are probably best addressed with a working example:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/school');
// Course model
const courseSchema = new Schema({
name: String,
info: String
});
const Course = mongoose.model('Course', courseSchema);
// Student Model
const studentCourseSchema = new Schema({
_id: { type: ObjectId, ref: 'Course' },
name: String,
subscriptionDays: Number,
daysPresent: [Date]
});
const studentSchema = new Schema({
name: String,
email: String,
courses: [studentCourseSchema]
});
studentSchema.index({ "email": 1 },{ "unique": true, "background": false });
const Student = mongoose.model('Student', studentSchema);
function logOutput(content) {
console.log( JSON.stringify( content, undefined, 2 ) )
}
async.series(
[
// Clear collections
(callback) =>
async.each(mongoose.models,
(model,callback) => model.remove({},callback),callback),
// Set up data
(callback) =>
async.parallel(
[
(callback) => Course.insertMany(
[
{ "name": "Course 1", "info": "blah blah blah" },
{ "name": "Course 2", "info": "fubble rumble" }
],
callback),
(callback) => Student.insertMany(
[
{ "name": "Bill", "email": "bill#example.com" },
{ "name": "Ted", "email": "ted#example.com" }
],
callback)
],
callback
),
// Give bill both courses
(callback) => {
async.waterfall(
[
(callback) => Course.find().lean().exec(callback),
(courses,callback) => {
courses = courses.map(
course => Object.assign(course,{ subscriptionDays: 5 }));
let ids = courses.map( c => c._id );
Student.findOneAndUpdate(
{ "email": "bill#example.com", "courses._id": { "$nin": ids } },
{ "$push": {
"courses": {
"$each": courses
}
}},
{ "new": true },
(err, student) => {
logOutput(student);
callback(err);
}
)
}
],
callback
)
},
// Attend one of bill's courses
(callback) => Student.findOneAndUpdate(
{ "email": "bill#example.com", "courses.name": 'Course 2' },
{ "$push": { "courses.$.daysPresent": new Date() } },
{ "new": true },
(err, student) => {
logOutput(student);
callback(err);
}
),
// Get Students .populate()
(callback) => Student.find().populate('courses._id')
.exec((err,students) => {
logOutput(students);
callback(err);
}
)
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
)
So that should give you a sample of how the operations you ask about actually work.
Add a course to the student Shows addition of a couple of courses where I think you would ideally use $push functionality of MongoDB. To ensure that you are not adding courses that are already there the "query" expression actually excludes selection if they are already present in the courses array. In the example a "list" is passed, so we use $nin but with a single item you would simply use $ne:
{ "email": "bill#example.com", "courses._id": { "$nin": ids } },
{ "$push": { "courses": { "$each": courses } } },
Add an attended date This actually demonstrates a case where you would want to "positionally match" the item within "courses" in order to know which one to update. This is done by providing much like before a condition to "match" as opposed to "exclude" the specific array element. Then in the actual "update" part, we apply the same $push operator so we can append to the "daysPresent"array, but also using the positional $ operator to point to the correct array index position which corresponds to the match condition:
{ "email": "bill#example.com", "courses.name": 'Course 2' },
{ "$push": { "courses.$.daysPresent": new Date() } },
As a bonus there are a few more operations in there showing the relational nature between keeping a list of "Courses" in their own collection with additional information that you probably do not want to embed on each student.
The last operation in the sample actually performs a .populate() to actually pull in this information from the other collection for display.
The whole example has debugging turned on with mongoose.set('debug',true); so you can see what the actual calls to MongoDB are really doing for each operation.
Also get acquainted with the .findOneAndUpdate() method used here, as well as the various "update operators" from the core MongoDB documentation.
Sample output
Mongoose: courses.remove({}, {})
Mongoose: students.remove({}, {})
Mongoose: students.ensureIndex({ email: 1 }, { unique: true, background: false })
(node:10544) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
Mongoose: courses.insertMany([ { __v: 0, name: 'Course 1', info: 'blah blah blah', _id: 5944d5bc32c6ae2930174289 }, { __v: 0, name: 'Course 2', info: 'fubble rumble', _id: 5944d5bc32c6ae293017428a } ], null)
Mongoose: students.insertMany([ { __v: 0, name: 'Bill', email: 'bill#example.com', _id: 5944d5bc32c6ae293017428b, courses: [] }, { __v: 0, name: 'Ted', email: 'ted#example.com', _id: 5944d5bc32c6ae293017428c, courses: [] } ], null)
Mongoose: courses.find({}, { fields: {} })
Mongoose: students.findAndModify({ 'courses._id': { '$nin': [ ObjectId("5944d5bc32c6ae2930174289"), ObjectId("5944d5bc32c6ae293017428a") ] }, email: 'bill#example.com' }, [], { '$push': { courses: { '$each': [ { daysPresent: [], _id: ObjectId("5944d5bc32c6ae2930174289"), name: 'Course 1', subscriptionDays: 5 }, { daysPresent: [], _id: ObjectId("5944d5bc32c6ae293017428a"), name: 'Course 2', subscriptionDays: 5 } ] } } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": "5944d5bc32c6ae2930174289",
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": "5944d5bc32c6ae293017428a",
"daysPresent": []
}
]
}
Mongoose: students.findAndModify({ 'courses.name': 'Course 2', email: 'bill#example.com' }, [], { '$push': { 'courses.$.daysPresent': new Date("Sat, 17 Jun 2017 07:09:48 GMT") } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": "5944d5bc32c6ae2930174289",
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": "5944d5bc32c6ae293017428a",
"daysPresent": [
"2017-06-17T07:09:48.662Z"
]
}
]
}
Mongoose: students.find({}, { fields: {} })
Mongoose: courses.find({ _id: { '$in': [ ObjectId("5944d5bc32c6ae2930174289"), ObjectId("5944d5bc32c6ae293017428a") ] } }, { fields: {} })
[
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": {
"_id": "5944d5bc32c6ae2930174289",
"__v": 0,
"name": "Course 1",
"info": "blah blah blah"
},
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": {
"_id": "5944d5bc32c6ae293017428a",
"__v": 0,
"name": "Course 2",
"info": "fubble rumble"
},
"daysPresent": [
"2017-06-17T07:09:48.662Z"
]
}
]
},
{
"_id": "5944d5bc32c6ae293017428c",
"__v": 0,
"name": "Ted",
"email": "ted#example.com",
"courses": []
}
]
Schema you can define like:-
var mongoose = require('mongoose');
var courseSchema = new mongoose.Schema({
name: String
});
var studentSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true},
phone: String,
gender: String,
age: String,
city: String,
street: String,
picture: String,
courses: [{
course:{type:mongoose.Schema.Types.ObjectId,ref:'courseSchema'},
isAttending:{type:Boolean ,default:false}
}],
subscriptionDays: Number,
daysPresent: [Date]
}, schemaOptions);
module.exports = mongoose.model('Student', studentSchema);
isAttending will solve your problem if studen subscribe 3 courses and going to particular one course then isAttending will be true otherwise false.
You Can use Cron npm module which will run a function on what time will you set and its make your life easy.
Thanks
I am quite a newbie and have a problem for mongodb aggregation. I used mongoose.
var SubjectScore = new Schema({
name: {type:String, required:true}, //math, science, history, ...
score: {type:Number, required:true } // 95, 85, 77,....
});
var Subject = new Schema({
year: Number, //2012, 2013, 2014
subjectScore : [SubjectScore]
});
var StudentSchema = new Schema({
name: String,
subject: [Subject], //array length varies for each student
profile: String,
});
So, Input data is like this.
{ _id: 54c921aaa7918d4e4a8c7e51,
name: John,
profile: "He is nice",
subject: [{ year: 2010,
subjectScore: [{ name:"history" score:66},
{ name:"math", score:65},
{ name:"science", score:87}] }]
},{ year: 2011,
subjectScore: [{ name:"history" score:75},
{ name:"math", score:61},
{ name:"science", score:92}] }]
},{ year: 2012,
subjectScore: [{ name:"history" score:83},
{ name:"math", score:82},
{ name:"science", score:86}] }]
},{ year: 2013,
subjectScore: [{ name:"history" score:77},
{ name:"math", score:99},
{ name:"science", score:71}] }]
}]
}
The final result I want to get is like follows.
[
{ _id: "54c921aaa7918d4e4a8c7e51",
name: "John"
profile: "He is nice",
avgScore: [
{name: "math", score: 77},
{name:"history", score:78},
{name:"science", score:86} ]
totalAvg: 82
},
{ _id: "54c921aaa7918d4e4a8c7e5b",
name: "Mary"
profile: "She is kind",
avgScore: [
{name: "math", score: 67},
{name:"history", score:99},
{name:"science", score:96} ]
totalAvg: 82
},
{ _id: "54c921aaa7918d4e4a8c7e56",
name: "Jane"
profile: "She is smart",
avgScore: [
{name: "math", score: 99},
{name:"history", score:99},
{name:"science", score:99} ],
totalAvg: 99
}
..... // 7 more student for first page result
]
I tried following but I couldn't get the field of name, profile. So I needed additional query for getting name&profile fields and sorting again.
{$project:{subject:1}},
{$unwind:"$subject"},
{$unwind:"$subject.subjectScore"},
{$group:{_id:{studentId:"$_id", subjectName:"$subject.subjectScore.name"},
avgScore:{$avg: "$subject.subjectScore.score"}}},
{$group:{_id:"$_id.studentId",
avgScore:{$push: {name:"$_id.subjectName", score:"$avgScore"}},
totalAvg:{$avg:"$avgScore"}}},
{$sort:{totalAvg:-1}},
{$limit:10} // for first page (students per page : 10)
I want know how to keep the fields which is not necessary for aggregation but need to show as result. If it's not possible, do I need additional queries for result I want?
Are there another way to get this result considering performance?
I've already put a few days for this job and googling, but didn't get an answer. Help me please.
Thank you.
You are losing the information in your first operation because the $project is only sending along the value of the subject key.
The operation to keep the value of fields unaffected by aggregation calculations (name and profile) is with $first. It will just grab the first value of that field which should be the same as the rest of them.
{ $unwind: "$subject" },
{ $unwind: "$subject.subjectScore" },
{ $group: { _id: { sId: "$_id", subjectName: "$subject.subjectScore.name" },
avgScore: { $avg: "$subject.subjectScore.score" },
name: { $first: "$name" },
profile: { $first: "$profile" } } },
{ $group: { _id: "$_id.sId",
name: { $first: "$name" },
profile: { $first: "$profile" },
avgScore: { $push: { name: "$_id.subjectName", score: "$avgScore" } },
totalAvg: { $avg: "$avgScore" } } }
{ $sort: { totalAvg: -1 } }, // this is sorting desc, but sample is asc?
{ $limit: 10 }