必须将时间归一化到「年-月-日-小时」粒度再分组,否则仅用HOUR()会混淆不同日期的同一小时;MySQL用DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00'),PostgreSQL用DATE_TRUNC('hour', created_at),SQL Server用DATEFROMPARTS()构造整点时间。

直接用 HOUR() 分组会把所有日期的同一小时混在一起,统计结果不可信——比如 2026-08-03 14:00 和 2026-08-04 14:00 全被归为 “14 点”,完全丢失时间上下文。必须先将时间归一化到「年-月-日-小时」粒度,再分组。
MySQL:用 DATE_FORMAT() 截断到整点
这是最常用也最稳妥的做法,生成字符串形式的小时槽,能明确区分不同日期的同一小时。
-
DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00')是标准写法;别用'%Y-%m-%d %H'(少秒位),某些版本会因隐式类型转换导致排序错乱 - 字段必须是
DATETIME或TIMESTAMP类型;如果是字符串,先用STR_TO_DATE(log_time, '%Y-%m-%d %H:%i:%s')转换,否则DATE_FORMAT(NULL, ...)返回NULL,空值全挤进一组 -
WHERE created_at IS NOT NULL必须加,否则空时间参与分组会虚高统计值 - 示例:
SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') AS hour_slot,
COUNT(*) AS visit_count
FROM access_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND created_at IS NOT NULL
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00')
ORDER BY hour_slot;PostgreSQL:用 DATE_TRUNC('hour', ...)
返回的是 TIMESTAMP 类型的整点时间(如 '2026-08-04 17:00:00'),比字符串更利于后续计算,也更容易走索引。
- 单位必须是单数
'hour',写成'hours'会报错invalid value for type interval - 如果
created_at是TIMESTAMPTZ,务必先统一时区:created_at AT TIME ZONE 'Asia/Shanghai',再截断;否则 UTC 时间和本地业务时间对不上 - 别用
EXTRACT(HOUR FROM created_at)替代——它只返回 0–23 的整数,彻底丢掉日期信息 - 示例:
SELECT DATE_TRUNC('hour', created_at) AS hour_slot,
COUNT(*) AS visit_count
FROM access_log
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY DATE_TRUNC('hour', created_at)
ORDER BY hour_slot;SQL Server:用 DATEFROMPARTS() 构造整点时间
没有原生小时截断函数,得靠 DATEPART 拆解再拼装,DATEFROMPARTS() 是最安全的构造方式,输出类型为 DATETIME,支持范围查询和索引下推。
-
DATEPART参数不能加引号:DATEPART(hour, created_at)✅,DATEPART('hour', created_at)❌(语法错误) - 构造整点时间推荐写法:
DATEFROMPARTS(YEAR(created_at), MONTH(created_at), DAY(created_at), DATEPART(HOUR, created_at), 0, 0) - 避免用字符串拼接 +
CONVERT,容易因格式不一致引发转换失败(如2026-8-4 5:00:00vs2026-08-04 05:00:00) - WHERE 条件里别对
created_at做函数运算(如WHERE DATEPART(HOUR, created_at) = 17),会强制全表扫描;改用范围:WHERE created_at >= '2026-08-04 17:00:00' AND created_at
补零、补空、时区——三个最容易漏掉的点
补零显示(如 09 而非 9)只是格式问题,但补空小时(0–23 缺失项补 0)和时区对齐直接影响分析结论是否成立。
- 补零:MySQL 用
LPAD(HOUR(created_at), 2, '0'),但注意——这只能用于HOUR()结果,而你真正该用的是DATE_FORMAT(..., '%H')(自带前导零) - 补空小时:仅靠
GROUP BY永远不会返回缺失小时;必须用数字表或 CTE LEFT JOIN,例如生成 0–23 的临时序列再关联 - 时区:数据库服务器时区、应用写入时区、查询会话时区三者不一致时,
DATE_TRUNC或DATE_FORMAT的结果可能和业务预期差几小时;最稳做法是在写入时就存好本地时间字段(如created_at_local),而不是每次查都算偏移

















