在写报表类 SQL 时,我们常常 not only 要统计总数,还要看不同时间段的分布,例如按天看订单量、按小时看访问量。所谓按时间段分组,本质是先从一个时间字段中截取出想要的粒度(如某天、某小时),再以此作为分组依据进行聚合。

一、核心思路
不管用什么数据库,按时间段分组的步骤都类似:
- 从时间列中提取出时间段标识,例如日期或小时。
- 在
GROUP BY后面使用这个提取结果。 - 配合
COUNT、SUM等聚合函数统计指标。
二、MySQL 按天和按小时分组
MySQL 中可以用 DATE 函数取日期,用 DATE_FORMAT 取小时段。
-- 按自然天分组统计订单数
SELECT DATE(create_time) AS day,
COUNT(*) AS order_cnt
FROM orders
GROUP BY DATE(create_time)
ORDER BY day;
-- 按小时段分组(例如 2024-01-01 08:00:00)
SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:00:00') AS hour_slot,
COUNT(*) AS pv
FROM access_log
GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d %H:00:00')
ORDER BY hour_slot;
三、PostgreSQL 使用 date_trunc
PostgreSQL 推荐用 date_trunc 直接截断到指定单位。
-- 按周分组
SELECT date_trunc('week', create_time) AS week_start,
SUM(amount) AS total_amount
FROM orders
GROUP BY date_trunc('week', create_time)
ORDER BY week_start;
-- 按月分组
SELECT date_trunc('month', create_time) AS month_start,
COUNT(*) AS cnt
FROM orders
GROUP BY date_trunc('month', create_time);
四、自定义时间段(如每 15 分钟)
有时业务要求每 15 分钟一段,可以用时间差整除实现。
-- MySQL 每 15 分钟一组
SELECT FLOOR(UNIX_TIMESTAMP(create_time) / (15 * 60)) AS slot,
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(create_time) / (15 * 60)) * 15 * 60) AS slot_time,
COUNT(*) AS req_cnt
FROM access_log
GROUP BY slot
ORDER BY slot_time;
五、注意事项
| 注意点 | 说明 |
|---|---|
| 索引使用 | 对时间列套函数会导致索引失效,数据量大时可先过滤再分组。 |
| 时区问题 | 确保数据库时区和业务时区一致,否则按天统计会偏移。 |
| 空时间段 | 原生 GROUP BY 不会产生空白时间段,需要借助数字表或日历表补全。 |
通过上述方式,你可以灵活地在 SQL 分组查询中按任意时间段做聚合,从而支撑各类周期报表与趋势分析需求。