Fill missing dates in records - Nodejs Mongoose - node.js

I'm using this query to get count of orders for 7 days of currently week. The result I get is something like this:
[
{ _id: '2021-01-31', orders: 3 },
{ _id: '2021-02-01', orders: 1 },
{ _id: '2021-02-02', orders: 2 },
{ _id: '2021-02-06', orders: 2 }
]
The problem is that if there was no order on specific day, the orders count should be 0 for that date. For example { _id: '2021-02-03', orders: 0 }, there was no order on 03-02-2021 it should be 0.
This is the query that I'm using:
let d = new Date()
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7));
let yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
let weekNumber = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7);
const ordersweekly = await Order.aggregate([
{
"$set": { "date": { "$week": "$createdAt" } }
},
{
"$match": { "date": weekNumber }
},
{
"$set": { "date": { "$dateToString": { "format": "%Y-%m-%d", "date": "$createdAt" } } }
},
{
"$group": { "_id": "$date", "orders": { "$sum": 1 } }
},
{ "$sort": { "_id": 1 } }
])

Related

Fetching last 30 days of Mongo records, summing by date, and filling in missing dates in range

I have a Mongo database filled with "Events" records, that look like this:
{
timestamp: 2022-03-15T22:11:34.711Z,
_id: new ObjectId("62310f16b0d71321e887a905")
}
Using a NodeJs server, I need to fetch the last 30 days of Events, grouped/summed by date, and any dates within that 30 days with no records need to be filled with 0.
Using this code I can get the correct events, grouped/summed by date:
Event.aggregate( [
{
$match: {
timestamp: {
$gte: start,
$lte: end,
}
}
},
{
$project: {
date: {
$dateToParts: { date: "$timestamp" }
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": { "$sum": 1 }
}
}
] )
This will return something like this:
[
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 14
}
},
"count": 3
},
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 15
}
},
"count": 8
},
]
I also have this Javascript code to generate the last 30 days of dates:
const getDateRange = (start, end) => {
const arr = [];
for(let dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)){
arr.push(new Date(dt));
}
return arr;
};
const subtractDays = (date, days) => {
return new Date(date.getTime() - (days * 24 * 60 * 60 * 1000));
}
const end = new Date();
const start = subtractDays(end, 30);
const range = getDateRange(start, end);
Which returns something like this:
[
2022-03-09T01:13:10.769Z,
2022-03-10T01:13:10.769Z,
2022-03-11T01:13:10.769Z,
2022-03-12T01:13:10.769Z,
2022-03-13T01:13:10.769Z,
...
]
It seems like I have all the pieces, but I'm having trouble putting all this together to do what I need in an efficient way. Any push in the right direction would be appreciated.
Whenever one has to work with date/time arithmetic then I recommend a library like moment.js
const end = moment().startOf('day').toDate();
const start = moment().startOf('day').subtract(30, 'day').toDate();
In MongoDB version 5.0 you can use $dateTrunc(), which is shorter than $dateToParts and { year: "$date.year", month: "$date.month", day: "$date.day" }
You need to put all data in an array ({$group: {_id: null, data: { $push: "$$ROOT" }}) and then at missing elements with $ifNull:
event.aggregate([
{
$match: {
timestamp: { $gte: start, $lte: end }
}
},
{
$group: {
_id: { $dateTrunc: { date: "$timestamp", unit: "day" } },
count: { $sum: 1 }
}
},
{ $project: {timestamp: "$_id", count: 1, _id: 0} },
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$set: {
data: {
$map: {
input: { $range: [0, 30] },
as: "i",
in: {
$let: {
vars: {
day: { $dateAdd: { startDate: start, amount: "day", unit: "$$i" } }
},
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
}
},
{ $unwind: "$data" }
])
$range operator supports only integer values, that's the reason for using $let. Otherwise, if you prefer to use the external generated range, it would be
{
$set: {
data: {
$map: {
input: range,
as: "day",
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
And for MongoDB version 5.1 you may have a look at $densify
Use aggregation stage densify if you're using MongoDB version 5.1 or later. But for lower version, below query can be used.
db.collection.aggregate([
{
$match: {
timestamp: {
$gte: {
"$date": "2022-03-01T00:00:00.000Z"
},
$lte: {
"$date": "2022-03-31T23:59:59.999Z"
},
}
}
},
{
$project: {
date: {
$dateToParts: {
date: "$timestamp"
}
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": null,
"originData": {
"$push": "$$ROOT"
}
}
},
{
"$project": {
"_id": 0,
"data": {
"$concatArrays": [
{
"$map": {
"input": {
"$range": [
0,
30,
1
]
},
"in": {
"$let": {
"vars": {
"date": {
"$add": [
{
"$date": "2022-03-01T00:00:00.000Z"
},
{
"$multiply": [
"$$this",
86400000
]
}
]
}
},
"in": {
"_id": {
"date": {
"day": {
"$dayOfMonth": "$$date"
},
"month": {
"$month": "$$date"
},
"year": {
"$year": "$$date"
}
}
},
"count": 0
}
}
}
}
},
"$originData"
]
}
}
},
{
"$unwind": "$data"
},
{
$group: {
_id: {
date: {
year: "$data._id.date.year",
month: "$data._id.date.month",
day: "$data._id.date.day"
}
},
"count": {
"$sum": "$data.count"
}
}
},
{
"$sort": {
"_id.date.year": 1,
"_id.date.month": 1,
"_id.date.day": 1
}
}
])
Link to online playground. https://mongoplayground.net/p/5I0I04HoHXm

How to sum up one record per day for one month?

I’m trying to do to sum up values in a month, but i only want to sum 1 entry per day for 1 month, i tried to use $limit before the group but only get 1 entry return.
Thanks.
[
{
$match:
{
"fPort": 60,
"rxInfo.time":
{
"$gte": "2020-12-01T00:00:00.000000Z",
"$lte": "2020-12-31T59:59:59.935Z"
}
}
},
{ $limit: '1' }, ///// This only returns 1 record and not 1 per day.
{
$group:
{
_id: "9cd9cb00000124c1",
"Total":
{
"$sum": "$object.TotalDaily"
}
}
}
]
I managed with the following query.
[
{
$match:
{
"fPort": 60,
"rxInfo.time":
{
"$gte": "2020-12-01T00:00:00.000000Z",
"$lte": "2020-12-31T59:59:59.99"
}
}
},
{
$project:
{
_id:1,
year:
{
$year:
{
$dateFromString:
{
dateString:
{"$arrayElemAt": [ "$rxInfo.time", 0 ] }
}
}
},
month:
{
$month:
{
$dateFromString:
{
dateString:
{"$arrayElemAt": [ "$rxInfo.time", 0 ] }
}
}
},
day:
{
$dayOfMonth:
{
$dateFromString:
{
dateString:
{"$arrayElemAt": [ "$rxInfo.time", 0 ] }
}
}
},
sum : "$object.TotalDaily"
}
},
{
$group:{ _id:{year:"$year",month:"$month",day:"$day"},TotalPerDay:{$sum:"$sum"}}
},
{
$group: { _id: "9cd9cb00000124c1","MonthlyTotal":{"$sum": "$TotalPerDay"},
}
}
]

Mongo Db aggregation pipeline with loop

I have a collection which contains half million data, and i want an average for all months between the date i enter. Right now i am getting the data for whole year, but i want it seperated by single month i.e 12 data range for every single month.
Below is the aggregation pipeline i am using.
let filter = 'y';
const date = new Date();
let checkDate = moment().subtract(1.5, 'years')._d;
MeterData.aggregate([
{
$group: {
_id: "$meter_id",
// total: { $sum: 1 },
totalEnergy: filter !== 'a' ? {
$sum: {
$toDouble: {
$cond: {
if: {
$gte: [
"$date", checkDate
]
},
then: "$energy.Energy",
else: 0
}
}
}
} : { $sum: { $toDouble:
"$energy.Energy"
} }
},
}
]);
Here i am getting totalEnergy for all year, in totalEnergy field, but now i want totalEnergy plus monthly calculations for the year i enter.
Any idea on how to do that. ?
Below is a sample document from the collection.
{"_id":{"$oid":"5e557779ed588826d84cef11"},
"meter_id":"1001",
"date":{"$date":{"$numberLong":"1509474600000"}},
"parameter_name":"hvac","voltage":{"unit":"V"},
"current":{"unit":"AMP"},
"powerFactor":{"unit":"phi"},
"angle":{"unit":"degree"},
"activePower":{"unit":"kwh"},
"reactivePower":{"unit":"kwh"},
"apparentPower":{"unit":"kwh"},
"frequency":{"unit":"hz"},
"thd":{"unit":"percentage"},
"energy":{"Energy":"5.7"},
"power":{"unit":"watt"},
As per suggested by Ryan Gunner, i got my answer which i am pasting below, i just have one more problem.
[
{
meter_id: '1001',
month: '2017-10',
totalEnergy: 0,
averageEnergy: 0
} + 11 more months......
]
Now what i need is the total of the energy for 12 months. For example total of totalEnergy field for all 12 months in a single variable.
how about something like this?
var startDate = new ISODate('2020-04-01');
var endDate = new ISODate('2019-04-01');
db.collection.aggregate(
{
$match: {
$expr: {
$and: [
{ $gt: ['$date', endDate] },
{ $lt: ['$date', startDate] }]
}
}
},
{
$group: {
_id: {
meter: '$meter_id',
month: { $dateToString: { format: '%Y-%m', date: '$date' } }
},
totalEnergy: { $sum: { $toDouble: '$energy.Energy' } },
averageEnergy: { $avg: { $toDouble: '$energy.Energy' } }
}
},
{
$project: {
meter_id: '$_id.meter',
month: '$_id.month',
totalEnergy: '$totalEnergy',
averageEnergy: '$averageEnergy',
_id: 0
}
},
{
$sort: { meter_id: 1 }
}
{
$group: {
_id: null,
grandTotalEnergy: { $sum: '$totalEnergy' },
monthlyData: { $push: '$$ROOT' }
}
},
{ $project: { _id: 0 } }
)
update: added grandTotalEnergy field and pushed monthlyData to an array.

Selecting dates(mm-dd-yyyy) this month from mongodb

How can I select the dates from my mongodb for this month only, I have a date:{type:String} and my format is: mm-dd-yyyy ex: 09-03-2019:
Here is my router to get the distinct dates then count them, but not for monthly:
router.get('/blooddonationarea', function(req, res) {
sess=req.session;
Blooddonation.aggregate([{$group: {_id : "$date" , count :{$sum:1}}},{$sort: {_id: 1}}],function(err, date) {
res.json({ success: true, date: date });
});
});
yields to:
[ { _id: '04-11-2019', count6: 1 },
{ _id: '05-21-2019', count6: 1 },
{ _id: '10-11-2019', count6: 3 },
{ _id: '10-22-2019', count6: 1 } ]
My desired output is to display the dates for example where date = month.now() then it will only show months for 01-dd-yyyy only.
I tried this :
const startOfMonth = moment().startOf('month').format('MM-DD-YYYY')
const endOfMonth = moment().endOf('month').format('MM-DD-YYYY')
Blooddonation.aggregate([
{ "$match": {
"date": { "$gte": startOfMonth, "$lte": endOfMonth }
}},
{ "$group": { "_id": "$date", "count": { "$sum": 1 } } },
{ "$sort": { "_id": 1 } }],function(err, date) {
res.json({ success: true, date: date });
console.log("dates are" + date);
});
});
You can use moment library to get the desired format and then use $gte and $lte operator to get the data for the current month only
const moment = require('moment')
const startOfMonth = moment().startOf('month').format('MM-DD-YYYY')
const endOfMonth = moment().endOf('month').format('MM-DD-YYYY')
Blooddonation.aggregate([
{ "$match": {
"date": { "$gte": startOfMonth, "$lte": endOfMonth }
}}
{ "$group": { "_id": "$date", "count": { "$sum": 1 } } },
{ "$sort": { "_id": 1 } }
])

Sub group Mongoose

I'm trying to get a sub gruop from a query using nodejs and mongoose.
The thing I'm trying to do is the following:
I have this collection:
I Need to count and group all the documents with the same 'intent' and make a subgroup with the 'entity' value, so far I have this running:
try {
//We first get the total interactions from all workspace
let workspace = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: "$workspace", data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec();
//We then get the total results from conversations
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
//workspaceItems = workspace.map(function (Interaction) { return Interaction._id; });
return res.json({
total: workspace,
result: results
})
} catch (err) {
console.log(err);
return res.status(500).send(err)
}
The result look like this:
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"data": 1
},
{
"_id": "REQUISITOS",
"data": 7
}
]
}
I need the result in this way :
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 1
},
{
"_id": "REQUISITOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 7
}
]
}
I Hope to be clear, please let me know if you know how to do this using mongoose.
Thank you in advance.
try changing the 2nd query to following
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', entities: {$push: "$entity"}, data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
if you want a unique list of entities you can use $addToSet instead of $push

Resources