
本文详解如何在 MongoDB 中正确按 15 分钟时间间隔(如 00:00–00:14、00:15–00:29 等)对时间序列数据(如传感器速度)进行 $group 聚合,确保时间对齐准确、结果可复现。
本文详解如何在 mongodb 中正确按 15 分钟时间间隔(如 00:00–00:14、00:15–00:29 等)对时间序列数据(如传感器速度)进行 `$group` 聚合,确保时间对齐准确、结果可复现。
在时间序列分析中,将数据按固定时长(如 15 分钟)切片并计算统计指标(如平均速度)是常见需求。但直接对 ISODate 字段做数学运算(如 "$minute" 取模)容易出错——关键问题在于:仅用 "$minute" 会忽略年、月、日、小时等更高精度的时间维度,导致跨小时/跨天的数据被错误归入同一分组(例如 01:58 和 02:13 的分钟值分别为 58 和 13,58 % 15 = 13,二者会被误判为同属“13 分钟区间”,实则相差近 15 分钟且跨小时)。
✅ 正确做法是:以完整时间点为基准,先提取年、年中第几天($dayOfYear)、小时($hour),再将分钟映射到最近的 15 分钟起点。MongoDB 提供了标准日期操作符,推荐使用以下结构:
[
{
"$group": {
"_id": {
"year": { "$year": "$timestamp" },
"dayOfYear": { "$dayOfYear": "$timestamp" },
"hour": { "$hour": "$timestamp" },
"minute_interval": {
"$subtract": [
{ "$minute": "$timestamp" },
{ "$mod": [{ "$minute": "$timestamp" }, 15] }
]
}
},
"averageSpeed": { "$avg": "$speed" },
"count": { "$sum": 1 }
}
}
]? minute_interval 字段值为 0, 15, 30, 45,代表该分组对应每小时内的第 0、15、30 或 45 分钟起始的 15 分钟窗口(如 "minute_interval": 15 表示 HH:15–HH:29 区间)。
在 Go 语言中使用 mgo(或现代替代库如 mongo-go-driver)时,需严格匹配 BSON 结构。以下是兼容性良好的 mgo 示例(已修正原始代码缺陷):
pipeline := []bson.M{
{
"$group": bson.M{
"_id": bson.M{
"year": bson.M{"$year": "$timestamp"},
"dayOfYear": bson.M{"$dayOfYear": "$timestamp"},
"hour": bson.M{"$hour": "$timestamp"},
"minute_interval": bson.M{
"$subtract": []interface{}{
bson.M{"$minute": "$timestamp"},
bson.M{"$mod": []interface{}{bson.M{"$minute": "$timestamp"}, 15}},
},
},
},
"averageSpeed": bson.M{"$avg": "$speed"},
"totalCount": bson.M{"$sum": 1},
},
},
}
pipe := collection.Pipe(pipeline)
iter := pipe.Iter()
var result struct {
ID struct {
Year int `bson:"year"`
DayOfYear int `bson:"dayOfYear"`
Hour int `bson:"hour"`
MinuteIntv int `bson:"minute_interval"`
} `bson:"_id"`
AverageSpeed float64 `bson:"averageSpeed"`
TotalCount int `bson:"totalCount"`
}
for iter.Next(&result) {
// 格式化输出时间窗口,例如:2024-05-20 14:15–14:29
t := time.Date(result.ID.Year, 1, 1, result.ID.Hour, result.ID.MinuteIntv, 0, 0, time.UTC)
t = t.AddDate(0, 0, result.ID.DayOfYear-1) // 调整为实际日期
windowStart := t.Format("2006-01-02 15:04")
windowEnd := t.Add(time.Minute * 14).Format("15:04")
fmt.Printf("[%s–%s] avgSpeed=%.2f (n=%d)\n", windowStart, windowEnd, result.AverageSpeed, result.TotalCount)
}⚠️ 注意事项:
- 务必包含 year + dayOfYear(而非 month + day):避免闰年或跨月时 dayOfYear 计算失准;$dayOfYear 是 MongoDB 原生安全的日期归一化字段。
- 不要省略 hour 字段:否则 01:58 和 02:13 仍可能因分钟取模后同为 13 而混入一组。
- 若需最终返回 ISODate 类型的窗口起始时间(便于前端解析),可在 $group 后追加 $addFields 阶段,用 $dateFromParts 构造精确时间戳。
- 在高并发写入场景下,建议为 timestamp 字段建立索引:db.collection.createIndex({"timestamp": 1}),显著提升聚合性能。
通过以上方法,即可稳定、准确地实现 15 分钟粒度的时间窗口聚合,支撑实时监控、报表统计等核心业务场景。


















