SQL Server用DATEDIFF+DATEPART算工作日不准确,因未排除节假日且DATEPART(dw)受SET DATEFIRST影响;PostgreSQL用generate_series枚举日期并过滤最可靠,MySQL 8.0+需用递归CTE模拟。

SQL Server 用 DATEDIFF + DATEPART 算工作日不准确,为什么?
直接用 DATEDIFF 计算两个日期差,再减去周末天数,会漏掉节假日和跨周边界问题。比如 2024-01-01(周一)到 2024-01-07(周日),看似 7 天含 2 个周末日,但若中间有元旦假期(已休),实际工作日可能只有 4 天。SQL Server 原生不维护节假日表,DATEPART(dw, ...) 还受 SET DATEFIRST 影响——默认周日为 1,但有些环境设成周一为 1,结果翻车。
PostgreSQL 中用 generate_series 枚举日期再过滤最可靠
核心思路:把起止日期之间每一天生成出来,逐行判断是否为工作日(周一至周五)且不在节假日表中。需要一张 holidays 表(字段:holiday_date DATE PRIMARY KEY)。
SELECT COUNT(*) AS workdays
FROM generate_series('2024-01-01'::DATE, '2024-01-15'::DATE, '1 day') AS d(dt)
WHERE EXTRACT(ISODOW FROM dt) BETWEEN 1 AND 5
AND dt NOT IN (SELECT holiday_date FROM holidays);
-
EXTRACT(ISODOW FROM ...)固定返回 ISO 标准:周一=1,周日=0,不受 locale 影响 - 必须用
NOT IN而非!= ANY(...),否则节假日表含NULL会导致整条 WHERE 失效 - 如果数据量大(跨度 > 3 个月),建议给
holidays(holiday_date)加索引
MySQL 8.0+ 只能靠递归 CTE 模拟日期序列
MySQL 不支持 generate_series,但 8.0+ 支持递归 CTE,可安全生成日期列表。注意:必须显式限制递归深度,否则可能超时或爆内存。
WITH RECURSIVE date_range AS ( SELECT '2024-01-01' AS dt UNION ALL SELECT dt + INTERVAL 1 DAY FROM date_range WHERE dt < '2024-01-15' ) SELECT COUNT(*) AS workdays FROM date_range WHERE WEEKDAY(dt) BETWEEN 0 AND 4 AND dt NOT IN (SELECT holiday_date FROM holidays);
-
WEEKDAY()返回 0=周一,6=周日,比DAYOFWEEK()更符合直觉 - CTE 中的
WHERE dt 是硬性终止条件,缺它会无限递归 - 若节假日表为空,
NOT IN仍会返回空结果集——务必确保节假日表存在且可读
所有方案都绕不开“节假日怎么管”这个现实问题
没有统一节假日表,再准的 SQL 也只是算出“周一到周五的天数”,不是真实工作日。建议:
- 把节假日表建在业务库中,每年初由 DBA 或运维脚本导入国家日历
- 避免用视图封装“工作日计算逻辑”,因为不同业务对“工作日”定义不同(例如有的要排除调休日,有的要包含特定加班日)
- 如果只是临时查一两次,宁可用应用层(Python/Java)调用成熟日历库(如 Python 的
workalendar),SQL 层只做基础过滤
真正难的从来不是写对一行 SQL,而是让“工作日”这个业务概念,在数据库里有唯一、可维护、可审计的落地形式。

















