Group data of linked document in aggregate - node.js

We currently have the following code to group all documents from a collection by creation date:
this.aggregate( [
{ $match: { language: options.criteria.language, status: 1, type:{ $ne: "challenge" } }},
{ $group: {
_id: {
y: { '$year': '$created' },
m: { '$month': '$created' },
d: { '$dayOfMonth': '$created' },
h: { '$hour': '$created' },
min: { '$minute': '$created' },
s: { '$second': '$created' }
},
count: { $sum : 1 }
}},
{$project: {
date: "$_id", // so this is the shorter way
count: 1,
_id: 0
}},
{ $sort: { "date": 1 } }
], function(err, result){
if(err) {
return callback(err);
}
callback(null, result);
});
However, we now would like to group the results based on the start date instead of the creation date. The start date is not a field of the current collection, but it is a field of the currentRevision object, that is linked in this collection.
I tried this:
this.aggregate( [
{ $match: { language: options.criteria.language, status: 1, type:{ $ne: "challenge" } }},
{ $group: {
_id: {
y: { '$year': '$currentRevision.start_date' },
m: { '$month': '$currentRevision.start_date' },
d: { '$dayOfMonth': '$currentRevision.start_date' },
h: { '$hour': '$currentRevision.start_date' },
min: { '$minute': '$currentRevision.start_date' },
s: { '$second': '$currentRevision.start_date' }
},
count: { $sum : 1 }
}},
{$project: {
date: "$_id", // so this is the shorter way
count: 1,
_id: 0
}},
{ $sort: { "date": 1 } }
], function(err, result){
if(err) {
return callback(err);
}
callback(null, result);
});
but that just gives me an error: "failed to query db"
any idea on how to solve this?

Related

I want to write a MongoDB query in NodeJS where it return the matching documents as well as the count of documents too

I want to write a MongoDB query in NodeJS where it return the matching documents as well as the count of documents too. For ex consider the below code -
const result = await Student.aggregate(
[
{
$match: {
...filter
}
},
{
$project: {
_id: 1,
payment: 1,
type: 1,
BirthDate: 1
}
},
{
$sort: { StudentData: -1 }
},
{
$count: 'count'
},
{
$skip: skip
},
{
$limit: limit
}
]
);
Here I want to save two things in the result variable - the number of documents and individually all the documents.
let [{ totalItems, result }] = await Student.aggregate(
[
{
$match: {
...filter
}
},
{
$project: {
_id: 1,
payment: 1,
type: 1,
BirthDate: 1
}
},
{
$facet: {
result: [
{
$sort: { BirthDate: -1 },
},
{
$skip: skip
},
{
$limit: limit
}
],
totalItems: [{ $count: 'count' }]
}
},
{
$addFields: {
totalItems: {
$arrayElemAt: ["$totalItems.count", 0]
},
}
}
]
);

How to find the latest date in nested array of objects (MongoDB)

I am trying to find the latest "order" in "orders" array in the whole collection (Not only in the one object).
Data:
[
{
_id: 1,
orders: [
{
title: 'Burger',
date: {
$date: '2021-07-18T13:12:08.717Z',
},
},
],
},
{
_id: 2,
orders: [
{
title: 'Salad',
date: {
$date: '2021-07-18T13:35:01.586Z',
},
},
],
},
];
Code:
var restaurant = await Restaurant.findOne({
'orders.date': 1,
});
Rather simple:
db.collection.aggregate([
{ $project: { latest_order: { $max: "$orders.date" } } }
])
If you like to get the full order use this:
db.collection.aggregate([
{
$project: {
latest_order: {
$first: {
$filter: {
input: "$orders",
cond: { $eq: [ "$$this.date", { $max: "$orders.date" } ] }
}
}
}
}
},
{ $sort: { "latest_order.date": 1 } },
{ $limit: 1 }
])
Mongo Playground
You have to use aggregation for that
db.collection.aggregate([
{ $unwind: "$orders" },
{ $sort: { "orders.date": -1 } },
{ $limit: 1 },
{
"$group": {
"_id": "$_id",
"orders": { "$first": "$orders" }
}
}
])
Working Mongo playground

Manipulate field from aggregate method

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

How do I sort data by date (json)

I want to sort json data by date , Actual data hava 1 ~ 31
example data:
{
Detail: { '60': 100, '68': 20 },
Time: 2020-01-02T07:08:41.059Z,
No: 'aaa'
},
{
Detail: { '60': 300, '68': 20 },
Time: 2020-01-02T09:10:21.059Z,
No: 'bbb'
},
{
Detail: { '60': 10 },
Time: 2020-01-11T07:08:45.521Z,
No: 'ccc'
}
How do I sort by Date like this :
{
"2":{
"aaa": { '60': 100, '68': 20 }
"bbb": { '60': 300, '68': 20 }
}
"11":{
"ccc": { '60': 100 },
}
}
Thanks
The following aggregation does that. Assuming that the Time field of type Date.
db.test.aggregate( [
{
$group: {
_id: { day: { $dayOfMonth: "$Time" } },
data:{ $push: { k: "$No", v: "$Detail" } }
}
},
{
$sort: { "_id.day": 1 }
},
{
$addFields: {
data: { $arrayToObject: "$data" },
day: "$_id.day"
}
},
{
$group: {
_id: null,
data: { $push: { k: { $toString: "$_id.day" }, v: "$data" } }
}
},
{
$project: {
_id: 0,
data: { $arrayToObject: "$data" }
}
},
{
$replaceRoot: { newRoot: "$data" }
}
] ).pretty()

Mongodb aggregate by date with empty daybins

I am trying to do a per-day aggregation in MongoDB. I already have an aggregation where I successfully group the data by day. However, I want to do the aggregation in such a way where days with no data show up, but empty. That is, they are empty bins.
Below is what I have so far. I have not been able to find anything in the MongoDB documentation or otherwise that suggests how to do aggregations and produce empty bins:
app.models.profile_view.aggregate(
{ $match: { user: req.user._id , 'viewing._type': 'user' } },
{ $project: {
day: {'$dayOfMonth': '$start'},month: {'$month':'$start'},year: {'$year':'$start'},
duration: '$duration'
} },
{ $group: {
_id: { day:'$day', month:'$month', year:'$year' },
count: { $sum: 1 },
avg_duration: { $avg: '$duration' }
} },
{ $project: { _id: 0, date: '$_id', count: 1, avg_duration: 1 }}
).exec().then(function(time_series) {
console.log(time_series)
return res.send(200, [{ key: 'user', values: time_series }])
}, function(err) {
console.log(err.stack)
return res.send(500, { error: err, code: 200, message: 'Failed to retrieve profile view data' })
})
I don't think you will be able to solve this problem using aggregation. When you use $group, mongo can only group based on the data you are providing it. In this case, how would mongo know which date values are missing or even what the range of acceptable dates is?
I think your best option would be to add the missing date values to the result of your aggregation.
Starting in Mongo 5.1, it's a perfect use case for the new $densify aggregation operator:
// { date: ISODate("2021-12-05") }
// { date: ISODate("2021-12-05") }
// { date: ISODate("2021-12-03") }
// { date: ISODate("2021-12-07") }
db.collection.aggregate([
{ $group: {
_id: { $dateTrunc: { date: "$date", unit: "day" } },
total: { $count: {} }
}},
// { _id: ISODate("2021-12-03"), total: 1 }
// { _id: ISODate("2021-12-05"), total: 2 }
// { _id: ISODate("2021-12-07"), total: 1 }
{ $densify: { field: "_id", range: { step: 1, unit: "day", bounds: "full" } } },
// { _id: ISODate("2021-12-03"), total: 1 }
// { _id: ISODate("2021-12-04") }
// { _id: ISODate("2021-12-05"), total: 2 }
// { _id: ISODate("2021-12-06") }
// { _id: ISODate("2021-12-07"), total: 1 }
{ $project: {
day: "$_id",
_id: 0,
total: { $cond: [ { $not: ["$total"] }, 0, "$total" ] }
}}
])
// { day: ISODate("2021-12-03"), total: 1 }
// { day: ISODate("2021-12-04"), total: 0 }
// { day: ISODate("2021-12-05"), total: 2 }
// { day: ISODate("2021-12-06"), total: 0 }
// { day: ISODate("2021-12-07"), total: 1 }
This:
$groups documents by day with their $count
$dateTrunc truncates your dates at the beginning of their day (the truncation unit).
$densifies documents ($densify) by creating new documents in a sequence of documents where certain values for a field (in our case field: "_id") are missing:
the step for our densification is 1 day: range: { step: 1, unit: "day" }
finally transforms ($project) fields:
renames _id to day
add the total field for new documents included during the densify stage ({ views: { $cond: [ { $not: ["$views"] }, 0, "$views" ] })

Resources