MONTH()返回1–12整数,YEAR()返回4位年份,均仅接受日期类型参数;传入字符串或NULL返回NULL;需避免隐式转换、索引失效及NULL导致统计偏差。

MONTH() 和 YEAR() 函数返回什么值?
MONTH() 返回 1–12 的整数,YEAR() 返回 4 位年份(如 2023),两者都只接受日期类型参数(DATE、DATETIME 或 TIMESTAMP)。传入字符串或 NULL 会返回 NULL,不报错但结果不可靠。
- 如果字段是
order_time VARCHAR存的 '2023-05-12 14:30:00',直接套MONTH(order_time)可能返回 NULL(取决于 MySQL 的隐式转换行为,不可依赖) - 正确做法是先用
STR_TO_DATE(order_time, '%Y-%m-%d %H:%i:%s')转换,或确保列类型本身就是DATETIME - PostgreSQL 没有
MONTH(),得用EXTRACT(MONTH FROM order_date);SQL Server 用MONTH(order_date),但注意它对datetime2同样有效
按年+月分组统计营收时,别只用 MONTH() 单独分组
只写 GROUP BY MONTH(order_date) 会把 2022 年 3 月和 2023 年 3 月合并成同一组,财务数据完全失真。
- 必须组合使用:
GROUP BY YEAR(order_date), MONTH(order_date) - 更稳妥写法是生成可排序的年月标识:
CONCAT(YEAR(order_date), '-', LPAD(MONTH(order_date), 2, '0')) AS ym,避免 2023-1 和 2023-10 排序错乱 - 如果只要某一年的数据,优先在
WHERE中过滤:WHERE order_date >= '2023-01-01' AND order_date < '2024-01-01',比YEAR(order_date) = 2023更可能走索引
WHERE 中用 YEAR()/MONTH() 会导致全表扫描
WHERE YEAR(order_date) = 2023 AND MONTH(order_date) = 5 看似直观,但数据库无法利用 order_date 上的索引——因为函数作用于字段,索引失效。
- 正确写法是范围查询:
WHERE order_date >= '2023-05-01' AND order_date < '2023-06-01' - 对于跨月统计(如“最近 6 个月”),用
DATE_SUB(NOW(), INTERVAL 6 MONTH)动态算下界,再配合范围条件 - 如果业务确实需要高频按年月查,考虑加生成列(MySQL 5.7+):
ALTER TABLE orders ADD COLUMN ym INT AS (YEAR(order_date)*100 + MONTH(order_date)) STORED,然后给ym加索引
NULL 日期或非法日期会让统计结果少几条记录
财务表里常有 payment_date 为 NULL(未回款)、或存了 '0000-00-00' 这类非法值。而 YEAR() 和 MONTH() 对 NULL 和非法日期一律返回 NULL,这些行会被 GROUP BY 自动归到 NULL 组,或者被 WHERE 条件直接过滤掉。
- 查漏:先运行
SELECT COUNT(*) FROM orders WHERE payment_date IS NULL OR payment_date = '0000-00-00' - 处理策略二选一:要么清洗数据(
UPDATE orders SET payment_date = NULL WHERE payment_date = '0000-00-00'),要么在统计 SQL 中显式包含 NULL:GROUP BY COALESCE(YEAR(payment_date), -1), COALESCE(MONTH(payment_date), -1),再单独解释 -1 组含义 - 别依赖
IFNULL(YEAR(payment_date), 0)做分组——0 年会和其他真实年份混淆
实际跑财务报表时,最常出问题的不是函数不会用,而是日期字段类型不对、隐式转换失败、索引没生效、以及 NULL 处理被忽略。这四点卡住,再漂亮的 GROUP BY 也出不来准确数字。

















