date_trunc('hour', timestamp) 是最直接的按小时分组方式,将时间截断为小时起点(如14:37:22→14:00:00),需用小写'hour'、在select和group by中一致使用,timestamptz需先转时区,where中避免直接用其过滤以防索引失效。

DATE_TRUNC('hour', timestamp) 是最直接的按小时分组方式
PostgreSQL 的 DATE_TRUNC 函数会把时间戳截断到指定精度,保留年月日时分秒中更粗粒度的部分。对小时级汇总来说,DATE_TRUNC('hour', created_at) 会把 2024-05-12 14:37:22 变成 2024-05-12 14:00:00,所有该小时内的时间都映射到同一个“小时起点”,从而实现自然分组。
常见错误是误用 'hh' 或 'HOUR' —— PostgreSQL 只接受小写单位字符串,且必须是 'hour'(不是 'hh'、'HOUR' 或 'hours');否则报错 ERROR: invalid value for type interval 或提示单位不支持。
-
SELECT DATE_TRUNC('hour', '2024-01-01 09:45:33'::timestamp)→2024-01-01 09:00:00 - 配合
GROUP BY使用:在SELECT和GROUP BY中都写DATE_TRUNC('hour', event_time),不能只在SELECT里用而GROUP BY写event_time - 若字段是
timestamptz,DATE_TRUNC默认按当前会话时区处理;需要 UTC 小时汇总时,先用event_time AT TIME ZONE 'UTC'转换再截断
WHERE 条件里别直接用 DATE_TRUNC 做范围过滤
如果想查“2024-05-12 14:00 到 15:00 的数据”,用 WHERE DATE_TRUNC('hour', event_time) = '2024-05-12 14:00:00' 看似简洁,但会导致索引失效(除非你建了函数索引),查询变慢。更高效的方式是用原生时间范围:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- ✅ 推荐:
WHERE event_time >= '2024-05-12 14:00:00' AND event_time - ❌ 避免:
WHERE DATE_TRUNC('hour', event_time) = '2024-05-12 14:00:00'(无法利用event_time上的标准 B-tree 索引) - 如确需频繁按小时过滤,可建函数索引:
CREATE INDEX idx_events_hour ON events (DATE_TRUNC('hour', event_time));
聚合结果里显示“小时区间”比只显示起点更直观
单纯返回 DATE_TRUNC('hour', event_time) 得到的是每小时的起始时刻(如 14:00:00),业务上常需表达“14:00–15:00”这样的区间。可用 to_char 或区间运算构造:
- 用
to_char格式化:to_char(DATE_TRUNC('hour', event_time), 'YYYY-MM-DD HH24:00') || '–' || to_char(DATE_TRUNC('hour', event_time) + INTERVAL '1 hour', 'HH24:00') - 更简洁写法:
DATE_TRUNC('hour', event_time)::text || '–' || (DATE_TRUNC('hour', event_time) + INTERVAL '1 hour')::time(注意类型转换) - 注意:
DATE_TRUNC返回的是timestamp类型,直接拼接字符串需显式转换,否则报错operator does not exist: timestamp without time zone || text
时区处理不当会导致跨小时数据错位
当表中存的是带时区的时间(timestamptz),而你的业务逻辑按本地时区(比如北京时间 UTC+8)统计每小时数据,DATE_TRUNC('hour', event_time) 默认按 current_setting('timezone') 截断。如果会话时区没设对,或者应用连接复用不同配置的连接池,结果就会偏移。
- 查当前会话时区:
SHOW timezone;,确认是'Asia/Shanghai'而非'UTC' - 稳妥做法:显式指定时区转换,例如
DATE_TRUNC('hour', event_time AT TIME ZONE 'Asia/Shanghai') - 避免依赖客户端设置:在 SQL 层统一用
AT TIME ZONE显式转换,而不是靠连接参数或环境变量
真正麻烦的不是语法写错,而是时区隐含行为导致某几个小时的数据被合并到相邻小时——这种问题在线上跑几天才暴露,排查成本远高于写对那一行代码。










