必须用日期函数提取年月日成分再分组,直接group by时间字段无法实现按年/月聚合;mysql用year()等、postgresql用extract()/to_char()、sql server用year()/datepart(),跨库推荐date_trunc或手动截断到月初。

MySQL里用 YEAR()、MONTH()、DAY() 提取时间字段再分组
直接对 DATETIME 或 DATE 字段 GROUP BY 会按完整时间戳分组,得不到“按年”这种聚合效果。必须先用日期函数提取年份、月份等成分,再分组。
常见错误是写成 GROUP BY created_at 却期望得到每年一条记录——这只会把每秒都算作一个分组。
-
GROUP BY YEAR(created_at):按年分组,返回每一年的统计(如 2022、2023) -
GROUP BY YEAR(created_at), MONTH(created_at):按年+月分组,结果像 (2023, 1)、(2023, 2) -
GROUP BY DATE(created_at):比DAY()更稳妥,能保留完整日期(如 '2023-05-17'),避免跨月时只取日导致混淆
注意:MONTH(created_at) 返回 1–12,不带年份,单独用它分组会把所有年份的1月全合并——除非你真想看“历年1月总和”。
PostgreSQL 必须用 EXTRACT() 或 TO_CHAR()
PostgreSQL 不支持 YEAR() 这类 MySQL 函数,硬写会报错 function year(timestamp without time zone) does not exist。
两种主流写法:
-
GROUP BY EXTRACT(YEAR FROM created_at):返回数值型年份(如 2023.0),常需CAST(... AS INTEGER)转整型 -
GROUP BY TO_CHAR(created_at, 'YYYY-MM'):返回字符串 '2023-05',适合按年月聚合且结果可读性强
TO_CHAR() 格式串大小写敏感:'yyyy' 和 'YYYY' 都行,但 'yy' 是两位年份,容易歧义;'MM' 是补零月份(01–12),'M' 是无零(1–12),分组时建议统一用 'YYYY-MM' 避免 2023-1 和 2023-01 被当成不同组。
SQL Server 用 YEAR()、MONTH(),但要注意 DATEPART() 的参数顺序
SQL Server 支持 YEAR(created_at),也支持更通用的 DATEPART(year, created_at)。后者参数是 DATEPART(部分, 时间表达式),顺序反了容易写错成 DATEPART(created_at, year),报错 Argument data type datetime is invalid for argument 1 of datepart function。
- 按年:
GROUP BY YEAR(created_at)或GROUP BY DATEPART(year, created_at) - 按年月:
GROUP BY YEAR(created_at), MONTH(created_at),或更安全地用GROUP BY DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1)构造月初日期
用 DATEFROMPARTS() 的好处是生成的是真实日期值(如 2023-05-01),后续排序、范围查询更可靠;纯 YEAR()+MONTH() 组合只是数字,没法直接判断“2023年5月是否早于2023年6月”。
跨数据库兼容写法:用 DATE_TRUNC()(PostgreSQL / ClickHouse / BigQuery)或模拟截断
如果项目要适配多个数据库,优先考虑标准 SQL 的时间截断逻辑。PostgreSQL、ClickHouse、BigQuery 原生支持 DATE_TRUNC('month', created_at),直接返回该月第一天(如 '2023-05-01'),既可分组又可排序。
MySQL 和 SQL Server 没有 DATE_TRUNC(),得手动模拟:
- MySQL 月截断:
DATE_SUB(created_at, INTERVAL DAYOFMONTH(created_at)-1 DAY) - SQL Server 月截断:
DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1)
这类写法稍长,但语义清晰——你不是在“提取年份”,而是在“把时间归到所属月份的第一天”,逻辑上更接近业务意图。实际中,很多人忽略这点,用 YEAR()+MONTH() 得到 (2023,5) 后再拼字符串,结果排序出错或无法加索引加速。










