Aggregate by Day produces duplicate day results - node.js

My mongodb aggregation query should return daily, weekly, monthly, yearly data within specific date ranges.
Example: let's assume dates between 1st Feb to 1st March.
the data for daily should be: 1st, 2nd, 3rd..
Weekly period will be: 1st Feb, 8th Feb, 15th Feb, 22nd Feb ..
monthly period will be 1st Feb, 1st march ..
Look at example below:
Let's say my API accepts: startDate, endDate, interval as body params.
req.body will be something like this:
{
startDate: "",
endDate: "",
platform: "",
interval: "daily" // could be "weekly", "monthly", "yearly"
}
These params will be passed to my model where I have some aggregation code which will be mentioned below:
MessagesSchema.statics.totalMessages = ( startDate, endDate, platform, interval ) => {
return Messages.aggregate([{
$match: {
platform: platform,
timestamp: {
$gte: new Date(startDate),
$lte: new Date(endDate)
}
}
},
{
$project: {
timestamp: {
$dateToString: {
format: '%Y-%m-%d',
date: '$timestamp'
}
}
}
},
{
$group: {
_id: {
timestamp: '$timestamp'
},
count: {
$sum: 1
}
}
},
{
$sort: {
'_id.timestamp': 1
}
}
]).exec();
Let's assume Weekly data from 1st Feb 2019 - 1st March 2019;
expected result:
[
{
"_id": {
"timestamp": "2019-02-01"
},
"count": 2
},
{
"_id": {
"timestamp": "2019-02-08"
},
"count": 2
},
{
"_id": {
"timestamp": "2019-02-15"
},
"count": 2
}
]
actual result:
[
{
"_id": {
"timestamp": "2019-02-01"
},
"count": 2
},
{
"_id": {
"timestamp": "2019-03-02"
},
"count": 2
},
{
"_id": {
"timestamp": "2019-03-02"
},
"count": 2
}
]

Related

Format date within $project in aggregate function nodejs

I am looking to format a date within aggregate $project pipeline in 12 hour format.
$project: {
UpdateDt:
{
$dateToString: { format: "%m/%d/%Y ,%H:%M:%S", date: "$UpdateDt", timezone: "GMT" }
},
}
I tried using above code but this does not seem to work,also I want the date format as :
8/31/2017, 10:30:00 AM GMT
With the code above ,I get the output as 09/14/2017,15:07:10 the requirement is for the date to be in 12 hour format with GMT appended,same as achieved with .toLocaleString() function.
Please suggest any way/workaround for same.
Demo - https://mongoplayground.net/p/fIlimwXuOWK
In 1st pipeline - $addFields add a new field UpdateNewDt with {date, hour, time} details in GMT.
2nd pipeline - $set UpdateNewDt.hour convert to 12 hour clock and add AM and PM eg:- 12 - 12 PM, 02- 02 AM, 23- 11 PM
3rd pipeline - $set combine data back to a string
4th pipeline - $project - UpdateDt
db.collection.aggregate([
{
"$addFields": {
UpdateNewDt: {
date: { $dateToString: { format: "%m/%d/%Y ,", date: "$UpdateDt", timezone: "GMT" }},
hour: { $dateToString: { format: "%H", date: "$UpdateDt", timezone: "GMT" }},
time: { $dateToString: { format: ":%M:%S", date: "$UpdateDt", timezone: "GMT" }}
}
}
},
{
$set: {
"UpdateNewDt.hour": {
$cond: {
"if": { $gt: [ { "$toInt": "$UpdateNewDt.hour" }, 11 ] },
"then": {
"$concat": [
{
$cond: [
{ $eq: [{ "$toInt": "$UpdateNewDt.hour"},12 ] },
"12",
{ $toString: {"$subtract": [ { "$toInt": "$UpdateNewDt.hour" }, 12 ] }}
]
},
" PM" ]
},
"else": { "$concat": [ "$UpdateNewDt.hour", " AM" ] }
}
}
}
},
{
$set: {
"UpdateNewDt": {
"$concat": [
"$UpdateNewDt.date",
{ "$arrayElemAt": [{ "$split": [ "$UpdateNewDt.hour", " " ] }, 0 ] },
"$UpdateNewDt.time",
" ",
{ "$arrayElemAt": [{ "$split": [ "$UpdateNewDt.hour", " " ] }, 1 ] }
]
}
}
},
{
$project: { UpdateDt: "$UpdateNewDt" }
}
])
Input -
[
{
"key": 1,
UpdateDt: ISODate("2021-04-06T02:07:47.231Z")
},
{
"key": 2,
UpdateDt: ISODate("2021-04-06T22:07:47.231Z")
},
{
"key": 3,
UpdateDt: ISODate("2021-04-06T12:07:47.231Z")
}
]
Output -
[
{
"UpdateDt": "04/06/2021 ,02:07:47 AM",
"_id": ObjectId("5a934e000102030405000000")
},
{
"UpdateDt": "04/06/2021 ,10:07:47 PM",
"_id": ObjectId("5a934e000102030405000001")
},
{
"UpdateDt": "04/06/2021 ,12:07:47 PM",
"_id": ObjectId("5a934e000102030405000002")
}
]
Hi As I understood your concern you can use below script
Collection
"users": [
{
"_id": 3,
"ts": ISODate("2016-06-18T18:30:00.288Z")
}
]
Query
db.users.aggregate([
{
$project: {
"date": {
"$dateToString": {
"format": "%m/%d/%Y ,%H:%M:%S",
"date": "$ts",
"timezone": "GMT"
}
}
}
}
])
Output
[
{
"_id": 3,
"date": "06/18/2016 ,18:30:00"
}
]
I think that is not supported.
Here is the official Docs for $dateToString: https://docs.mongodb.com/manual/reference/operator/aggregation/dateToString/
For the specified available formats, there is only 24-hour clock.
If it isn't necessary for you to format that in the aggregate step, I would suggest you date-fns package. You can import only the format function and you can format the Date in format that you need.
Here is the package: https://www.npmjs.com/package/date-fns
Here is the Docs: https://date-fns.org/v2.19.0/docs/format

How to count daily orders for one week that currently we are?

I have saved orders on mongodb something like this:
_id: 5feb0b6cc0ea5745f8d931e3
...
totalPrice: 2935.02
createdAt:2020-12-25T10:56:44.798+00:00
If today is 25/01/2021, I need to count for each day of this week how many orders are made.
Example:
25/01/2021: 5 (orders)
26/01/2021: 21 (orders)
27/01/2021: 24 (orders)
...
31/01/2021: 22 (orders)
Thank you :)
If I've understood correctly you want to do: Given a week of the year, get all results for the week grouped by day.
Then you need to perform an aggregation query:
Use $set and $week to get the the week for every document.
Use $match to find the desired week of the year.
Use $set again, to get your date as a string, excluding time values, and selected using only: year-month-day.
Group by date using $sum to get the total count.
Optionally include the names of the fields at output.
Here is code to find the current week, based on the current date:
let today = new Date();
let oneJan = new Date(today.getFullYear(), 0, 1);
let numberOfDays = Math.floor((today - oneJan) / (24 * 60 * 60 * 1000));
let result = Math.ceil(( today.getDay() + 1 + numberOfDays) / 7);
db.collection.aggregate([
{ "$set": { "date": { "$week": "$createdAt" }}},
{ "$match": { "date": your_desired_week }},
{ "$set": { "date": {
"$dateToString": { "format": "%Y-%m-%d", "date": "$createdAt" }}}},
{ "$group": { "_id": "$date", "orders": { "$sum": 1 }}},
{ "$project": { "date": "$_id", "orders": 1, "_id": 0 }}
])
Example here
Should be this one:
{
$group: {
_id: {
$dateFromParts: {
'isoWeekYear': { $isoWeekYear: "$createdAt" }, 'isoWeek': { $isoWeek: "$createdAt" }
}
},
orders: { $sum: 1 }
}
}
Try out with this query.
var today = new Date();
var first = today.getDate() - today.getDay();
var firstDayWeek = new Date(today.setDate(first));
var lastDayWeek = new Date(today.setDate(first + 6));
db.getCollection('Collection').aggregate([{
$project: {
date: {
$dateFromString: {
dateString: '$createdAt'
}
}
}}, {
$match: {
"date": {
$lt: lastDayWeek,
$gt: firstDayWeek
}
},
{$group:{_id:"$createdAt",count:{$sum:1}}}}])

How to get the Sum from independent two subdocument Arrays in mongoose

I have this schema
var salesExpenseSchema = new Schema({
date : {
month: Number
},
sales: [{amount : Schema.Types.Decimal128}],
expenses: [{amount : Schema.Types.Decimal128}]
});
Example of a database record is like this
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:11},
sales:[{amount:3000},{amount:5000}],
expenses: [{amount:5000},{amount:500}]
},
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:10},
sales:[{amount:2000},{amount:5000}],
expenses: [{amount:500},{amount:800}]
},
{
_id:'5dbac5dfa1488240cbc4f838',
date:{month:09},
sales:[{amount:2000},{amount:4000}],
expenses: [{amount:200},{amount:300}]
}
Now I want to get the Summation of sales and expenses.
I have used Aggregate with $unwind for both sales and expenses like this below:
SalesExpense.aggregate([
{$unwind: "$sales"},
{$unwind: "$expenses"},
{$group:{
_id:'$_id',
sales:{$sum: "$sales.sellPrices"},
expenses:{$sum: "$expenses.amount"},
}
},
But the problem is... If one array document has data and the other has no data, then it gives 0 ie, the real summation isn't obtained. This is to say, if there are sales but no expenses then their sum becomes 0, and vice-versa.
I want to get the summation for both sales and expenses regardless of one of them not having data. How do I achieve this?
EDIT:
I have edited the question and added the date object in my schema and in the database records: I want to make this summation based on each month, that is to say... each month to have its own sales and expenses... Sort of a timeline with each month having its own sales and expenses.
I have tried using $group before $project
{$group:{
_id:'$date.month'}}
But it seems not to give the expected results.
I want an output like this one:
[
{
"month": "11",
"sales": {
"$numberDecimal": "8000"
},
"expenses": {
"$numberDecimal": "5500"
}
},
{
"month": "10",
"sales": {
"$numberDecimal": "7000"
},
"expenses": {
"$numberDecimal": "1100"
}
},
{
"month": "09",
"sales": {
"$numberDecimal": "6000"
},
"expenses": {
"$numberDecimal": "500"
}
},
]
How can I achieve this?
You can group by month and get the totals like this:
db.collection.aggregate([
{
$group: {
_id: "$date.month",
"sales": {
"$sum": {
"$sum": "$sales.amount"
}
},
"expenses": {
"$sum": {
"$sum": "$expenses.amount"
}
}
}
}
])
Sample Data:
[
{
_id: "5dbac5dfa1488240cbc4f838",
date: {
month: 11
},
sales: [
{
amount: 1
},
{
amount: 2
}
],
expenses: []
},
{
_id: "5dbac5dfa1488240cbc4f839",
date: {
month: 11
},
sales: [
{
amount: 5
},
{
amount: 6
}
],
expenses: [
{
amount: 7
},
{
amount: 8
}
]
},
{
_id: "5dbac5dfa1488240cbc4f840",
date: {
month: 12
},
sales: [],
expenses: [
{
amount: 7
},
{
amount: 8
}
]
}
]
Result:
[
{
"_id": 12,
"expenses": 15,
"sales": 0
},
{
"_id": 11,
"expenses": 15,
"sales": 14
}
]
Playground:
https://mongoplayground.net/p/K9ofoZx5ORI

MongoDB Aggregate function for returning day wise count for a particular date range

I need to get the count of individual users for a particular date range that too on each day basis. Let's say, there are a total of 100 users within a month (1st - 30th), I need to get the count like
{
1st - 2 users
2nd - 10 users
}
MessagesSchema.statics.totalMessagesGraph = (id, startDate, endDate, platform) => {
return Messages.aggregate([
{
$match: {
id: id,
platform: platform,
timestamp: {
$gte: new Date(startDate),
$lte: new Date(endDate)
}
}
}
])
}
What should be here to get the desired result ?
Expected Result:
For that particular date ranges the count for each day.
{
date1 - 20,
date2 - 22,
date3 - 24,
...
date30 - 12
}
The expected output should look like above. What query should be proceeded after $match. If possible please take a sample dataset and provide the output.
Use $group to get day wise count
for example
db.collection.aggregate([
{
$match: {
//id: id,
//platform: platform,
//timestamp: {
//$gte: new Date(startDate),
//$lte: new Date(endDate)
//}
//}
// Your matching logic
},
/* Now grouping users based on _id or id parameter for each day
from the above match results.
$createdAt can be replaced by date property present in your database.
*/
{ $group : {
id : { day: { $dayOfMonth: "$createdAt" },
month: { $month: "$createdAt" },
year: { $year: "$createdAt" } },
count : {$sum : 1}
}
}
])
Based on this you will get output like :
{
"_id" : {
"day" : 14,
"month" : 1,
"year" : 2017
},
"count" : 2.0
}
/* 2 */
{
"_id" : {
"day" : 31,
"month" : 1,
"year" : 2017
},
"count" : 8.0
}
/* 3 */
{
"_id" : {
"day" : 2,
"month" : 1,
"year" : 2017
},
"count" : 4.0
}
...
You can use the above query results to get required output.
More precisely you can remove month and year parameters from group query to get output like :
/* 1 */
{
"_id" : {
"day" : 25
},
"count" : 7.0
}
/* 2 */
{
"_id" : {
"day" : 18
},
"count" : 4.0
}
/* 3 */
{
"_id" : {
"day" : 17
},
"count" : 4.0
}
...
For reference you can check the mongoDB documentation also refer this.
MongoDB Aggregation Queries for "Counts Per Day"
Hope above example help you in getting the required output.
Here is the solution which I figured out after few trials.
{
'$project': {
timestamp: {'$dateToString': {format: '%Y-%m-%d', date: '$timestamp'}} }
}, {
'$group': {
_id: {timestamp: '$timestamp'},
count: {'$sum': 1}
}
}
And here is the output
"response": [
{
"_id": {
"timestamp": "2019-01-08"
},
"count": 1
},
{
"_id": {
"timestamp": "2019-01-13"
},
"count": 1
},
{
"_id": {
"timestamp": "2019-01-16"
},
"count": 1
},
{
"_id": {
"timestamp": "2019-01-17"
},
"count": 1
},
{
"_id": {
"timestamp": "2019-01-19"
},
"count": 1
},
{
"_id": {
"timestamp": "2019-02-01"
},
"count": 1
}
]

How to split date from datetime in mongodb?

I have write below the code.I try to split date and time but I can't get correct solution. I write the service in Node JS. If i try get date between dates, It is working. But if i try to fetch exact date, it is not working.
exports.screenLog = function(req,res){
console.log(req.query);
Timesheet.find({userId: req.params.id,startTime: $match:{$gte : new Date(req.query.startTime),$lte: new Date(req.query.endTime)}}, function (err, timesheet) {
console.log(timesheet);
var timesheetIdArray = timesheet.map(function(ele){return ele._id});
Screenshot.find()
.where('timesheetId')
.in(timesheetIdArray)
.exec(function(err,data){
//console.log('ScreenData:',data);
if(err) {
res.send(err);
}
res.send(data);
});
});
This is My input format below:
[
{
"_id": "5963653e6b43611240189ea2",
"timesheetId": "595f4f2ec456a422bc291169",
"imageUrl": "/images/2017-07-10_05_00_06_PM.jpg",
"__v": 0,
"createdTime": "2017-07-07T09:06:54.000Z"
},
{
"_id": "5964bef37302792b0864009e",
"timesheetId": "595f4f2ec456a422bc291169",
"imageUrl": "/images/2017-07-11_05_35_07_PM.jpg",
"__v": 0,
"createdTime": "2017-07-11T12:05:07.687Z"
},
{
"_id": "5964bf897302792b086400ad",
"timesheetId": "595f4f2ec456a422bc291169",
"imageUrl": "/images/2017-07-11_05_37_37_PM.jpg",
"__v": 0,
"createdTime": "2017-07-11T12:07:37.446Z"
},
{
"_id": "5964ddf0ee77e90288d26eec",
"timesheetId": "5964ddf0ee77e90288d26eeb",
"imageUrl": "/images/2017-07-11_07_47_20_PM.jpg",
"__v": 0,
"createdTime": "2017-07-11T14:17:20.651Z"
}]
Date and Time Can't be Split in MongoDB.They are Store in Date object in database.
But You can Compare the dates By $gte,$lte,$eq etc.
For the Comparisons you can Only Compare with UTC date ,For this You can find out moment Library
eg : date:{ $gte : moment(YOUR_DATE_IN_STRING) }
I am not too familiar with Node.Js, but I think the project aggregation may help you out here:
db.MyCollection.aggregate(
[
{
$project:
{
year: { $year: "$MyDateField" },
month: { $month: "$MyDateField" },
day: { $dayOfMonth: "$MyDateField" },
hour: { $hour: "$MyDateField" },
minutes: { $minute: "$MyDateField" },
seconds: { $second: "$MyDateField" },
milliseconds: { $millisecond: "$MyDateField" },
dayOfYear: { $dayOfYear: "$MyDateField" },
dayOfWeek: { $dayOfWeek: "$MyDateField" },
week: { $week: "$MyDateField" }
}
}
]
)
That will return this:
{
"_id" : "5897697667f26827dc9c9028",
"year" : 2017,
"month" : 2,
"day" : 5,
"hour" : 18,
"minutes" : 5,
"seconds" : 41,
"milliseconds" : 822,
"dayOfYear" : 36,
"dayOfWeek" : 1,
"week" : 6
}
Is that getting you any closer to a solution?
You can do smth like this in mongo shell:
db.foo_collection.aggregate([
{'$addFields': {
'extracted_date': {'$dateFromParts': {
'year': {'$year': "$some_datetime_field"},
'month': {'$month': "$some_datetime_field"},
'day': {'$dayOfMonth': "$some_datetime_field"}
}}
}},
{'$match': {'$extracted_date': ...}}
])
This query extracts date from datetime field and then applies some filter expression on this date.

Resources