正确方法是用date_sub按月粒度计算上月首尾日:上月1号为date_sub(date_sub(curdate(), interval dayofmonth(curdate()) day), interval 1 month),上月最后一天为last_day(date_sub(curdate(), interval 1 month))。

MySQL中用DATE_SUB获取上个月数据
MySQL没有直接的“上个月”语法糖,得靠DATE_SUB配合INTERVAL手动推算。关键不是减30天,而是按月粒度减——否则跨月时(比如1月31日减30天会落到12月),结果就错乱了。
常见错误是写成WHERE date_col >= DATE_SUB(CURDATE(), INTERVAL 30 DAY),这实际取的是“最近30天”,不是“上个月整月”。正确做法是先定位上个月的第一天和最后一天:
-
DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) DAY), INTERVAL 1 MONTH)→ 上月1号 -
LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))→ 上月最后一天
所以完整查询通常是:
SELECT * FROM orders
WHERE order_date >= DATE_SUB(DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) DAY), INTERVAL 1 MONTH)
AND order_date <h3>PostgreSQL里用GENERATE_SERIES或日期运算</h3><p>PostgreSQL更直观,<code>current_date - INTERVAL '1 month'</code>能直接按月回退,但要注意它不自动对齐到月初/月末。想取上个月整月数据,推荐用<code>date_trunc('month', current_date) - INTERVAL '1 month'</code>得到上月1号,再加<code>INTERVAL '1 month' - INTERVAL '1 day'</code>得到上月最后一天。</p><p>容易踩的坑:直接用<code>current_date - INTERVAL '1 month'</code>在1月31日执行,会返回2024-01-31,而2024年2月只有29天,PostgreSQL会自动转成2024-02-29——这不是你想要的“上个月范围”。</p><p>稳妥写法:</p><pre class="brush:php;toolbar:false;">SELECT * FROM sales
WHERE sale_date >= date_trunc('month', current_date) - INTERVAL '1 month'
AND sale_date <p>注意这里用<code>而不是<code>,因为<code>date_trunc('month', current_date)</code>是本月1号,这样能干净覆盖整个上月。</code></code></p><h3>SQL Server用DATEFROMPARTS或EOMONTH</h3><p>SQL Server 2012+ 推荐用<code>EOMONTH</code>函数,它专为月末计算设计,比拼接年月字符串靠谱得多。上个月最后一天是<code>EOMONTH(GETDATE(), -1)</code>,上个月第一天就是<code>DATEFROMPARTS(YEAR(DATEADD(MONTH, -1, GETDATE())), MONTH(DATEADD(MONTH, -1, GETDATE())), 1)</code>。</p><p>更简洁的写法是结合<code>DATEADD</code>和<code>DAY</code>:</p>
- 上月1号:
DATEADD(DAY, 1 - DAY(GETDATE()), DATEADD(MONTH, -1, GETDATE())) - 上月最后一天:
EOMONTH(GETDATE(), -1)
示例:
SELECT * FROM logs WHERE log_time >= DATEADD(DAY, 1 - DAY(GETDATE()), DATEADD(MONTH, -1, GETDATE())) AND log_time <p>注意<code>EOMONTH</code>在SQL Server 2012才引入,老版本得用<code>DATEADD</code>嵌套模拟,逻辑更绕。</p><h3>跨数据库兼容写法的现实约束</h3><p>真要写“一次写、多库跑”的SQL几乎不可能——各数据库对日期截断、月末处理、时区行为差异太大。比如<code>DATE_TRUNC</code>在PostgreSQL可用,在MySQL 8.0+才支持类似函数,SQL Server压根没这玩意儿。</p><p>实际项目中更可行的策略是:在应用层生成起止日期(如用Python的<code>datetime.replace(day=1) - timedelta(days=1)</code>算上月范围),再作为参数传入SQL。这样既避开方言差异,又避免SQL里一堆嵌套函数影响可读性和索引下推。</p><p>最容易被忽略的一点:所有这些计算都默认基于服务器本地时区。如果业务涉及多地时间,必须确认<code>CURDATE()</code>、<code>GETDATE()</code>、<code>current_date</code>是否已按需转换为统一时区(比如UTC),否则凌晨时段查出来的“上个月”可能漏掉或重复数据。</p>










