how to group data based on months in nodejs? - node.js

Sale.aggregate({
$match: filter
}, {
$group: {
"_id": {
"store_id": "$store_id",
//"created_on": { $dateToString: { format: "%Y-%m-%d", date: "$strBillDate" } }
},
month: {
$month: "$strBillDate"
},
store_id: {
$first: "$store_id"
},
strBillAmt: {
$sum: "$strBillAmt"
},
strBillNumber: {
$sum: 1
}
}
})
Instead of date, I need to group sales in months, how to group sales in months in nodejs

I used a projection first in the aggregate chain to extract monthly and yearly values and did the grouping afterwards:
<doc-name>.aggregate([
{ $project:
{ _id: 1,
year: { $year: "$date" },
month: { $month: "$date"},
amount: 1
}
},
{ $group:
{ _id: { year: "$year", month: "$month" },
sum: { $sum: "$amount" }
}
}])
I also tried with your model:
var testSchema = mongoose.Schema({
store_id: { type: String },
strBillNumber: { type: String },
strBillDate: { type: Date },
strBillAmt: { type: Number }
});
var Test = mongoose.model("Test", testSchema);
Create some test data:
var test = new Test({
store_id: "1",
strBillNumber: "123",
strBillDate: new Date("2016-04-02"),
strBillAmt: 25
});
var test2 = new Test({
store_id: "1",
strBillNumber: "124",
strBillDate: new Date("2016-04-01"),
strBillAmt: 41
});
var test3 = new Test({
store_id: "3",
strBillNumber: "125",
strBillDate: new Date("2016-05-13"),
strBillAmt: 77
});
Run the query:
Test.aggregate([
{ $project:
{ store_id: 1,
yearBillDate: { $year: "$strBillDate" },
monthBillDate: { $month: "$strBillDate" },
strBillAmt: 1
}
},
{ $group:
{ _id: {yearBillDate: "$yearBillDate", monthBillDate:"$monthBillDate"},
sum: { $sum: "$strBillAmt" }
}
}
], function(err, result) {
console.log(err, result)});
And got a reasonable result:

Related

How to create mongodb aggregation pipeline between two collections?

I want to create a Mongodb aggregation pipeline for a collection named Transaction.
The Transaction collection has values amount, categoryID, description and I also have a Category collection with values type, icon and color.
I want the pipeline to show the top3 categories with their percentage values and a others category with its percentage value.
the transaction type should be Expense which it should get from the Category collection and it should show all transactions having category type Expense. The top3 should then give the results as transaction with category (example)
type : Rent
percentage:45
type: Entertainment
percentage: 30
type: Food
percentage: 20
type: Others
percentage: 5
I tried it with Category collection but I don't want category to store amount, but Transaction should store amount.
Category.aggregate([
{
$match: {
type: 'expense'
}
},
{
$group: {
_id: "$name",
amount: { $sum: "$amount" }
}
},
{
$group: {
_id: null,
totalExpense: { $sum: "$amount" },
categories: {
$push: {
name: "$_id",
amount: "$amount"
}
}
}
},
{
$project: {
_id: 0,
categories: {
$map: {
input: "$categories",
as: "category",
in: {
name: "$$category.name",
percent: { $multiply: [{ $divide: ["$$category.amount", "$totalExpense"] }, 100] }
}
}
}
}
},
{
$unwind: "$categories"
},
{
$sort: { "categories.percent": -1 }
},
{
$limit: 3
}
])
This was the pipeline I used for it.
//edit
Tried the method suggested by Joe
Transaction.aggregate([
// Join the Transaction collection with the Category collection
{
$lookup: {
from: 'Category',
localField: 'categoryID',
foreignField: '_id',
as: 'category',
},
},
// Unwind the category array to separate documents
{
$unwind: '$category',
},
// Filter for transactions where the category type is "Expense"
{
$match: {
'category.type': 'Expense',
},
},
// Group transactions by category type and calculate the percentage
{
$group: {
_id: '$category.type',
total: { $sum: '$amount' },
count: { $sum: 1 },
},
},
{
$project: {
_id: 0,
category: '$_id',
percentage: {
$multiply: [{ $divide: ['$count', { $sum: '$count' }] }, 100],
},
},
},
// Sort the categories by percentage in descending order
{
$sort: { percentage: -1 },
},
// Limit the result to top 3 categories
{
$limit: 3,
},
// group the rest of the categories as others
{
$group: {
_id: null,
top3: { $push: '$$ROOT' },
others: { $sum: { $subtract: [100, { $sum: '$top3.percentage' }] } },
},
},
{
$project: {
top3: 1,
others: { category: 'Others', percentage: '$others' },
},
},
]);
I am getting an empty array rather than the values. I have data in the collections with the correct ID's. What might be the issue?
//Answer
This aggregation worked for me
Transaction.aggregate([
{
$match: {
userID: { $eq: UserID },
type: 'Expense',
},
},
{
$addFields: { categoryID: { $toObjectId: '$categoryID' } },
},
{
$lookup: {
from: 'categories',
localField: 'categoryID',
foreignField: '_id',
as: 'category_info',
},
},
{
$unwind: '$category_info',
},
{
$group: {
_id: '$category_info.name',
amount: { $sum: '$amount' },
},
},
{
$sort: {
amount: -1,
},
},
{
$group: {
_id: null,
total: { $sum: '$amount' },
data: { $push: '$$ROOT' },
},
},
{
$project: {
results: {
$map: {
input: {
$slice: ['$data', 3],
},
in: {
category: '$$this._id',
percentage: {
$round: {
$multiply: [{ $divide: ['$$this.amount', '$total'] }, 100],
},
},
},
},
},
others: {
$cond: {
if: { $gt: [{ $size: '$data' }, 3] },
then: {
amount: {
$subtract: [
'$total',
{
$sum: {
$slice: ['$data.amount', 3],
},
},
],
},
percentage: {
$round: {
$multiply: [
{
$divide: [
{
$subtract: [
'$total',
{ $sum: { $slice: ['$data.amount', 3] } },
],
},
'$total',
],
},
100,
],
},
},
},
else: {
amount: null,
percentage: null,
},
},
},
},
},
]);

How do you count records for the current month?

Using mongo or mongoose, how would I get the total number of records for the current month?
I have this but it is giving me a total for every month, I just want a count of records for the current month.
const genTotal = await General.aggregate([
{
$group: {
_id: {
year: { $year: "$visitDate" },
month: { $month: "$visitDate" },
},
Total: { $sum: 1 },
},
},
]);
I also tried this:
const genTotal = await General.aggregate([
{
$group: {
_id: {
month: { $month: "$visitDate" },
},
Total: { $sum: 1 },
},
},
{
$match: { $month: 3 },
},
]);
Add a match stage in the beginning to filter out the past month's documents try this:
let month = new Date().getMonth();
const genTotal = await General.aggregate([
{
$match: {
$expr: {
$eq: [{ $month: "$visitDate" }, month]
}
}
},
{
$group: {
_id: {
year: { $year: "$visitDate" },
month: { $month: "$visitDate" },
},
Total: { $sum: 1 }
}
}
]);

not getting data if i choose same date for from and to

i am doing date range using mongoose aggregation when i choose two different dates am getting data but when choose same date am not getting data on perticular date,for example if i choose 23 and 24 dates am getting data but when i choose 23 & 23 am not getting data ,please help me to fix the issue
if(from && to ) {
let fromdate = moment(from).format();
let todate = moment(to).format()
console.log(new Date(fromdate),new Date(todate),'dfdfd')
console.log(fromdate,todate,'dfdfd')
return await Message.aggregate([
{
$match: {unanswered: true}
},
{
$match: {
createdAt: {
$gte: new Date(fromdate),
$lte: new Date(todate)
}
}
},
{
$group: {
_id: {$toLower: '$message'},
id: {$first: '$_id'},
display: {$first: '$message'},
createdAt: {$first: '$createdAt'},
totalQuantity: {$sum: 1}
}
}
]).sort({totalQuantity: 'desc'});
}```
Why you don't use $gte function for greater than or equal ?
[{
$match: {
"unanswered": true
}
},
{
$match: {
"createdAt": {
$gte: new Date(fromdate),
$lte: new Date(todate)
}
}
},
{
$group: {
_id: {
$toLower: '$message'
},
id: {
$first: '$_id'
},
display: {
$first: '$message'
},
createdAt: {
$first: '$createdAt'
},
totalQuantity: {
$sum: 1
}
}
}
]

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,

Sort on nested column with aggregation

I have following query using aggregation framework in Mongoose:
Comment.aggregate([{
$match: {
isActive: true
}
}, {
$group: {
_id: {
year: {
$year: "$creationDate"
},
month: {
$month: "$creationDate"
},
day: {
$dayOfMonth: "$creationDate"
}
},
comments: {
$push: {
comment: "$comment",
username: "$username",
creationDate: "$creationDate",
}
}
}
}, {
$sort: {
'comments.creationDate': -1
}
}, {
$limit: 40
}], function (err, comments) {
//...
});
Finally, I want to sort the records using creationDate inside comments array. I've used comments.creationDate but it doesn't work!
What is the correct approach to sort items using aggregation framework?
You need to move your $sort on creationDate above the $group so that it affects the order the comments array is built using $push. As you have it now, you're sorting the overall set of docs, not the array.
Comment.aggregate([{
$match: {
isActive: true
}
}, {
$sort: {
creationDate: -1
}
}, {
$group: {
_id: {
year: {
$year: "$creationDate"
},
month: {
$month: "$creationDate"
},
day: {
$dayOfMonth: "$creationDate"
}
},
comments: {
$push: {
comment: "$comment",
username: "$username",
creationDate: "$creationDate",
}
}
}
}, {
$limit: 40
}], function (err, comments) {
//...
});

Resources