SQL 怎么取每月最后一天?月末和最后一个工作日有什么区别?
面试怎么回答
我会先区分自然月末、业务最后工作日和财务周期。自然月用本月月初到下月月初定义范围,展示月末日期时再减一天;工作日则关联维护好的业务日历,不能只排除周六周日。时间戳筛选还要明确业务时区,测试闰年、跨年和最后一秒之后的记录,避免硬编码天数造成遗漏。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
财务说“月底出一份结算表”,开发马上写了一个月末日期函数。等到节假日前,需求却变成“最后一个工作日提前出”。同一句月底,实际上可能指两个不同的日期。
自然月末由日历决定,最后工作日由业务日历决定;过滤整月记录优先使用月初到下月初的左闭右开区间。
月末有三种常见含义
自然月末是某个月的最后一个自然日,二月可能是二十八日,也可能是二十九日。不能用统一的三十天跨度去替代月份。
最后工作日取决于地区、企业和结算安排。节假日以及调休工作日,未必符合“周一到周五”的简单规则。交易所日历、银行日历和公司考勤日历,也不能默认共用一套标识。
财务周期则可能并不从每月一号开始。若结算区间另有定义,应该使用周期表的开始与结束边界,而不是给自然月函数换一个名字。
这些是口径问题,不是找一个更长的日期表达式就能解决的问题。
先得到两个边界,再展示月末
以下示例使用 PostgreSQL 17,输入是明确的DATE,不涉及会话时区的隐含转换:
WITH bounds AS (
SELECT DATE_TRUNC('month', DATE '2024-02-12')::date AS month_start
)
SELECT month_start,
(month_start + INTERVAL '1 month')::date AS next_month_start,
(month_start + INTERVAL '1 month' - INTERVAL '1 day')::date
AS month_end
FROM bounds;
二〇二四年二月的结果应是二月一日、三月一日与二月二十九日。先对齐月初,再加一个月,表达的是月份边界,不是从任意日期开始猜下一月的对应日。
展示“月底是哪天”需要最后一天;筛选本月事件却更适合 event_time 大于等于月初、同时小于下月初。两者目的不同,不必强行共用一个截止值。

为什么不写到23:59:59?
如果时间戳有毫秒或微秒精度,最后一秒之后还可能存在合法记录。写成小于等于月底23:59:59,可能漏掉当天尾部的数据。
左闭右开区间不需要猜测字段最高精度,而且相邻月份在边界处不重不漏。前提是边界与数据字段使用兼容类型,并明确它们表示同一个时间基准。
跨时区业务还要先确定按哪个地区划分月份。如果事实以UTC时刻保存,应从业务当地的月初推导对应UTC边界,再进行比较;不要简单把每个自然日都看成固定二十四小时。夏令时地区的日期边界需要按实际时区规则计算。
最后工作日应该查日历
业务日历至少需要日期、日历标识、是否工作日与规则版本。按目标月份和日历标识过滤工作日,再取最大日期,才能得到对应业务的最后工作日。
如果日历没有覆盖未来日期,MAX返回空值应该触发待补齐,而不是默认回退到自然月末。若某个月本来就没有允许结算的日期,也要由业务定义处理方式。
日历发生补发或修订时,已经发布的结算日期是否跟随变化,需要留版本记录。工作日不是一个从星期几永远推导正确的自然属性。
一个典型场景:正常月份都过了,二月漏了数据
假设报表以月初加三十天作为结束点。早期测试只覆盖某个三十天月份,结果正好正确,开发便判断日期逻辑已经通用。
到了二月,查询把下月部分记录也纳入本月;另一个用月底午夜作为结束时间的任务,却少算了最后一天。两个问题叠加后,团队最初怀疑的是上游数据补发。
排查把每条异常记录的业务日期与筛选边界并排展示,才看到区间定义不一致。修正时统一月初与下月初,并增加闰年二月、十二月跨年、月末尾部时间戳和空日历的测试。
最后还要确认调度日期与业务统计日期并非一回事。下月一号运行上月报表,参数应指向上月业务范围;临时重跑某一天,也不应让系统当前日期悄悄改变统计月份。
资料与适用边界
机制以所链接版本为准;业务示例用于说明判断方法,不代表作者真实项目或已验证的生产性能。