最近要做一個日志記錄功能,記錄一些表的操作記錄,我的第一想法就是使用觸發器來實現。雖然想到了觸發器,但是自己還真沒有辦法立刻動手寫出觸發器,對於觸發器的語法都有很多地方不熟悉,甚至不理解。借著這次機會,好好的把觸發器又溫習了一篇,所以總結成文,以便後期查閱。
觸發器是存儲在數據庫服務器中的程序單元,當一個表或一個視圖被改變,或者數據庫發生某些事件時,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:JellyThinknew 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
觸發器的知識點還是蠻多的,而我這裡總結的只是一些基本知識(皮毛),如果你想更全面的去了解和學習觸發器,推薦這篇文章。
很多人都說觸發器有很多缺點,為什麼呢?看這裡的討論。畢竟觸發器是在背後偷偷執行的,很多時候,我們去維護別的系統時,很多時候會被偷偷觸發的觸發器而搞暈;所以,你以後在使用觸發器的時候,請三思。還是那句話,存在即合理。