MySQL用YEAR()提取年份最直接,但WHERE中对日期字段使用函数会阻碍索引使用;PostgreSQL须用EXTRACT(YEAR FROM ...),不支持YEAR();跨库可选DATE_PART()或SUBSTR(col,1,4),但需注意兼容性与NULL处理。

MySQL里用YEAR()提取年份最直接
如果数据表里有个DATE或DATETIME类型的字段(比如order_date),想查2023年的订单,YEAR(order_date) = 2023就能用上索引——前提是order_date有索引且没被函数包裹在左边(如WHERE YEAR(order_date) = 2023可以走索引,但WHERE order_date = DATE_ADD('2023-01-01', INTERVAL 1 DAY)这类写法反而更难优化)。
注意别写成YEAR('order_date')——单引号会让它变成字符串字面量,结果永远是NULL;也别对NULL值调用YEAR(),它返回NULL,不是报错,容易漏数据。
SELECT id, YEAR(create_time) AS year FROM logs WHERE YEAR(create_time) = 2024- 如果
create_time是TIMESTAMP且带时区,YEAR()按服务器时区解析,不是UTC - 聚合场景下,
GROUP BY YEAR(created_at)比GROUP BY DATE_FORMAT(created_at, '%Y')更轻量
PostgreSQL必须用EXTRACT(YEAR FROM ...)
PostgreSQL不支持YEAR()函数,硬写会报错:ERROR: function year(timestamp without time zone) does not exist。必须用标准SQL的EXTRACT语法,而且关键字YEAR要大写、紧跟FROM,不能加括号或引号。
EXTRACT返回的是double precision类型,所以比较时写EXTRACT(YEAR FROM paid_at) = 2023没问题,但若要转成整数参与计算(比如算“距今几年”),得显式::int转换,否则可能触发隐式类型转换警告。
SELECT COUNT(*) FROM sales WHERE EXTRACT(YEAR FROM sale_time) = 2023-
EXTRACT(YEAR FROM '2023-12-31'::date)返回2023.0,不是2023 - 对
TIMESTAMPTZ字段,EXTRACT按当前会话时区计算,不是存储时区
跨数据库写法:用DATE_PART()或字符串截取保底
如果SQL要同时兼容MySQL和PostgreSQL(比如ORM动态生成),YEAR()和EXTRACT没法统一。这时可退一步:用DATE_PART('year', col)——PostgreSQL支持,而MySQL 8.0+也引入了同名函数(但行为略有差异:MySQL的DATE_PART只接受DATETIME,不接受DATE)。
更通用但不够语义化的办法是字符串截取:SUBSTR(col, 1, 4)。它对DATE类型在多数数据库里会自动转成'YYYY-MM-DD'格式再截,但风险在于:一旦列定义为CHAR(10)存日期,或区域设置导致格式变成'DD/MM/YYYY',结果就错了。
- SQLite没有
YEAR或EXTRACT,只能用strftime('%Y', date_col) -
TO_CHAR(col, 'YYYY')在PostgreSQL里返回字符串,不能直接和数字2023比较,得转::int - 所有方案对
NULL都返回NULL,记得用IS NOT NULL额外过滤
WHERE条件里别用函数包装日期字段
无论用YEAR()还是EXTRACT,只要写成WHERE YEAR(created_at) = 2023,MySQL可能无法使用created_at上的索引(取决于版本和优化器)。真正高效的做法是把函数移到右边,让左边保持字段原样:
- ✅
WHERE created_at >= '2023-01-01' AND created_at - ❌
WHERE YEAR(created_at) = 2023(索引失效风险高) - ❌
WHERE EXTRACT(YEAR FROM created_at) = 2023(PostgreSQL中同样可能跳过索引)
时间范围写法不仅快,还能准确包含毫秒级时间,而YEAR()会把'2023-12-31 23:59:59.999'和'2024-01-01 00:00:00.000'一刀切开——这点很容易被忽略。

















