month函数返回1–12的整数,需结合年份分组并避免在where中对month()计算以保障索引生效。

MONTH函数返回的是数字,不是月份名称
SQL 的 MONTH() 函数只返回 1–12 的整数,不带前导零,也不返回“Jan”“February”这类字符串。如果直接用它分组,结果里显示的是 1、2、3……容易让人困惑,尤其在报表或导出时。需要搭配 CASE 或日期格式化函数(如 FORMAT()、DATE_FORMAT())才能可读。
- MySQL 用户优先用
DATE_FORMAT(order_date, '%Y-%m')同时提取年月,避免跨年数据混在一起 - SQL Server 用户可用
FORMAT(order_date, 'yyyy-MM')(2012+),但注意性能开销;更轻量的写法是CONCAT(YEAR(order_date), '-', RIGHT('0' + CAST(MONTH(order_date) AS VARCHAR(2)), 2)) - PostgreSQL 没有
MONTH(),得用EXTRACT(MONTH FROM order_date),且必须配合EXTRACT(YEAR FROM order_date)才能正确按自然月分组
只用MONTH()分组会导致跨年数据合并
这是最常踩的坑:写成 GROUP BY MONTH(order_date),结果把 2023-05 和 2024-05 的销售额全加到“5”里,完全失去时间维度意义。
- 正确做法一定是组合年份和月份,例如:
GROUP BY YEAR(order_date), MONTH(order_date)(SQL Server) - MySQL 更推荐:
GROUP BY DATE_FORMAT(order_date, '%Y-%m'),语义清晰且索引友好(如果order_date有索引) - 若需排序,别只写
ORDER BY MONTH(order_date),应写ORDER BY MIN(order_date)或ORDER BY YEAR(), MONTH()
WHERE条件里别对MONTH()字段做计算
比如写 WHERE MONTH(order_date) = 3 看似没问题,但会阻止数据库使用 order_date 上的索引(因为对列施加了函数)。执行计划里常看到 Index Scan 变成 Index Seek 失效。
- 替代方案:用范围查询代替,例如查 3 月数据 →
WHERE order_date >= '2024-03-01' AND order_date - 如果日期字段含时间(datetime/timestamp),一定要用左闭右开区间,避免漏掉
'2024-03-31 23:59:59.999'这类边界值 - 动态生成时,可用
DATEFROMPARTS(2024, 3, 1)(SQL Server)或STR_TO_DATE('2024-03', '%Y-%m')(MySQL)构造起始日
聚合后补全缺失月份要用LEFT JOIN或递归CTE
原始销售表里某个月没订单,GROUP BY 就不会输出那行——但业务常要求“显示所有月份,空值为 0”。这时不能靠 MONTH() 解决,得额外生成月份序列。
- SQL Server:用
VALUES (1),(2),...,(12)或递归 CTE 生成 1–12,再LEFT JOIN销售结果 - MySQL 8.0+:可用
WITH RECURSIVE months AS (...)构造月份列表 - 更通用的做法是建一张最小化的
dim_month维度表(含month_num,month_name,quarter),JOIN 一次,复用性高
MONTH() 很少单独出现;它只是拼图的一小块,真正关键的是怎么把年、月、索引、空值处理串成一条可靠链路。漏掉其中一环,报表就可能对不上数。










