上个月第一天是date_sub(date_sub(curdate(), interval day(curdate())-1 day), interval 1 month),最后一天是last_day(date_sub(curdate(), interval 1 month));二者纯函数组合、跨年安全、索引友好,避免str_to_date拼接导致的1月崩坏和逻辑遗漏。

MySQL里用LAST_DAY()和DATE_SUB()算上个月的边界日期
直接给结论:上个月第一天是DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY), INTERVAL 1 MONTH),最后一天是LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))。这两个表达式不依赖日历表、不用拼字符串,纯函数组合,稳定且可索引友好(如果用在WHERE条件里配合日期字段)。
为什么不用STR_TO_DATE()拼接年月?
常见错误是先取YEAR(CURDATE())和MONTH(CURDATE())-1,再拼成'2024-02-01'——这在1月会崩:MONTH()返回1,减1得0,拼出'2024-0-01',STR_TO_DATE()返回NULL。更隐蔽的问题是跨年时手动处理逻辑易漏。用DATE_SUB()天然支持进位退位,1月减1个月自动变成去年12月。
-
DATE_SUB(CURDATE(), INTERVAL 1 MONTH)拿到本月任意一天的上个月同日(比如今天是2024-03-15 → 得到2024-02-15) -
LAST_DAY(...)直接取该月最后一天(2024-02-15 → 2024-02-29) - 算第一天:先用
DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY)回到本月1号,再减1个月
实际查询中怎么用?注意时区和函数执行时机
在SELECT里直接用没问题,但放进WHERE子句时要注意:如果字段是DATETIME类型,只用日期部分做范围查询,得写成WHERE date_col >= '2024-02-01' AND date_col (左闭右开),而不是<code>BETWEEN。因为BETWEEN '2024-02-01' AND '2024-02-29'会漏掉2月29日23:59:59之后的数据。
示例(查上个月全部订单):
SELECT * FROM orders WHERE order_time >= DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE())-1 DAY), INTERVAL 1 MONTH) AND order_time <p>这个写法保证了无论当月多少天、是否闰年、是否跨年,结果都精确到毫秒级。</p><h3>如果需要固定格式字符串输出怎么办?</h3><p>用<code>DATE_FORMAT()</code>包装,但仅限展示场景。例如<code>DATE_FORMAT(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)), '%Y-%m-%d')</code>输出<code>'2024-02-29'</code>。别在WHERE里套<code>DATE_FORMAT()</code>,它会让索引失效——数据库没法把函数结果反推回原始字段值。</p><p>真正容易被忽略的是:所有这些函数都基于服务器当前时区计算。如果应用部署在UTC时区,但业务要求按东八区算“上个月”,就得先用<code>CONVERT_TZ(NOW(), '+00:00', '+08:00')</code>对齐时区,再套日期函数。没对齐时区,1号凌晨的查询可能错判成上上个月。</p>











