上月第一天为DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY), INTERVAL 1 MONTH),最后一天为LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH));前者先归整到本月首日再减一月,后者直接获取上月月末,二者均安全兼容跨年与闰年。

MySQL里用 DATE_SUB 和 LAST_DAY 算上月首尾日期最稳
直接给结论:上个月第一天是 DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY), INTERVAL 1 MONTH),最后一天是 LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))。别用 DATE_FORMAT(NOW(), '%Y-%m-01') 减一个月,它在 1 月会崩成 0000 年。
为什么 DATE_SUB(CURDATE(), INTERVAL 1 MONTH) 不能直接当上月第一天用
因为 DATE_SUB(CURDATE(), INTERVAL 1 MONTH) 只是“往前推 30 多天”,不是“切到上月月初”。比如今天是 '2024-03-31',减一个月得 '2024-02-29'(闰年),再套 DATE_FORMAT(..., '%Y-%m-01') 是 '2024-02-01'——这看似对,但换成 '2024-01-31' 就变成 '2023-12-31',再格式化还是 '2023-12-01',没问题;可一旦输入是 '2024-03-30',减一月是 '2024-02-28',格式化后仍是 '2024-02-01',结果没错但逻辑不可靠——你依赖的是“日期自动归整”,而 MySQL 不保证这种归整行为在所有版本/时区下一致。
真正安全的做法是:先归整到本月第一天,再减一月。
-
DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY)→ 得到本月第一天(如'2024-03-01') - 再套一层
DATE_SUB(..., INTERVAL 1 MONTH)→ 稳定得到上月第一天('2024-02-01')
LAST_DAY 是唯一靠谱的“找月末”函数
别手写 DATE_ADD(..., INTERVAL 1 MONTH) 再减一天,也别硬算 28/30/31。MySQL 的 LAST_DAY 内置处理闰年、大小月、跨年,输入任意日期都返回当月最后一天。
所以“上月最后一天”就是:LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
常见错误:
- 写成
LAST_DAY(CURDATE()) - INTERVAL 1 MONTH→ 先取本月末('2024-03-31'),再减一月得'2024-02-31',MySQL 会自动转成'2024-03-02',彻底错乱 - 用
MAKEDATE(YEAR(CURDATE()), 365)类推 → 忽略闰年,且和“上月”完全无关
实际查询中怎么嵌入这两个值
在 WHERE 条件里直接用,别先查再拼字符串。例如查上月订单:
SELECT * FROM orders WHERE order_date >= DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY), INTERVAL 1 MONTH) AND order_date <p>注意两点:</p>
- 边界用
>=和,别漏掉最后一秒(如果字段含时间) - 如果
order_date是DATETIME类型,且你想覆盖整月,更稳妥是写成:order_date (即本月第一天),配合 >= 上月第一天 —— 这样避免时区或微秒导致的边界遗漏
跨年场景(比如现在是 2024-01-15)完全不用额外判断,所有函数自动处理年份进退。
真正容易被忽略的是:在存储过程或视图里复用这些表达式时,务必加括号明确优先级,MySQL 对多层 DATE_SUB 的解析顺序很严格,少一个括号就可能先算错月份再取日。











