SQL Server计算工作日需自建节假日表并批量处理:先建dbo.Holidays表存假期及调休标记,再用CTE生成日期序列并左连接该表,结合SET DATEFIRST统一周末判断,避免逐行查询和硬编码。

SQL Server里用函数计算工作日,必须自己建表存节假日
SQL Server没有内置的「排除节假日」工作日计算函数,DATEPART 或 DATEDIFF 都不认自定义假期。你得先建一张节假日表,比如 dbo.Holidays,字段至少包含 holiday_date DATE 和 is_workday BIT(用来标记调休补班日)。别试图用硬编码的 CASE WHEN 去列春节、国庆——每年日期变,维护成本爆炸。
写标量函数时,避免在循环里查表导致性能崩盘
很多人第一反应是写个 WHILE 循环逐天判断,再对每条日期去 SELECT COUNT(*) FROM Holidays。这在输入跨度大(比如5年)时,可能触发上万次单行查询,执行几秒甚至超时。正确做法是:一次性把范围内的所有日期生成出来(用 CTE 或数字表),再左连接节假日表做批量标记。示例核心逻辑:
WITH dates AS ( SELECT DATEADD(DAY, n.n, @start_date) AS dt FROM dbo.Numbers n WHERE n.n BETWEEN 0 AND DATEDIFF(DAY, @start_date, @end_date) ) SELECT COUNT(*) FROM dates d LEFT JOIN dbo.Holidays h ON d.dt = h.holiday_date WHERE DATEPART(WEEKDAY, d.dt) NOT IN (1,7) -- 周日=1,周六=7(取决于SET DATEFIRST) AND (h.holiday_date IS NULL OR h.is_workday = 1);
注意 SET DATEFIRST 对 weekday 判断的影响
SQL Server 的 DATEPART(WEEKDAY, ...) 返回值依赖会话级设置 DATEFIRST。默认是7(周日为第一天),但有些环境设成1(周一为第一天),这时 DATEPART(WEEKDAY, '2024-06-01')(周六)会返回6或7,直接写 NOT IN (1,7) 就会漏判或误判。稳妥做法是显式重置或用 DATEPART(WEEKDAY, ...)%7 归一化,更推荐前者:
SET DATEFIRST 1; —— 放在函数开头,确保周一=1、周日=7,后续用 NOT IN (6,7) 判断周末
或者不用依赖设置,改用:
AND (DATEPART(WEEKDAY, d.dt) + @@DATEFIRST - 2) % 7 NOT IN (5,6) —— 这种写法绕过配置,但可读性差,调试困难
PostgreSQL 或 MySQL 用户别照搬,语法和函数名全不同
如果你实际用的是 PostgreSQL,generate_series() 替代 CTE 生成日期序列,EXTRACT(ISODOW FROM ...) 返回 0–6(周一=1),周末是 0 和 6;MySQL 8.0+ 要用 CTE + RECURSIVE,且没有 DATEPART,得用 WEEKDAY()(周一=0)或 DAYOFWEEK()(周日=1)。函数名、参数顺序、空值处理逻辑(比如 LEFT JOIN 后 h.holiday_date IS NULL 在不同引擎里对 NULL 的判断一致性)都得单独验证。别在 SQL Server 写完就直接扔到别的库跑。

















