Oracle无内置工作日函数,需自定义实现;常用TO_CHAR(dt,'D')判断周末但受NLS_TERRITORY影响,推荐用TO_CHAR(dt,'DY','NLS_DATE_LANGUAGE=AMERICAN')确保稳定;基础函数按整周+余数分段计算工作日,节假日应外置表管理,调休日需额外字段标识。
Oracle里没有内置工作日计算函数,得自己写
oracle原生不提供类似 networkdays 的函数,sysdate 和日期算术只认自然日。想排除周六、周日(甚至法定节假日),必须靠自定义函数封装逻辑。直接在 sql 里用 to_char(date, 'd') 判断星期几最常用,但要注意 nls 设置影响 'd' 返回值(1=周日 or 1=周一),默认常是周日为 1,这点极易出错。
用 TO_CHAR(..., 'D') 判断周末要先确认 NLS_TERRITORY
不同地区设置下,TO_CHAR(dt, 'D') 中的 D 含义不同:美国环境返回 1(周日)~7(周六),而部分欧洲环境是 1(周一)~7(周日)。不查就硬写 WHERE TO_CHAR(dt, 'D') NOT IN ('1','7') 可能把周一当周末跳过。
- 查当前设置:
SELECT SYS_CONTEXT('USERENV', 'NLS_TERRITORY') FROM DUAL - 稳妥做法是改用
'DY'+NLS_DATE_LANGUAGE:TO_CHAR(dt, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN')返回'SUN'/'SAT',稳定且可读 - 若必须用
D,统一显式设置会话:ALTER SESSION SET NLS_TERRITORY = 'AMERICA'
基础工作日函数:从 start_date 到 end_date(含)之间的工作日数
常见需求是算两个日期间的工作日天数,不是简单循环判断——大数据量时性能差。推荐用整周数 + 剩余天数分段处理:
CREATE OR REPLACE FUNCTION count_workdays ( p_start DATE, p_end DATE ) RETURN NUMBER IS v_total_days NUMBER; v_weeks NUMBER; v_remainder NUMBER; v_start_dow NUMBER; -- 周几,按 AMERICA:1=Sun, 2=Mon...7=Sat v_end_dow NUMBER; v_workdays NUMBER := 0; BEGIN IF p_start > p_end THEN RETURN 0; END IF; <p>v_total_days := p_end - p_start; v_weeks := FLOOR(v_total_days / 7); v_remainder := MOD(v_total_days, 7);</p><p>v_workdays := v_weeks * 5; -- 每整周5个工作日</p><p>-- 算剩余天数中哪些是工作日(从 start_date 开始向后推 v_remainder 天) v_start_dow := TO_NUMBER(TO_CHAR(p_start, 'D', 'NLS_DATE_LANGUAGE=AMERICAN')); FOR i IN 0..v_remainder LOOP IF TO_NUMBER(TO_CHAR(p_start + i, 'D', 'NLS_DATE_LANGUAGE=AMERICAN')) NOT IN (1,7) THEN v_workdays := v_workdays + 1; END IF; END LOOP;</p><p>RETURN v_workdays; END;
注意:该函数不含节假日逻辑;p_start 和 p_end 都包含在计算范围内;MOD 对负数行为需留意,所以先做大小判断。
加节假日支持的关键是外置表,别硬编码进函数
把节假日写死在函数里(比如 IF dt IN (DATE '2024-01-01', ...))会导致每次调假都要重编译函数,无法热更新。正确方式是建一张标准节假日表:
CREATE TABLE holidays ( holiday_date DATE PRIMARY KEY, description VARCHAR2(100) );
然后在函数里用子查询或游标关联过滤。例如在上一版函数末尾加:
v_workdays := v_workdays
- (SELECT COUNT(*)
FROM holidays h
WHERE h.holiday_date BETWEEN p_start AND p_end
AND TO_CHAR(h.holiday_date, 'D', 'NLS_DATE_LANGUAGE=AMERICAN') NOT IN ('1','7'));这样增删假期只需维护表,函数本身不变。但注意:如果节假日表数据量大,又没在 holiday_date 上建索引,这个子查询会拖慢性能。
真正麻烦的是调休日——比如某周六被定为上班日。这种必须额外字段标记“是否实际工作日”,不能只靠日期或星期几推断。业务越复杂,越要靠数据驱动,而不是在函数里堆 if-else。


















