Reputation: 1193
I have the following document structure:
{
"_id":"5c59c35d8610f702d00e6f70",
"ipAddress":"50.116.14.48",
"startTime":"2018-02-06T12:01:59.000Z",
"endTime":"2018-02-06T12:31:00.000Z",
}
I would like to be able to group occurrences within a 15 min window of time. For example; Given the structure of the document above I would assume this document would count as an occurrence for both 0-15mins, 15-30mins, and 30-45mins.
The result would look something like this:
[
{
"occurrences":1,
"startWindow":"2018-02-06T12:00:00.000Z",
"endWindow":"2018-02-06T12:15:00.000Z"
},
{
"occurrences":1,
"startWindow":"2018-02-06T12:15:01.000Z",
"endWindow":"2018-02-06T12:30:00.000Z"
},
{
"occurrences":1,
"startWindow":"2018-02-06T12:30:01.000Z",
"endWindow":"2018-02-06T12:45:00.000Z"
}
]
I have seen many examples that group only by a single date on an interval, but how about this situation where the document has a window of time?
How do you build this aggregation?
Upvotes: 1
Views: 432
Reputation: 37018
In addition to mickl's time math you need to use $range to "spread" the document across all "windows" between start and end:
db.col.aggregate([
{ $addFields: {
// an array of 15 min intervals between startTime and endTime
window: { $range: [
{ $floor: { $divide: [ { $toLong: { $toDate: "$startTime" } }, 900000 ] } },
{ $ceil: { $divide: [ { $toLong: { $toDate: "$endTime" } }, 900000 ] } }
] }
} },
// 1 document per interval
{ $unwind: "$window" },
// group by interval
{ $group: {
_id: "$window",
occurrences: { $sum: 1 }
}},
// to match expected order
{$sort: {_id:1}},
// calculate window boundaries
{ $project: {
_id: 0,
occurrences: 1,
startWindow: { $toDate: { $add: [ { $multiply: [ "$_id", 900000 ] }, 1000 ] } },
endWindow: { $toDate: { $multiply: [ { $add: [ "$_id", 1 ] }, 900000 ] } }
} }
])
Upvotes: 2
Reputation: 49945
It's easy if you can operate on miliseconds instead of strings. To convert your startTime
and endTime
to the number of miliseconds you can use $toDate and $toLong operators (MongoDB 4.0 or newer).
The formula to "classify" you date to 15-minutes range is also pretty simple: 15
minutes is equal to 900000
miliseconds so you can use $mod to get the value that needs to be $subtract -ed from original date.
Then from every document you can generate two documents (for startDate
and endDate
) using $unwind and then $group them.
db.col.aggregate([
{
$project: {
startTime: { $toLong: { $toDate: "$startTime" } },
endTime: { $toLong: { $toDate: "$endTime" } }
}
},
{
$project: {
ranges: [
{
start: { $subtract: [ "$startTime", { $mod: [ "$startTime", 900000 ] } ] },
end: { $add: [ { $subtract: [ "$startTime", { $mod: [ "$startTime", 900000 ] } ] }, 900000 ] }
},
{
start: { $subtract: [ "$endTime", { $mod: [ "$endTime", 900000 ] } ] },
end: { $add: [ { $subtract: [ "$endTime", { $mod: [ "$endTime", 900000 ] } ] }, 900000 ] }
}
]
}
},
{
$unwind: "$ranges"
},
{
$group: {
_id: "$ranges",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
occurences: "$count",
startWindow: { $toString: { $toDate: "$_id.start" } },
endWindow: { $toString: { $toDate: "$_id.end" } }
}
}
])
Upvotes: 2