应使用date_format(date_col, '%y-%m')替代year()和month()组合分组,因其返回'2023-01'格式字符串,天然有序且可读性强;但where中避免直接对date_format()计算,需改用范围查询以保持索引有效性。

MySQL中用YEAR()和MONTH()组合分组会丢失月份前导零
直接写 GROUP BY YEAR(date_col), MONTH(date_col) 看似合理,但结果里月份是数字 1~12,没法直接当“2023-01”这种格式用,后续拼接或排序容易出错。
- 推荐改用
DATE_FORMAT(date_col, '%Y-%m')—— 它返回字符串如'2023-01',天然有序、可读性强、能直接用于报表展示 - 注意:如果
date_col是DATETIME或TIMESTAMP,DATE_FORMAT同样适用,无需先转日期 - 别在
WHERE条件里对DATE_FORMAT()做计算(比如DATE_FORMAT(create_time, '%Y-%m') = '2023-01'),这会让索引失效;应改用范围查询:create_time >= '2023-01-01' AND create_time
PostgreSQL里不能用DATE_FORMAT(),得用TO_CHAR()
PostgreSQL 没有 DATE_FORMAT(),等效写法是 TO_CHAR(date_col, 'YYYY-MM')。它返回的也是字符串,行为和 MySQL 的 DATE_FORMAT 一致。
-
TO_CHAR(create_time, 'YYYY-MM')和TO_CHAR(create_time, 'yyyy-mm')效果一样,大小写不敏感 - 如果字段可能为
NULL,TO_CHAR(NULL, 'YYYY-MM')返回NULL,分组时会单独成一组,需要确认业务是否允许空时间参与统计 - 想按真实年月顺序排序?直接
ORDER BY TO_CHAR(date_col, 'YYYY-MM')就行,字符串字典序刚好匹配时间顺序
SQL Server用FORMAT()要注意性能和兼容性
SQL Server 2012+ 支持 FORMAT(date_col, 'yyyy-MM'),但这个函数在大数据量下性能较差,且无法下推到索引扫描。
- 更稳妥的做法是用
CONVERT(VARCHAR(7), date_col, 120)——120是 ODBC 规范格式,输出形如'2023-01',性能更好 -
CONVERT(VARCHAR(7), date_col, 120)实际截取前7位,依赖CONVERT默认输出带年月日(如'2023-01-01'),所以截断安全 - 如果用
YEAR(date_col) * 100 + MONTH(date_col)得到数值型年月(如202301),虽可排序,但后续转展示格式还得再处理,不如字符串直观
分组后统计值为空时,要不要补0?
原始数据里某个月没记录,分组结果就不会出现那行 —— 这是 SQL 的默认行为,不是 bug。但报表常要求“所有年月都显示,无数据填0”。
- 没法靠单条
GROUP BY实现补0,必须构造一个完整年月序列(比如用递归 CTE 或临时表),再和业务表LEFT JOIN - 简单场景(如只查最近12个月)可用
VALUES构造:SELECT ym FROM (VALUES ('2023-01'), ('2023-02'), ...),再关联主表 - 真正麻烦的是动态范围(如从最早订单到当前月),这时补0逻辑就得交给应用层做,或者用数据库侧较重的日期生成逻辑,别硬塞进一个 SQL 里
实际写的时候,年月格式统一用字符串比数值稳妥,跨数据库迁移也少踩坑。补0需求一旦出现,就说明分组逻辑已超出基础聚合范畴,该考虑拆解步骤了。










