floor 直接除时间戳会出错,因 mysql/postgresql 对负数取整方向不一致且忽略时区、夏令时与跨天边界;正确做法是:mysql 8.0+ 用 date_sub + date_add 对齐,postgresql 必用 date_trunc('hour', ts),字符串需先转 timestamp 再截断。

为什么 FLOOR 直接除时间戳会出错?
因为 MySQL / PostgreSQL 的 FLOOR 对负数取整方向不一致,而 Unix 时间戳在 1970 年前是负数;更关键的是,直接用 FLOOR(ts / 3600) 会把时区、夏令时、跨天边界全绕过去——结果看着像按小时分桶,实则每条数据都可能掉进错误的桶里。
- MySQL 8.0+ 推荐用
DATE_SUB(ts, INTERVAL SECOND(ts) SECOND)配合DATE_ADD做对齐,而不是硬除 - PostgreSQL 必须用
date_trunc('hour', ts),FLOOR(EXTRACT(EPOCH FROM ts) / 3600)在跨 DST 时会产生重复或空桶 - 如果原始字段是字符串(如
'2024-03-15 14:22:07'),先转TIMESTAMP再截断,别在字符串层面做 substring 截取
date_trunc 在 PostgreSQL 里怎么选粒度?
date_trunc 是唯一靠谱的时间分桶函数,但粒度参数不是随便填的:填 'minute' 和 'min' 效果完全不同——后者根本无效,PostgreSQL 会静默忽略并返回原值。
- 合法粒度只有:
'second'、'minute'、'hour'、'day'、'week'、'month'、'quarter'、'year' -
week从周日开始截断,若业务要求周一为起点,得配合EXTRACT(ISODOW FROM ts)手动偏移 - 聚合时务必把
date_trunc放在GROUP BY和SELECT两边,漏一边就会导致行数爆炸或聚合失效
MySQL 5.7 怎么安全实现 5 分钟分桶?
MySQL 5.7 没有 date_trunc,也不能依赖 FLOOR(UNIX_TIMESTAMP(ts)/300)——它会在 UTC 和本地时区混用时翻车,尤其当服务器时区设为 +08:00 但字段存的是 UTC 时间。
- 先统一转成 UTC 时间:
CONVERT_TZ(ts, @@session.time_zone, '+00:00') - 再用
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(...) / 300) * 300)得到左闭右开的 5 分钟起始时间 - 注意:如果
ts是DATETIME类型且无时区信息,CONVERT_TZ会把它当作系统时区时间处理,必须确认源头语义
聚合后补全空时间段的坑在哪?
用 generate_series(PG)或自连接构造时间序列补空,最容易被忽略的是边界对齐方式:你生成的“桶”和实际分组用的“桶”必须用同一套逻辑算,否则看起来补全了,其实每个桶里都是空的。
- PostgreSQL 补 1 小时桶,必须用
date_trunc('hour', ...)生成序列,不能用generate_series(start, end, '1 hour')后再 cast - MySQL 没有
generate_series,用递归 CTE 或临时数字表时,要确保ADDTIME('2024-01-01', SEC_TO_TIME((n-1)*3600))的起始点与主查询中FROM_UNIXTIME(...)的基准完全一致 - 补空之后做
LEFT JOIN,ON 条件必须是两个分桶结果完全相等,别写成t1.bucket >= t2.start AND t1.bucket 这种区间匹配
时间分桶真正的复杂点不在函数调用,而在所有环节对“这个时间代表什么时刻、属于哪个时区、是否含夏令时偏移”的隐式假设是否一致。只要有一处没对齐,统计结果就不可信。










