应使用 CASE WHEN 结合自定义节假日表和调休标识区分工作日与节假日,避免依赖数据库周几函数差异及 NOT IN 空值陷阱,推荐预计算或维度表方案提升性能。

如何用 CASE WHEN 区分工作日和节假日统计
直接在 SELECT 或 GROUP BY 中用 CASE WHEN 判断日期类型是最常用、最可控的方式。关键不是依赖数据库内置函数自动识别节假日,而是自己定义规则——因为「节假日」没有全球统一标准,必须结合业务数据源(比如一张 holidays 表)或固定规则(如排除周末+硬编码法定假日)。
常见错误是试图用 EXTRACT(DOW FROM date)(PostgreSQL)或 WEEKDAY()(MySQL)只判断周一到周五,却忽略调休日(比如周日上班、周六放假),导致统计偏差。
- 工作日:通常指周一至周五 且 不在
holidays表中 - 节假日:包含法定假日 和 周末,但注意调休日要单独标记(不能仅靠星期几推断)
- 建议建一张
calendar_dim维度表,字段至少含date、is_workday(布尔)、holiday_name(可空),每日一行,维护成本远低于每次 SQL 里硬编码
PostgreSQL / MySQL 中判断星期几的陷阱
不同数据库对「周几」的定义不一致:EXTRACT(DOW FROM '2024-01-01'::DATE) 在 PostgreSQL 返回 1(周一为 1,周日为 0);而 MySQL 的 WEEKDAY('2024-01-01') 返回 0(周一为 0,周日为 6)。直接照搬示例代码会翻车。
更危险的是用 DAYOFWEEK()(MySQL)返回 1=周日、2=周一……这种偏移容易写反条件。不如统一转成布尔:
-- PostgreSQL 示例:安全写法
CASE
WHEN EXTRACT(ISODOW FROM order_date) IN (1,2,3,4,5)
AND order_date NOT IN (SELECT holiday_date FROM holidays)
THEN 'workday'
ELSE 'holiday'
END-
ISODOW更可靠:1=周一,7=周日,符合 ISO 标准 - 避免用
NOT IN匹配空值——如果holidays.holiday_date允许 NULL,整个条件会变 NULL,导致该行被归入 ELSE 分支。改用NOT EXISTS更稳妥 - MySQL 可用
WEEKDAY(date)(0–6)替代易混淆的DAYOFWEEK()
如何让分组统计结果包含「调休工作日」
真正的业务难点不在周末和法定假日,而在调休——比如 2024 年 2 月 10 日(周六)是春节假期,但 2 月 4 日(周日)被调整为工作日。这类数据无法通过函数计算,必须人工维护或对接 HR 系统接口。
- 最简方案:在
holidays表中加字段is_compensatory(是否调休日),值为true表示「这天本该休息但要上班」 - 统计时逻辑变成:若
is_compensatory = true→ 强制归为workday;若在holidays表中且is_compensatory = false→ 归为holiday - 别在 SQL 里用
OR堆砌调休日期(如date = '2024-02-04' OR date = '2024-02-18'),难维护、易遗漏、无法复用
性能问题:为什么不要在 WHERE 或 GROUP BY 里实时算工作日
在大表上对日期字段反复调用 EXTRACT、子查询查 holidays,会导致无法使用索引,全表扫描。尤其当订单表有千万级数据时,单次查询可能从秒级升到分钟级。
- 提前物化:每天凌晨跑一个 job,把当日的
is_workday结果写入订单表的扩展字段(如order_workday_flag),查询时直接GROUP BY order_workday_flag - 用分区表:按日期范围分区后,在每个分区里再过滤,比全表扫快得多
- 如果必须实时计算,确保
holidays(holiday_date)有 B-tree 索引,且子查询尽量简单(不要 JOIN 多张表)
调休规则每年更新,维度表字段设计要留余量;临时加个「调休日」比改十处 SQL 安全得多。

















