MySQL按月分组须用DATE_FORMAT(created_at,'%Y-%m')或YEAR()+MONTH()组合,SELECT与GROUP BY必须一致;PostgreSQL用date_trunc('month',created_at);跨库推荐EXTRACT(YEAR FROM created_at)*100+EXTRACT(MONTH FROM created_at)。

MySQL里用DATE_FORMAT按月分组最直接
MySQL不支持TRUNC(那是Oracle/PostgreSQL的函数),强行写会报错 FUNCTION xxx.TRUNC does not exist。必须用DATE_FORMAT或YEAR()+MONTH()组合。
常见错误是只用DATE_FORMAT(created_at, '%Y-%m')但没在SELECT和GROUP BY里保持一致,导致结果混乱或SQL报错。
- 正确写法:
SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*) FROM orders GROUP BY DATE_FORMAT(created_at, '%Y-%m') - 如果还要排序,加
ORDER BY month;注意这里month是字符串,'2023-10'会排在'2023-9'后面,所以更稳妥的是用ORDER BY YEAR(created_at), MONTH(created_at) - 别用
DATE_FORMAT(created_at, '%m')单独分组——不同年份的1月会被合并,数据就错了
PostgreSQL中TRUNC可用但要注意参数
PostgreSQL的TRUNC对日期类型支持'month'精度,但函数名是date_trunc,不是TRUNC(后者是数值截断函数)。写错名字会提示function trunc(timestamp without time zone, unknown) does not exist。
- 正确写法:
SELECT date_trunc('month', created_at) AS month, COUNT(*) FROM orders GROUP BY date_trunc('month', created_at) ORDER BY month -
date_trunc('month', ...)返回的是当月第一天的timestamp(如2023-10-01 00:00:00),不是字符串,显示可能带时分秒,需要TO_CHAR(..., 'YYYY-MM')格式化展示 - 不能写
date_trunc('MM', ...)或date_trunc('m', ...)——只认'month'这个字符串
兼容多种数据库的写法:用EXTRACT组合年月
EXTRACT是SQL标准函数,在MySQL 8.0+、PostgreSQL、SQL Server(部分版本)、Oracle都支持,可避免语法碎片化。但要注意MySQL 5.7不支持EXTRACT(YEAR_MONTH FROM ...),只能分开提。
- 安全写法(全平台通用):
SELECT EXTRACT(YEAR FROM created_at) * 100 + EXTRACT(MONTH FROM created_at) AS ym, COUNT(*) FROM orders GROUP BY ym ORDER BY ym - 这样生成的是整数型年月(如
202310),天然可排序,也方便后续程序解析 - 如果字段是字符串类型(如
'2023-10-15'),先用CAST(created_at AS DATE)转成日期再提取,否则MySQL会静默失败或返回0
性能陷阱:别在WHERE或GROUP BY里对字段做函数运算
无论用DATE_FORMAT还是date_trunc,只要在GROUP BY子句里对日期字段套函数,数据库就无法使用created_at上的索引——全表扫描几乎不可避免,百万级表可能从毫秒变秒级。
- 优化办法:加一个生成列(MySQL 5.7+ / PostgreSQL 12+):
ALTER TABLE orders ADD COLUMN created_month DATE GENERATED ALWAYS AS (DATE_TRUNC('month', created_at)) STORED,再给它建索引 - 或者业务层预计算:插入时就存一份
order_month CHAR(7)字段,值为'2023-10',查询直接GROUP BY order_month - 临时救急但不推荐:用范围查询替代分组,比如
WHERE created_at >= '2023-10-01' AND created_at ,再逐月查
实际用哪一种,取决于你手头数据库版本、数据量大小、以及是否能改表结构。函数写法看着短,但线上大表一跑慢,第一反应不该是换写法,而是看执行计划里有没有Using filesort或Using temporary——那基本就是索引失效了。

















