last_day函数不能直接用于分组统计“当月最后一天发生的订单”,正确做法是先用where date(order_time) = last_day(order_time)筛选,再按年月分组。

LAST_DAY函数能直接拿到每月最后一天,但不能直接用于分组统计
很多人看到 LAST_DAY 就想当然地写成 GROUP BY LAST_DAY(order_time),结果发现统计的不是“当月最后一天发生的订单”,而是把所有订单按其所属月份的最后一天归并——比如 2024-03-15 和 2024-03-31 的订单都会被映射到 '2024-03-31',再按这个值分组。这确实能分组,但逻辑错了:你要的是「订单时间恰好等于当月最后一天」的那些记录,不是「把整月订单都挪到月底来算」。
先筛选出订单时间 = 当月最后一天的记录,再聚合
核心是两步:先用 WHERE 过滤出真正发生在每月最后一天的订单,再按日期或月份聚合。关键判断条件是:order_time = LAST_DAY(order_time)。
常见错误写法:WHERE DAY(order_time) = DAY(LAST_DAY(order_time)) —— 这只比对了“是不是31号”,但2月可能只有28/29天,4月只有30天,会漏判。
正确做法:
- MySQL / Oracle / PostgreSQL(支持
LAST_DAY):用WHERE order_time = LAST_DAY(order_time) - 注意时区和字段类型:如果
order_time是DATETIME或带时分秒的TIMESTAMP,而LAST_DAY返回的是DATE(如'2024-03-31'),直接等值比较会失败('2024-03-31 14:22:03' != '2024-03-31') - 稳妥写法是截断时间部分:
WHERE DATE(order_time) = LAST_DAY(order_time)
按年月分组统计更实用,避免跨年同日混淆
单纯按 LAST_DAY(order_time) 分组,2023-12-31 和 2024-12-31 都会映射成 '2023-12-31' 和 '2024-12-31',没问题;但如果你只想看“每年12月的最后一天”,或需要补全空月份,就得显式提取年月。
推荐写法(MySQL 示例):
SELECT YEAR(order_time) AS y, MONTH(order_time) AS m, COUNT(*) AS cnt FROM orders WHERE DATE(order_time) = LAST_DAY(order_time) GROUP BY y, m ORDER BY y, m;
这样既明确又可读,也方便后续做同比(比如对比 2023-12 和 2024-12)。Oracle 用户可用 EXTRACT(YEAR FROM order_time) 和 EXTRACT(MONTH FROM order_time) 替代。
没有 LAST_DAY 的数据库(如 SQLite、旧版 SQL Server)得手动构造
SQLite 没有 LAST_DAY,SQL Server 2005 及更早版本也不支持。这时要靠日期运算推导月末:
- SQLite:
WHERE date(order_time, '+1 month', '-1 day')得到当月最后一天,再与date(order_time)比较 - SQL Server(2008+):
WHERE order_time = EOMONTH(order_time)(EOMONTH是替代方案) - SQL Server(2005):
WHERE order_time = DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEFROMPARTS(YEAR(order_time), MONTH(order_time), 1)))
这些写法容易出错的地方在于:没处理 NULL 值、忽略 order_time 为 0000-00-00 或非法日期、或者在 WHERE 中用了不可SARGable 的表达式导致索引失效(比如对字段套函数)。建议把 DATE(order_time) 单独建生成列索引(MySQL 5.7+ / PG 12+ 支持)。
最容易被忽略的是时间精度问题:哪怕你确认订单只在 23:59:59 下单,只要数据库存储精度是微秒级,LAST_DAY 返回的仍是午夜零点,而你的数据可能是 '2024-03-31 23:59:59.123',直接等值比较就失效。所以 DATE() 截断几乎是必选项,别省。










