I collect data off my database table. I'd like to manipulate one of the fields I'm getting back from the aggregate() method.
Currently, this is how I use aggregate() method:
const weeklyLogs = await Log.aggregate([
{ $match: { timestamp: { $gte: moment.tz('Asia/Jerusalem').subtract(6, 'days').startOf('day').toDate() } } },
{
$group: {
_id: {
$dateFromParts: {
year: { $year: { date: "$timestamp", timezone: "Asia/Jerusalem" } },
month: { $month: { date: "$timestamp", timezone: "Asia/Jerusalem" } },
day: { $dayOfMonth: { date: "$timestamp", timezone: "Asia/Jerusalem" } },
timezone: "Asia/Jerusalem"
}
},
start: {
$sum: {
$cond: [{ $eq: ['$eventName', 'connect'] }, 1, 0]
}
},
end: {
$sum: {
$cond: [{ $ne: ['$eventName', 'connect'] }, 1, 0]
}
}
}
}
]);
This is how timestamp field (which is of type Data) looks in my DB (example): 2020-09-24T05:25:42.608+00:00. So basically, When I get back the data from the method, it typically looks like:
end: 0
start: 9
_id: "2020-10-09T21:00:00.000Z"
But I'd like get back in the _id field the format of dd/mm/yyyy instead of 2020-10-09T21:00:00.000Z. How could I manipulate it in the aggregate() method?
You could use the built-in $dateToString utility:
{
$group: {
_id: {
$dateToString: {
format: "%d/%m/%Y",
date: "$timestamp"
}
},
start: {
$sum: {
$cond: [{
$eq: ['$eventName', 'connect']
}, 1, 0]
}
},
end: {
$sum: {
$cond: [{
$ne: ['$eventName', 'connect']
}, 1, 0]
}
}
}
}
Related
I am trying to show results where the sum of records is greater or equal to 4 and the status matches a string. If I leave off the status field it works fine but adding it in always gives me an empty array even when there should be data.
const bookings = await Booking.aggregate([
{
$group: {
_id: {
$dateToString: {
format: "%Y/%m/%d",
date: "$bookingDate",
},
},
totalBookings: {
$sum: 1,
},
},
},
{
$match: {
totalBookings: {
$gte: 4,
},
status: "Accepted",
},
},
]);
Each booking will have it's own status. So you need to add that as part of $group
{
$group: {
_id: {
status: "$status",
"d": {
$dateToString: {
format: "%Y/%m/%d",
date: "$bookingDate",
}
},
},
totalBookings: {
$sum: 1,
},
}
}
Then you need to change your match as below
$match: {
totalBookings: {
$gte: 4,
},
"_id.status": "Accepted",
}
It will give you all the Accepted Booking on the given BookinDate which is >= 4.
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
I have the below code snippet which will retrieve Date and count from a MongoDb collection from a specific date. Example: Retrieve date, count from 05-05-2020.
||Date||Count||
|05-06-2020|4|
|05-07-2020|25| and so on.
i want to add another logic to retrieve aggregate sum of 7 days instead of individual dates. Appreciate any help.
mongoClient.db().collection(COLLECTION.AUDIT).aggregate([
{
$match: { created_at: { $gt: date } }
},
{
$group: {
_id: {
$dateToString: { format: "%Y-%m-%d", date: "$created_at" }
},
count: { $sum: 1 }
}
},
{
$sort: { "_id": -1 }
}
])
The simplest way to do what I think you're asking would be to transform your group operator to $week instead of $dateToString. Since a week is 7 days, this will group all the documents from the same week, and return a count of the documents, along with the number of the week. To get both results from 1 query, combine them into a facet. So:
mongoClient.db().collection(COLLECTION.AUDIT).aggregate([
{
$match: { created_at: { $gt: date } }
},
{
$facet: {
by_week: {
$group: {
_id: { $week: $created_at},
count: { $sum: 1 }
},
{ $sort: { "_id": -1 }}
},
by_day: {
$group: {
_id: {
$dateToString: { format: "%Y-%m-%d", date: "$created_at" }
},
count: { $sum: 1 }
}
},
{ $sort: { "_id": -1 }}
}
},
])
I am using the following aggregation that get accounts by specific date
mongo.accounts.aggregate([
{
$project: {
doc: "$$ROOT",
month: {
$month: "$created_at"
},
day: {
$dayOfMonth: "$created_at"
}
}
},
{
$match: {
"month": 6,
"day": 26
}
}
]).toArray((err, docs) => {
console.log(docs);
});
It works just fine, but I don't like the format of data the query returns, currently this:
[{
_id: 5d13cf6f4d0b262cafc415a4,
doc: {
_id: 5d13cf6f4d0b262cafc415a4,
created_at: 2019-06-26T20:02:55.813Z
},
month: 6,
day: 26
}
{
_id: 5d793f8770bddb7d274efe62,
doc: {
_id: 5d793f8770bddb7d274efe62,
created_at: 2019-06-26T18:40:07.031Z
},
month: 6,
day: 26
}]
Is there a way to remove the redundant _id keys on the top-level as well as month and day? I am guessing this has something to do with doc: "$$ROOT". The ideal response would be just the documents:
[{
_id: 5d13cf6f4d0b262cafc415a4,
created_at: 2019-06-26T20:02:55.813Z
},
{
_id: 5d793f8770bddb7d274efe62,
created_at: 2019-06-26T18:40:07.031Z
}]
Like #Chridam suggested, we can use $replaceRoot to make the sub-document doc, a root document. The following is the updated query:
mongo.accounts.aggregate([
{
$project: {
doc: "$$ROOT",
month: {
$month: "$created_at"
},
day: {
$dayOfMonth: "$created_at"
}
}
},
{
$match: {
"month": 6,
"day": 26
}
},
{
$replaceRoot:{
newRoot: "$doc"
}
}
]).toArray((err, docs) => {
console.log(docs);
});
We can further rewrite the query in the following way:
db.accounts.aggregate([
{
$match:{
$expr:{
$and:[
{
$eq:[
{
$month: "$created_at"
},
6
]
},
{
$eq:[
{
$dayOfMonth: "$created_at"
},
26
]
}
]
}
}
}
]).pretty()
I'm trying to make a query in my javascript code, when I try to execute the query it in robo3t it works, but when I try it in my angular code, it doesn't can you please help me?
Here is the code in robo3t.
db.getCollection('interviews').aggregate({
$match: {
status: {
$ne: 'Callback'
},
dateInserted: {
$gte: ISODate("2019-02-07 00:00:00"),
$lte: ISODate("2019-02-08 00:00:00")
},
'insertedBy.adminId': '5c353f840fe0fd000440df01'
}
},
{
$group: {
_id: {
insertedBy: '$insertedBy.email'
},
timeExported: {$first: '$dateInserted'},
total: {
$sum: 1
}
},
},
{
$limit: 100
}
)
and the result shows:
result image
Now here is my code in angular
query = [{
$match: {
status: {
$ne: 'Callback'
},
dateInserted: {
$gte: new Date("2019-02-07 00:00:00").toISOString(),
$lte: new Date("2019-02-08 00:00:00").toISOString()
},
'insertedBy.adminId': localStorage.getItem('_lgu_')
}
},
{
$group: {
_id: {
insertedBy: '$insertedBy.email'
},
timeExported: {$last: '$dateInserted'},
total: {
$sum: 1
}
},
},
{
$limit: 100
},
{
$sort: {
total: 1
}
}
]
Now when I try the query in angular, it doesn't give any result and when I remove the date condition:
dateInserted: {
$gte: new Date("2019-02-07 00:00:00").toISOString(),
$lte: new Date("2019-02-08 00:00:00").toISOString()
},
It will give a result but not what I am expecting.