用trunc和mod可高效计算工作日天数:先算总天数,再减周末天数(基于trunc(date,'iw')确保iso标准),最后校正起止日是否为周末;节假日须通过holidays表显式过滤,不可硬编码。

直接用 TRUNC 和 MOD 算出工作日天数,别写循环
Oracle 没有内置的“工作日差”函数,但用 TRUNC 和 MOD 组合就能避开游标或递归,避免性能崩盘。核心思路是:先算总天数,再减去周末天数(含跨周末的边界情况),最后手动校正起止日是否为周末。
常见错误是直接用 TO_CHAR(date, 'D') 判断星期几——它依赖 NLS_TERRITORY 设置,德国返回周日=1、美国可能周一=1,结果不可靠。必须用 TRUNC(date) - TRUNC(date, 'IW') 或固定偏移法。
-
TRUNC(date, 'IW')返回本周一(ISO 标准,稳定不随 NLS 变) - 用
(TRUNC(end_date) - TRUNC(start_date))得总自然日 - 用
FLOOR((TRUNC(end_date, 'IW') - TRUNC(start_date, 'IW')) / 7) * 2算完整周末天数(每个整周扣 2 天) - 再检查 start_date 和 end_date 所在周的周一到周五之间是否包含它们,用
LEAST/GREATEST处理跨周边界
处理节假日必须显式传入表,不能硬编码
业务系统里“工作日”必然包含法定假日,PL/SQL 无法自动识别国庆/春节。硬编码 CASE WHEN date IN (DATE'2024-01-28', ...) 会随时间失效,且难以维护。
正确做法是建一张 holidays 表(字段:hol_date DATE PRIMARY KEY),然后在计算逻辑里 LEFT JOIN 或用 NOT EXISTS 过滤:
SELECT COUNT(*) FROM ( SELECT TRUNC(start_date) + LEVEL - 1 AS dt FROM DUAL CONNECT BY TRUNC(start_date) + LEVEL - 1 <p>注意:<code>CONNECT BY</code> 在大数据区间(如跨年)会慢,仅适合小范围(</p><h3> <code>TO_CHAR(date, 'D')</code> 的坑:NLS 设置让结果飘忽不定</h3><p>很多人用 <code>TO_CHAR(my_date, 'D')</code> 判断周几,结果开发环境返回 1=周日,测试环境变成 1=周一,上线就错乱。根本原因是 <code>NLS_TERRITORY</code> 控制该格式符行为,而它常被会话级设置覆盖。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4440" title="Market Oracle"><img src="https://img.php.cn/upload/skill/000/000/081/179006049259421.jpg" alt="Market Oracle" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill4440" title="Market Oracle" class="overflowclass">Market Oracle</a> <p class="overflowclass">金融事件影响分析器 — 获取突发新闻,追踪金属/石油/加密货币/股票价格,预测短中长期市场连锁反应</p> </div> <a rel="nofollow" href="/xiazai/skill4440" title="Market Oracle" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div><p>安全写法只有两种:</p>
- 强制指定语言:
TO_CHAR(my_date, 'D', 'NLS_DATE_LANGUAGE=AMERICAN')(此时周日=1,周六=7) - 用 ISO 周计算:
TRUNC(my_date) - TRUNC(my_date, 'IW') + 1(返回 1=周一,7=周日,完全不受 NLS 影响)
如果业务要求“周一至周五为工作日”,必须统一用第二种,否则节假日脚本在不同数据库实例上跑出不同结果。
函数封装时务必声明 DETERMINISTIC
把工作日计算封装成函数(比如 workdays_between)后,若没加 DETERMINISTIC,Oracle 无法在函数索引、物化视图或查询重写中复用结果,性能损失明显。
但要注意:只要函数内部查了 holidays 表,就不能标 DETERMINISTIC——因为表数据会变。这时得拆成两层:
- 底层纯计算函数(只依赖输入日期,加
DETERMINISTIC) - 上层包装函数(JOIN holidays 表,不加该关键字)
否则优化器可能缓存过期结果,导致某天突然多算/少算 1 天,排查极难。










