EXTRACT(HOUR FROM timestamp)是最直接的写法,支持PostgreSQL、MySQL 8.0+和Oracle;需大写HOUR、带FROM关键字,不支持MySQL 5.7及更早版本。

EXTRACT(HOUR FROM timestamp) 是最直接的写法
PostgreSQL、MySQL 8.0+ 和 Oracle 都支持标准 SQL 的 EXTRACT 函数,提取小时只需明确指定 HOUR 作为字段名,并用 FROM 指向时间戳表达式。注意不是 extract(hour from col)(小写)这种写法——虽然部分数据库容忍,但标准写法要求 HOUR 全大写。
常见错误是把单位写成 hours 或 hr,实际只认 HOUR;另外别漏掉 FROM 关键字,写成 EXTRACT(HOUR, timestamp) 会报错(那是 PostgreSQL 的旧式 date_part 风格,不通用)。
-
EXTRACT(HOUR FROM '2023-05-12 14:37:22'::TIMESTAMP)→ 返回14 - 若字段是
timestamptz,提取的是当前时区下的小时(如2023-05-12 14:37:22+08提取仍是14) - 对
NULL输入,结果恒为NULL,不会报错
MySQL 5.7 及更早版本不支持 EXTRACT(HOUR FROM ...)
MySQL 5.7 只接受 EXTRACT 提取 YEAR、MONTH、DAY 等日期部分,不支持 HOUR、MINUTE 等时间部分——强行用会提示 FUNCTION extract does not exist 或返回 0(取决于模式)。这时候得换函数:
- 用
HOUR(col):最简洁,专为提取小时设计,支持所有 MySQL 版本 - 用
EXTRACT(HOUR_SECOND FROM col) DIV 3600:绕路但可行,不过没必要 - 避免用
DATE_FORMAT(col, '%H')转字符串再转数字——多一次类型转换,且%H是 24 小时制,%h是 12 小时制,容易混淆
时区偏移会影响 EXTRACT(HOUR FROM timestamptz) 的结果
在 PostgreSQL 或带时区的时间戳上,EXTRACT(HOUR FROM col) 返回的是该值在**存储时区上下文中的小时数**,不是 UTC 小时。比如:'2023-01-01 15:00:00+01' 提取得 15,而 '2023-01-01 15:00:00+08' 同样提取得 15——它们本地时间都是下午三点,尽管 UTC 时间不同。
如果业务需要统一按 UTC 小时统计(比如全球日志归一化),得先转时区:
- PostgreSQL:
EXTRACT(HOUR FROM col AT TIME ZONE 'UTC') - MySQL 8.0+:
HOUR(CONVERT_TZ(col, @@session.time_zone, '+00:00')) - 别直接对
timestamptz列做EXTRACT(HOUR FROM col AT TIME ZONE 'UTC')——语法合法但语义易错,优先显式转为TIMESTAMP再提取
性能上,EXTRACT(HOUR FROM ...) 通常无法走索引
绝大多数数据库对 EXTRACT 表达式不支持函数索引(除非显式创建),所以 WHERE EXTRACT(HOUR FROM created_at) = 9 这类查询基本都会全表扫描。真要按小时范围高效过滤,建议:
- 加生成列(PostgreSQL/MySQL 5.7+):
ALTER TABLE logs ADD COLUMN hour_of_day SMALLINT GENERATED ALWAYS AS (EXTRACT(HOUR FROM created_at)) STORED,再给该列建索引 - 用范围条件替代:
created_at >= '2023-01-01 09:00:00' AND created_at ,可走时间字段上的 B-tree 索引 - 别依赖
TO_CHAR(created_at, 'HH24')或类似字符串截取——既慢又难索引
时区转换和函数包裹会让优化器更难推导范围,能用原生时间比较就别绕。

















