最近要做一个日志记录功能,记录一些表的操作记录,我的第一想法就是使用触发器来实现。虽然想到了触发器,但是自己还真没有办法立刻动手写出触发器,对于触发器的语法都有很多地方不熟悉,甚至不理解。借着这次机会,好好的把触发器又温习了一篇,所以总结成文,以便后期查阅。
触发器是存储在数据库服务器中的程序单元,当一个表或一个视图被改变,或者数据库发生某些事件时,Oracle会自动触发触发器,并执行触发器中的代码。只有在触发器中定义的事件发生时,触发器才被触发。触发器是自动执行的代码块,和存储过程的区别在于,用户可以直接调用存储过程,而不能直接调用触发器。
能够触发触发器的事件有以下几种:
上述的这些语句都可以触发触发器。如果你想在这些事件发生时干些别的事情,这个时候只需要定义对应的触发器即可,在触发器中完成你的工作。
一个触发器由三部分组成:
触发器是基于表、视图、模式、数据库的,于此,我们可以把触发器分为下面的几类:
BEFORE
和AFTER
触发器BEFORE
表示在触发语句运行前先运行“触发动作”。AFTER
表示触发语句运行之后才运行“触发动作”。BEFORE
和AFTER
适用于行级触发器和语句级触发器。触发语句必须是DML。如果触发语句没有影响任何一行数据,并且也没有指定BEFORE STATEMENT和AFTER STATEMENT两个时间点,则触发器也不会被触发。
INSTEAD OF
触发器在触发器中,最重要的是触发动作部分,实际完成工作的部分也就是触发动作。触发动作是一个PL/SQL块或者一个对子程序(存储过程和函数)的调用。
create trigger tri_p
before insert or update of id on tb_student
for each row
when (new.id <> '00813027')
call check_id(:new.id)
上面的代码创建了名为tri_p
的触发器,它的主体是子程序调用,调用存储过程check_id
。上面的代码是调用的子程序,如果触发主体不是一个子程序调用,而是一个PL/SQL块,则需要对PL/SQL块进行编码。进行PL/SQL编码时,需要注意以下几个方面的东西。
同时,需要注意的是,INSERT没有旧值,只有新值。DELETE没有新值,只有旧值。:new和:old只用于行级触发器。比如以下的代码:
create or replace trigger tri_tb_student
after update on jelly.tb_student
for each row
begin
dbms_output.put_line('old value:' || :old.name);
dbms_output.put_line('new value:' || :new.name);
end;
我运行语句:
update jelly.tb_student set name='Jelly' where id='00813017';
就会输出:
old value:JellyThink
new value:Jelly
INSERTING
、 DELETING
、 UPDATING
),用于判断触发触发器的是INSERT
、UPDATE
还是DELETE
操作。
create or replace trigger dml_trg
after insert or update or delete on jelly.tb_student
begin
if updating then
dbms_output.put_line('updating data from tb_student');
elsif deleting then
dbms_output.put_line('deleting data from tb_student');
elsif inserting then
dbms_output.put_line('inserting data into tb_student');
end if;
end;
上面对触发器进行了简单的分类,并总结了编写触发主体时需要注意的事项,下面就对不同类型的触发器进行实际的操作。
使用CREATE TRIGGER命令创建触发器。用户可以在自己的模式下创建触发器,但是必须具有CREATE TRIGGER权限。用户也可以在其他模式下创建触发器,但是必须具有系统权限CREATE ANY TRIGGER。用户也可以创建系统级的触发器,但是必须具有ADMINISTER DATABASE TRIGGER权限。创建触发器的常规语法如下:
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
[OR {INSERT | DELETE | UPDATE [OF column [, column …]]}...]
ON [schema.]table_name | [schema.]view_name
[REFERENCING {OLD [AS] old | NEW [AS] new| PARENT as parent}]
[FOR EACH ROW ]
[WHEN condition]
PL/SQL_BLOCK | CALL procedure_name;
下面就来进行具体的代码编写:
create or replace trigger row_trg
before update of name
on jelly.tb_student
for each row
begin
dbms_output.put_line('ID:' || :old.id || '=>' || :new.id);
dbms_output.put_line('Name:' || :old.name || '=>' || :new.name);
dbms_output.put_line('Sex:' || :old.sex || '=>' || :new.sex);
dbms_output.put_line('Age:' || :old.age || '=>' || :new.age);
end;
重点是for each row
。
create or replace trigger sentence_trg
before delete or insert
on jelly.tb_student
begin
dbms_output.put_line('Called Once');
end;
没有使用for each row
,表示创建的语句级触发。当我运行以下语句:
insert into jelly.tb_student select * from jelly.tb_student;
此时,这个触发器将只输出一次Called Once
。
参见上面的行级触发器和语句级触发器,都是使用的BEFORE
触发器。
create or replace trigger before_trg
after delete
on jelly.tb_student
begin
dbms_output.put_line('After Trigger');
end;
和BEFORE
触发器基本一样,就是将BEFORE
换成AFTER
了。
create or replace trigger compound_trigger
for update of name on jelly.tb_student compound trigger
info1 constant varchar2(200) := 'Before Statement';
info2 constant varchar2(200) := 'Before Each Row';
info3 constant varchar2(200) := 'After Each Row';
info4 constant varchar2(200) := 'After Statement';
before statement is
begin
dbms_output.put_line(info1);
end before statement;
before each row is
begin
dbms_output.put_line(info2);
end before each row;
after each row is
begin
dbms_output.put_line(info3);
end after each row;
after statement is
begin
dbms_output.put_line(info4);
end after statement;
end
我们可以对触发器进行禁用和启用管理;如果实在不想要这个触发器了,也可以把这个触发器直接干掉。
操作 | SQL语句 |
---|---|
禁用触发器 | alter trigger before_trg disable |
启用触发器 | alter trigger before_trg enable |
删除触发器 | drop trigger before_trg |
触发器的知识点还是蛮多的,而我这里总结的只是一些基本知识(皮毛),如果你想更全面的去了解和学习触发器,推荐这篇文章。
很多人都说触发器有很多缺点,为什么呢?看这里的讨论。毕竟触发器是在背后偷偷执行的,很多时候,我们去维护别的系统时,很多时候会被偷偷触发的触发器而搞晕;所以,你以后在使用触发器的时候,请三思。还是那句话,存在即合理。
2015年7月29日 于呼和浩特。