SQL重采样需时间分组+聚合,核心是对齐周期边界;PostgreSQL用DATE_TRUNC最安全,MySQL需FLOOR(UNIX_TIMESTAMP/3600)*3600并显式处理时区,且WHERE中禁用函数以免索引失效。

SQL 本身没有 resample 这种时间序列专用操作,重采样必须靠时间分组 + 聚合实现,核心是“对齐到周期边界”,不是简单取模或截断。
PostgreSQL 中用 DATE_TRUNC 实现小时级降采样
这是最安全、语义最清晰的做法。它自动处理时区、夏令时和闰秒,且能下推到索引层优化执行。
常见错误是写成 EXTRACT(HOUR FROM ts) 或 ts::date || ' ' || EXTRACT(HOUR FROM ts) || ':00' —— 这类拼接无法跨天对齐,也不支持时区转换。
- 正确写法:
GROUP BY DATE_TRUNC('hour', ts AT TIME ZONE 'Asia/Shanghai') - 若原始列是
TIMESTAMP WITHOUT TIME ZONE,必须显式加AT TIME ZONE,否则按数据库默认时区解释,容易导致凌晨 1 点的数据被归入前一日 - 聚合时搭配
AVG()、SUM()、selector_last(value, ts)(InfluxDB 风格)等函数,取决于业务语义
MySQL 8.0+ 模拟 DATE_TRUNC 的可靠写法
MySQL 没有原生 DATE_TRUNC,但可用 FLOOR(UNIX_TIMESTAMP(ts) / 3600) * 3600 得到整点 Unix 时间戳,再转回时间类型。
关键陷阱在于时区:如果 ts 是 DATETIME 类型(无时区),UNIX_TIMESTAMP() 默认按系统时区转为 UTC;如果 ts 是 TIMESTAMP 类型,则已隐式存为 UTC,FROM_UNIXTIME() 又默认按系统时区输出 —— 一来一回可能偏差一小时。
- 统一做法:先用
CONVERT_TZ(ts, '+00:00', '+08:00')显式转为北京时间,再做FLOOR(UNIX_TIMESTAMP(...) / 3600) * 3600 - 避免在
WHERE子句中对ts使用任何函数(如DATE_TRUNC('hour', ts)),否则 B-tree 索引失效;改用范围查询:WHERE ts >= '2024-01-01 10:00:00' AND ts
InfluxDB SQL 风格的聚合与选择器函数
InfluxDB 的 SQL 扩展支持专为时间序列设计的 selector_* 函数,比通用聚合更贴近实际需求。
比如传感器数据中,“每小时最后一条记录”比“每小时均值”更能反映设备最新状态;“每小时最大值”比 MAX(value) 更明确指向峰值事件。
-
selector_last(value, time)返回该小时内时间戳最大的那条value -
selector_max(value, time)返回该小时内value最大的那条完整记录(含对应时间) - 注意:这些函数返回的是结构体(类似 JSON),不能直接参与算术运算,需用
.value或.time提取字段
真正难的不是写出 GROUP BY DATE_TRUNC('hour', ts),而是确认你的时区上下文是否一致、索引是否被绕过、以及凌晨跨天时夏令时切换是否影响分组边界 —— 这些细节不验证,结果可能每天漂移 1 小时。

















