判断节假日需建表维护,字段含holiday_date和is_holiday;排除节假日再分组更高效;按周/月汇总时用LEFT JOIN+CASE标记工作日;补全日期需递归CTE生成序列后关联过滤。

怎么判断某天是不是节假日
核心是建一张节假日表,字段至少包含 holiday_date(DATE 类型)和 is_holiday(比如 1 表示法定假日,0 或 NULL 表示工作日)。别指望用函数自动识别——不同国家、年份、调休安排差异太大,硬编码或手动维护才是稳定做法。
常见错误是试图用 DAYOFWEEK() 或 WEEKDAY() 过滤周末就完事,但国庆调休后的工作日(比如 10 月 8 日星期六上班)会被误判为假日。必须依赖真实节假日数据源。
- 国内可用国务院每年发布的放假安排 CSV,导入成表
- 表建议加唯一索引:
UNIQUE KEY (holiday_date),避免重复插入 - 查询时用
LEFT JOIN关联,比子查询更易读且性能可控
如何在 GROUP BY 前排除节假日
关键不是“先分组再过滤”,而是“先筛掉节假日再分组”。直接在 WHERE 子句里排除掉节假日日期,比在 HAVING 里过滤更高效,也避免聚合后才发现数据无效。
假设你有订单表 orders,含 order_date 和 amount,节假日表叫 holidays:
SELECT DATE(order_date) AS day, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM orders o WHERE DATE(o.order_date) NOT IN ( SELECT holiday_date FROM holidays WHERE is_holiday = 1 ) GROUP BY DATE(order_date);
注意:NOT IN 对 NULL 敏感,如果 holidays.holiday_date 允许 NULL,改用 NOT EXISTS 更安全:
WHERE NOT EXISTS ( SELECT 1 FROM holidays h WHERE h.holiday_date = DATE(o.order_date) AND h.is_holiday = 1 )
按自然周/月汇总时怎么跳过节假日
这时候不能只靠 WHERE 排除单日,得把“工作日”作为维度参与分组。例如统计每周工作日订单量,需先生成一个工作日标记字段。
推荐方式:用 LEFT JOIN 把日期关联到节假日表,再用 CASE 标记是否为有效工作日:
SELECT YEARWEEK(order_date, 1) AS week_id, SUM(CASE WHEN h.holiday_date IS NULL THEN amount ELSE 0 END) AS workday_amount, COUNT(CASE WHEN h.holiday_date IS NULL THEN 1 END) AS workday_order_cnt FROM orders o LEFT JOIN holidays h ON DATE(o.order_date) = h.holiday_date AND h.is_holiday = 1 GROUP BY YEARWEEK(order_date, 1);
这里 YEARWEEK(..., 1) 表示周一为每周起点,和大多数企业考勤对齐。若用 WEEK() 默认周日开头,容易错位。
- 别用
DATE_SUB(order_date, INTERVAL WEEKDAY(order_date) DAY)算周一——它不考虑节假日,只是机械取周一 - 如果需要“最近 5 个工作日”的滚动统计,得用窗口函数 + 日期序列生成,不是单纯过滤
MySQL 8.0+ 用 CTE 生成日期序列再排除节假日
当原始表缺少连续日期(比如某天没订单),又想补全工作日统计(如每日销售趋势图),就得主动构造日期范围,再剔除节假日。
用递归 CTE 生成近 90 天日期,再 LEFT JOIN 排除:
WITH RECURSIVE date_range AS ( SELECT '2024-01-01' AS dt UNION ALL SELECT DATE_ADD(dt, INTERVAL 1 DAY) FROM date_range WHERE dt < '2024-03-31' ) SELECT d.dt AS business_day, COALESCE(SUM(o.amount), 0) AS daily_sales FROM date_range d LEFT JOIN holidays h ON d.dt = h.holiday_date AND h.is_holiday = 1 LEFT JOIN orders o ON DATE(o.order_date) = d.dt WHERE h.holiday_date IS NULL -- 只保留非假日 GROUP BY d.dt ORDER BY d.dt;
注意:RECURSIVE CTE 在 MySQL 中默认递归深度为 1000,90 天安全;超过要设 SET SESSION cte_max_recursion_depth = 5000;
这个逻辑看起来绕,但它是唯一能同时满足“补全日期”和“精准排除节假日”的通用解法。业务上常被忽略的是:调休日(如周六上班)没进节假日表,就会被当成空日漏掉——所以节假日表必须包含调休工作日标记,哪怕值为 is_holiday = 0。

















