Mongoose Aggregate with $match on date range or local time zone - node.js

I have invoice Model as following
{
...
"itemDetails": [
{
"item": "593a1a01bbb00000043d9a4c",
"purchasingPrice": 100,
"sellingPrice": 150,
"qty": 200,
"_id": "59c39c2a5149560004173a05",
"discount": 0
}
],
"payments": [],
"status": "PENDING",
"created": {
"$date": "2017-09-21T11:02:02.675Z"
},
...
}
Sample Invoice Document is as follows.
{
"_id": {
"$oid": "59c39c2a5149560004173a04"
},
"customer": {
"$oid": "5935013832f9fc0004fa9a16"
},
"order": {
"$oid": "59c1df8393cbba0004a0e956"
},
"employee": {
"$oid": "592d0a6238880f0004637e84"
},
"status": "PENDING",
"deliveryStatus": "PROCESSING",
"created": {
"$date": "2017-09-21T11:02:02.675Z"
},
"discount": 0,
"payments": [],
"itemDetails": [
{
"item": {
"$oid": "593a1a01bbb00000043d9a4c"
},
"purchasingPrice": 100,
"sellingPrice": 150,
"qty": 200,
"_id": {
"$oid": "59c39c2a5149560004173a05"
},
"discount": 0
}
],
"__v": 0
}
Item details Item is an object Id which refers to Item collection.
I'm writing a mongoose Aggregate query to get the sale by Item. for that I need to filter the invoice from a given date range and status does not equal to "CANCELED". for that, I have written following code
module.exports.saleByItem = (req, res) => {
let fromDate;
let toDate;
if ((req.query.fromDate && moment(req.query.fromDate, config.dateFormat, true).isValid()) && (req.query.toDate && moment(req.query.toDate, config.dateFormat, true).isValid())) {
fromDate = moment(req.query.fromDate, config.dateFormat).startOf('day');
toDate = moment(req.query.toDate, config.dateFormat).endOf('day');
}
Invoice.aggregate([
{
"$match": {
"created": {
"$gte": fromDate
? fromDate.toDate()
: undefined,
"$lte": toDate
? toDate.toDate()
: undefined
},
"status": {
"$ne": "CANCELED"
}
}
}, {
"$unwind": "$itemDetails"
}, {
"$group": {
"_id": "$itemDetails.item",
"qty": {
"$sum": "$itemDetails.qty"
},
"value": {
"$sum": {
"$multiply": [
"$itemDetails.qty", {
"$subtract": ["$itemDetails.sellingPrice", "$itemDetails.discount"]
}
]
}
},
"avarageSellingPrice": {
"$avg": {
"$subtract": ["$itemDetails.sellingPrice", "$itemDetails.discount"]
}
}
}
}, {
"$sort": {
"value": -1
}
}, {
"$lookup": {
from: "items",
localField: "_id",
foreignField: "_id",
as: "item"
}
}, {
"$unwind": "$item"
}, {
"$project": {
_id: 1,
itemName: "$item.itemName",
qty: 1,
value: 1,
avarageSellingPrice: 1
}
}
]).then(salesFigures => {
res.status(200).json(salesFigures);
}).catch((err) => {
res.status(422).json(err);
});
};
The issue is when I put today date to both dates it returns sale of today. Gives []
How to handle date ranges in $match with local time-zone?

Related

Use Mongoose aggregate to fetch object inside of an array

Here is my MongoDB schema:
{
"_id": "603f23ff6c1d862e5ced9e35",
"reviews": [
{
"like": 0,
"dislike": 0,
"_id": "603f23ff6c1d862e5ced9e34",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
},
{
"like": 0,
"dislike": 0,
"_id": "603f242a6c1d862e5ced9e36",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
}
]
productID:"hdy6nch99dndn"
}
I want to use aggregate to get the review object of a particular id. I tried but not with any success.
Here is my code:
ProductReview.aggregate([
{ $match: { productID: productID } }
])
$match
$unwind
db.collection.aggregate([
{
$match: {
productID: 1
}
},
{
$unwind: "$reviews"
},
{
$match: {
"reviews._id": 2
}
}
])
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"productID": 1,
"reviews": {
"_id": 2,
"comment": "second comment",
"dislikes": [
{
"userID": 3
},
{
"userID": 4
}
],
"likes": [
{
"userID": 1
},
{
"userID": 2
}
]
}
}
]
Mongo Playground: https://mongoplayground.net/p/qfWS1rCuMfc

How to get average of one field value per min from mongo with aggregate query?

I saved my data in MongoDb as
{
"_id": "ObjectId(\"5fec31b6b9022035abbbf7cc\")",
"message": {
"date": "2020-12-30 13:20:26",
"time": "2020-12-30T07:50:26.000Z",
"ID": "005",
"P": 1.36
}
},
{
"_id": "ObjectId(\"5fec31b5b9022035abbbf7c2\")",
"message": {
"date": "2020-12-30 13:20:24",
"time": "2020-12-30T07:50:24.000Z",
"ID": "005",
"P": 1.5
}
},
{
"_id": "ObjectId(\"5fec31b0b9022035abbbf7b3\")",
"message": {
"date": "2020-12-30 13:20:19",
"time": "2020-12-30T07:50:19.000Z",
"ID": "005",
"P": 1.63
}
}
I want to find the average of P value per min.
I've tried Group result by 15 minutes time interval in MongoDb
but I got an error in the time field.
db.pressure.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "message.time" },
"dayOfYear": { "$dayOfYear": "message.time" },
"hour": { "$hour": "message.time" },
"interval": {
"$subtract": [
{ "$minute": "message.time" },
{ "$mod": [{ "$minute": "message.time"}, 1] }
]
}
},
"count": { "$sum": 1 }
}
}
])
{
"message" : "can't convert from BSON type string to Date",
"ok" : 0,
"code" : 16006,
"codeName" : "Location16006",
"name" : "MongoError"
}
Few Fixes:
You need to convert your string date to date type using $toDate (From MongoDB 4.0), $addFields will update message.time field to date type
$year, $hour etc operators requires reference of field using $ sign you missed it in message.time
use $avg to get average of message.P
db.collection.aggregate([
{ $addFields: { "message.time": { $toDate: "$message.time" } } },
{
"$group": {
"_id": {
"year": { "$year": "$message.time" },
"dayOfYear": { "$dayOfYear": "$message.time" },
"hour": { "$hour": "$message.time" },
"interval": {
"$subtract": [
{ "$minute": "$message.time" },
{ "$mod": [{ "$minute": "$message.time" }, 15] }
]
}
},
"count": { "$count": 1 },
"average": { "$avg": "$message.P" }
}
}
])
Playground
MongoDB 3.6: you can use $dateFromString
{
$addFields: {
"message.time": {
$dateFromString: { dateString: "$message.time" }
}
}
}
Playground

MongoDB aggregation : Group by Category and sum up the amount

I have the following structure in my collection (you don't have to mind the status) :
{
"_id": {
"$oid": "5e6355e71b14ee00175698cb"
},
"finance": {
"expenditure": [
{
"status": true,
"_id": { "$oid": "5e63562d1b14ee00175698df" },
"amount": { "$numberInt": "100" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e6356491b14ee00175698e0" },
"amount": { "$numberInt": "200" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e63565b1b14ee00175698e1" },
"amount": { "$numberInt": "50" },
"category": "Outdoor"
},
{
"status": true,
"_id": { "$oid": "5e63566d1b14ee00175698e2" },
"amount": { "$numberInt": "400" },
"category": "Outdoor"
}
]
}
}
My previos command was this:
User.aggregate([
{ $match: {_id: req.user._id} },
{ $unwind: '$finance.expenditure' },
{ $match: {'finance.expenditure.status': true} },
{ $sort: {'finance.expenditure.currentdate': -1} },
{
$group: {
_id: '$_id',
expenditure: { $push: '$finance.expenditure' }
}
}
])
With this I just get every single expenditure back.
But now I want to group the expenditures by their category and sum up the amount of every single expenditure for their group.
So it should look like this:
{ "amount": 300 }, "category": "Sport" },
{ "amount": 450 }, "category": "Outdoor" }
Thanks for your help
Instead of grouping on _id field group on category field & sum amount field:
db.collection.aggregate([
{ $match: {_id: req.user._id}},
{
$unwind: "$finance.expenditure"
},
{
$match: {
"finance.expenditure.status": true
}
},
{
$sort: {
"finance.expenditure.currentdate": -1
}
},
{
$group: {
_id: "$finance.expenditure.category",
amount: {
$sum: "$finance.expenditure.amount"
}
}
},
{
$project: {
_id: 0,
category: "$_id",
amount: 1
}
}
])
Test : MongoDB-Playground

How to get most repeated string array in Pymongo?

How can I get most repeated value for gender and age respectively?
My data:
[{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe7"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "female","age": "0-10"},
{"Id": 2,"gender": "female","age": "20-30"}]
},
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe8"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "male","age": "0-10"},
{"Id": 2,"gender": "female","age": "30-40"}]
} ,
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe9"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "male","age": "10-15"},
{"Id": 2,"gender": "female","age": "30-40"},
{"Id": 3,"gender": "male","age": "0-10"}]
},
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fea"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 2,"gender": "male","age": "40-50"},
{"Id": 3,"gender": "male","age": "0-10"},
{"Id": 4,"gender": "male","age": "0-10"}]
}]
I have written below query,
mongo.db.xyz.aggregate([
{ "$unwind" : "$object"},
{"$group" : {"_id" : "$object.Id","_gen":{"$push":"$object.gender"},"_age":{"$push":"$object.age"}}},
{ "$project": { "_id" : "$_id", "gender":"$_gen","age":"$_age"}}
])
Below is the result I am getting,
[{"_id": 3,"age": ["0-10","0-10"],"gender": ["male","male"]},
{"_id": 2,"age": ["20-30","30-40","30-40","40-50"],"gender": ["female","female","female","male"]},
{"_id": 4,"age": ["0-10"],"gender": ["male"]},
{"_id": 1,"age": ["0-10","0-10","10-15"],"gender": ["female","male","male"]}
]
But I want the output to be ,
[{"_id": 3,"age": "0-10","gender": "male"},
{"_id": 2,"age": "30-40","gender": "female"},
{"_id": 4,"age": "0-10","gender": "male"},
{"_id": 1,"age": "0-10","gender": "male"}
]
Thinking about this problem I realized it was not so simple to get the mode of some fields independently in an array of objects with just one db query. To solve that I created a query to do it generically, based on your sample data.
db.collection.aggregate([
{
$unwind: {
path: "$arr"
}
},
{
$project: {
arr: {
$objectToArray: "$arr"
}
}
},
{
$unwind: {
path: "$arr"
}
},
{
$group: {
_id: {
_id: "$_id",
k: "$arr.k",
v: "$arr.v"
},
count: {
$sum: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$group: {
_id: {
_id: "$_id._id",
k: "$_id.k"
},
v: {
$first: "$_id.v"
},
count: {
$first: "$count"
}
}
},
{
$group: {
_id: {
_id: "$_id._id"
},
arr: {
$push: {
k: "$_id.k",
v: {
mode: "$v",
count: "$count"
}
}
}
}
},
{
$project: {
arr: {
$arrayToObject: "$arr"
}
}
}
])
The secret to do that is to use the $objectToArray and the $arrayToObject operations, along with the $unwind and the correct $group stages. If you need a more detailed response on any stage please ask in the comments.
The output of the sample data is:
[
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fea")
},
"arr": {
"Id": {
"count": 1,
"mode": 3
},
"age": {
"count": 2,
"mode": "0-10"
},
"gender": {
"count": 3,
"mode": "male"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe7")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "0-10"
},
"gender": {
"count": 2,
"mode": "female"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe9")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "30-40"
},
"gender": {
"count": 2,
"mode": "male"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe8")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "30-40"
},
"gender": {
"count": 1,
"mode": "female"
}
}
}
]
After running it for a collection that has an array of objects named "arr" (edit the query if in your collection it has a different name) it will return the mode value and the number of occurrences of that value for each field. Objects that has that field unset will be not considered, but the ones with "null" will.

mongoose 4.7.5: how to fetch subdocuments in array

Am trying to fetch and filter subdocuments in array.
The document has this structure:
{
"_id": {
"$oid": "58bc4fa0fd85f439ee3ce716"
},
"updatedAt": {
"$date": "2017-03-08T20:39:19.390Z"
},
"createdAt": {
"$date": "2017-03-05T17:49:20.455Z"
},
"app": {
"$oid": "58ae10852035431d5a746cbd"
},
"stats": [
{
"meta": {
"key": "value",
"key": "value"
},
"_id": {
"$oid": "58bc4fc4fd85f439ee3ce718"
},
"data": "data",
"updatedAt": {
"$date": "2017-03-05T17:49:56.305Z"
},
"createdAt": {
"$date": "2017-03-05T17:49:56.305Z"
}
},
{
"meta": {
"key": "value",
"key": "value"
},
"_id": {
"$oid": "58c06bf79eaf1f15aafe39d0"
},
"data": "data",
"updatedAt": {
"$date": "2017-03-08T20:39:19.391Z"
},
"createdAt": {
"$date": "2017-03-08T20:39:19.391Z"
}
}
]
}
What i want to get is the subdocuments in the stats array between two dates
I tried mongoose queries chain:
Model.findById(id)
.select('stats')
.where('stats.createdAt').gt(data-value).lt(data-value)
But the result always the full document including the all the subdocuments.
Also I tried aggregation like this:
Model.aggregate({
$match: {
'stats.createdAt': '2017-03-05T17:49:56.305Z'
}
})
The result is always null
Your result is null because '2017-03-05T17:49:56.305Z' is a String, you are looking for a Date : new Date('2017-03-05T17:49:56.305Z')
You can filter subdocuments with a date range with $unwind and $match :
var startDate = new Date('2017-03-05T17:49:56.305Z');
var endDate = new Date('2017-03-08T17:49:56.305Z');
Model.aggregate([{
$match: {
"_id": new mongoose.mongo.ObjectId("58bc4fa0fd85f439ee3ce716"),
"stats.createdAt": {
$gte: startDate,
$lt: endDate
}
}
}, {
$unwind: "$stats"
}, {
$match: {
"stats.createdAt": {
$gte: startDate,
$lt: endDate
}
}
}], function(err, res) {
console.log(res);
})
Or more straightforward with $filter :
Model.aggregate([{
$match: {
"_id": new mongoose.mongo.ObjectId("58bc4fa0fd85f439ee3ce716"),
"stats.createdAt": {
$gte: startDate,
$lt: endDate
}
}
}, {
$project: {
"stats": {
$filter: {
input: "$stats",
as: "stat",
cond: {
$and: [
{ $gte: ["$$stat.createdAt", startDate] },
{ $lte: ["$$stat.createdAt", endDate)] }
]
}
}
}
}
}], function(err, res) {
console.log(res);
})

Resources