必须用 BEFORE UPDATE(或 BEFORE INSERT/BEFORE DELETE)行级触发器配合 RAISE_APPLICATION_ERROR 才能真正拦截非工作时间变更;AFTER 触发器无法阻止已提交修改,语句级触发器无法访问 :NEW/:OLD 且难扩展;跨零点时段判断须用 OR 而非 BETWEEN,因 BETWEEN 要求左值≤右值。

必须用 BEFORE UPDATE(或 BEFORE INSERT/BEFORE DELETE)行级触发器,配合 RAISE_APPLICATION_ERROR 报错,才能真正拦住非工作时间的数据变更。其他时机或写法都无效。
为什么只能用 BEFORE + FOR EACH ROW?
Oracle 的 AFTER UPDATE 触发器无法阻止数据写入——行已经改完了,报错只相当于“事后喊一声”,事务照样提交。而语句级触发器(没写 FOR EACH ROW)拿不到 :NEW 和 :OLD,没法做字段级校验;虽然纯时间判断可以不用这些变量,但一旦后续要扩展逻辑(比如只限制某几列修改),就得重写。所以直接从一开始就声明 FOR EACH ROW,省去重构成本。
跨零点时段判断必须用 OR,不能用 BETWEEN
当前时间是 2026年6月22日23:02,属于夜间禁写区间(22:00–06:00)。如果写成 EXTRACT(HOUR FROM SYSDATE) BETWEEN 22 AND 6,永远为假——因为 Oracle 的 BETWEEN a AND b 要求 a ≤ b,而 22 > 6。正确写法只有:
EXTRACT(HOUR FROM SYSDATE) >= 22 OR EXTRACT(HOUR FROM SYSDATE)- 若需精确到分钟(如 22:30 开始禁写):
(EXTRACT(HOUR FROM SYSDATE) = 22 AND EXTRACT(MINUTE FROM SYSDATE) >= 30) OR EXTRACT(HOUR FROM SYSDATE) > 22 OR EXTRACT(HOUR FROM SYSDATE)
注意:别用 TO_CHAR(SYSDATE, 'HH24') 后转数字,虽可行,但 EXTRACT 更明确、不易受 NLS 设置影响。
RAISE_APPLICATION_ERROR 会自动回滚当前行,但不负责外层事务
在 BEFORE UPDATE FOR EACH ROW 中执行 RAISE_APPLICATION_ERROR(-20001, '非业务时间禁止修改'),Oracle 会立刻终止该行的更新,并回滚这一行的变更。但它不会自动 ROLLBACK 整个事务——如果应用层用 PL/SQL 块包了一堆 UPDATE,其中某条触发报错,其余成功的语句仍留在事务里。所以关键不是“触发器里要不要写 ROLLBACK”,而是应用代码必须捕获 ORA-20001 类错误并主动处理事务边界。
周末+工作日时段要分开判断,且注意语言环境
判断星期几不能直接写 TO_CHAR(SYSDATE, 'DAY') 然后比 '星期六'——这依赖数据库的 NLS_DATE_LANGUAGE 设置。稳妥做法是:
- 用
TO_CHAR(SYSDATE, 'D', 'NLS_DATE_LANGUAGE=AMERICAN')得到 1(周日)~7(周六),再判断是否为 1 或 7 - 或更简单:用
TO_CHAR(SYSDATE, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN')得到'SUN'/'SAT',避免中文歧义 - 组合条件示例:
TO_CHAR(SYSDATE, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN') IN ('SAT', 'SUN') OR EXTRACT(HOUR FROM SYSDATE) >= 22 OR EXTRACT(HOUR FROM SYSDATE)
现在是周一 23:02,刚过 22:00,正好卡在禁写起始点——这种边界时刻最容易暴露判断逻辑漏洞。


















