Before, after, each row and table level triggers [Oracle]

本文详细介绍了Oracle数据库中触发器的不同类型及其工作原理,包括before/foreach row触发器如何在更新前改变新值,after/foreach row触发器为何不能修改新值,以及table level触发器的特点。通过具体的例子展示了各种触发器的行为差异。
The goal of this page is to demonstrate the most important differences between before and after triggers as well as the differences between for each row and table level triggers.

Before / for each row trigger

A before trigger is called before because it fires before the new values ( :new.field_name) are stored in the table. That means that the new value can be changed in the trigger.
create table t_update_before_each_row (
  txt varchar2(10)
);

create table log (
  txt varchar2(20)
);


create trigger update_before_each_row 
  before update on t_update_before_each_row
  for each row
begin

  :new.txt := upper(:new.txt);

  insert into log values ('old: ' || :old.txt);
  insert into log values ('new: ' || :new.txt);

end update_before_each_row;
/
insert into t_update_before_each_row values('one');
insert into t_update_before_each_row values('two');
insert into t_update_before_each_row values('three');
insert into t_update_before_each_row values('four');
Updating (that is: concatenating the value with itself) the rows containing two and three:
update t_update_before_each_row set txt = txt || txt 
 where substr(txt,1,1) = 't';
select * from t_update_before_each_row;
As can be seen by the output of the select statement, the trigger changed the values of the new values; they're in uppercase now:
one
TWOTWO
THREETHREE
four
The log displays the old and new values:
select * from log;
old: two
new: TWOTWO
old: three
new: THREETHREE
Cleaning up:
drop table t_update_before_each_row;
drop table log;

After / for each row trigger

In contrast to a before trigger, an after trigger does not allow to change :new.field_name because the value is, when the trigger fires, already written to the table.
If one tries to assign a value to :new.field_name, Oracle throws an ORA-04084: cannot change NEW values for this trigger type.
create table t_update_after_each_row (
  txt varchar2(10)
);

create table log (
  txt varchar2(20)
);


create trigger update_after_each_row 
  after update on t_update_after_each_row
  for each row
begin

  -- :new.txt := upper(:old.txt); -- ORA-04084: cannot change NEW values for this trigger type

  insert into log values ('old: ' || :old.txt);
  insert into log values ('new: ' || :new.txt);

end update_after_each_row;
/
insert into t_update_after_each_row values('one');
insert into t_update_after_each_row values('two');
insert into t_update_after_each_row values('three');
insert into t_update_after_each_row values('four');
update t_update_after_each_row set txt = txt || txt 
 where substr(txt,1,1) = 't';
select * from t_update_after_each_row;
one
twotwo
threethree
four
select * from log;
As the log table shows, it is possible to use :new and :old although it's not possible to assign something to :new.
old: two
new: twotwo
old: three
new: threethree
Cleaning up:
drop table t_update_after_each_row;
drop table log;

Table level trigger

A table level trigger is a trigger that doesn't fire for each row to be changed. Accordingly, it lacks the for each row. Consequently, both, the :new and :old are not permitted in the trigger's PL/SQL block, otherwise, an ORA-04082: NEW or OLD references not allowed in table level triggers is thrown.
create table t_update_before (
  txt varchar2(10)
);

create table log (
  txt varchar2(20)
);


create trigger update_before 
  before update on t_update_before
begin

  -- :new.txt := upper(:old.txt); -- ORA-04082

  insert into log values ('update trigger');

end update_before;
/
insert into t_update_before values('one');
insert into t_update_before values('two');
insert into t_update_before values('three');
insert into t_update_before values('four');
update t_update_before set txt = txt || txt 
 where substr(txt,1,1) = 't';
select * from t_update_before;
one
twotwo
threethree
four
select * from log;
Although two rows were updated, only one record is found in the log table:
select * from log;
update trigger
An update statement that doesn't update any row:
update t_update_before set txt = txt || txt 
 where txt = 'no update';
Still, the trigger fires...
select * from log;
... which results in another row found in the log table:
update trigger
update trigger
Cleaning up:
drop table t_update_before;
drop table log;

Order of execution

Oracle allows to create multiple triggers on the same table. The order of the execution of these triggers is undeterministic (or random, if you want this word) except that all before triggers fire before the after triggers.
[root@yfw ~]# cd /www/wwwroot/szrengjing.com [root@yfw szrengjing.com]# mysql -u szrengjing_com -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 93707 Server version: 5.7.42-log Source distribution Copyright (c) 2000, 2023, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> USE szrengjing_com; Database changed mysql> CREATE TABLE IF NOT EXISTS ecs_user_credit ( -> id INT AUTO_INCREMENT PRIMARY KEY, -> user_id INT NOT NULL UNIQUE, -> score INT DEFAULT 80, -> level VARCHAR(20) AS ( -> CASE -> WHEN score >= 95 THEN '★★★★★' -> WHEN score >= 85 THEN '★★★★☆' -> WHEN score >= 75 THEN '★★★☆☆' -> WHEN score >= 60 THEN '★★☆☆☆' -> ELSE '★☆☆☆☆' -> END -> ) STORED, -> updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> CREATE TABLE IF NOT EXISTS ecs_user_credit_log ( -> id BIGINT AUTO_INCREMENT PRIMARY KEY, -> user_id INT NOT NULL, -> old_score INT, -> new_score INT, -> change_value INT, -> reason VARCHAR(100), -> created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> DROP TRIGGER IF EXISTS tr_after_update_credit; Query OK, 0 rows affected, 1 warning (0.01 sec) mysql> DROP PROCEDURE IF EXISTS sp_add_credit; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> DELIMITER ;; mysql> CREATE TRIGGER tr_after_update_credit -> AFTER UPDATE ON ecs_user_credit -> FOR EACH ROW -> BEGIN -> INSERT INTO ecs_user_credit_log (user_id, old_score, new_score, change_value, reason) -> VALUES (NEW.user_id, OLD.score, NEW.score, NEW.score - OLD.score, '系统调整'); -> END;; Query OK, 0 rows affected (0.01 sec) mysql> CREATE PROCEDURE sp_add_credit( -> IN p_user_id INT, -> IN p_change INT, -> IN p_reason VARCHAR(100) -> ) -> BEGIN -> DECLARE current_score INT DEFAULT 80; -> -- 查询当前分数并加锁防止并发 -> SELECT score INTO current_score FROM ecs_user_credit WHERE user_id = p_user_id FOR UPDATE; -> -> -- 插入或更新分数 -> INSERT INTO ecs_user_credit (user_id, score) -> VALUES (p_user_id, current_score + p_change) -> ON DUPLICATE KEY UPDATE score = score + p_change; -> END;; Query OK, 0 rows affected (0.00 sec) mysql> DELIMITER ; mysql> SHOW TABLES LIKE 'ecs_user_credit%'; +---------------------------------------------+ | Tables_in_szrengjing_com (ecs_user_credit%) | +---------------------------------------------+ | ecs_user_credit | | ecs_user_credit_log | +---------------------------------------------+ 2 rows in set (0.00 sec) mysql> SHOW TRIGGERS LIKE 'ecs_user_credit'\G *************************** 1. row *************************** Trigger: tr_update_credit_level Event: UPDATE Table: ecs_user_credit Statement: BEGIN IF NEW.score >= 95 THEN SET NEW.level = '★★★★★'; ELSEIF NEW.score >= 85 THEN SET NEW.level = '★★★★☆'; ELSEIF NEW.score >= 75 THEN SET NEW.level = '★★★☆☆'; ELSEIF NEW.score >= 60 THEN SET NEW.level = '★★☆☆☆'; ELSE SET NEW.level = '★☆☆☆☆'; END IF; END Timing: BEFORE Created: 2025-11-09 05:25:20.26 sql_mode: NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION Definer: szrengjing_com@localhost character_set_client: utf8 collation_connection: utf8_general_ci Database Collation: utf8mb4_general_ci *************************** 2. row *************************** Trigger: tr_after_update_credit Event: UPDATE Table: ecs_user_credit Statement: BEGIN INSERT INTO ecs_user_credit_log (user_id, old_score, new_score, change_value, reason) VALUES (NEW.user_id, OLD.score, NEW.score, NEW.score - OLD.score, '系统调整'); END Timing: AFTER Created: 2025-11-09 06:19:33.39 sql_mode: NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION Definer: szrengjing_com@localhost character_set_client: utf8 collation_connection: utf8_general_ci Database Collation: utf8mb4_general_ci 2 rows in set (0.00 sec) mysql> SHOW PROCEDURE STATUS WHERE Name = 'sp_add_credit'\G *************************** 1. row *************************** Db: szrengjing_com Name: sp_add_credit Type: PROCEDURE Definer: szrengjing_com@localhost Modified: 2025-11-09 06:19:48 Created: 2025-11-09 06:19:48 Security_type: DEFINER Comment: character_set_client: utf8 collation_connection: utf8_general_ci Database Collation: utf8mb4_general_ci 1 row in set (0.00 sec) mysql> CALL sp_add_credit(1, 5, '手动测试加分'); Query OK, 2 rows affected (0.01 sec) mysql> SELECT * FROM ecs_user_credit_log ORDER BY id DESC LIMIT 1\G *************************** 1. row *************************** id: 4 user_id: 1 old_score: 90 new_score: 95 change_value: 5 reason: 系统调整 created_at: 2025-11-09 06:21:06 1 row in set (0.00 sec) mysql>
最新发布
11-10
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值