应使用 DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') 截断到小时粒度再分组,避免仅用 HOUR() 导致跨日同小时混淆;同时 WHERE 必须显式限定时间范围以确保统计准确。

不能只用 HOUR() 或 DATEPART(hour, ...) 直接分组——它会把所有日期的同一小时(比如所有“14点”)全混在一起,统计结果完全失真。
MySQL 里必须用 DATE_FORMAT() 截断到小时粒度
直接 GROUP BY HOUR(created_at) 只返回 0–23 的数字,丢失日期信息。真实需求是 “2026-08-10 14:00:00 到 2026-08-10 14:59:59” 这样的独立小时段。
-
DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00')是安全写法,生成可读、可索引的时间槽字符串 - WHERE 条件必须显式限定时间范围,例如
WHERE created_at >= '2026-08-10 00:00:00' AND created_at ,否则跨天数据全叠进同一小时 - 时间字段类型要是
DATETIME或TIMESTAMP;如果是毫秒时间戳(如1723305600000),先除以 1000 再用FROM_UNIXTIME() - 别在 WHERE 里对时间字段套函数,比如
WHERE HOUR(created_at) = 14—— 这会让索引失效,强制全表扫描
PostgreSQL 和 SQL Server 要用 DATE_TRUNC() 或拼接逻辑
PostgreSQL 推荐 DATE_TRUNC('hour', event_time),它返回带日期的 timestamp,天然支持跨天区分;SQL Server 没有原生截断函数,得手动拼: DATEFROMPARTS(YEAR(t), MONTH(t), DAY(t), DATEPART(HOUR, t), 0, 0) 或更稳妥的 CAST(CAST(t AS DATE) AS DATETIME) + DATEPART(HOUR, t) / 24.0。
- PostgreSQL 中若时区不一致(比如存的是 UTC,业务按东八区看),必须加
AT TIME ZONE 'Asia/Shanghai'再DATE_TRUNC - SQL Server 的
DATEPART(hour, t)本身不处理时区,旧版本只能靠DATEADD(hour, 8, t)偏移,但注意夏令时风险(中国不用,可简化) - 避免用
TO_CHAR(t, 'YYYY-MM-DD HH24')—— 返回文本,排序和后续 join 不稳定
补全缺失小时(比如某小时没数据,也要显示 0)得手动生成时间序列
数据库不会自动补 0,要靠 UNION 或 CTE 构造 24 小时基准表再 LEFT JOIN。
- MySQL 简单做法:用
(SELECT 0 UNION ALL SELECT 1 ... UNION ALL SELECT 23)生成 hour 列,再 LEFT JOIN 实际统计结果 - PostgreSQL 更优雅:
GENERATE_SERIES('2026-08-10 00:00', '2026-08-10 23:00', '1 hour')直接生成时间序列 - LEFT JOIN 时 ON 条件必须对齐,比如 MySQL 里是
ON h.hour = HOUR(t.hour_slot),但前提是已限定同一天;更稳的是用DATE_FORMAT(t.created_at, '%Y-%m-%d %H')和生成的字符串匹配 - 补全后记得用
COALESCE(count, 0)把 NULL 变成 0
真正容易被忽略的不是语法,而是时间字段是否含时区、是否已归一化到业务本地时间、以及 WHERE 条件有没有真正切出你要的自然小时窗口——这三个点错一个,结果就全偏了。

















