Mongoose: I need to calculate totalizer for some fields in aggregate - node.js

I have a query (aggregate) and I need to calculate a totalizer for some fields (value and comissionValue) and count how many registers this query have.
My query(aggregate)
let findTerm = {
store: req.body.store,
status: {
$in: resultStatusServices
}
}
if (req.body.start) {
findTerm.scheduleStart = {
$lte: req.body.start
};
}
if (req.body.period) {
findTerm.scheduleEnd = {
$gte: req.body.period
};
}
Schedule.aggregate([{
$match: findTerm
},
{
$project: {
"employee.name": 1,
"customer.name": 1,
"service.name": 1,
"value": 1,
"scheduleDate": 1,
"scheduleStart": 1,
"scheduleEnd": 1,
"comissionValue": 1,
"status": 1,
"paymentMethod": 1
}
},
{
$group:{
_id: {
"employee.name" : "$employee.name",
"customer.name" : "$customer.name",
"service.name": "$service.name",
"value": "$value",
"scheduleDate": "$scheduleDate",
"scheduleStart" :"$scheduleStart",
"scheduleEnd": "$scheduleEnd",
"comissionValue" : "$comissionValue",
"status" : "$value",
"paymentMethod" : "$paymentMethod"
},
}
},
{
$match: findTerm
},
{
$group: {
_id: {
id: "$store"
},
totalValue: {
$sum: "$value"
},
totalServices: {
"$sum": 1
},
totalComission: {
$sum: "$comissionValue"
},
count: {
$sum: 1
}
}
},
{
$sort: sort
},
{
$skip: req.body.limit * req.body.page
},
{
$limit: req.body.limit
}
Schedule (model)
store: {
type: String,
required: true
},
customer: {
id: {
type: String
},
name: {
type: String
},
avatar: String,
phone: {
type: String
},
email: { type: String },
doc: {
type: String
},
},
employee: {
id: {
type: String,
required: true
},
name: {
type: String,
required: true
},
avatar: String,
},
service: {
id: {
type: String
},
name: {
type: String,
required: true
},
filters: [String]
},
info: {
channel: {
type: String,
required: true,
default: 'app'
},
id: String,
name: String
},
scheduleDate: {
type: String,
required: true
},
scheduleStart: {
type: String,
required: true
},
scheduleEnd: {
type: String,
required: true
},
value: {
type: Number
},
comissionType: {
type: String,
default: '$'
},
comissionValue: {
type: Number,
default: 0
},
status: {
type: Number,
required: true
},
observation: String,
paymentMethod: {
type: Number,
default: 0
},
What I'am trying to do as a result of this query:
[
0:{
comissionValue: 14
customer: {name: "Marcelo"}
employee: {name: "Andy"}
paymentMethod: 0
scheduleDate: "2019-01-01"
scheduleEnd: "2019-01-01 09:30"
scheduleStart: "2019-01-01 09:00"
service: {name: "Barber"}
status: 2
value: 20
_id: "5c26275ffe046d25a07cb466"}
1: {...}
2: {...}
...
],[totalizers: { count: 2, totalServices: 50, totalComission:65}]
How can i do this, how can i make this totalizers?

You can use $facet to accomplish this type of query since it allows you to run various aggregations on the same set of input documents, without needing to retrieve the input documents multiple times. The first facet can have the query pipeline with the sort and limit and the other facet will yield the aggregate sums (totalizers).
For instance the following aggregate operation will give you the desired result:
Schedule.aggregate([
{ '$match': findTerm },
{ '$facet': {
'data': [
{ '$project': {
'employee.name': 1,
'customer.name': 1,
'service.name': 1,
'value': 1,
'scheduleDate': 1,
'scheduleStart': 1,
'scheduleEnd': 1,
'comissionValue': 1,
'status': 1,
'paymentMethod': 1
} },
{ '$sort': sort },
{ '$skip': req.body.limit * req.body.page },
{ '$limit': req.body.limit }
],
'totalizer': [
{ '$group': {
'_id': '$store',
'count': { '$sum': 1 },
'totalValue': { '$sum': '$value' },
'totalComission': { '$sum': '$comissionValue' }
} },
{ '$group': {
'_id': null,
'storesCount': {
'$push': {
'store': '$_id',
'count': '$count'
}
},
'totalValue': { '$sum': '$totalValue' },
'totalServices': { '$sum': '$count' },
'totalComission': { '$sum': '$totalComission' }
} }
]
} }
]).exec((err, results) => {
if (err) handleError(err);
console.log(results[0]);
})

Related

Use group by on a group by result in mongoose

I am working on a NodeJS and a Mongoose Project and I have the following two schemas.
UserSchema.js
const UserSchema = mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
incharge: {
type: String,
enum: ['Adhihariharan', 'Anuja', 'Dhivya', 'Govind', 'Joann'],
required: true
},
)}
ContactSchema.js
const ContactSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
required: [true, 'Please add a name'],
},
status: {
type: String,
enum: [
'Not Called',
'Wrong Number',
'Called/Declined',
'Called/Not Reachable',
'Called/Postponed',
'Called/Accepted',
'Emailed/Awaiting Response',
'Emailed/Declined',
'Emailed/Confirmed',
],
default: 'Not Called',
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
I am looking for a query which would give me a result which looks as the following:
[
{
_id: "5d7a514b5d2c12c7449be048",
name: "Benita",
incharge: "Joann",
statuses: [
{ status: "Not Called", count: 1 },
{ status: "Called/Accepted", count: 1 },
{ status: "Called/Declined", count: 1 },
{ status: "Called/Not Reachable", count: 1 },
]
},
{
_id: "5d7a514b5d2c12c7449be048",
name: "Febia",
incharge: "Dhivya",
statuses: [
{ "Not Called": 2 },
{ "Called/Postponed": 2 },
{ "Called/Declined": 3 },
{ "Called/Not Reachable": 1 },
]
},
... and so on
]
Here, the integer, is the number of times that status appears for a particular user and in charge is the manager in charge of the user. The _id mentioned is the ID of the user.
The _id, user, in charge belong to the UserSchema and the status belongs to the ContactSchema
I have tried the following query:
teams = await Contact.aggregate([
{
$group: {
_id: { user: '$user', status: '$status' },
count: { $sum: '$count' },
},
},
{
$lookup: {
from: 'members',
localField: '_id.user',
foreignField: '_id',
as: 'user',
},
},
{
$unwind: { path: '$user' },
},
{
$project: {
'user.name': 1,
'user.incharge': 1,
count: 1,
},
},
{
$sort: { 'user.incharge': 1, 'user.name': 1 },
},
]);
And the following was the output:
{
_id: { user: 5ff52b10fa237b001c93ef18, status: 'Not Called' },
count: 1,
user: { name: 'Benita', incharge: 'Joann' }
},
{
_id: { user: 5ff4ca05fa237b001c93ef15, status: 'Not Called' },
count: 2,
user: { name: 'Febia', incharge: 'Dhivya' }
},
{
_id: { user: 5ff4ca05fa237b001c93ef15, status: 'Called/Accepted' },
count: 4,
user: { name: 'Febia', incharge: 'Dhivya' }
}
Can someone please help me get the desired result?
Thanks in advance.
EDIT:
I did try #turivishal's approach but this is what I got:-
{
_id: 5ff52b10fa237b001c93ef18,
name: 'Sadana',
incharge: 'Joann',
statuses: [ [Object] ]
},
{
_id: 5ff4ca05fa237b001c93ef15,
name: 'Sudarshan B',
incharge: 'Joann',
statuses: [ [Object], [Object] ]
}
Can you please tell me how I can access the [Object] inside the status array in mongoose so that I can get a result as below...
{
_id: "5ff4ca05fa237b001c93ef15",
name: "Sudarshan B",
incharge: "Joann",
statuses: [
{ "Not Called": 2 },
{ "Called/Postponed": 2 },
]
},
You can try lookup with aggregation pipeline,
$lookup with contact pass _id in let,
$match user id condition
$group by status and get total count
$project to change name of the key and value
$addFields to convert statuses array to object using $arrayToObject
teams = await User.aggregate([
{
$lookup: {
from: "contact",
let: { user: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$$user", "$user"] } } },
{
$group: {
_id: "$status",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
k: "$_id",
v: "$count"
}
}
],
as: "statuses"
}
},
{ $addFields: { statuses: { $arrayToObject: "$statuses" } } }
])
Playground

MongoDB : How to pick with $lte/lt all the documents that answer to specific $sum condition?

Consider :
EightWeekGamePlan.aggregate(
[
{ $match: { LeadId: { $in: leads }, Week: week,
// total: { $lt: 5 } // This part doesn't work
} },
{
$group: {
_id: {
LeadId: "$LeadId",
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}
}
]
How can I pick all the documents that their sum of $TotalClaimsLeftToBeClaimedByClientType is less than 5 ?
I've tried with total: { $lt: 5 }but I got an empty array.
Here is the Schema :
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const EightWeekGamePlanSchema = new Schema({
Week: {
type: Number,
required: true
},
LeadId: {
type: String,
required: true
},
PackageId: {
type: String,
required: true
},
BusinessName: {
type: String,
required: true
},
PhoneNumberMasque: {
type: String,
required: true
},
City: {
type: String,
required: true
},
Rooms: {
type: Number,
required: true
},
LeadStartDate: {
type: Date
},
LeadEndDate: {
type: Date
},
TargetedToBeClaimedByClientType: {
type: Number,
required: true
},
TotalClaimsLeftToBeClaimedByClientType: {
// incresed by 1 every time it's claimed
type: Number,
required: true
},
TotalClaimsToBeClaimedByClientType: {
// Stays fixed
type: Number,
required: true
},
Status: {
type: Number,
required: true
},
InsertDate: {
type: Date,
default: Date.now
}
});
module.exports = EightWeekGamePlan = mongoose.model(
"eightweekgameplan",
EightWeekGamePlanSchema
);
From your query, try this :
EightWeekGamePlan.aggregate(
[
{
$match: {
LeadId: { $in: leads }, Week: week
}
},
{
$group: {
_id: {
LeadId: "$LeadId",
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}
}, {
$match: {
'_id.total': { $lte: 5 }
}
}
])
From the above it $match did not work because your total is not a top level field, it's inside _id. So it's basically grouped based on LeadId + sum of TotalClaimsLeftToBeClaimedByClientType. Just in case if you wanted to group only based on LeadId check below one.
(Or) you can change the query :
EightWeekGamePlan.aggregate(
[
{
$match: {
LeadId: { $in: leads }, Week: week
}
},
{
$group: {
_id: {
LeadId: "$LeadId"
},
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}, {
$match: {
'total': { $lte: 5 }
}
}
])

Group by not working using populate in mongoose

I have trouble to fill the fields of group in .populate({}), I tried many times but I could not understand the problem:
Company.find({
_id: req.body.idCompany,
})
.populate({
path: 'listAgencys',
model: 'Agency',
match: {
idClient: req.body.idClient,
createdAt: {
$gte: new Date(req.body.startDate),
$lte: new Date(req.body.endDate)
}
},
group: {
_id: {
subscriptionType: '$subscriptionName',
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
month: {
$let: {
vars: {
monthsInString: [, 'Jan.', 'Fev.', 'Mars', ......]
},
in: {
$arrayElemAt: ['$$monthsInString', { $month: '$createdAt' }]
}
}
}
},
countLines: { $sum: 1 },
} ,
group: {
_id: "$_id.subscriptionType",
info: {
$push: {
year: "$_id.year",
month: "$_id.month",
allLines: "$countLines",
}
},
}
})
.exec();
SCHEMAS:
const companySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
trim: true,
},
listAgencys: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Agency",
}
]
});
module.exports = mongoose.model("Company", companySchema);
--
const agencySchema = new mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
idClient: {
type: Number,
trim: true,
//unique: true,
},
adress: {
type: String,
trim: true,
lowercase: true,
},
createdAt: {
type: Date,
default: Date.now()
},
listOfSubscribers: [{
numberSubscriber: {
type: Number,
},
numberPhone: {
type: Number,
},
subscriptionName: {
type: String
},
}],
});
module.exports = mongoose.model("Agency", agencySchema);
Example of parameters;
{
"idCompany": "5c71ba1c1376b034f8dbceb6",
"startDate":"2019-02-23",
"endDate":"2019-03-31",
"idClient" : "021378009"
}
I want to display the number of subscribers per month and subscriptionType according to idAgency and idCompany that will be passed in parameter.
Edit1 with aggregate:
Company.aggregate(
[
{ $unwind: "$listAgencys" },
{
$match: {
_id: req.body.idCompany,
idClient: req.body.idClient,
createdAt: {
"istAgencys.createdAt": {
$gte: new Date(req.body.startDate),
$lte: new Date(req.body.endDate)
}
}
},
{
$group: {
_id: {
subscriptionType: '$listAgencys.subscriptionName',
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
month: {
$let: {
vars: {
monthsInString: [, 'Jan.', 'Fev.', 'Mars', ......]
},
in: {
$arrayElemAt: ['$$monthsInString', { $month: '$createdAt' }]
}
}
}
}
countLines: { $sum: 1 },
} ,
{
$group: {
_id: "$_id.subscriptionType",
info: {
$push: {
year: "$_id.year",
month: "$_id.month",
allLines: "$countLines",
}
}
}
}
])
Result of edit1:
{
success: true,
datalines : []
}
Example of output:
{
"success": true,
"datalines": [
{
"idClient": 0213400892124
{
"_id": {
"subscriptionType": "ADL",
"year": 2019,
"month" : "Fev."
},
"allLines": 3,
},
{
"_id": {
"subscriptionType": "Lines",
"year": 2019,
"month" : "Jan."
},
"allLines": 10,
},
{
"_id": {
"subscriptionType": "Others",
"year": 2019,
"month" : "Mars"
},
"allLines": 35,
}
},
{
"idClient": 78450012365
.........
}
]
}
thank you in advance,

MongoDB: A complex query with array input

I'm stuck at finding a solution for the following query.
1) a user can select many categories and subcategories.
2) the user can see all other users how are selected the same categories and subcategories within a certain radius.
Here is the Schema of the user
const userSchema = new Schema(
{
image: { type: String, default: 'NA' },
firstName: { type: String, default: 'first name' },
lastName: { type: String, default: 'last name' },
email: { type: String, lowercase: true, unique: true, trim: true },
password: { type: String, min: 6 },
gender: { type: String, emun: ['male','female','other'] },
about: { type: String, default: 'about you' },
address: {
zipCode: { type: Number, default: 000000 },
place: { type: String, default: 'place' },
street: { type: String, default: 'street' },
country: { type: String, default: 'Country' },
location: {
type: { type: String, default:'Point'},
coordinates: { type:[Number], index:'2dsphere', default:[0,0] }
}
},
interests: [
{
_id : false,
category: {
id: { type: Schema.Types.ObjectId, ref: 'Category' },
name: { type: String }
},
subCategory: [
{
_id : false,
id: { type: Schema.Types.ObjectId, ref: 'Subcategory' },
name: { type: String }
}
]
}
]
}
);
In my controller here is what I tried
homeData: async (req, res, next) => {
const limit = Number(req.params.limit);
const { latitude, longitude, minDistance, maxDistance } = getUserCurrentLocation(req);
const usersWithSameInterests = await User.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"distanceField": "distance",
"minDistance": minDistance,
"maxDistance": maxDistance,
"spherical": true,
"query": { "location.type": "Point" }
}
},
{
"$match": { "interests": { "$elemMatch": {name: 'Sports'} }} // hard coded for testing
},
{ "$sort": { "distance": 1 } },
{ "$limit" : limit },
{
"$project": {
"_id": 1,
"image": 1,
"firstName":1,
"lastName":1,
"distance": 1,
"createdAt": 1
}
}
]);
return respondSuccess(res, null, {
newNotification: false,
usersWithSameInterests: usersWithSameInterests
});
},
The response i'm getting is
{
"success": true,
"message": "query was successfull",
"data": {
"newNotification": false,
"usersWithSameInterests": []
}
}
Sample categories and subcategories
Category: Sports
Subcategories: Cricket, Football, Hockey, Tennis
Category: Learning Languages
Subcategories: English, German, Spanish, Hindi
looking forward for much-needed help.
thank you.
It seems that you have a few mismatched columns.
On the $geonear pipeline, the line "query": { "location.type": "Point" } should be: 'query': {'address.location.type': 'Point'}.
And on the $match pipeline, the line { "interests": { "$elemMatch": {name: 'Sports'} } should be 'interests': { '$elemMatch:' {'category.name': 'Sports'} }
Edit:
To match multiple interests on the category and subcategory field, You can use the $in operator on the $match pipeline. Like this:
{
'interests.category.name': { $in: ['Sports'] },
'interests.subCategory.name': {$in: ['Soccer']}
}
It'll return anyone that have Sports in the category name, and Soccer on subcategory name.

Get conversation's last message with unread count

I'm making an in app messaging system in which I have to show the list of conversations with their last message and the unread count. My schema is as follows--
var schema = new Schema({
senderID: {
type: Schema.Types.ObjectId,
ref: 'Member'
},
receiversID: [{
type: Schema.Types.ObjectId,
ref: 'Member'
}],
content: {
type: String,
default: ''
},
isRead: {
type: Boolean,
default: false,
},
createdAt: {
type: Number,
default: Date.now
}
});
I did this initially to get all the conversations with their last message --
messageModel.aggregate(
[{ $match: { senderID: userId } },
{ $unwind: '$receiversID' },
{ $sort: { createdAt: -1 } },
{ $group: { _id: '$receiversID', unreadCount: { $sum: { $cond: [{ $eq: ["$isRead", false] }, 1, 0] } }, senderID: { $first: '$senderID' }, receiversID: { $first: '$receiversID' }, content: { $first: '$content' } } },
{ $skip: pagingData.pageSize * (pagingData.pageIndex - 1) },
{ $limit: pagingData.pageSize }
], function (err, docs) {
resolve(docs);
}
);
But it doesn't shows the messages if you are a receiver. I want to show the conversation whether you are receiver or sender.
i use something like this:
{
'$or': [
{
'$and': [
{
'receiversID': userId
}, {
'senderID': toUserId
}
]
}, {
'$and': [
{
'receiversID': toUserId
}, {
'senderID': userId
}
]
},
],
}

Resources