Reputation: 197
How can I bucket documents in MongoDB 3.4.1 with two fields representing a date range into the days that are covered by that range?
For example, I have the following documents:
{ start: ISODate('2017-04-01T12:30:00Z'), end: ISODate('2017-04-02T12:30:00Z') },
{ start: ISODate('2017-04-04T12:00:00Z'), end: ISODate('2017-04-04T12:30:00Z') },
{ start: ISODate('2017-04-04T12:30:00Z'), end: ISODate('2017-04-08T12:30:00Z') },
{ start: ISODate('2017-04-05T12:30:00Z'), end: ISODate('2017-04-06T12:30:00Z') },
{ start: ISODate('2017-04-08T12:30:00Z'), end: ISODate('2017-04-09T12:30:00Z') }
and I'm looking to bucket those into the following histogram:
[
{ '2017-04-01': 1 },
{ '2017-04-02': 1 },
{ '2017-04-04': 2 },
{ '2017-04-05': 2 },
{ '2017-04-06': 2 },
{ '2017-04-07': 1 },
{ '2017-04-08': 2 },
{ '2017-04-09': 1 }
]
If a document's date range covers any portion of a day, the document should count for 1 item on that day.
I'd like to perform this bucketing with a single aggregation pipeline, if possible. This is fairly straightforward with individual timestamp fields by projecting them with a day formatter such as:
{ $project: { day: { $dateToString: { format: '%Y-%m-%d', date: '$start' } } } }
but it does not seem as those there are aggregation operators that support this for a date range.
Upvotes: 1
Views: 2790
Reputation: 406
Not very simple, but this worked. If you want your exact output format, you'll need the new $objectToArray expression introduced in 3.4.4.
edit: this does not take leap years into account, since it assumes each year has 365 days.
const millisPerDay = 1000 * 60 * 60 * 24;
var results = db.dates.aggregate([
{
$project: {
start: 1,
range: {
$range: [
0,
{
$add: [
1,
{
$add: [
{$multiply: [365, {$subtract: [{$year: "$end"}, {$year: "$start"}]}]},
{$subtract: [{$dayOfYear: "$end"}, {$dayOfYear: "$start"}]}
]
}
]
}
]
}
}
},
{
$project: {
daysOfEvent: {
$map: {
input: "$range",
in : {$add: ["$start", {$multiply: ["$$this", millisPerDay]}]}
}
}
}
},
{$unwind: "$daysOfEvent"},
{$group: {_id: {$dateToString: {format: '%Y-%m-%d', date: "$daysOfEvent"}}, count: {$sum: 1}}},
{$sort: {_id: -1}},
{$replaceRoot: {newRoot: {$arrayToObject: [[{k: "$_id", v: "$count"}]]}}}
]);
Upvotes: 4