觸發(fā)器使用教程和命名規(guī)范
屬性 |
內(nèi)容 |
文檔名稱: |
觸發(fā)器使用教程和命名規(guī)范 |
文檔版本號(hào): |
V1.0 |
文檔編寫(xiě)日期: |
2006-12-4 |
文檔狀態(tài): |
修訂稿 |
作者: |
李振國(guó) |
發(fā)布日期: |
2006-12-5 |
目 錄
1,觸發(fā)器簡(jiǎn)介
觸發(fā)器(Trigger)是數(shù)據(jù)庫(kù)對(duì)象的一種,編碼方式類似存儲(chǔ)過(guò)程,與某張表(Table)相關(guān)聯(lián),當(dāng)有DML語(yǔ)句對(duì)表進(jìn)行操作時(shí),可以引起觸發(fā)器的執(zhí)行,達(dá)到對(duì)插入記錄一致性,正確性和規(guī)范性控制的目的。在當(dāng)年C/S時(shí)代盛行的時(shí)候,由于客戶端直接連接數(shù)據(jù)庫(kù),能保證數(shù)據(jù)庫(kù)一致性的只有數(shù)據(jù)庫(kù)本身,此時(shí)主鍵(Primary Key),外鍵(Foreign Key),約束(Constraint)和觸發(fā)器成為必要的控制機(jī)制。而觸發(fā)器的實(shí)現(xiàn)比較靈活,可編程性強(qiáng),自然成為了最流行的控制機(jī)制。到了B/S時(shí)代,發(fā)展成4層架構(gòu),客戶端不再能直接訪問(wèn)數(shù)據(jù)庫(kù),只有中間件才可以訪問(wèn)數(shù)據(jù)庫(kù)。要控制數(shù)據(jù)庫(kù)的一致性,既可以在中間件里控制,也可以在數(shù)據(jù)庫(kù)端控制。很多的青睞Java的開(kāi)發(fā)者,隨之將數(shù)據(jù)庫(kù)當(dāng)成一個(gè)黑盒,把大多數(shù)的數(shù)據(jù)控制工作放在了Servlet中執(zhí)行。這樣做,不需要了解太多的數(shù)據(jù)庫(kù)知識(shí),也減少了數(shù)據(jù)庫(kù)編程的復(fù)雜性,但同時(shí)增加了Servlet編程的工作量。從架構(gòu)設(shè)計(jì)來(lái)看,中間件的功能是檢查業(yè)務(wù)正確性和執(zhí)行業(yè)務(wù)邏輯,如果把數(shù)據(jù)的一致性檢查放到中間件去做,需要在所有涉及到數(shù)據(jù)寫(xiě)入的地方進(jìn)行數(shù)據(jù)一致性檢查。由于數(shù)據(jù)庫(kù)訪問(wèn)相對(duì)于中間件來(lái)說(shuō)是遠(yuǎn)程調(diào)用,要編寫(xiě)統(tǒng)一的數(shù)據(jù)一致性檢查代碼并非易事,一般采用在多個(gè)地方的增加類似的檢查步驟。一旦一致性檢查過(guò)程發(fā)生調(diào)整,勢(shì)必導(dǎo)致多個(gè)地方的修改,不僅增加工作量,而且無(wú)法保證每個(gè)檢查步驟的正確性。觸發(fā)器的應(yīng)用,應(yīng)該放在關(guān)鍵的,多方發(fā)起的,高頻訪問(wèn)的數(shù)據(jù)表上,過(guò)多使用觸發(fā)器,會(huì)增加數(shù)據(jù)庫(kù)負(fù)擔(dān),降低數(shù)據(jù)庫(kù)性能。而放棄使用觸發(fā)器,則會(huì)導(dǎo)致系統(tǒng)架構(gòu)設(shè)計(jì)上的問(wèn)題,影響系統(tǒng)的穩(wěn)定性。
2,觸發(fā)器示例
觸發(fā)器代碼類似存儲(chǔ)過(guò)程,以PL/SQL腳本編寫(xiě)。下面是一個(gè)觸發(fā)器的示例:
新建員工工資表salary
create table SALARY
(
EMPLOYEE_ID NUMBER, --員工ID
MONTH VARCHAR2(6), --工資月份
AMOUNT NUMBER --工資金額
)
創(chuàng)建與salary關(guān)聯(lián)的觸發(fā)器salary_trg_rai
1 Create or replace trigger salary_trg_rai
2 After insert on salary
3 For each row
4 declare
5 Begin
6 Dbms_output.put_line(‘員工ID:’ || :new.employee_id);
7 Dbms_output.put_line(‘工資月份:’ || :new.month);
8 Dbms_output.put_line(‘工資:’ || :new.amount);
9 Dbms_output.put_line(‘觸發(fā)器已被執(zhí)行’);
10 End;
打開(kāi)一個(gè)SQL Window窗口(使用PL/SQL Developer工具),或在sqlplus中輸入:
Insert into salary(employee_id, month, amount) values(1, ‘200606’, 10000);
執(zhí)行后可以在sqlplus中,或在SQL Window窗口的Output中見(jiàn)到
員工ID:1
工資月份:200606
工資:10000
觸發(fā)器已執(zhí)行
在代碼的第一行,定義了數(shù)據(jù)庫(kù)對(duì)象的類型是trigger,定義觸發(fā)器的名稱是salary_trg_rai
第二行說(shuō)明了這是一個(gè)after觸發(fā)器,在DML操作實(shí)施之后執(zhí)行。緊接著的insert說(shuō)明了這是一個(gè)針對(duì)insert操作的觸發(fā)器,每個(gè)對(duì)該表進(jìn)行的insert操作都會(huì)執(zhí)行這個(gè)觸發(fā)器。
第三行說(shuō)明了這是一個(gè)針對(duì)行級(jí)的觸發(fā)器,當(dāng)插入的記錄有n條時(shí),在每一條插入操作時(shí)都會(huì)執(zhí)行該觸發(fā)器,總共執(zhí)行n次。
Declare后面跟的是本地變量定義部分,如果沒(méi)有本地變量定義,此部分可以為空
Begin和end括起來(lái)的代碼,是觸發(fā)器的執(zhí)行部分,一般會(huì)對(duì)插入記錄進(jìn)行一致性檢查,在本例中打印了插入的記錄和“觸發(fā)器已執(zhí)行”。
其中:new對(duì)象表示了插入的記錄,可以通過(guò):new.column_name來(lái)引用記錄的每個(gè)字段值
3,觸發(fā)器語(yǔ)法和功能
觸發(fā)器的語(yǔ)法如下
CREATE OR REPLACE TRIGGER trigger_name
<before | after | instead of> <insert | update | delete> ON table_name
[FOR EACH ROW]
WHEN (condition)
DECLARE
BEGIN
--觸發(fā)器代碼
END;
Trigger_name是觸發(fā)器的名稱。<before | after | instead of>可以選擇before或者after或instead of。Before表示在DML語(yǔ)句實(shí)施前執(zhí)行觸發(fā)器,而after表示在在dml語(yǔ)句實(shí)施之后執(zhí)行觸發(fā)器,instead of觸發(fā)器用在對(duì)視圖的更新上。<insert | update | delete>可以選擇一個(gè)或多個(gè)DML語(yǔ)句,如果選擇多個(gè),則用or分開(kāi),如:insert or update。Table_name是觸發(fā)器關(guān)聯(lián)的表名。
[FOR EACH ROW]為可選項(xiàng),如果注明了FOR EACH ROW,則說(shuō)明了該觸發(fā)器是一個(gè)行級(jí)的觸發(fā)器,DML語(yǔ)句處理每條記錄都會(huì)執(zhí)行觸發(fā)器;否則是一個(gè)語(yǔ)句級(jí)的觸發(fā)器,每個(gè)DML語(yǔ)句觸發(fā)一次。
WHEN后跟的condition是觸發(fā)器的響應(yīng)條件,只對(duì)行級(jí)觸發(fā)器有效,當(dāng)操作的記錄滿足condition時(shí),觸發(fā)器才被執(zhí)行,否則不執(zhí)行。Condition中可以通過(guò)new對(duì)象和old對(duì)象(注意區(qū)別于前面的:new和:old,在代碼中引用需要加上冒號(hào))來(lái)引用操作的記錄。
觸發(fā)器代碼可以包括三種類型:未涉及數(shù)據(jù)庫(kù)事務(wù)代碼,涉及關(guān)聯(lián)表(上文語(yǔ)法中的table_name)數(shù)據(jù)庫(kù)事務(wù)代碼,涉及除關(guān)聯(lián)表之外數(shù)據(jù)庫(kù)事務(wù)代碼。其中第一種類型代碼只對(duì)數(shù)據(jù)進(jìn)行簡(jiǎn)單運(yùn)算和判斷,沒(méi)有DML語(yǔ)句,這種類型代碼可以在所有的觸發(fā)器中執(zhí)行。第二種類型代碼涉及到對(duì)關(guān)聯(lián)表的數(shù)據(jù)操作,比如查詢關(guān)聯(lián)表的總記錄數(shù)或者往關(guān)聯(lián)表中插入一條記錄,該類型代碼只能在語(yǔ)句級(jí)觸發(fā)器中使用,如果在行級(jí)觸發(fā)器中使用,將會(huì)報(bào)ORA-04091錯(cuò)誤。第三種類型代碼涉及到除關(guān)聯(lián)表之外的數(shù)據(jù)庫(kù)事務(wù),這種代碼可以在所有觸發(fā)器中使用。
從觸發(fā)器的功能上來(lái)看,可以分成3類:
l 重寫(xiě)列(僅限于before觸發(fā)器)
l 采取行動(dòng)(任何觸發(fā)器)
l 拒絕事務(wù)(任何觸發(fā)器)
“重寫(xiě)列”用于對(duì)表字段的校驗(yàn),當(dāng)插入值為空或者插入值不符合要求,則觸發(fā)器用缺省值或另外的值代替,在多數(shù)情況下與字段的default屬性相同。這種功能只能在行級(jí)before觸發(fā)器中執(zhí)行。“采取行動(dòng)”針對(duì)當(dāng)前事務(wù)的特點(diǎn),對(duì)相關(guān)表進(jìn)行操作,比如根據(jù)當(dāng)前表插入的記錄更新其他表,銀行中的總帳和分戶帳間的總分關(guān)系就可以通過(guò)這種觸發(fā)器功能來(lái)維護(hù)。“拒絕事務(wù)”用在對(duì)數(shù)據(jù)的合法性檢驗(yàn)上,當(dāng)更新的數(shù)據(jù)不滿足表或系統(tǒng)的一致性要求,則通過(guò)拋出異常的方式拒絕事務(wù),在其上層的代碼可以捕獲這個(gè)異常并進(jìn)行相應(yīng)操作。
下面將通過(guò)舉例說(shuō)明,在例子中將觸發(fā)器主體的語(yǔ)法一一介紹,讀者可以在例子中體會(huì)觸發(fā)器的功能。
4,例一:行級(jí)觸發(fā)器之一
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
BEGIN
IF inserting THEN
dbms_output.put_line(‘插入’);
ELSIF updating THEN
dbms_output.put_line(‘更新amount列’);
END IF;
END;
以上是一個(gè)after insert和after update的行級(jí)觸發(fā)器。在第二行中of amount on salary的意思是只有當(dāng)amount列被更新時(shí),update觸發(fā)器才會(huì)有效。所以,以下語(yǔ)句將不會(huì)執(zhí)行觸發(fā)器:
Update salary set month = ‘200601’ where month = ‘200606’;
在觸發(fā)器主體的if語(yǔ)句表達(dá)式中,inserting, updating和deleting可以用來(lái)區(qū)分當(dāng)前是在做哪一種DML操作,可以作為把多個(gè)類似觸發(fā)器合并在一個(gè)觸發(fā)器中判別觸發(fā)事件的屬性。
5,例二:行級(jí)觸發(fā)器之二
新建員工表employment
CREATE TABLE EMPLOYMENT
(
EMPLOYEE_ID NUMBER, --員工ID
MAXSALARY NUMBER --工資上限
)
插入兩條記錄
Insert into employment values(1, 1000);
Insert into employment values(2, 2000);
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
WHEN ( NEW.amount >= 1000 AND (old.amount IS NULL OR OLD.amount <= 500))
DECLARE
v_maxsalary NUMBER;
BEGIN
SELECT maxsalary
INTO v_maxsalary
FROM employment
WHERE employee_id = :NEW.employee_id;
IF :NEW.amount > v_maxsalary THEN
raise_application_error(-20000, '工資超限');
END IF;
END;
以上的例子引入了一個(gè)新的表employment,表中的maxsalary字段代表該員工每月所能分配的最高工資。下面的觸發(fā)器根據(jù)插入或修改記錄的employee_id,在employment表中查到該員工的每月最高工資,如果插入或修改后的amount超過(guò)這個(gè)值,則報(bào)錯(cuò)誤。
代碼中的when子句表明了該觸發(fā)器只針對(duì)修改或插入后的amount值超過(guò)1000,而修改前的amount值小于500的記錄。New對(duì)象和old對(duì)象分別表示了操作前和操作后的記錄對(duì)象。對(duì)于insert操作,由于當(dāng)前操作記錄無(wú)歷史對(duì)象,所以old對(duì)象中所有屬性是null;對(duì)于delete操作,由于當(dāng)前操作記錄沒(méi)有更新對(duì)象,所以new對(duì)象中所有屬性也是null。但在這兩種情況下,并不影響old和new對(duì)象的引用和在觸發(fā)器主體中的使用,和普通的空值作同樣的處理。
在觸發(fā)器主體中,先通過(guò):new.employee_id,得到該員工的工資上限,然后在if語(yǔ)句中判斷更新后的員工工資是否超限,如果超限則錯(cuò)誤代碼為-20000,錯(cuò)誤信息為“工資超限”的自定義錯(cuò)誤。其中的raise_application_error包含兩個(gè)參數(shù),前一個(gè)是自定義錯(cuò)誤代碼,后一個(gè)是自定義錯(cuò)誤代碼信息。其中自定義錯(cuò)誤代碼必須小于或等于-20000。執(zhí)行完該語(yǔ)句后,一個(gè)異常被拋出,如果在上一層有exception子句,該異常將被捕獲。如下面代碼:
DECLARE
code NUMBER;
msg VARCHAR2(500);
BEGIN
INSERT INTO salary (employee_id, amount) VALUES (2, 5000);
EXCEPTION
WHEN OTHERS THEN
code := SQLCODE;
msg := substr(SQLERRM, 1, 500);
dbms_output.put_line(code);
dbms_output.put_line(msg);
END;
執(zhí)行后,將在output中或者sqlplus窗口中見(jiàn)著以下信息:
-20000
ORA-20000: 工資超出限制
ORA-06512: 在"SCOTT.SALARY_RAI", line 9
ORA-04088: 觸發(fā)器 'SCOTT.SALARY_RAI' 執(zhí)行過(guò)程中出錯(cuò)
這里的raise_application_error相當(dāng)于拒絕了插入或者修改事務(wù),當(dāng)上層代碼接受到這個(gè)異常后,判斷該異常代碼等于-20000,可以作出回滾事務(wù)或者繼續(xù)其他事務(wù)的處理。
以上兩個(gè)例子中用到的inserting, updating, deleting和raise_application_error都是dbms_standard包中的函數(shù),具體的說(shuō)明可以參照Oracle的幫助文檔。
create or replace package sys.dbms_standard is
procedure raise_application_error(num binary_integer, msg varchar2,
function inserting return boolean;
function deleting return boolean;
function updating return boolean;
function updating (colnam varchar2) return boolean;
end;
對(duì)于before和after行級(jí)觸發(fā)器,:new和:old對(duì)象的屬性值都是一樣的,主要是對(duì)于在Oracle約束(Constraint)之前或之后的執(zhí)行觸發(fā)器的選擇。需要注意的是,可以在before行觸發(fā)器中更改:new對(duì)象中的值,但是在after行觸發(fā)器就不行。
下面介紹一種instead of觸發(fā)器,該觸發(fā)器主要使用在對(duì)視圖的更新上,以下是instead of觸發(fā)器的語(yǔ)法:
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF <insert | update | delete> ON view_name
[FOR EACH ROW]
WHEN (condition)
DECLARE
BEGIN
--觸發(fā)器代碼
END;
其他部分語(yǔ)法同前面所述的before和after語(yǔ)法是一樣的,唯一不同的是在第二行用上了instead of關(guān)鍵字。對(duì)于普通的視圖來(lái)說(shuō),進(jìn)行insert等操作是被禁止的,因?yàn)?/span>Oracle無(wú)法知道操作的字段具體是哪個(gè)表中的字段。但我們可以通過(guò)建立instead of觸發(fā)器,在觸發(fā)器主體中告訴Oracle應(yīng)該更新,刪除或者修改哪些表的哪部分字段。如:
6,例三:instead of觸發(fā)器
新建視圖
CREATE VIEW employee_salary(employee_id, maxsalary, MONTH, amount) AS
SELECT a.employee_id, a.maxsalary, b.MONTH, b.amount
FROM employment a, salary b
WHERE a.employee_id = b.employee_id
如果執(zhí)行插入語(yǔ)句
INSERT INTO employee_salary(employee_id, maxsalary, MONTH, amount)
VALUES(10, 100000, '200606', 10000);
系統(tǒng)會(huì)報(bào)錯(cuò):
ORA-01779:無(wú)法修改與非鍵值保存表對(duì)應(yīng)的列
我們可以通過(guò)建立以下的instead of存儲(chǔ)過(guò)程,將插入視圖的值分別插入到兩個(gè)表中:
create or replace trigger employee_salary_rii
instead of insert on employee_salary
for each ROW
DECLARE
v_cnt NUMBER;
BEGIN
--檢查是否存在該員工信息
SELECT COUNT(*)
INTO v_cnt
FROM employment
WHERE employee_id = :NEW.employee_id;
IF v_cnt = 0 THEN
INSERT INTO employment
(employee_id, maxsalary)
VALUES
(:NEW.employee_id, :NEW.maxsalary);
END IF;
--檢查是否存在該員工的工資信息
SELECT COUNT(*)
INTO v_cnt
FROM salary
WHERE employee_id = :NEW.employee_id
AND MONTH = :NEW.MONTH;
IF v_cnt = 0 THEN
INSERT INTO salary
(employee_id, MONTH, amount)
VALUES
(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
END IF;
END employee_salary_rii;
該觸發(fā)器被建立后,執(zhí)行上述insert操作,系統(tǒng)就會(huì)提示成功插入一條記錄。
但需要注意的是,這里的“成功插入一條記錄”,只是Oracle并未發(fā)現(xiàn)觸發(fā)器中有異常拋出,而根據(jù)insert語(yǔ)句中涉及的記錄數(shù)作出一個(gè)判斷。若觸發(fā)器的主體什么都沒(méi)有,只是一個(gè)空語(yǔ)句,Oracle也會(huì)報(bào)“成功插入一條記錄”。同樣道理,即使在觸發(fā)器主體里往多個(gè)表中插入十條記錄,Oracle的返回也是“成功插入一條記錄”。
行級(jí)觸發(fā)器可以解決大部分的問(wèn)題,但是如果需要對(duì)本表進(jìn)行掃描檢查,比如要檢查總的工資是否超限了,用行級(jí)觸發(fā)器是不行的,因?yàn)樾屑?jí)觸發(fā)器主體中不能有涉及到關(guān)聯(lián)表的事務(wù),這時(shí)就需要用到語(yǔ)句級(jí)觸發(fā)器。以下是語(yǔ)句級(jí)觸發(fā)器的語(yǔ)法:
CREATE OR REPLACE TRIGGER trigger_name
<before | after | instead of ><insert | update | delete > ON table_name
DECLARE
BEGIN
--觸發(fā)器主體
END;
從語(yǔ)法定義上來(lái)看,行級(jí)觸發(fā)器少了for each row,也不能使用when子句來(lái)限定入口條件,其他部分都是一樣的,包括insert, update, delete和instead of都可以使用。
7,例四:語(yǔ)句級(jí)觸發(fā)器之一
CREATE OR REPLACE TRIGGER salary_saiu
AFTER INSERT OR UPDATE OF amount ON salary
DECLARE
v_sumsalary NUMBER;
BEGIN
SELECT SUM(amount) INTO v_sumsalary FROM salary;
IF v_sumsalary > 500000 THEN
raise_application_error(-20001, '總工資超過(guò)500000');
END IF;
END;
以上代碼定義了一個(gè)語(yǔ)句級(jí)觸發(fā)器,該觸發(fā)器檢查在insert和update了amount字段后操作后,工資表中所有工資記錄累加起來(lái)是否超過(guò)500000,如果超過(guò)則拋出異常。從這個(gè)例子可以看出,語(yǔ)句級(jí)觸發(fā)器可以對(duì)關(guān)聯(lián)表表進(jìn)行掃描,掃描得到的結(jié)果可以用來(lái)作為判斷一致性的標(biāo)志。需要注意的是,在before語(yǔ)句觸發(fā)器主體和after語(yǔ)句觸發(fā)器主體中對(duì)關(guān)聯(lián)表進(jìn)行掃描,結(jié)果是不一樣的。在before語(yǔ)句觸發(fā)器主體中掃描,掃描結(jié)果將不包括新插入和更新的記錄,也就是說(shuō)當(dāng)以上代碼換成 before觸發(fā)器后,以下語(yǔ)句將不報(bào)錯(cuò):
INSERT INTO salary(employee_id, month, amount) VALUEs(2, '200601', 600000)
這是因?yàn)樵谥黧w中得到的v_sumsalary并不包括新插入的600000工資。
另外,在語(yǔ)句級(jí)觸發(fā)器中不能使用:new和:old對(duì)象,這一點(diǎn)和行級(jí)觸發(fā)器是顯著不同的。如果需要檢查插入或更新后的記錄,可以采用臨時(shí)表技術(shù)。
臨時(shí)表是一種Oracle數(shù)據(jù)庫(kù)對(duì)象,其特點(diǎn)是當(dāng)創(chuàng)建數(shù)據(jù)的進(jìn)程結(jié)束后,進(jìn)程所創(chuàng)建的數(shù)據(jù)也隨之清除。進(jìn)程與進(jìn)程不可以互相訪問(wèn)同一臨時(shí)表中對(duì)方的數(shù)據(jù),而且對(duì)臨時(shí)表進(jìn)行操作也不產(chǎn)生undo日志,減少了數(shù)據(jù)庫(kù)的消耗。具體有關(guān)臨時(shí)表的知識(shí),可以參看有關(guān)書(shū)籍。
為了在語(yǔ)句級(jí)觸發(fā)器中訪問(wèn)新插入后修改后的記錄,可以增加行級(jí)觸發(fā)器,將更新的記錄插入臨時(shí)表中,然后在語(yǔ)句級(jí)觸發(fā)器中掃描臨時(shí)表,獲得修改后的記錄。臨時(shí)表的表結(jié)構(gòu)一般與關(guān)聯(lián)表的結(jié)構(gòu)一致。
8,例五:語(yǔ)句級(jí)觸發(fā)器之二
目的:限制每個(gè)員工的總工資不能超過(guò)50000,否則停止對(duì)該表操作。
創(chuàng)建臨時(shí)表
create global temporary table SALARY_TMP
(
EMPLOYEE_ID NUMBER,
MONTH VARCHAR2(6),
AMOUNT NUMBER
)
on commit delete rows;
為了把操作記錄插入到臨時(shí)表中,創(chuàng)建行級(jí)觸發(fā)器:
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
BEGIN
INSERT INTO salary_tmp(employee_id, month, amount)
VALUES(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
END;
該觸發(fā)器的作用是把更新后的記錄信息插入到臨時(shí)表中,如果更新了多條記錄,則每條記錄都會(huì)保存在臨時(shí)表中。
創(chuàng)建語(yǔ)句級(jí)觸發(fā)器:
CREATE OR REPLACE TRIGGER salary_sai
AFTER INSERT OR UPDATE OF amount ON salary
DECLARE
v_sumsalary NUMBER;
BEGIN
FOR cur IN (SELECT * FROM salary_tmp) LOOP
SELECT SUM(amount)
INTO v_sumsalary
FROM salary
WHERE employee_id = cur.employee_id;
IF v_sumsalary > 50000 THEN
raise_application_error(-20002, '員工累計(jì)工資超過(guò)50000');
END IF;
DELETE FROM salary_tmp;
END LOOP;
END;
該觸發(fā)器首先用游標(biāo)從salary_tmp臨時(shí)表中逐條讀取更新或插入的記錄,取employee_id,在關(guān)聯(lián)表salary中查找所有相同員工的工資記錄,并求和。若某員工工資總和超過(guò)50000,則拋出異常。如果檢查通過(guò),則清空臨時(shí)表,避免下次檢查相同的記錄。
執(zhí)行以下語(yǔ)句:
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200601', 20000);
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200602', 20000);
INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200603', 20000);
在執(zhí)行第三句時(shí)系統(tǒng)報(bào)錯(cuò):
ORA-20002:?jiǎn)T工累計(jì)工資超過(guò)50000
查詢salary表,發(fā)現(xiàn)前兩條記錄正常插入了,第三條記錄沒(méi)有插入。
如果系統(tǒng)結(jié)構(gòu)比較復(fù)雜,而且觸發(fā)器的代碼比較多,在觸發(fā)器主體中寫(xiě)過(guò)多的代碼,對(duì)于維護(hù)來(lái)說(shuō)是一個(gè)困難。這時(shí)可以將所有觸發(fā)器的代碼寫(xiě)到同一個(gè)包中,不同的觸發(fā)器代碼以不同的存儲(chǔ)過(guò)程封裝,然后觸發(fā)器主體中調(diào)用這部分代碼。
9,例六:用包封裝觸發(fā)器代碼
目的:改寫(xiě)例五,封裝觸發(fā)器主體代碼
創(chuàng)建代碼包:
CREATE OR REPLACE PACKAGE BODY salary_trigger_pck IS
PROCEDURE load_salary_tmp(i_employee_id IN NUMBER,
i_month IN VARCHAR2,
i_amount IN NUMBER) IS
BEGIN
INSERT INTO salary_tmp VALUES (i_employee_id, i_month, i_amount);
END load_salary_tmp;
PROCEDURE check_salary IS
v_sumsalary NUMBER;
BEGIN
FOR cur IN (SELECT * FROM salary_tmp) LOOP
SELECT SUM(amount)
INTO v_sumsalary
FROM salary
WHERE employee_id = cur.employee_id;
IF v_sumsalary > 50000 THEN
raise_application_error(-20002, '員工累計(jì)工資超過(guò)50000');
END IF;
DELETE FROM salary_tmp;
END LOOP;
END check_salary;
END salary_trigger_pck;
包salary_trigger_pck中有兩個(gè)存儲(chǔ)過(guò)程,load_salary_tmp用于在行級(jí)觸發(fā)器中調(diào)用,往salary_tmp臨時(shí)表中裝載更新或插入記錄。而check_salary用于在語(yǔ)句級(jí)觸發(fā)器中檢查員工累計(jì)工資是否超限。
修改行級(jí)觸發(fā)器和語(yǔ)句級(jí)觸發(fā)器:
CREATE OR REPLACE TRIGGER salary_raiu
AFTER INSERT OR UPDATE OF amount ON salary
FOR EACH ROW
BEGIN
salary_trigger_pck.load_salary_tmp(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
END;
CREATE OR REPLACE TRIGGER salary_sai
AFTER INSERT OR UPDATE OF amount ON salary
BEGIN
salary_trigger_pck.check_salary;
END;
這樣主要代碼就集中到了salary_trigger_pck中,觸發(fā)器主體中只實(shí)現(xiàn)了一個(gè)調(diào)用功能。
10,觸發(fā)器命名規(guī)范
為了方便對(duì)觸發(fā)器命名和根據(jù)觸發(fā)器名稱了解觸發(fā)器含義,需要定義觸發(fā)器的命名規(guī)范:
Trigger_name = table_name_trg_<R|S><A|B|I><I|U|D>
觸發(fā)器名限于30個(gè)字符。必須縮寫(xiě)表名,以便附加觸發(fā)器屬性信息。
<R|S>基于行級(jí)(row)還是語(yǔ)句級(jí)(statement)的觸發(fā)器
<A|B|I>after, before或者是instead of觸發(fā)器
<I|U|D>觸發(fā)事件是insert,update還是delete。如果有多個(gè)觸發(fā)事件則連著寫(xiě)
例如:
Salary_rai salary表的行級(jí)after觸發(fā)器,觸發(fā)事件是insert
Employee_sbiud employee表的語(yǔ)句級(jí)before觸發(fā)器,觸發(fā)事件是insert,update和delete