I have two fields for a vote poll like vote1 and vote2 which hold a candidateId. What I was trying to do is, calculate the number of vote counts for each candidate in my VotePoll collection.
My VotePollSchema looks like the following:
VotePollSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
vote1: {
desc: "First Voted Candidate",
type: mongoose.Schema.Types.ObjectId,
ref: "Candidate",
type: String,
},
vote2: {
desc: "Second Voted Candidate",
type: mongoose.Schema.Types.ObjectId,
ref: "Candidate",
type: String,
},
isActive: {
desc: "is Active.",
type: Boolean,
default: true,
required: true,
},
});
What I want to achieve here is that count the number of votes for a candidate which might be in vote1 or vote2. Since each User is able to vote for one or two candidates at the same time.
So, how should I group this collection by the candidate? Which could be either in vote1 or in vote2. I've tried something like this but I don't think it's the appropriate way of doing it.
const votePolls = await VotePoll.aggregate([
{ $match: { isActive: true } },
{
$group: {
_id: {
$or: [{ firstVotedCandidate: "$firstVotedCandidate" }],
},
voteCount: { $sum: 1 },
},
},
]);
My expected result:
{
candidateOne: {
candidateName: "Joshua Solomon"
voteCount: 140
}
candidateTwo: {
candidateName: "Jenny Doe"
voteCount: 25
}
...
}
Sample input data:
[
{
"isActive": true,
"user": {
"fullName": "Joshua Solomon",
"createdAt": "2021-08-02T13:33:14.584Z",
"updatedAt": "2021-08-02T13:36:35.041Z",
"registeredBy": "61026c5e4fd8a53c98b8c579",
"id": "6107f4182e21a42bd8b2b9cb"
},
"vote1": {
"isActive": true,
"fullName": "Candidate 1",
"title": "Available Candidate",
"createdAt": "2021-07-28T12:35:27.868Z",
"updatedAt": "2021-07-28T12:35:27.868Z",
"id": "61014f0f8d0ad041c0cf493a"
},
"vote2": {
"isActive": true,
"fullName": "Candidate 4",
"title": "Fourth Candidate",
"createdAt": "2021-07-28T12:36:13.229Z",
"updatedAt": "2021-07-28T12:36:13.229Z",
"id": "61014f3d8d0ad041c0cf4940"
},
"createdAt": "2021-08-02T13:36:34.859Z",
"updatedAt": "2021-08-02T13:36:34.859Z",
"id": "6107f4e22e21a42bd8b2dae2"
},
{
"isActive": true,
"user": {
"fullName": "Jenny Doe",
"createdAt": "2021-08-02T13:33:15.351Z",
"updatedAt": "2021-08-03T06:10:53.632Z",
"registeredBy": "61026c5e4fd8a53c98b8c579",
"id": "6107f4192e21a42bd8b2d136"
},
"vote1": {
"isActive": true,
"fullName": "Candidate 4",
"title": "Fourth Candidate",
"createdAt": "2021-07-28T12:36:13.229Z",
"updatedAt": "2021-07-28T12:36:13.229Z",
"id": "61014f3d8d0ad041c0cf4940"
},
"vote2": {
"isActive": true,
"fullName": "Candidate 2",
"title": "Second Candidate",
"createdAt": "2021-07-28T12:35:56.425Z",
"updatedAt": "2021-07-28T12:35:56.425Z",
"id": "61014f2c8d0ad041c0cf493c"
},
"createdAt": "2021-08-03T06:10:53.389Z",
"updatedAt": "2021-08-03T06:10:53.389Z",
"id": "6108ddedcc1c4f2a505b4028"
}
]
This aggregate will return results in a sorted array:
[
{
'$addFields': {
'votes': [
'$vote1', '$vote2'
]
}
}, {
'$unwind': {
'path': '$votes'
}
}, {
'$group': {
'_id': {
'name': '$votes.fullName'
},
'voteCount': {
'$sum': 1
}
}
}, {
'$project': {
'candidateName': '$_id.name',
'voteCount': 1,
'_id': 0
}
}, {
'$sort': {
'voteCount': -1
}
}
]
The $addfields the vote fields into an array,
The $unwind separates the documents based on the votes,
The $group gets the voteCount, it will get the total votes regardless of whether they were the first or second vote
The $project is just to clean things up
And the $sort is to sort the array from most votes to least.
I think you'd be best suited by changing your schema, vote1 and vote2 store practically the same information so you could simplify by having a schema where you store votes as an array of objects:
VotePollSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
votes: [{
desc: String,
type: mongoose.Schema.Types.ObjectId,
ref: "Candidate",
type: String,
}],
isActive: {
desc: "is Active.",
type: Boolean,
default: true,
required: true,
},
});
Then you could avoid the $addFields at the start
Related
I want to keep only that data in counts which will be matched with it's parent id for each individual document.
Student Schema
const StudentSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Please Provide Name"],
maxlength: 100,
minlength: 2,
},
email: {
type: String,
required: [true, "Please Provide Email"],
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please Provide a Valid Email",
],
unique: true,
},
number: {
type: String,
required: [true, "Please Provide Number"],
match: [
/^(?:(?:\+|0{0,2})91(\s*[\-]\s*)?|[0]?)?[789]\d{9}$/,
"Please Provide a Valid Number",
],
unique: true,
},
rollNumber: {
type: Number,
required: [true, "Please Provide Roll Number"],
maxlength: 5,
},
departmentID: {
type: mongoose.Types.ObjectId,
ref: "Department",
required: [true, "Please Provide departmentID"],
},
classID: {
type: mongoose.Types.ObjectId,
ref: "Class",
required: [true, "Please Provide classID"],
},
position: {
type: String,
required: [true, "Please Provide Position"],
enum: ["Student"],
default: "Student",
},
password: {
type: String,
required: [true, "Please Provide Password"],
minlength: 6,
},
},
{ timestamps: true }
);
Attendance Schema
const AttendanceSchema = new Schema(
{
date: {
type: String,
required: [true, "Please Provide Date"],
maxlength: 15,
minlength: 5,
},
subjectID: {
type: mongoose.Types.ObjectId,
ref: "Subject",
required: [true, "Please Provide Subject"],
},
studentID: {
type: mongoose.Types.ObjectId,
ref: "Student",
required: [true, "Please Provide Student"],
},
teacherID: {
type: mongoose.Types.ObjectId,
ref: "Faculty",
required: [true, "Please Provide Teacher"],
},
classID: {
type: mongoose.Types.ObjectId,
ref: "Class",
required: [true, "Please Provide Class"],
},
departmentID: {
type: mongoose.Types.ObjectId,
ref: "Department",
required: [true, "Please Provide Department"],
},
},
{ timestamps: true }
);
My Query
const data = await StudentSchema.aggregate([
{ $match: { classID: mongoose.Types.ObjectId(`${req.params.id}`) } },
{
$lookup: {
from: "attendances",
pipeline: [
{
$match: {
subjectID: mongoose.Types.ObjectId(`${req.params.Sid}`),
},
},
{ $group: { _id: "$studentID", count: { $sum: 1 } } },
],
as: "counts",
},
},
]);
This is the data I'm getting from this query:
{
"data": [
{
"_id": "63677d2960fa65e95aef5e95",
"name": "Lavannya Urkande",
"email": "lavannya#gmail.com",
"number": "9130354519",
"rollNumber": 201,
"departmentID": "6365531fdc02a121ffeed944",
"classID": "636554e8dc02a121ffeed982",
"position": "Student",
"password": "$2a$10$mqysVgtIGrYbvMGtHE2vbu0z5g05BlwJizcc.CfWMld78VPrnvcrO",
"createdAt": "2022-11-06T09:23:53.803Z",
"updatedAt": "2022-11-06T09:23:53.803Z",
"__v": 0,
"counts": [
{
"_id": "6367819d60fa65e95aef5ea7",
"count": 2,
},
{
"_id": "63677d2960fa65e95aef5e95",
"count": 3,
}
]
},
{
"_id": "6367819d60fa65e95aef5ea7",
"name": "Sohan Shinde",
"email": "soham#gmail.com",
"number": "9130354510",
"rollNumber": 202,
"departmentID": "6365531fdc02a121ffeed944",
"classID": "636554e8dc02a121ffeed982",
"position": "Student",
"password": "$2a$10$DuXjtayCPgGwkNnpog5IYeEEkY56igtlA/m6vobT44wmlSLcXp1eK",
"createdAt": "2022-11-06T09:42:53.861Z",
"updatedAt": "2022-11-06T09:42:53.861Z",
"__v": 0,
"counts": [
{
"_id": "6367819d60fa65e95aef5ea7",
"count": 2,
},
{
"_id": "63677d2960fa65e95aef5e95",
"count": 3,
}
]
}
]
}
But I want this type of data from my query(counts)
The only data which matched with it's parent _id. Notice the _id and counts._id then you can understand my request.
{
"data": [
{
"_id": "63677d2960fa65e95aef5e95",
"name": "Lavannya Urkande",
"email": "lavannya#gmail.com",
"number": "9130354519",
"rollNumber": 201,
"departmentID": "6365531fdc02a121ffeed944",
"classID": "636554e8dc02a121ffeed982",
"position": "Student",
"password": "$2a$10$mqysVgtIGrYbvMGtHE2vbu0z5g05BlwJizcc.CfWMld78VPrnvcrO",
"createdAt": "2022-11-06T09:23:53.803Z",
"updatedAt": "2022-11-06T09:23:53.803Z",
"__v": 0,
"counts": [
{
"_id": "63677d2960fa65e95aef5e95",
"count": 3,
}
]
},
{
"_id": "6367819d60fa65e95aef5ea7",
"name": "Sohan Shinde",
"email": "soham#gmail.com",
"number": "9130354510",
"rollNumber": 202,
"departmentID": "6365531fdc02a121ffeed944",
"classID": "636554e8dc02a121ffeed982",
"position": "Student",
"password": "$2a$10$DuXjtayCPgGwkNnpog5IYeEEkY56igtlA/m6vobT44wmlSLcXp1eK",
"createdAt": "2022-11-06T09:42:53.861Z",
"updatedAt": "2022-11-06T09:42:53.861Z",
"__v": 0,
"counts": [
{
"_id": "6367819d60fa65e95aef5ea7",
"count": 2,
}
]
}
]
}
You can use filter method in your $project
const data = await StudentSchema.aggregate([
{ $match: { classID: mongoose.Types.ObjectId(`${req.params.id}`) } },
{
$lookup: {
from: "attendances",
pipeline: [
{
$match: {
subjectID: mongoose.Types.ObjectId(`${req.params.Sid}`),
},
},
{ $group: { _id: "$studentID", count: { $sum: 1 } } },
],
as: "counts",
},
},
{
$project: {
classID: 1,
departmentID: 1,
email: 1,
name: 1,
number: 1,
position: 1,
rollNumber: 1,
counts: {
$filter: {
input: "$counts",
cond: {
$eq: ["$$this._id", "$_id"],
},
},
},
},
},
]);
I have two mongoose schema one for keywords :
const keywordSchema = new Schema<keywordI>(
{
sentence: { type: String, required: true },
example: { type: String, required: true },
translate: { type: String, required: true },
imageUrl: { type: String, required: true },
audioUrl: { type: String, required: true },
deletedAt: { type: Date, select: false },
},
{ timestamps: true }
);
and one for keywordsUser :
const keywordUserSchema = new Schema<keywordUserI>(
{
keywordId: {
type: mongoose.Types.ObjectId,
ref: "Keyword",
required: true,
},
userId: {
type: mongoose.Types.ObjectId,
ref: "User",
required: true,
},
isBookMarked: { type: Boolean, default: false },
correctAnswer: { type: Number, default: 0 },
wrongAnswer: { type: Number, default: 0 },
},
{ timestamps: true }
);
I want to get keywords by user grouped by type which I calculate from wrongAnswer and correctAnswer
I try to do this using aggregate
return KeyWordUser.aggregate([
{
$match: { userId },
},
{
$addFields: {
type: {
$function: {
body: getType,
args: ["$correctAnswer", "$wrongAnswer"],
lang: "js",
},
},
},
},
{
$group: {
_id: "$type",
words: {
$push: "$keywordId",
},
isBookMarked: {
$push: { isBookMarked: "$isBookMarked", keywordId: "$keywordId" },
},
},
},
{
$lookup: {
localField: "words",
from: "keywords",
foreignField: "_id",
as: "props",
},
},
{
$addFields: { type: "$_id" },
},
{
$project: {
_id: 0,
words: 0,
},
},
]);
I have this result from aggregate
returned data from. postman
I want to add new aggregate stage to get this result
previous result
[
{
"isBookMarked": [
{
"keywordId": "62e2a0ad3113b8234511cd72"
},
{
"isBookMarked": false,
"keywordId": "62e2a0ba3113b8234511cd74"
}
],
"props": [
{
"_id": "62e2a0ad3113b8234511cd72",
"sentence": "hello",
"example": "Hello , what's your name",
"translate": "مرحبا ما اسمك",
"imageUrl": "src/img/keywords/keyword-A7H_M-1659019437698.png",
"audioUrl": "src/audio/keywords/keyword-YyhUp-1659019437700.mpeg",
"createdAt": "2022-07-28T14:43:57.708Z",
"updatedAt": "2022-07-28T14:43:57.708Z",
"__v": 0
},
{
"_id": "62e2a0ba3113b8234511cd74",
"sentence": "hello 2 ",
"example": "Hello , what's your name 2 ",
"translate": "مرحبا ما اسمك 2",
"imageUrl": "src/img/keywords/keyword-2JO7D-1659019450396.png",
"audioUrl": "src/audio/keywords/keyword-kt5Tc-1659019450397.mpeg",
"createdAt": "2022-07-28T14:44:10.400Z",
"updatedAt": "2022-07-28T14:44:10.400Z",
"__v": 0
}
],
"type": "strong"
}
]
and i want this result :
[
{
"props": [
{
"_id": "62e2a0ad3113b8234511cd72",
"sentence": "hello",
"example": "Hello , what's your name",
"translate": "مرحبا ما اسمك",
"imageUrl": "src/img/keywords/keyword-A7H_M-1659019437698.png",
"audioUrl": "src/audio/keywords/keyword-YyhUp-1659019437700.mpeg",
"createdAt": "2022-07-28T14:43:57.708Z",
"updatedAt": "2022-07-28T14:43:57.708Z",
"__v": 0
},
{
"_id": "62e2a0ba3113b8234511cd74",
"isBookMarked" : false
"sentence": "hello 2 ",
"example": "Hello , what's your name 2 ",
"translate": "مرحبا ما اسمك 2",
"imageUrl": "src/img/keywords/keyword-2JO7D-1659019450396.png",
"audioUrl": "src/audio/keywords/keyword-kt5Tc-1659019450397.mpeg",
"createdAt": "2022-07-28T14:44:10.400Z",
"updatedAt": "2022-07-28T14:44:10.400Z",
"__v": 0
}
],
"type": "strong"
}
]
You can just populate with the keywordId not with aggregate
I have 2 schemas:
const SiteSchema = new mongoose.Schema(
{
placement: Number,
groupId: mongoose.SchemaTypes.ObjectId,
title: {
type: String,
required: true,
trim: true,
},
url: {
type: String,
required: true,
},
},
{ timestamps: true }
)
and
const GroupSchema = new mongoose.Schema(
{
placement: Number,
header: {
type: String,
required: true,
trim: true,
},
items: [Site.schema],
},
{ timestamps: true }
)
I'm trying to get all groups with up to 5 of their corresponding sites based on the sites placement value like this:
roup.find({ 'items.placement': { $lte: 5 } })
.skip(offset)
.limit(maxNumberOfResults)
.sort('placement')
.lean()
.then(groups => {res.send({ sites: groups}))
however the result i get is all the sites in the group, regardless of their placement value.
for example:
I'm expecting to get:
[
{
"_id": "60dd2c314d98940d90fe8861",
"placement": 0,
"createdAt": "some date",
"updatedAt": "some date",
"header": "group",
"items": [
{
"_id": "60dd2cb54d98940d90fefb2a",
"groupId": "60dd2c314d98940d90fe8861",
"placement": 1,
"title": "something",
"url": "some url",
"createdAt": "some date",
"updatedAt": "some date"
},
{
"_id": "60dd2cb54d98940d90fefb2d",
"groupId": "60dd2c314d98940d90fe8861",
"placement": 2,
},
{
"_id": "60dd2cb54d98940d90fefb2f",
"groupId": "60dd2c314d98940d90fe8861",
"placement": 3,
},
{
"_id": "60dd2cb54d98940d90fefb30",
"groupId": "60dd2c314d98940d90fe8861",
"placement": 4,
},{
"_id": "60dd2cb54d98940d90fefb30",
"groupId": "60dd2c314d98940d90fe8861",
"placement": 5,
},
]
but getting placements 6 and onwards as well
***If someone could also tell me how to sort the subdocument array according to the placement value, would be greatly appreciated.
Try changing items: [Site.schema] to items: [SiteSchema],
My requirement is for a particular user get all the expenses which are grouped based on the month and return the Month and the total expenses for that month.
I have collection something like below.
expenses = {{
"_id": {
"$oid": "5bc0f74df46dae0bf4ffdc39"
},
"user": {
"$oid": "5bab847a5b0d2e2ce8b4cbe5"
},
"date": "15-Mar-2018",
"expenseInfo": "Clothes",
"category": "Shopping",
"amount": 100,
"__v": 0
},
{
"_id": {
"$oid": "5bc0f78bf46dae0bf4ffdc3b"
},
"user": {
"$oid": "5bab847a5b0d2e2ce8b4cbe5"
},
"date": "11-Apr-2018",
"expenseInfo": "mobile",
"category": "Bills",
"amount": 100,
"__v": 0
},
{
"_id": {
"$oid": "5bc0f76cf46dae0bf4ffdc3a"
},
"user": {
"$oid": "5bab847a5b0d2e2ce8b4cbe5"
},
"date": "18-Apr-2018",
"expenseInfo": "Petrol",
"category": "Fuel",
"amount": 20,
"__v": 0
}
}
Here is my Expense schema.
Also, Note that user object(user is a different schema which is referred in Expense schema)
const ExpenseSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
date: {
type: String
},
expenseInfo: {
type: String,
required: true
},
category: {
type: String,
required: true
},
amount: {
type: Number,
required: true
}
});
I tried something like this, but I am not getting any data. What is the issue here?
Expense.aggregate([{
$match: {
"_id": request.user.id
}
},
{
"$group": {
"_id": {
"$arrayElemAt": [{
"$split": ["$date", "-"]
}, 1]
},
"Total": {
"$sum": "$amount"
}
}
}
])
I am use NodeJS, ExpressJS with Mongoose Mongo ORM.
My Schema is as follows.
const chatMessageSchema = new Schema({
_id: {
type: String,
default: () => (uuidV1().replace(/-/g, ''))
},
roomid: String,
message: Schema.Types.Mixed,
type: String, // text, card, qrt, attachment
notes: [notesSchema],
posted_by_user: String,
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
},
collection: 'ChatMessages'
});
The nested sub schema for notes is as follows;
const notesSchema = new Schema({
_id: {
type: String,
default: () => (uuidV1().replace(/-/g, ''))
},
roomid: String,
messageid: String,
message: Schema.Types.Mixed,
type: String, // text, attachment
posted_by_user: String,
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}});
Initially when a user creates a chatMessage the notes is default kept as an empty array initially [].
The user can explicitly create a note message after the message has been created, the notes is an array sub documents.
Now what happens is when I get a list of messages. I need to show posted_by_user which is an id of string. Against that id of string i perform a $lookup which gets the user profile. The aggregation is as follows.
return this.aggregate([
{ $match : {roomid: roomid} },
{ $sort: {created_at: -1} },
{
$lookup: {
from: "RefUserProfiles",
localField: "posted_by_user",
foreignField: "_id",
as: "posted_by_user"
}
},
{ $unwind: "$posted_by_user" },
{ $skip: pageOptions.page * pageOptions.limit },
{ $limit: pageOptions.limit },
{ $sort: {created_at: 1} },
{
$unwind: { path: "$notes", preserveNullAndEmptyArrays: true }
},
{
$lookup: {
from: "RefUserProfiles",
localField: "notes.posted_by_user",
foreignField: "_id",
as: "notes.posted_by_user"
}
},
{
$unwind: { path: "$notes.posted_by_user", preserveNullAndEmptyArrays: true }
},
{
$group: {
_id: "$_id",
created_at: { $last: "$created_at" },
roomid: { $last: "$roomid" },
message: { $last: "$message" },
notes: { $addToSet: "$notes" },
type: { $last: "$type" },
posted_by_user: { $last: "$posted_by_user" },
read_by_recipients: { $last: "$read_by_recipients" },
}
}
]);
Now since in some cases the notes for some messages is an empty array, so for that very particular case the object I get in responses is like [{}]. Is this how it is suppose to be. or is there a better use case involved.
For example the response I get based on this query is as follows.
"conversation": [
{
"_id": "7cdbf630e6ec11e79166559abfb987ac",
"created_at": "2017-12-22T07:48:23.956Z",
"roomid": "aacc6b70e68211e79166559abfb987ac",
"message": {
"text": "message 1"
},
"notes": [{}],
"type": "text",
"posted_by_user": {
"id": "4d6fd5a95d57436d944586a1d5b2b2ff",
"name": "Adeel Imran",
"profilePicture": "",
"phone": "",
"email": "",
"designation": "",
"type": "user"
},
},
{
"_id": "80d91470e6ec11e79166559abfb987ac",
"created_at": "2017-12-22T07:48:30.648Z",
"roomid": "aacc6b70e68211e79166559abfb987ac",
"message": {
"text": "message 4"
},
"notes": [
{
"posted_by_user": {
"id": "86049b3d28f640a8a6722c57b73a292e",
"name": "Paul Johnson",
"profilePicture": "",
"phone": "",
"email": "",
"designation": "",
"type": "sp"
},
"type": "text",
"message": {
"text": "yeah "
},
"messageid": "26401d30e5a511e7acc4c734d11adb25",
"roomid": "27d3ed80e0cc11e78318b3cd036890f2",
"updated_at": "2017-12-20T16:45:44.132Z",
"created_at": "2017-12-20T16:45:44.132Z",
"_id": "38ad3750e5a511e7acc4c734d11adb25"
}
],
"type": "text",
"posted_by_user": {
"id": "4d6fd5a95d57436d944586a1d5b2b2ff",
"name": "Adeel Imran",
"profilePicture":"",
"phone": "",
"email": "",
"designation": "",
"type": "user"
},
}
],
Any suggestions or directions for this will be highly appreciated. Thank you for taking the time out to review this question.
Cheers.