Group by date in mongoose/mongodb? - node.js

Note: Mongo version 3.6.2.
I have a document that looks like this:
const Ticket = new mongoose.Schema({
event_id: [
{
required: false,
type: mongoose.Schema.Types.ObjectId,
ref: 'Event'
}
],
ticket_type: String,
createdAt: String,
}, { collection: 'tickets' });
I want to do a mongodb groupBy on ticket_type and createdAt where an event_id = X. So the output should like this:
[{ ticket_type: 'VIP', date: 2011-11-11, count: 12}, {..}]
The hard part is that createdAt is stored as timemillis in a string, ex:
{
_id : ObjectId(123),
ticket_type: 'VIP',
createdAt: '1233434',
event_id: [ObjectId(345)]
}
The answer should look something like this:
Ticket.find({ event_id: req.params.event_id }, function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});
Any help is greatly appreciated. Thanks.
This is what I came up with:
Ticket.aggregate([
{ $match: { event_id: ObjectId(req.body.event_id)} },
{
$addFields: {
"createdAt": {
$reduce: {
"input": {
$map: { // split string into char array so we can loop over individual characters
"input": {
$range: [ 0, { $strLenCP: "$createdAt" } ] // using an array of all numbers from 0 to the length of the string
},
"in":{
$substrCP: [ "$createdAt", "$$this", 1 ] // return the nth character as the mapped value for the current index
}
}
},
"initialValue": { // initialize the parser with a 0 value
"n": 0, // the current number
"sign": 1, // used for positive/negative numbers
"div": null, // used for shifting on the right side of the decimal separator "."
"mult": 10 // used for shifting on the left side of the decimal separator "."
}, // start with a zero
"in": {
$let: {
"vars": {
"n": {
$switch: { // char-to-number mapping
branches: [
{ "case": { $eq: [ "$$this", "1" ] }, "then": 1 },
{ "case": { $eq: [ "$$this", "2" ] }, "then": 2 },
{ "case": { $eq: [ "$$this", "3" ] }, "then": 3 },
{ "case": { $eq: [ "$$this", "4" ] }, "then": 4 },
{ "case": { $eq: [ "$$this", "5" ] }, "then": 5 },
{ "case": { $eq: [ "$$this", "6" ] }, "then": 6 },
{ "case": { $eq: [ "$$this", "7" ] }, "then": 7 },
{ "case": { $eq: [ "$$this", "8" ] }, "then": 8 },
{ "case": { $eq: [ "$$this", "9" ] }, "then": 9 },
{ "case": { $eq: [ "$$this", "0" ] }, "then": 0 },
{ "case": { $and: [ { $eq: [ "$$this", "-" ] }, { $eq: [ "$$value.n", 0 ] } ] }, "then": "-" }, // we allow a minus sign at the start
{ "case": { $eq: [ "$$this", "." ] }, "then": "." }
],
default: null // marker to skip the current character
}
}
},
"in": {
$switch: {
"branches": [
{
"case": { $eq: [ "$$n", "-" ] },
"then": { // handle negative numbers
"sign": -1, // set sign to -1, the rest stays untouched
"n": "$$value.n",
"div": "$$value.div",
"mult": "$$value.mult",
},
},
{
"case": { $eq: [ "$$n", null ] }, // null is the "ignore this character" marker
"then": "$$value" // no change to current value
},
{
"case": { $eq: [ "$$n", "." ] },
"then": { // handle decimals
"n": "$$value.n",
"sign": "$$value.sign",
"div": 10, // from the decimal separator "." onwards, we start dividing new numbers by some divisor which starts at 10 initially
"mult": 1, // and we stop multiplying the current value by ten
},
},
],
"default": {
"n": {
$add: [
{ $multiply: [ "$$value.n", "$$value.mult" ] }, // multiply the already parsed number by 10 because we're moving one step to the right or by one once we're hitting the decimals section
{ $divide: [ "$$n", { $ifNull: [ "$$value.div", 1 ] } ] } // add the respective numerical value of what we look at currently, potentially divided by a divisor
]
},
"sign": "$$value.sign",
"div": { $multiply: [ "$$value.div" , 10 ] },
"mult": "$$value.mult"
}
}
}
}
}
}
}
}
}, {
$addFields: { // fix sign
"createdAt": { $multiply: [ "$createdAt.n", "$createdAt.sign" ] }
}
},
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $dateToString: { format: "%Y-%m-%d", date: { $add: [ new Date(0), "$createdAt" ] }} },
count: { $sum: 1 }
}
}
},
{ $sort: { "createdAt": 1 } }
], function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});

You can use $group aggregation pipeline stage. To convert string to number you can use $toLong operator and then you can use $add (works for ISODate as well) to add that value to the date with zero miliseconds (new Date(0)) to get ISODate. Try:
Ticket.aggregate([
{ $match: { event_id: req.params.event_id } },
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $add: [ new Date(0), { $toLong: "$createdAt" } ] }
}
}
}
], function(err, tickCount) {
if (err) {
console.log(err);
} else {
res.json(tickCount);
}
});
EDIT: assuming that you manually converted string to number you can run following query to aggregate on date part of ISODate:
db.col.aggregate([
{ $match: { event_id: req.params.event_id } },
{
$group: {
_id: {
ticket_type: "$ticket_type",
createdAt: { $dateToString: { format: "%Y-%m-%d", date:{ $add: [ new Date(0), "$createdAt" ] } } },
count: { $sum: 1 }
}
}
},
{ $sort: { "_id.createdAt": 1 } }
])

Related

Find the number of consecutive days that users are regularly active on MongoDB?

I want to find last the number of maximum consecutive number of days for user id.
userId(string) active_date(string) Note: Today (2022-02-20)
------------------------------------------
{ "userId": "DbdBve", "day": "2022-02-20" }
{ "userId": "DbdBve", "day": "2022-02-19" }
{ "userId": "DbdBve", "day": "2022-02-18" }
{ "userId": "DbdBve", "day": "2022-02-17" } <- Gap here | so user's been active for the last 3 days
{ "userId": "DbdBve", "day": "2022-02-15" }
userId(string) active_date(string)
------------------------------------------
{ "userId": "Gj6WEth", "day": "2022-02-20" }
{ "userId": "Gj6WEth", "day": "2022-02-15" } <- Gap here | so user's been active for the last 1 days
{ "userId": "Gj6WEth", "day": "2022-02-14" }
{ "userId": "Gj6WEth", "day": "2022-02-13" }
using mongodb v5:
first convert date to numeric value using $toLong
then using $setWindowFields extract the user active_days ranges
mongoplayground
db.collection.aggregate([
{
"$addFields": {
"active_date": {
"$toLong": "$active_date"
}
}
},
{
$setWindowFields: {
partitionBy: "$user_id",
sortBy: {
active_date: 1
},
output: {
days: {
$push: "$active_date",
window: {
range: [
-86400000, // one day in millisecond
0
]
}
}
}
}
},
{
"$set": {
"days": {
"$cond": [
{
"$gt": [
{
"$size": "$days"
},
1
]
},
0,
1
]
}
}
},
{
$setWindowFields: {
partitionBy: "$user_id",
sortBy: {
active_date: 1
},
output: {
count: {
$sum: "$days",
window: {
documents: [
"unbounded",
"current"
]
}
}
}
}
},
{
"$group": {
"_id": {
user_id: "$user_id",
count: "$count"
},
"active_days": {
$sum: 1
},
"to": {
"$max": "$active_date"
},
"from": {
"$min": "$active_date"
}
}
}
])
and at the end get the latest active_day range by adding these two stages:
{
"$sort": {
to: -1
}
},
{
"$group": {
"_id": "$_id.user_id",
"last_active_days": {
"$first": "$active_days"
}
}
}
older versions of mongodb using $reduce
mongoplayground
db.collection.aggregate([
{
$sort: {
active_date: 1
}
},
{
"$group": {
_id: "$user_id",
dates: {
"$push": {
"$toLong": "$active_date"
},
},
from: {
$first: {
"$toLong": "$active_date"
}
},
to: {
$last: {
"$toLong": "$active_date"
}
}
}
},
{
$project: {
active_days: {
$let: {
vars: {
result: {
$reduce: {
input: "$dates",
initialValue: {
prev: {
$subtract: [
"$from",
86400000
]
},
range: {
from: "$from",
to: 0,
count: 0
},
ranges: []
},
in: {
$cond: [
{
$eq: [
{
$subtract: [
"$$this",
"$$value.prev"
]
},
86400000
]
},
{
prev: "$$this",
range: {
from: "$$value.range.from",
to: "$$value.range.to",
count: {
$add: [
"$$value.range.count",
1
]
}
},
ranges: "$$value.ranges"
},
{
ranges: {
$concatArrays: [
"$$value.ranges",
[
{
from: "$$value.range.from",
to: "$$value.prev",
count: "$$value.range.count"
}
]
]
},
range: {
from: "$$this",
to: "$to",
count: 1
},
prev: "$$this"
},
]
}
}
}
},
in: {
$concatArrays: [
"$$result.ranges",
[
"$$result.range"
]
]
}
}
}
}
},
{
"$project": {
active_days: {
"$last": "$active_days.count"
}
}
}
])

Aggregate total and unique counts based on value type and unique visitorId - MongoDB

Similar to another question I had (Here). But now I'm trying to count unique and total events on daily basis for each event type, based on the following data shape:
{
username: "jack",
events: [
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "party",
createdAt: "2022-01-23T10:26:11.214Z",
visitorInfo: {
visitorId: "01234567-0ebb-4238-8bf7-01234567"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T12:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T11:26:11.214Z",
visitorInfo: {
visitorId: "87654321-0ebb-4238-8bf7-87654321"
}
},
{
eventType: "meeting",
createdAt: "2022-01-23T12:26:11.214Z",
visitorInfo: {
visitorId: "01234567-0ebb-4238-8bf7-01234567"
}
},
{
eventType: "party",
createdAt: "2022-01-30T10:26:11.214Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "party",
createdAt: "2022-01-30T10:16:11.214Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "meeting",
createdAt: "2022-01-30T12:36:11.224Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
},
{
eventType: "meeting",
createdAt: "2022-01-30T11:46:11.314Z",
visitorInfo: {
visitorId: "12345678-0ebb-4238-8bf7-12345678"
}
}
]
}
I'm trying to count events (all and unique ones based on visitorId) on date (daily).
This is what I have so far (thanks to #R2D2's guide on the approach):
Event.aggregate([
{ $match: { username: 'jack' } },
{ $unwind: "$events" },
{
$project: {
totalPartyEvents: {
$cond: [
{
$eq: ["$events.eventType", "party"],
},
1,
0,
],
},
uniquePartyEvents: { // where I'm stuck. I need to count unique events based on visitorId on current date for 'party' event type.
$cond: [
{
$eq: ["$events.eventType", "party"],
},
1,
0,
],
},
totalMeetingEvents: {
$cond: [
{
$eq: ["$events.eventType", "meeting"],
},
1,
0,
],
},
uniqueMeetingEvents: { // do the same for other events. maybe there's a better way to combine these (with facets).
$cond: [
{
$eq: ["$events.eventType", "meeting"],
},
1,
0,
],
},
date: "$events.createdAt",
},
},
{
$group: {
_id: {
$dateToString: { format: "%Y-%m-%d", date: "$date" },
},
totalPartyEvents: {
$sum: "$totalMeetingEvents",
},
uniquePartyEvents: {
$sum: "$totalMeetingEvents",
},
totalMeetingEvents: {
$sum: "$totalMeetingEvents",
},
uniqueMeetingEvents: {
$sum: "$uniqueMeetingEvents",
},
},
},
{
$project: {
date: "$_id",
uniquePartyEvents: 1,
totalPartyEvents: 1,
totalMeetingEvents:1,
uniqueMeetingEvents: 1,
},
},
{
$group: {
_id: "0",
dateAndEventFrequency: {
$push: "$$ROOT",
},
},
},
{
$project: {
_id: 0,
dateAndEventFrequency: 1,
},
},
]);
I tried using $addToSet but it's not used with $project (it works with $group).
Any new approach is welcome based on the data shape and the desired result I'm expecting. I used $project because I was already using it.
Basically what I'm hoping to get in the end:
dateAndEventFrequency: [
{
_id: "2022-01-23",
totalPartyEvents: 3,
uniquePartyEvents: 2,
totalMeetingEvents: 3,
uniqueMeetingEvents: 2,
date: "2022-01-23",
},
{
_id: "2022-01-30",
totalPartyEvents: 2,
uniquePartyEvents: 1,
totalMeetingEvents: 2,
uniqueMeetingEvents: 1,
date: "2022-01-30",
},
]
I'm using Mongoose and Nodejs. Any help or guidance is appreciated. Thanks!
mongo playground
db.collection.aggregate([
{
$match: {
username: "jack"
}
},
{
"$unwind": "$events"
},
{
"$match": {
"events.eventType": {
"$in": [
"meeting",
"party"
]
}
}
},
{
"$group": {
"_id": {
date: {
"$dateToString": {
format: "%Y-%m-%d",
date: "$events.createdAt"
}
},
"visitorId": "$events.visitorInfo.visitorId",
"eventType": "$events.eventType"
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": {
"date": "$_id.date",
"eventType": "$_id.eventType"
},
"uniqueTotal": {
"$sum": 1
},
total: {
"$sum": "$count"
}
}
},
{
"$group": {
"_id": "$_id.date",
"partyUniqueTotal": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"party"
],
},
"$uniqueTotal",
0
]
}
},
"totalPartyEvents": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"party"
],
},
"$total",
0
]
}
},
"meetingUniqueTotal": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"meeting"
],
},
"$uniqueTotal",
0
]
}
},
"totalmeetingEvents": {
"$sum": {
"$cond": [
{
$eq: [
"$_id.eventType",
"meeting"
],
},
"$total",
0
]
}
}
}
}
])

How to change this mongo query to return "00:00" if $match condition does not satisfy in aggregate?

Consider I have a timesheets collection like this:
[
{
_id: 1,
createdBy: "John",
duration: "00:30"
},
{
_id: 2,
createdBy: "John",
duration: "01:30"
},
{
_id: 3,
createdBy: "Mark",
duration: "00:30"
},
]
My input is an array of usernames:
["John", "Mark", "Rikio"]
I want to use mongodb aggregate to calculate the total duration of timesheets for each user in the usernames array and If there are no timesheets found, it should return duration: "00:00". For example, it should return:
[
{createdBy: "John", totalDuration: "02:00"},
{createdBy: "Mark", totalDuration: "00:30"},
{createdBy: "Rikio", totalDuration: "00:00"}
]
However, when I use $match query, if there are no timesheets it will not return anything so I don't know which user does not have the timesheets and return "00:00" for them.
I totally agree with #turivishal , but still can make it through mongo query with an ugly one.
db.collection.aggregate([
{
$match: {}
},
{
$set: {
minutes: {
$let: {
vars: {
time: {
$split: [
"$duration",
":"
]
}
},
in: {
"$add": [
{
"$multiply": [
{
$toInt: {
$first: "$$time"
}
},
60
]
},
{
$toInt: {
$last: "$$time"
}
}
]
}
}
}
}
},
{
$group: {
"_id": "$createdBy",
"totalMinutes": {
"$sum": "$minutes"
}
}
},
{
$group: {
"_id": null,
"docs": {
"$push": "$$ROOT"
}
}
},
{
$set: {
"docs": {
$map: {
"input": [
"John",
"Mark",
"Rikio"
],
"as": "name",
"in": {
$let: {
vars: {
findName: {
"$first": {
"$filter": {
"input": "$docs",
"as": "d",
"cond": {
"$eq": [
"$$d._id",
"$$name"
]
}
}
}
}
},
in: {
"$cond": {
"if": "$$findName",
"then": "$$findName",
"else": {
_id: "$$name",
totalMinutes: 0
}
}
}
}
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
"newRoot": "$docs"
}
},
{
$set: {
"hours": {
$floor: {
"$divide": [
"$totalMinutes",
60
]
}
},
"minutes": {
"$mod": [
"$totalMinutes",
60
]
}
}
},
{
$set: {
"hours": {
"$cond": {
"if": {
"$lt": [
"$hours",
10
]
},
"then": {
"$concat": [
"0",
{
"$toString": "$hours"
}
]
},
"else": {
"$toString": "$hours"
}
}
},
"minutes": {
"$cond": {
"if": {
"$lt": [
"$minutes",
10
]
},
"then": {
"$concat": [
"0",
{
"$toString": "$minutes"
}
]
},
"else": {
"$toString": "$minutes"
}
}
}
}
},
{
$project: {
duration: {
"$concat": [
"$hours",
":",
"$minutes"
]
}
}
}
])
mongoplayground

distinct strings value and concatenating the results

i'm trying to distinct different string values of "comment" and "dates" and concatenate them in two different strings one for comments and one for reclamation dates I used $addToSet to distinct values but every time the concatenating is giving empty result
and this is m code :
db.collection.aggregate([
{
$group: {
_id: {
b: "$type"
},
total: {
$sum: 1
},
root: {
$push: "$$ROOT"
}
}
},
{
"$unwind": "$root"
},
{
$group: {
_id: {
r: "$root.situation",
b: "$root.type"
},
cnt: {
$sum: 1
},
total: {
"$first": "$total"
},
}
},
{
$project: {
a: [
{
k: "$_id.r",
v: "$cnt"
}
],
type: "$_id.b",
total: "$total",
_id: 0,
}
},
{
$project: {
d: {
$arrayToObject: "$a"
},
type: 1,
total: 1
}
},
{
$group: {
_id: "$type",
situation: {
$push: "$d"
},
sum: {
"$first": "$total"
},
//add to set of commets and dates
comment: {
$addToSet: "$comment"
},
daterec: {
$addToSet: "$daterec"
},
}
},
{
$project: {
_id: 0,
type: "$_id",
sum: 1,
"options": {
$mergeObjects: "$situation"
},
//concatenating results
comment: {
$reduce: {
input: "$comment",
initialValue: "",
in: {
$cond: [
{
"$eq": [
"$$value",
""
]
},
"$$this",
{
$concat: [
"$$value",
" ",
"$$this"
]
}
]
}
}
},
daterec: {
$reduce: {
input: "$daterec",
initialValue: "",
in: {
$cond: [
{
"$eq": [
"$$value",
""
]
},
"$$this",
{
$concat: [
"$$value",
" ",
"$$this"
]
}
]
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
"$options"
]
}
}
},
{
$project: {
options: 0,
}
},
])
and this is the output :
{
"after sales management": 4,
"comment": "",
"daterec": "",
"instock": 1,
"sum": 5,
"type": "pc"
}
its always giving "comment" and"daterec": empty while i want it to show the concatenating of distinct strings it should looks something like this :
{
"after sales management": 4,
"comment": "waiting for pieces waiting for approval nothing",
"daterec": "1",//this is just an example beacause all dates are set to "1"
"instock": 1,
"sum": 5,
"type": "pc"
}
this is an example of my work with a collection example:
https://mongoplayground.net/p/dB3xBFsIfun
PS : i think the problem is in the of using the $addToSet insite $group

Aggregation in mongodb and group by 15 min

I have a Schema in mongodb like:
INPUT
{
_id: ObjectId("5e05c1089b3e4e333cee8c39"),
name:"Alex",
activity:[
{
_id: ObjectId("5e05c1089b3e4e333cee8c39"),
type: 'run',
start_timestamp: ISODate("2020-01-11T11:34:59.804Z"),
end_timestamp: ISODate("2020-01-11T11:40:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c40"),
type: 'stop',
start_timestamp: ISODate("2020-01-11T11:40:00.804Z"),
end_timestamp: ISODate("2020-01-11T11:42:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'wait',
start_timestamp: ISODate("2020-01-11T11:42:00.804Z"),
end_timestamp: ISODate("2020-01-11T11:52:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'stop',
start_timestamp: ISODate("2020-01-11T11:52:00.804Z"),
end_timestamp: ISODate("2020-01-11T12:02:00.804Z")
},
{
_id: ObjectId("5e05c1089b3e4e333cee8c41"),
type: 'sleep',
start_timestamp: ISODate("2020-01-11T12:02:00.804Z"),
end_timestamp: ISODate("2020-01-11T12:48:00.804Z")
}
]
}
This is a schema for a man activity, i need to find brake-up of every 15 minute (brake-up duration in minute),i have found a solution stackoverflow but here only single timestamp but in my case there are 2 timestamp and first i have to calculate duration and then group by according to 15 minute
OUTPUT
[
{
_id: "2020-01-11T11:34 to 2020-01-11T11:49" ,
duration: "15 min",
"brake-up":{
run:"6 min",
stop:"2 min",
wait:"7 min"
}
},
{
_id: "2020-01-11T11:49 to 2020-01-11T12:04" ,
duration: 15 min,
"brake-up":{
wait:"3 min"
stop:"10 min"
sleep:"2 min"
}
{
_id: "2020-01-11T12:04 to 2020-01-11T12:19" ,
duration: 15 min,
"brake-up":{
sleep:"15 min"
}
}
]
Thanks
It's a bit tedious solution.
Explanation
I assume activity.type not repeated inside X min break-up
I assume start_timestamp and end_timestamp as is (don't ignore seconds:milliseconds)
We calculate min / max dates from start_timestamp and end_timestamp
We calculate how many 15 min breaks are between min / max dates
We create from and to variables that includes X min break-up from min / max dates
For each from and to, we filter activities
Once we filter, we calculate waste time taking in mind from and to and start_timestamp and end_timestamp dates
We create Array with activity + waste time and transform it into object
db.collection.aggregate([
{
$project: {
root: "$$ROOT",
duration: {
$toInt: 15
},
"start": {
$reduce: {
"input": "$activity",
initialValue: ISODate("2100-01-01"),
in: {
$min: [
"$$value",
"$$this.start_timestamp",
"$$this.end_timestamp"
]
}
}
},
"end": {
$reduce: {
"input": "$activity",
initialValue: ISODate("1970-01-01"),
in: {
$max: [
"$$value",
"$$this.start_timestamp",
"$$this.end_timestamp"
]
}
}
}
}
},
{
$addFields: {
interval: {
$range: [
0,
{
$round: {
$divide: [
{
$toLong: {
$subtract: [
"$end",
"$start"
]
}
},
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
},
1
]
}
}
},
{
$unwind: "$interval"
},
{
$addFields: {
from: {
$add: [
"$start",
{
$multiply: [
"$interval",
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
]
},
to: {
$min: [
"$end",
{
$add: [
"$start",
{
$multiply: [
{
$add: [
"$interval",
1
]
},
{
$multiply: [
"$duration",
60,
1000
]
}
]
}
]
}
]
},
activity: "$root.activity"
}
},
{
$addFields: {
activity: {
$filter: {
input: "$activity",
cond: {
$or: [
{
$and: [
{
$gte: [
"$$this.start_timestamp",
"$from"
]
},
{
$lte: [
"$$this.end_timestamp",
"$to"
]
}
]
},
{
$and: [
{
$lte: [
"$$this.start_timestamp",
"$to"
]
},
{
$gte: [
"$$this.end_timestamp",
"$from"
]
}
]
}
]
}
}
}
}
},
{
$project: {
_id: {
$concat: [
{
$toString: "$from"
},
" to ",
{
$toString: "$to"
}
]
},
name: "$root.name",
duration: {
$concat: [
{
$toString: "$duration"
},
" min"
]
},
"brake-up": {
$map: {
input: "$activity",
in: {
k: "$$this.type",
v: {
$round: {
$divide: [
{
"$subtract": [
{
$min: [
"$$this.end_timestamp",
"$to"
]
},
{
$max: [
"$$this.start_timestamp",
"$from"
]
}
]
},
{
$multiply: [
60,
1000
]
}
]
}
}
}
}
}
}
},
{
$unwind: "$brake-up"
},
{
$group: {
_id: {
_id: "$_id",
duration: "$duration",
name: "$name",
"brake-up-k": "$brake-up.k"
},
"brake-up-v": {
$sum: "$brake-up.v"
}
}
},
{
$group: {
_id: {
_id: "$_id._id",
duration: "$_id.duration",
name: "$_id.name"
},
"brake-up": {
$push: {
k: "$_id.brake-up-k",
v: {
$concat: [
{
$toString: "$brake-up-v"
},
" min"
]
}
}
}
}
},
{
$project: {
_id: "$_id._id",
name: "$_id.name",
duration: "$_id.duration",
"brake-up": {
$arrayToObject: "$brake-up"
}
}
},
{
$sort: {
_id: 1
}
}
])
MongoPlayground

Resources