How to handle unordered bucket boundaries in mongodb? - mongodb

I need to retrieve all profile views a user received within 15 days. I made a working query that group all views by day.
A view is a very simple document:
{
viewerId: string,
username: string,
profileId: string,
day: 4
}
The day key matches the current day of the month when the view was stored. So I made this query:
static getProfileViews(userId: string) {
const fifteenDaysRange = this.getDaysRange(15);
return ProfileViewsDB.aggregate([
{ $match: { profileId: userId } },
{
$bucket: {
groupBy: "$day",
boundaries: fifteenDaysRange,
default: fifteenDaysRange[fifteenDaysRange.length - 1] + 1,
output: {
count: {
$sum: 1,
},
viewers: {
$push: {
_id: "$viewerId",
username: "$username",
},
},
},
},
},
]);
}
It works, except when the days range is across two months. So the boundaries are: [29 ,30, 31, 1, 2, 3, 4], instead of a regular incremental list. In this case, mongo crashes, claiming that the boundaries must be properly ordered.
How to fix this? Is there a trick here to handle this edge-case?

Not clear what you are looking for, but I assume it should work with this:
db.ProfileViewsDB.aggregate([
{ $match: { profileId: userId } },
{ $match: { date: { $gte: moment().startOf('day').subtract(15, 'day').toDate() } } },
{
$group: {
_id: { y: { $year: "$date" }, m: { $month: "$date" }, d: { $dayOfMonth: "$date" } },
count: { $sum: 1 },
viewers: {
$push: { _id: "$viewerId", username: "$username", },
}
}
}
])
Whenever one has to work with date/times then I recommend the moment.js library, it makes your life easier.
Note, you may add the timezone, i.e.
_id: {
y: { $year: "$date", timezone: "Europe/Zurich" },
m: { $month: "$date", timezone: "Europe/Zurich" },
d: { $dayOfMonth: "$date", timezone: "Europe/Zurich" }
}

Related

How to calculate average records per month?

My records like this [{ createdAt }, {createdAt}, {createdAt} ]
I need average records per month.
january => 3 records
february => 2 records etc..
You can try to $group by month and year when counting and by month when averaging:
db.collection.aggregate([
{
$group: {
_id: {
month: {
$month: "$createdAt"
},
year: {
$year: "$createdAt"
},
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: {
month: "$_id.month"
},
average: {
$avg: "$count"
}
}
},
{
$project: {
_id: 0,
month: "$_id.month",
average: 1
}
}
])
Link to playground
Not fully clear what you mean by "average records per month" but I think it would be this:
db.collection.aggregate([
{
$group: {
_id: {
$dateTrunc: {
date: "$createdAt",
unit: "month"
}
},
count: { $count: {} }
}
},
{
$group: {
_id: null,
data: { $push: { k: { $toString: { $month: "$_id" } }, v: "$count" } }
}
},
{ $replaceWith: { $arrayToObject: "$data" } }
])
Getting the month name is not so easy, either you use a external library or build your own with $switch

How to calculate the difference between max of today and max of previous day value in MONGODB

{
metadata:{
dat:jkjcsvbdskjcbdskjcbdac,
meterId:kahcvajc
}
activeEnergy:1111,
actualtime:1689827191000
}
The document is something like that, I am facing problem only with activeEnergy so I want to focus on that only. Below I have written the code, in the first group object I have divided by year, month, day but in actual code it is dynamic, if the payload from frontend I receive is month and week then I group accordingly, but if I receive day in payload then I group by hour, I calculate the max and min energy of that hour and then sum for all hour, as activeEnergy is continuous, but the problem is that I am not getting data at every second so it is possible that I get the first data at 10:25am and last data at 10:45am therefore by taking max and min I only calculate for that 20 min and miss on all the remaining time data, ideally what I should do is max of this hour by max of previous hour. Similarly for the day the first document is at 12:59 AM and last Data at 11 pm, IF I calculate the max and min of the day I will still miss 2 hour data approx, so I have to find a way to find difference of max of today and max of previous day.
That becomes a problem because data is not grouped like that by me and I don't know any other way I can group data, how to solve the problem?
db.ts_events.aggregate([
{
$project: {
"y":{"$year": {$toDate: "$actualtime"}},
"m":{"$month": {$toDate: "$actualtime"}},
"d":{"$dayOfMonth": {$toDate: "$actualtime"}},
"h":{"$hour": {$toDate: "$actualtime"}},
"activeEnergy": 1,
"metadata.meterId": 1,
"activePower": 1,
"actualtime": 1,
"powerFactor": 1,
"metadata.dat": 1
}
},
{
$match: {
"metadata.dat": "62f0f3459731692a5eab5ad6/south0tpbit/tamilnadu5dvs8w/chennaidzc2yd/kknagarj4ffzo",
"actualtime": {
$gte: 1656613800000, $lte: 1659292199999,
},
// "metadata.device":"ObjectId(62f0f9b5f757672222282d9)" how to check using object id?,
"metadata.meterId": "911615402222257_2",
}
},
{
$group: {
_id: {
date: {
year: "$y",
month: "$m",
day: "$d",
// hour: "$h",
},
meter: "$metadata.meterId",
},
maxValue: {
$max: "$activeEnergy"
},
minValue: {
$min: "$activeEnergy"
},
averageActivePowerOfDay: { $avg: "$activePower" },
averagePowerFactorOfDay: { $avg: "$powerFactor" },
}
},
{
$addFields: {
differnce: {
$subtract: [
"$maxValue",
"$minValue"
]
},
}
},
//
//
//
{
$group: {
_id: null, res: {
$push: '$$ROOT'
}, differnceSum: {
$sum: '$differnce'
},
averageActivePowerOverThePeriod: {
$avg: "$averageActivePowerOfDay"
},
averagePowerFactorOverThePeriod: {
$avg: "$averagePowerFactorOfDay"
}
}
}
])
You can try with $setWindowFields
db.collection.aggregate([
{ $set: { actualTime: { $toDate: "$actualtime" } } },
{
$setWindowFields: {
partitionBy: "$metadata.dat",
sortBy: { actualTime: 1 },
output: {
max_today: {
$max: "$activeEnergy",
window: { range: [-1, 1], unit: "day" }
},
max_yesterday: {
$max: "$activeEnergy",
window: { range: [-2, -1], unit: "day" }
},
day: {
$last: "$actualTime",
window: { range: [-1, 1], unit: "day" }
},
}
}
},
{
$group: {
_id: { metadata: "$metadata", day: "$day" },
values: {
$addToSet: {
max_today: "$max_today",
max_yesterday: "$max_yesterday"
}
}
}
},
{ $replaceWith: { $mergeObjects: ["$_id", { $first: "$values" }] } }
])

MongoDB group ISODate but maintain date format

I know I can group data inside a collection by
{
$group: {
_id: {
day: {
$dayOfMonth: '$timeplay'
},
month: {
$month: '$timeplay'
},
year: {
$year: '$timeplay'
}
},
listeners: {
$sum: '$count'
}
}
However, it does not show the date in ISOdate format anymore which is what I need it to still do.
Is there a way to create ISOdate based on this information? As the output currently looks like the following
You can use $dateFromParts operator to convert to date from parts,
{
$group: {
_id: {
$dateFromParts: {
day: { $dayOfMonth: "$timeplay" },
month: { $month: "$timeplay" },
year: { $year: "$timeplay" }
}
},
listeners: { $sum: '$count' }
}
}
Playground
Use this
db.collection.aggregate([
{
$group: {
_id: { $dateTrunc: { date: "$timeplay", unit: "day" } },
listeners: { $sum: '$count' }
}
}
])

Mongo query: compare multiple of the same field (timestamps) to only show documents that have a few second difference

I am spinning my wheels on this. I am needing to find all documents within a collection that have a timestamp ("createdTs") that have a 3 second or less difference (to be clear: month/day/time/year all the same, save those few seconds). An example of createdTs field (it's type Date): 2021-04-26T20:39:01.851Z
db.getCollection("CollectionName").aggregate([
{ $match: { memberId: ObjectId("1234") } },
{
$project:
{
year: { $year: "$createdTs" },
month: { $month: "$createdTs" },
day: { $dayOfMonth: "$createdTs" },
hour: { $hour: "$createdTs" },
minutes: { $minute: "$createdTs" },
seconds: { $second: "$createdTs" },
milliseconds: { $millisecond: "$createdTs" },
dayOfYear: { $dayOfYear: "$createdTs" },
dayOfWeek: { $dayOfWeek: "$createdTs" },
week: { $week: "$createdTs" }
}
}
])
I've tried a lot of different variances. Where I'm struggling is how to compare these findings to one another. I'd also prefer to just search the entire collection and not match on the "memberId" field, just collect any documents that have less than a 3 second createdTs difference, and group/display those.
Is this possible? Newer to Mongo, and spun my wheels on this for two days now. Any advice would be greatly appreciated, thank you!
I saw this on another post, but not sure how to utilize it since I'm wanting to compare the same field:
db.collection.aggregate([
{ "$project": {
"difference": {
"$divide": [
{ "$subtract": ["$logoutTime", "$loginTime"] },
60 * 1000 * 60
]
}
}},
{ "$group": {
"_id": "$studentID",
"totalDifference": { "$sum": "$difference" }
}},
{ "$match": { "totalDifference": { "$gte": 20 }}}
])
Also am trying...
db.getCollection("CollectionName").aggregate([
{ $match: { memberId: ObjectId("1234") } },
{
$project:
{
year: { $year: "$createdTs" },
month: { $month: "$createdTs" },
total:
{ $add: ["$year", "$month"] }
}
}
])
But this returns a total of null. Not sure if it's because $year and $month are difference? The types are both int32, so I thought that'd work. Was wondering if there's a way to compare if all the fields are 0, then if seconds is not/difference is $gte 3 when using $group, could go from there.

MongoDB Aggregate Query, Logins Averages

let pipeline = [{
$match: {
time: { $gt: 980985600 },
user_id: mongoose.Types.ObjectId("60316a2e7641bd0017ced7b1")
}
},
{
$project: {
newDate: { '$toDate': "$time" },
user_id: '$user_id'
}
},
{
$group: {
_id: { week: { $week: "$newDate" }, year: { $year: "$newDate" }},
count: { $sum: 1 }
}
}]
I am currently trying to perform an aggregate through mongoose to find the average logins per week for a specific user. So far I have been able to get to the total number of logins each week, but was curious if there was a way to find the average of these final groupings within the same function. How would I go about doing this?
Just add one last stage to your query:
{
$group: {
_id: null,
avg: { $avg: "$count" }
}
}
So try this:
let pipeline = [
{
$match: {
time: { $gt: 980985600 },
user_id: mongoose.Types.ObjectId("60316a2e7641bd0017ced7b1")
}
},
{
$project: {
newDate: { '$toDate': "$time" },
user_id: '$user_id'
}
},
{
$group: {
_id: { week: { $week: "$newDate" }, year: { $year: "$newDate" } },
count: { $sum: 1 }
}
},
{
$group: {
_id: null,
avg: { $avg: "$count" }
}
}
];