Sort on nested column with aggregation - node.js

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) {
//...
});

Related

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 }
}
}
]);

Convert Field With Unix TimeStamp to Date (Mongodb/NodeJS)

I am using aggregate in MongoDB to group fields by $year, $month & $dayOfMonth.
Transaction.aggregate(
[{
$group: {
_id: {
year : { $year : "$createdAt" },
month : { $month : "$createdAt" },
day : { $dayOfMonth : "$createdAt" },
},
totalQuantity: {
$sum: "$totalQuantity"
},
totalAmount: {
$sum: "$totalAmount"
},
totalPayment: {
$sum: "$totalPayment"
},
count: { $sum: 1 }
}
}
]).then( res => console.log(res));
In the above code, I'm using the default $createdAt field but when I try to use $date which has the date in Unix timestamp, it throws an error.
What have I tried?
Transaction.aggregate(
[{
$group: {
_id: {
year : { $year : new Date("$date") },
month : { $month : new Date("$date") },
day : { $dayOfMonth : new Date("$date") },
},
totalQuantity: {
$sum: "$totalQuantity"
},
totalAmount: {
$sum: "$totalAmount"
},
totalPayment: {
$sum: "$totalPayment"
},
count: { $sum: 1 }
}
}
]).then( res => console.log(res));
But this didn't work as "$date" is passed as a string to the Date constructor. Any workaround this?
You can do it with $toDate, converts unix timestamp milisecond to iso date,
$addFields to convert createdAt and replace value
{
$addFields: {
createdAt: { $toDate: "$createdAt" }
}
},
you can use directly in $group
{
$group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" },
day: { $dayOfMonth: "$createdAt" }
}
}
}
Playground: https://mongoplayground.net/p/-gWq0YLtLSX
you can convert inside $group also, but this will convert three time and instead of this you can add one time and use in group like above example.
year: { $year: { $toDate: "$createdAt" } },
month: { $month: { $toDate: "$createdAt" } },
day: { $dayOfMonth: { $toDate: "$createdAt" } }

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
}
}
}
]

How to convert string to ObjectId type and use in $lookup for mongodb?

I want to get the business information with businessId as a reference. However, I can't get the correct data because the previous developer did not use ObjectId type on the model. Now what I want to do is convert the businessId type to objectId withough altering the model, it would be easy if I do it but the old data will be affected, which is not good. Please see below for the model
const scanHistory = new Schema({
businessId: { type: String },
domains: [
{
domainId: { type: String },
scanType: { type: String },
status: { type: String },
reportUrl: { type: String },
scanStart: { type: Date, default: Date.now },
scanFinish: { type: Date, default: Date.now }
}
],
scanStart: { type: Date, default: Date.now },
scanStatus: { type: String },
scanType: { type: String }
});
This is my aggregate query
.collection("scanhistories")
.aggregate([
{
$addFields: {
businessObjId: {
$convert: {
input: "businessId",
to: "objectId",
onError: "Could not convert to type ObjectId."
}
}
}
},
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $max: "$businessId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},
{
$lookup: {
from: "businesses",
as: "businessInfo",
localField: "businessObjId",
foreignField: "_id"
}
},
{
$project: {
_id: 1,
businessObjId: 1,
primaryDomain: { $arrayElemAt: ["$businessInfo.primaryDomain", 0] },
businessName: { $arrayElemAt: ["$businessInfo.businessName", 0] },
frequency: { $arrayElemAt: ["$businessInfo.scanFrequency", 0] },
scanStatus: 1,
domains: 1
}
},
{
$match: {
scanStatus: { $in: ["running", "undef"] },
domains: { $exists: true }
}
}
])
.toArray();
for (let x = 0; x < history.length; x++) {
console.log(history[x]);
}
Now the output is like this which is not the one I expected.
{ _id: 5de09321bdb7cc07b7595de4,
businessObjId: 5de09321bdb7cc07b7595de4,
scanStatus: 'undef',
domains:
[ { _id: 5dfa626300007c243c1528b3,
domainId: '5de09321bdb7cc07b7595de5',
scanType: 'scheduled',
status: 'running',
reportUrl: '',
scanStart: 2019-12-18T17:31:14.754Z,
scanFinish: 2019-12-18T17:31:14.754Z } ] }
The expected result should have been with the lookup businessInfo that I wanted
{ _id: 5de09321bdb7cc07b7595de4,
businessObjId: 5de09321bdb7cc07b7595de4,
scanStatus: 'undef',
domains:
[ { _id: 5dfa626300007c243c1528b3,
domainId: '5de09321bdb7cc07b7595de5',
scanType: 'scheduled',
status: 'running',
reportUrl: '',
scanStart: 2019-12-18T17:31:14.754Z,
scanFinish: 2019-12-18T17:31:14.754Z } ],
primaryDomain: "mydomainxxx.xy",
businessName: "The biz",
scanFrequency: "daily"
}
Can you help me? I am really new to MongoDB and my background is PHP/SQL so all advises will be much appreciated. Thank you!
So I have found the solution for this. It was just a single mistake. :(
So on the aggregate code above where the group pipeline is. I made a mistake here.
Previous code above
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $max: "$businessId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},
Correct one
I change this part here:
businessObjId: { $first: "$businessObjId" }
{
$group: {
_id: { $max: "$businessId" },
businessObjId: { $first: "$businessObjId" },
scanStatus: { $max: "$scanStatus" },
scanStart: { $max: "$scanStart" },
domains: { $max: "$domains" }
}
},

how to group data based on months in nodejs?

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:

Resources