Mongodb count results grouped by date range - mongodb

I have documents like these:
{name: 'doc1', date: "2015-01-01T02:00:12+01:00"},
{name: 'doc2', date: "2015-01-01T03:02:12+01:00"},
{name: 'doc3', date: "2015-01-01T02:17:55+01:00"}
Is it possible to count them by time-intervals (for example: 15 minutes) and get result like this:
{startDate: "2015-01-01T02:00:12+01:00", count: 15},
{startDate: "2015-01-01T02:15:12+01:00", count: 11},
{startDate: "2015-01-01T02:30:12+01:00", count: 21},
...`

You can't get an "actual" date object returned this way but you can get a timestamp value which can be used to construct a date object. I'ts just a simple matter of date math:
db.collection.aggregate([
{ "$group":
"_id": {
"$subtract": [
{ "$subtract": [ "$date", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$date", new Date("1970-01-01") ] },
1000 * 60 * 15
]}
]
},
"count": { "$sum": 1 }
}}
])
Subtracting a "date object" with the epoch date will result in the current timestamp value as a number. The basic math is the difference from the modulo at a 15 minute interval ( 1000 milis * 60 secs * 15 minutes ).
If you prefer there are actually Date Aggregation Operators which can split up the date as well. Same case is that these are numbers and not a date, but you can re-construct a date object from the values there.
db.collection.aggregation([
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"dayOfMonth": { "$dayOfMonth": "$date" },
"hour": { "$hour" },
"minute": {
"$subtract": [
{ "$minute": "$date" },
{ "$mod": [
{ "$minute": "$date" },
15
]}
]
}
},
"count": { "$sum": 1 }
}}
])

Related

MongoDB 2.4 - Aggreation group by date with 120 days interval [duplicate]

I have a "status" collection like this strcture -
{
_id: ObjectId("545a0b63b03dbcd1238b4567"),
status: 1004,
comment: "Rem dolor ipsam placeat omnis non. Aspernatur nobis qui nisi similique.",
created_at: ISODate("2014-11-05T11:34:59.804Z")
},
{
_id: ObjectId("545a0b66b03dbcd1238b4568"),
status: 1001,
comment: "Sint et eos vero ipsa voluptatem harum. Hic unde voluptatibus et blanditiis quod modi.",
created_at: ISODate("2014-11-05T11:35:02.814Z")
}
....
....
I need to get result grouped by 15 minutes interval from that collection.
There are a couple of ways to do this.
The first is with Date Aggregation Operators, which allow you to dissect the "date" values in documents. Specifically for "grouping" as the primary intent:
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created_at" },
"dayOfYear": { "$dayOfYear": "$created_at" },
"hour": { "$hour": "$created_at" },
"interval": {
"$subtract": [
{ "$minute": "$created_at" },
{ "$mod": [{ "$minute": "$created_at"}, 15] }
]
}
}},
"count": { "$sum": 1 }
}}
])
The second way is by using a little trick of when a date object is subtracted (or other direct math operation) from another date object, then the result is a numeric value representing the epoch timestamp milliseconds between the two objects. So just using the epoch date you get the epoch milliseconds representation. Then use date math for the interval:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
1000 * 60 * 15
]}
]
},
"count": { "$sum": 1 }
}}
])
So it depends on what kind of output format you want for the grouping interval. Both basically represent the same thing and have sufficient data to re-construct as a "date" object in your code.
You can put anything else you want in the "grouping operator" portion after the grouping _id. I'm just using the basic "count" example in lieu of any real statement from yourself as to what you really want to do.
MongoDB 4.x and Upwards
There were some additions to Date Aggregation Operators since the original writing, but from MongoDB 4.0 there will be actual "real casting of types" as opposed to the basic math tricks done here with BSON Date conversion.
For instance we can use $toLong and $toDate as new helpers here:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$created_at" },
{ "$mod": [ { "$toLong": "$created_at" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
That's a bit shorter and does not require defining an external BSON Date for the "epoch" value as a constant in defining the pipeline so it's pretty consistent for all language implementations.
Those are just two of the "helper" methods for type conversion which all tie back to the $convert method, which is a "longer" form of the implementation allowing for custom handling on null or error in conversion.
It's even possible with such casting to get the Date information from the ObjectId of the primary key, as this would be a reliable source of "creation" date:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
So "casting types" with this sort of conversion can be pretty powerful tool.
Warning - ObjectId values are limited to precision to the second only for the internal time value that makes up part of their data allowing the $toDate conversion. The actual inserted "time" is most probably dependent on the driver in use. Where precision is required, it's still recommended to use a discrete BSON Date field instead of relying on ObjectId values.
I like the other answer here, and mostly for the use of date math instead of aggregation date operators which while helpful can also be a little obscure.
The only thing I want to add here is that you can also return a Date object from the aggregation framework by this approach as opposed to the "numeric" timestamp as the result. It's just a little extra math on the same principles, using $add:
db.collection.aggregate([
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$current_date", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$current_date", new Date(0) ] },
1000 * 60 * 15
]}
] },
new Date(0)
]
},
"count": { "$sum": 1 }
}}
])
The Date(0) contructs in JavaScript here represent the same "epoch" date in a shorter form, as 0 millisecond from epoch is epoch. But the main point is that when the "addition" to another BSON date object is done with a numeric identifier, then the inverse of the described condition is true and the end result is actually now a Date.
All drivers will return the native Date type to their language by this approach.
Another useful way:
db.collection.aggregate([
{$group: {
_id: {
overallTime: {
$dateToString: { format: "%Y-%m-%dT%H", date: "$created_at" }
},
interval: { $trunc: { $divide: [{ $minute: "$created_at" }, 15 ]}}
},
}},
])
And more easier for min, hour, day intervals:
var format = "%Y-%m-%dT%H:%M"; // 1 min
var format = "%Y-%m-%dT%H"; // 1 hour
var format = "%Y-%m-%d"; // 1 day
db.collection.aggregate([
{$group: {
_id: { $dateToString: { format: format, date: "$created_at" } },
}},
])
A little more beautiful for mongo db.version() < 3.0
db.collection.aggregate([
{$match: {created_at:{$exists:1}}},
{$group: {
_id: {$add:[
{$dayOfYear: "$created_at" },
{$multiply: [{$year: "$created_at"}, 1000]}
]},
count: {$sum: 1 }
}},
{$sort:{_id:-1}}
])
MongoDB 5.x and Upwards
date truncation is now supported in aggergation pipelines, example:
{
$group: {
"_id": { "$dateTrunc": { date: "$created_at", unit: "minute", binSize: 15 } },
"count" : { $sum: 1 }
}
},
You can also find useful info about window functions and dateTrunc here
#Neil Lunn's answer at https://stackoverflow.com/a/26814496/8474325 for MongoDb 4.x upwards is fantastic. But there is a small mistake in the code where he uses ObjectId for the aggregation. The Line { "$toDate": "_id" } has to be changed to { "$toDate": "$_id" } for the code to work.
Here's the corrected code.
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
With MongoDB v5.0+, you can use $setWindowFields to perform computation on nearby documents(i.e. documents within 15 minute interval). In following example, it will count all documents which is 15 minutes before or after the current document. You can adjust it by changing the window param.
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: null,
sortBy: {
created_at: 1
},
output: {
count: {
$count: {},
window: {
range: [
-15,
15
],
unit: "minute"
}
}
}
}
}
])
Here is the Mongo Playground for your reference.

MongoDB:Convert day month year to timestamp in $project [duplicate]

I have a "status" collection like this strcture -
{
_id: ObjectId("545a0b63b03dbcd1238b4567"),
status: 1004,
comment: "Rem dolor ipsam placeat omnis non. Aspernatur nobis qui nisi similique.",
created_at: ISODate("2014-11-05T11:34:59.804Z")
},
{
_id: ObjectId("545a0b66b03dbcd1238b4568"),
status: 1001,
comment: "Sint et eos vero ipsa voluptatem harum. Hic unde voluptatibus et blanditiis quod modi.",
created_at: ISODate("2014-11-05T11:35:02.814Z")
}
....
....
I need to get result grouped by 15 minutes interval from that collection.
There are a couple of ways to do this.
The first is with Date Aggregation Operators, which allow you to dissect the "date" values in documents. Specifically for "grouping" as the primary intent:
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created_at" },
"dayOfYear": { "$dayOfYear": "$created_at" },
"hour": { "$hour": "$created_at" },
"interval": {
"$subtract": [
{ "$minute": "$created_at" },
{ "$mod": [{ "$minute": "$created_at"}, 15] }
]
}
}},
"count": { "$sum": 1 }
}}
])
The second way is by using a little trick of when a date object is subtracted (or other direct math operation) from another date object, then the result is a numeric value representing the epoch timestamp milliseconds between the two objects. So just using the epoch date you get the epoch milliseconds representation. Then use date math for the interval:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
1000 * 60 * 15
]}
]
},
"count": { "$sum": 1 }
}}
])
So it depends on what kind of output format you want for the grouping interval. Both basically represent the same thing and have sufficient data to re-construct as a "date" object in your code.
You can put anything else you want in the "grouping operator" portion after the grouping _id. I'm just using the basic "count" example in lieu of any real statement from yourself as to what you really want to do.
MongoDB 4.x and Upwards
There were some additions to Date Aggregation Operators since the original writing, but from MongoDB 4.0 there will be actual "real casting of types" as opposed to the basic math tricks done here with BSON Date conversion.
For instance we can use $toLong and $toDate as new helpers here:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$created_at" },
{ "$mod": [ { "$toLong": "$created_at" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
That's a bit shorter and does not require defining an external BSON Date for the "epoch" value as a constant in defining the pipeline so it's pretty consistent for all language implementations.
Those are just two of the "helper" methods for type conversion which all tie back to the $convert method, which is a "longer" form of the implementation allowing for custom handling on null or error in conversion.
It's even possible with such casting to get the Date information from the ObjectId of the primary key, as this would be a reliable source of "creation" date:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
So "casting types" with this sort of conversion can be pretty powerful tool.
Warning - ObjectId values are limited to precision to the second only for the internal time value that makes up part of their data allowing the $toDate conversion. The actual inserted "time" is most probably dependent on the driver in use. Where precision is required, it's still recommended to use a discrete BSON Date field instead of relying on ObjectId values.
I like the other answer here, and mostly for the use of date math instead of aggregation date operators which while helpful can also be a little obscure.
The only thing I want to add here is that you can also return a Date object from the aggregation framework by this approach as opposed to the "numeric" timestamp as the result. It's just a little extra math on the same principles, using $add:
db.collection.aggregate([
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$current_date", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$current_date", new Date(0) ] },
1000 * 60 * 15
]}
] },
new Date(0)
]
},
"count": { "$sum": 1 }
}}
])
The Date(0) contructs in JavaScript here represent the same "epoch" date in a shorter form, as 0 millisecond from epoch is epoch. But the main point is that when the "addition" to another BSON date object is done with a numeric identifier, then the inverse of the described condition is true and the end result is actually now a Date.
All drivers will return the native Date type to their language by this approach.
Another useful way:
db.collection.aggregate([
{$group: {
_id: {
overallTime: {
$dateToString: { format: "%Y-%m-%dT%H", date: "$created_at" }
},
interval: { $trunc: { $divide: [{ $minute: "$created_at" }, 15 ]}}
},
}},
])
And more easier for min, hour, day intervals:
var format = "%Y-%m-%dT%H:%M"; // 1 min
var format = "%Y-%m-%dT%H"; // 1 hour
var format = "%Y-%m-%d"; // 1 day
db.collection.aggregate([
{$group: {
_id: { $dateToString: { format: format, date: "$created_at" } },
}},
])
A little more beautiful for mongo db.version() < 3.0
db.collection.aggregate([
{$match: {created_at:{$exists:1}}},
{$group: {
_id: {$add:[
{$dayOfYear: "$created_at" },
{$multiply: [{$year: "$created_at"}, 1000]}
]},
count: {$sum: 1 }
}},
{$sort:{_id:-1}}
])
MongoDB 5.x and Upwards
date truncation is now supported in aggergation pipelines, example:
{
$group: {
"_id": { "$dateTrunc": { date: "$created_at", unit: "minute", binSize: 15 } },
"count" : { $sum: 1 }
}
},
You can also find useful info about window functions and dateTrunc here
#Neil Lunn's answer at https://stackoverflow.com/a/26814496/8474325 for MongoDb 4.x upwards is fantastic. But there is a small mistake in the code where he uses ObjectId for the aggregation. The Line { "$toDate": "_id" } has to be changed to { "$toDate": "$_id" } for the code to work.
Here's the corrected code.
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
With MongoDB v5.0+, you can use $setWindowFields to perform computation on nearby documents(i.e. documents within 15 minute interval). In following example, it will count all documents which is 15 minutes before or after the current document. You can adjust it by changing the window param.
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: null,
sortBy: {
created_at: 1
},
output: {
count: {
$count: {},
window: {
range: [
-15,
15
],
unit: "minute"
}
}
}
}
}
])
Here is the Mongo Playground for your reference.

Grouping records by timestamp within x minutes of each other [duplicate]

I have a "status" collection like this strcture -
{
_id: ObjectId("545a0b63b03dbcd1238b4567"),
status: 1004,
comment: "Rem dolor ipsam placeat omnis non. Aspernatur nobis qui nisi similique.",
created_at: ISODate("2014-11-05T11:34:59.804Z")
},
{
_id: ObjectId("545a0b66b03dbcd1238b4568"),
status: 1001,
comment: "Sint et eos vero ipsa voluptatem harum. Hic unde voluptatibus et blanditiis quod modi.",
created_at: ISODate("2014-11-05T11:35:02.814Z")
}
....
....
I need to get result grouped by 15 minutes interval from that collection.
There are a couple of ways to do this.
The first is with Date Aggregation Operators, which allow you to dissect the "date" values in documents. Specifically for "grouping" as the primary intent:
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created_at" },
"dayOfYear": { "$dayOfYear": "$created_at" },
"hour": { "$hour": "$created_at" },
"interval": {
"$subtract": [
{ "$minute": "$created_at" },
{ "$mod": [{ "$minute": "$created_at"}, 15] }
]
}
}},
"count": { "$sum": 1 }
}}
])
The second way is by using a little trick of when a date object is subtracted (or other direct math operation) from another date object, then the result is a numeric value representing the epoch timestamp milliseconds between the two objects. So just using the epoch date you get the epoch milliseconds representation. Then use date math for the interval:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
1000 * 60 * 15
]}
]
},
"count": { "$sum": 1 }
}}
])
So it depends on what kind of output format you want for the grouping interval. Both basically represent the same thing and have sufficient data to re-construct as a "date" object in your code.
You can put anything else you want in the "grouping operator" portion after the grouping _id. I'm just using the basic "count" example in lieu of any real statement from yourself as to what you really want to do.
MongoDB 4.x and Upwards
There were some additions to Date Aggregation Operators since the original writing, but from MongoDB 4.0 there will be actual "real casting of types" as opposed to the basic math tricks done here with BSON Date conversion.
For instance we can use $toLong and $toDate as new helpers here:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$created_at" },
{ "$mod": [ { "$toLong": "$created_at" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
That's a bit shorter and does not require defining an external BSON Date for the "epoch" value as a constant in defining the pipeline so it's pretty consistent for all language implementations.
Those are just two of the "helper" methods for type conversion which all tie back to the $convert method, which is a "longer" form of the implementation allowing for custom handling on null or error in conversion.
It's even possible with such casting to get the Date information from the ObjectId of the primary key, as this would be a reliable source of "creation" date:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
So "casting types" with this sort of conversion can be pretty powerful tool.
Warning - ObjectId values are limited to precision to the second only for the internal time value that makes up part of their data allowing the $toDate conversion. The actual inserted "time" is most probably dependent on the driver in use. Where precision is required, it's still recommended to use a discrete BSON Date field instead of relying on ObjectId values.
I like the other answer here, and mostly for the use of date math instead of aggregation date operators which while helpful can also be a little obscure.
The only thing I want to add here is that you can also return a Date object from the aggregation framework by this approach as opposed to the "numeric" timestamp as the result. It's just a little extra math on the same principles, using $add:
db.collection.aggregate([
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$current_date", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$current_date", new Date(0) ] },
1000 * 60 * 15
]}
] },
new Date(0)
]
},
"count": { "$sum": 1 }
}}
])
The Date(0) contructs in JavaScript here represent the same "epoch" date in a shorter form, as 0 millisecond from epoch is epoch. But the main point is that when the "addition" to another BSON date object is done with a numeric identifier, then the inverse of the described condition is true and the end result is actually now a Date.
All drivers will return the native Date type to their language by this approach.
Another useful way:
db.collection.aggregate([
{$group: {
_id: {
overallTime: {
$dateToString: { format: "%Y-%m-%dT%H", date: "$created_at" }
},
interval: { $trunc: { $divide: [{ $minute: "$created_at" }, 15 ]}}
},
}},
])
And more easier for min, hour, day intervals:
var format = "%Y-%m-%dT%H:%M"; // 1 min
var format = "%Y-%m-%dT%H"; // 1 hour
var format = "%Y-%m-%d"; // 1 day
db.collection.aggregate([
{$group: {
_id: { $dateToString: { format: format, date: "$created_at" } },
}},
])
A little more beautiful for mongo db.version() < 3.0
db.collection.aggregate([
{$match: {created_at:{$exists:1}}},
{$group: {
_id: {$add:[
{$dayOfYear: "$created_at" },
{$multiply: [{$year: "$created_at"}, 1000]}
]},
count: {$sum: 1 }
}},
{$sort:{_id:-1}}
])
MongoDB 5.x and Upwards
date truncation is now supported in aggergation pipelines, example:
{
$group: {
"_id": { "$dateTrunc": { date: "$created_at", unit: "minute", binSize: 15 } },
"count" : { $sum: 1 }
}
},
You can also find useful info about window functions and dateTrunc here
#Neil Lunn's answer at https://stackoverflow.com/a/26814496/8474325 for MongoDb 4.x upwards is fantastic. But there is a small mistake in the code where he uses ObjectId for the aggregation. The Line { "$toDate": "_id" } has to be changed to { "$toDate": "$_id" } for the code to work.
Here's the corrected code.
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
With MongoDB v5.0+, you can use $setWindowFields to perform computation on nearby documents(i.e. documents within 15 minute interval). In following example, it will count all documents which is 15 minutes before or after the current document. You can adjust it by changing the window param.
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: null,
sortBy: {
created_at: 1
},
output: {
count: {
$count: {},
window: {
range: [
-15,
15
],
unit: "minute"
}
}
}
}
}
])
Here is the Mongo Playground for your reference.

convert date to timestamp(10 or 13 digit) in Mongodb [duplicate]

I have a "status" collection like this strcture -
{
_id: ObjectId("545a0b63b03dbcd1238b4567"),
status: 1004,
comment: "Rem dolor ipsam placeat omnis non. Aspernatur nobis qui nisi similique.",
created_at: ISODate("2014-11-05T11:34:59.804Z")
},
{
_id: ObjectId("545a0b66b03dbcd1238b4568"),
status: 1001,
comment: "Sint et eos vero ipsa voluptatem harum. Hic unde voluptatibus et blanditiis quod modi.",
created_at: ISODate("2014-11-05T11:35:02.814Z")
}
....
....
I need to get result grouped by 15 minutes interval from that collection.
There are a couple of ways to do this.
The first is with Date Aggregation Operators, which allow you to dissect the "date" values in documents. Specifically for "grouping" as the primary intent:
db.collection.aggregate([
{ "$group": {
"_id": {
"year": { "$year": "$created_at" },
"dayOfYear": { "$dayOfYear": "$created_at" },
"hour": { "$hour": "$created_at" },
"interval": {
"$subtract": [
{ "$minute": "$created_at" },
{ "$mod": [{ "$minute": "$created_at"}, 15] }
]
}
}},
"count": { "$sum": 1 }
}}
])
The second way is by using a little trick of when a date object is subtracted (or other direct math operation) from another date object, then the result is a numeric value representing the epoch timestamp milliseconds between the two objects. So just using the epoch date you get the epoch milliseconds representation. Then use date math for the interval:
db.collection.aggregate([
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$created_at", new Date("1970-01-01") ] },
1000 * 60 * 15
]}
]
},
"count": { "$sum": 1 }
}}
])
So it depends on what kind of output format you want for the grouping interval. Both basically represent the same thing and have sufficient data to re-construct as a "date" object in your code.
You can put anything else you want in the "grouping operator" portion after the grouping _id. I'm just using the basic "count" example in lieu of any real statement from yourself as to what you really want to do.
MongoDB 4.x and Upwards
There were some additions to Date Aggregation Operators since the original writing, but from MongoDB 4.0 there will be actual "real casting of types" as opposed to the basic math tricks done here with BSON Date conversion.
For instance we can use $toLong and $toDate as new helpers here:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$created_at" },
{ "$mod": [ { "$toLong": "$created_at" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
That's a bit shorter and does not require defining an external BSON Date for the "epoch" value as a constant in defining the pipeline so it's pretty consistent for all language implementations.
Those are just two of the "helper" methods for type conversion which all tie back to the $convert method, which is a "longer" form of the implementation allowing for custom handling on null or error in conversion.
It's even possible with such casting to get the Date information from the ObjectId of the primary key, as this would be a reliable source of "creation" date:
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
So "casting types" with this sort of conversion can be pretty powerful tool.
Warning - ObjectId values are limited to precision to the second only for the internal time value that makes up part of their data allowing the $toDate conversion. The actual inserted "time" is most probably dependent on the driver in use. Where precision is required, it's still recommended to use a discrete BSON Date field instead of relying on ObjectId values.
I like the other answer here, and mostly for the use of date math instead of aggregation date operators which while helpful can also be a little obscure.
The only thing I want to add here is that you can also return a Date object from the aggregation framework by this approach as opposed to the "numeric" timestamp as the result. It's just a little extra math on the same principles, using $add:
db.collection.aggregate([
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$current_date", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$current_date", new Date(0) ] },
1000 * 60 * 15
]}
] },
new Date(0)
]
},
"count": { "$sum": 1 }
}}
])
The Date(0) contructs in JavaScript here represent the same "epoch" date in a shorter form, as 0 millisecond from epoch is epoch. But the main point is that when the "addition" to another BSON date object is done with a numeric identifier, then the inverse of the described condition is true and the end result is actually now a Date.
All drivers will return the native Date type to their language by this approach.
Another useful way:
db.collection.aggregate([
{$group: {
_id: {
overallTime: {
$dateToString: { format: "%Y-%m-%dT%H", date: "$created_at" }
},
interval: { $trunc: { $divide: [{ $minute: "$created_at" }, 15 ]}}
},
}},
])
And more easier for min, hour, day intervals:
var format = "%Y-%m-%dT%H:%M"; // 1 min
var format = "%Y-%m-%dT%H"; // 1 hour
var format = "%Y-%m-%d"; // 1 day
db.collection.aggregate([
{$group: {
_id: { $dateToString: { format: format, date: "$created_at" } },
}},
])
A little more beautiful for mongo db.version() < 3.0
db.collection.aggregate([
{$match: {created_at:{$exists:1}}},
{$group: {
_id: {$add:[
{$dayOfYear: "$created_at" },
{$multiply: [{$year: "$created_at"}, 1000]}
]},
count: {$sum: 1 }
}},
{$sort:{_id:-1}}
])
MongoDB 5.x and Upwards
date truncation is now supported in aggergation pipelines, example:
{
$group: {
"_id": { "$dateTrunc": { date: "$created_at", unit: "minute", binSize: 15 } },
"count" : { $sum: 1 }
}
},
You can also find useful info about window functions and dateTrunc here
#Neil Lunn's answer at https://stackoverflow.com/a/26814496/8474325 for MongoDb 4.x upwards is fantastic. But there is a small mistake in the code where he uses ObjectId for the aggregation. The Line { "$toDate": "_id" } has to be changed to { "$toDate": "$_id" } for the code to work.
Here's the corrected code.
db.collection.aggregate([
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": { "$toDate": "$_id" } },
{ "$mod": [ { "$toLong": { "$toDate": "$_id" } }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
])
With MongoDB v5.0+, you can use $setWindowFields to perform computation on nearby documents(i.e. documents within 15 minute interval). In following example, it will count all documents which is 15 minutes before or after the current document. You can adjust it by changing the window param.
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: null,
sortBy: {
created_at: 1
},
output: {
count: {
$count: {},
window: {
range: [
-15,
15
],
unit: "minute"
}
}
}
}
}
])
Here is the Mongo Playground for your reference.

GroupBy DayOfMonth in mongodb but project Complete Date

I have a Collection containing a date field. I want to group it by dayOfMonth but at the time of projection I want to project the complete Date and associated count.
I have a raw Collection in mongodb containing a Timestamp (Date field)
This is my Aggregation query:
db.raw.aggregate(
{
"$match" : { "Timestamp":{$gte:new Date("2012-05-30T00:00:00.000Z"),$lt:new Date("2014-05-31T00:00:00.000Z")}}
},
{
$group:
{
_id: { ApplicationId: "$ApplicationId", date: {$dayOfMonth: '$Timestamp'} },
count: { $sum: 1 }
}
}
)
In the above query I'm grouping with dayOfMonth but how can I project complete the Date with count?
Your "Timestamp" values are clearly actual points in time so there really isn't a "complete date" to return. You could just generally "do the math" based on the date range you are applying and the "day of month" values returned as you process the results returned.
But alternately you could just "apply the math" to the date values in order by rounding the "timestamp" values out to the day. The returned values are no longer date objects, but they are the millisecond since epoch values, so it is relatively easy to "seed" those to date functions:
db.raw.aggregate([
{ "$match" : {
"Timestamp":{
"$gte": new Date("2012-05-30"),
"$lt": new Date("2014-05-31")
}
}},
{ "$group": {
"_id": {
"$subtract": [
{ "$subtract": [ "$Timestamp", new Date("1970-01-01") ] },
{ "$mod": [
{ "$subtract": [ "$Timestamp", new Date("1970-01-01") ] },
1000 * 60 * 60 * 24
])
]
},
"count": { "$sum": 1 }
}}
])
So when you subtract one date object from another the difference is milliseconds is returned as a number. So this just normalizes to epoch seconds by subtracting the epoch date. The rest is basic date math to round the result to the current day.
Alternately again you could just use other date aggregation operators and concatenate to a string, but there would be usually a bit more work involved unless those values were for direct use:
db.raw.aggregate([
{ "$match" : {
"Timestamp":{
"$gte": new Date("2012-05-30"),
"$lt": new Date("2014-05-31")
}
}},
{ "$group": {
"_id": {
"$concat": [
{ "$substr": [{ "$year": "$Timestamp" },0,4] },
"-",
{ "$substr": [{ "$month": "$Timestamp" },0,2] },
"-",
{ "$substr": [{ "$dayOfMonth": "$Timestamp" },0,2] }
]
},
"count": { "$sum": 1 }
}}
])
Neil Lunn has provides a great answer.
Theirs one more approach that u can use :
db.raw.aggregate([
{
"$match" :
{
"Timestamp":{"$gte": new Date("2012-05-30"), "$lt": new Date("2014-07-31")}
}
},
{
"$group" :
{
"_id":{"$dayOfMonth": "$Timestamp"},
"Date":{"$first":"$Timestamp"},
"count": { "$sum": 1 }
}
}
])
It will return you date.
Hope so this helps you.