亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

ORACLE觸發(fā)器具體解釋

系統(tǒng) 1971 0

ORACLE PL/SQL 編程之八: ?

把觸發(fā)器說透 ?

?

本篇主要內(nèi)容例如以下:

8.1 觸發(fā)器類型

8.1.1 DML觸發(fā)器

8.1.2 替代觸發(fā)器

8.1.3 系統(tǒng)觸發(fā)器

8.2?創(chuàng)建觸發(fā)器

8.2.1 觸發(fā)器觸發(fā)次序

8.2.2 創(chuàng)建DML觸發(fā)器

8.2.3 創(chuàng)建替代(INSTEAD OF)觸發(fā)器

8.2.3 創(chuàng)建系統(tǒng)事件觸發(fā)器

8.2.4 系統(tǒng)觸發(fā)器事件屬性

8.2.5 使用觸發(fā)器謂詞

8.2.6 又一次編譯觸發(fā)器

8.3?刪除和使能觸發(fā)器

8.4?觸發(fā)器和數(shù)據(jù)字典

8.5?? 數(shù)據(jù)庫(kù)觸發(fā)器的應(yīng)用舉例

?


?

觸發(fā)器是很多關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)都提供的一項(xiàng)技術(shù)。在ORACLE系統(tǒng)里,觸發(fā)器類似過程和函數(shù),都有聲明,運(yùn)行和異常處理過程的PL/SQL塊。

8.1?觸發(fā)器類型

??? 觸發(fā)器在數(shù)據(jù)庫(kù)里以獨(dú)立的對(duì)象存儲(chǔ),它與存儲(chǔ)過程和函數(shù)不同的是,存儲(chǔ)過程與函數(shù)須要用戶顯示調(diào)用才執(zhí)行,而觸發(fā)器是由一個(gè)事件來啟動(dòng)執(zhí)行。即觸發(fā)器是當(dāng)某個(gè)事件發(fā)生時(shí) 自己主動(dòng)地隱式執(zhí)行 。而且,觸發(fā)器 不能接收參數(shù) 。所以執(zhí)行觸發(fā)器就叫觸發(fā)或點(diǎn)火(firing)。ORACLE事件指的是對(duì)數(shù)據(jù)庫(kù)的表進(jìn)行的INSERT、UPDATE及DELETE操作或?qū)σ晥D進(jìn)行類似的操作。ORACLE將觸發(fā)器的功能擴(kuò)展到了觸發(fā)ORACLE,如數(shù)據(jù)庫(kù)的啟動(dòng)與關(guān)閉等。所以觸發(fā)器經(jīng)常使用來完畢由數(shù)據(jù)庫(kù)的完整性約束難以完畢的復(fù)雜業(yè)務(wù)規(guī)則的約束,或用來監(jiān)視對(duì)數(shù)據(jù)庫(kù)的各種操作,實(shí)現(xiàn)審計(jì)的功能。

?

8.1.1 DML觸發(fā)器

??? ORACLE能夠在DML語(yǔ)句進(jìn)行觸發(fā),能夠在DML操作前或操作后進(jìn)行觸發(fā),而且能夠?qū)γ恳粋€(gè)行或語(yǔ)句操作上進(jìn)行觸發(fā)。

?

8.1.2 替代觸發(fā)器

??? 因?yàn)樵贠RACLE里,不能直接對(duì)由兩個(gè)以上的表建立的視圖進(jìn)行操作。所以給出了替代觸發(fā)器。它就是ORACLE 8專門為進(jìn)行視圖操作的一種處理方法。

?

8.1.3 系統(tǒng)觸發(fā)器

ORACLE 8i 提供了第三種類型的觸發(fā)器叫系統(tǒng)觸發(fā)器。它能夠在ORACLE數(shù)據(jù)庫(kù)系統(tǒng)的事件中進(jìn)行觸發(fā),如ORACLE系統(tǒng)的啟動(dòng)與關(guān)閉等。

?

觸發(fā)器組成:?

l???????? 觸發(fā)事件: 引起觸發(fā)器被觸發(fā)的事件。 比如:DML語(yǔ)句(INSERT, UPDATE, DELETE語(yǔ)句對(duì)表或視圖運(yùn)行數(shù)據(jù)處理操作)、DDL語(yǔ)句(如CREATE、ALTER、DROP語(yǔ)句在數(shù)據(jù)庫(kù)中創(chuàng)建、改動(dòng)、刪除模式對(duì)象)、數(shù)據(jù)庫(kù)系統(tǒng)事件(如系統(tǒng)啟動(dòng)或退出、異常錯(cuò)誤)、用戶事件(如登錄或退出數(shù)據(jù)庫(kù))。

l???????? 觸發(fā)時(shí)間 :即該TRIGGER 是在觸發(fā)事件發(fā)生之前(BEFORE)還是之后(AFTER)觸發(fā),也就是觸發(fā)事件和該TRIGGER 的操作順序。

l???????? 觸發(fā)操作: 即該TRIGGER 被觸發(fā)之后的目的和意圖,正是觸發(fā)器本身要做的事情。 比如:PL/SQL 塊。

l???????? 觸發(fā)對(duì)象: 包含表、視圖、模式、數(shù)據(jù)庫(kù)。僅僅有在這些對(duì)象上發(fā)生了符合觸發(fā)條件的觸發(fā)事件,才會(huì)運(yùn)行觸發(fā)操作。

l???????? 觸發(fā)條件: 由WHEN子句指定一個(gè)邏輯表達(dá)式。僅僅有當(dāng)該表達(dá)式的值為TRUE時(shí),遇到觸發(fā)事件才會(huì)自己主動(dòng)運(yùn)行觸發(fā)器,使其運(yùn)行觸發(fā)操作。

l???????? 觸發(fā)頻率 :說明觸發(fā)器內(nèi)定義的動(dòng)作被運(yùn)行的次數(shù)。即語(yǔ)句級(jí)(STATEMENT)觸發(fā)器和行級(jí)(ROW)觸發(fā)器。

語(yǔ)句級(jí)(STATEMENT)觸發(fā)器:是指當(dāng)某觸發(fā)事件發(fā)生時(shí),該觸發(fā)器僅僅運(yùn)行一次;

行級(jí)(ROW)觸發(fā)器:是指當(dāng)某觸發(fā)事件發(fā)生時(shí),對(duì)受到該操作影響的每一行數(shù)據(jù),觸發(fā)器都單獨(dú)運(yùn)行一次。

編寫觸發(fā)器時(shí),須要注意下面幾點(diǎn):

l???????? 觸發(fā)器不接受參數(shù)。

l???????? 一個(gè)表上最多可有12個(gè)觸發(fā)器,但同一時(shí)間、同一事件、同一類型的觸發(fā)器僅僅能有一個(gè)。并各觸發(fā)器之間不能有矛盾。

l???????? 在一個(gè)表上的觸發(fā)器越多,對(duì)在該表上的DML操作的性能影響就越大。

l????????觸發(fā)器最大為32KB。若確實(shí)須要,能夠先建立過程,然后在觸發(fā)器中用CALL語(yǔ)句進(jìn)行調(diào)用。

l???????? 在觸發(fā)器的運(yùn)行部分僅僅能用DML 語(yǔ)句(SELECT、INSERT、UPDATE、DELETE),不能使用DDL語(yǔ)句(CREATE、ALTER、DROP) 。

l???????? 觸發(fā)器中不能包括事務(wù)控制語(yǔ)句(COMMIT,ROLLBACK,SAVEPOINT)。由于觸發(fā)器是觸發(fā)語(yǔ)句的一部分,觸發(fā)語(yǔ)句被提交、回退時(shí),觸發(fā)器也被提交、回退了。

l???????? 在觸發(fā)器主體中調(diào)用的不論什么過程、函數(shù),都不能使用事務(wù)控制語(yǔ)句。

l???????? 在觸發(fā)器主體中不能申明不論什么Long和blob變量。新值new和舊值old也不能是表中的不論什么long和blob列。

l???????? 不同類型的觸發(fā)器(如DML觸發(fā)器、INSTEAD OF觸發(fā)器、系統(tǒng)觸發(fā)器)的語(yǔ)法格式和作用有較大差別。
?

8.2?創(chuàng)建觸發(fā)器

創(chuàng)建觸發(fā)器的一般語(yǔ)法是:

?

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;

?

?

當(dāng)中:

BEFORE 和AFTER指出觸發(fā)器的觸發(fā)時(shí)序分別為前觸發(fā)和后觸發(fā)方式,前觸發(fā)是在運(yùn)行觸發(fā)事件之前觸發(fā)當(dāng)前所創(chuàng)建的觸發(fā)器,后觸發(fā)是在運(yùn)行觸發(fā)事件之后觸發(fā)當(dāng)前所創(chuàng)建的觸發(fā)器。

?????? FOR EACH ROW選項(xiàng)說明觸發(fā)器為 行觸發(fā)器 。行觸發(fā)器和語(yǔ)句觸發(fā)器的差別表如今:行觸發(fā)器要求當(dāng)一個(gè)DML語(yǔ)句操作影響數(shù)據(jù)庫(kù)中的多行數(shù)據(jù)時(shí),對(duì)于當(dāng)中的每一個(gè)數(shù)據(jù)行,僅僅要它們符合觸發(fā)約束條件,均激活一次觸發(fā)器;而 語(yǔ)句觸發(fā)器 將整個(gè)語(yǔ)句操作作為觸發(fā)事件,當(dāng)它符合約束條件時(shí),激活一次觸發(fā)器。當(dāng)省略FOR EACH ROW 選項(xiàng)時(shí),BEFORE 和AFTER 觸發(fā)器為語(yǔ)句觸發(fā)器,而 INSTEAD OF 觸發(fā)器則僅僅能為行觸發(fā)器 。

???????????REFERENCING 子句說明相關(guān)名稱,在行觸發(fā)器的PL/SQL塊和WHEN 子句中能夠使用相關(guān)名稱參照當(dāng)前的新、舊列值,默認(rèn)的相關(guān)名稱分別為OLD和NEW。觸發(fā)器的PL/SQL塊中應(yīng)用相關(guān)名稱時(shí),必須在它們之前加冒號(hào)(:),但在WHEN子句中則不能加冒號(hào)。

WHEN 子句說明觸發(fā)約束條件。Condition 為一個(gè)邏輯表達(dá)時(shí),當(dāng)中必須包括相關(guān)名稱,而不能包括查詢語(yǔ)句,也不能調(diào)用PL/SQL 函數(shù)。WHEN 子句指定的觸發(fā)約束條件僅僅能用在BEFORE 和AFTER 行觸發(fā)器中,不能用在INSTEAD OF 行觸發(fā)器和其他類型的觸發(fā)器中。

??? 當(dāng)一個(gè)基表被修改( INSERT, UPDATE, DELETE)時(shí)要運(yùn)行的存儲(chǔ)過程,運(yùn)行時(shí)依據(jù)其所依附的基表修改而自己主動(dòng)觸發(fā),因此與應(yīng)用程序無關(guān),用數(shù)據(jù)庫(kù)觸發(fā)器能夠保證數(shù)據(jù)的一致性和完整性。

?

每張表最多可建立12 種類型的觸發(fā)器,它們是:

BEFORE INSERT

BEFORE INSERT FOR EACH ROW

AFTER INSERT

AFTER INSERT FOR EACH ROW

?

BEFORE UPDATE

BEFORE UPDATE FOR EACH ROW

AFTER UPDATE

AFTER UPDATE FOR EACH ROW

?

BEFORE DELETE

BEFORE DELETE FOR EACH ROW

AFTER DELETE

AFTER DELETE FOR EACH ROW

?

8.2.1 觸發(fā)器觸發(fā)次序

1.???????運(yùn)行 BEFORE語(yǔ)句級(jí)觸發(fā)器;

2.???????對(duì)與受語(yǔ)句影響的每一行:

l???????? 運(yùn)行 BEFORE行級(jí)觸發(fā)器

l???????? 運(yùn)行 DML語(yǔ)句

l???????? 運(yùn)行 AFTER行級(jí)觸發(fā)器?

3.???????運(yùn)行 AFTER語(yǔ)句級(jí)觸發(fā)器

?

8.2.2 創(chuàng)建DML觸發(fā)器

??? 觸發(fā)器名與過程名和包的名字不一樣,它是單獨(dú)的名字空間,因而觸發(fā)器名能夠和表或過程有同樣的名字,但在一個(gè)模式中觸發(fā)器名不能同樣。

?

DML觸發(fā)器的限制

l???????? CREATE TRIGGER語(yǔ)句文本的字符長(zhǎng)度不能超過32KB;

l???????? 觸發(fā)器體內(nèi)的SELECT 語(yǔ)句僅僅能為SELECT … INTO …結(jié)構(gòu),或者為定義游標(biāo)所使用的SELECT 語(yǔ)句。

l???????? 觸發(fā)器中不能使用數(shù)據(jù)庫(kù)事務(wù)控制語(yǔ)句 COMMIT; ROLLBACK, SVAEPOINT 語(yǔ)句;

l???????? 由觸發(fā)器所調(diào)用的過程或函數(shù)也不能使用數(shù)據(jù)庫(kù)事務(wù)控制語(yǔ)句;

l???????? 觸發(fā)器中不能使用LONG, LONG RAW 類型;

l???????? 觸發(fā)器內(nèi)能夠參照LOB 類型列的列值,但不能通過 :NEW 改動(dòng)LOB列中的數(shù)據(jù);

?

DML觸發(fā)器基本要點(diǎn)

l???????? 觸發(fā)時(shí)機(jī): 指定觸發(fā)器的觸發(fā)時(shí)間。假設(shè)指定為BEFORE,則表示在運(yùn)行DML操作之前觸發(fā),以便防止某些錯(cuò)誤操作發(fā)生或?qū)崿F(xiàn)某些業(yè)務(wù)規(guī)則;假設(shè)指定為AFTER,則表示在運(yùn)行DML操作之后觸發(fā),以便記錄該操作或做某些事后處理。

l???????? 觸發(fā)事件: 引起觸發(fā)器被觸發(fā)的事件,即DML操作(INSERT、UPDATE、DELETE)。既能夠是單個(gè)觸發(fā)事件,也能夠是多個(gè)觸發(fā)事件的組合(僅僅能使用OR邏輯組合,不能使用AND邏輯組合)。

l???????? 條件謂詞: 當(dāng)在觸發(fā)器中包括多個(gè)觸發(fā)事件(INSERT、UPDATE、DELETE)的組合時(shí),為了分別針對(duì)不同的事件進(jìn)行不同的處理,須要使用ORACLE提供的例如以下條件謂詞。

1)。 INSERTING: 當(dāng)觸發(fā)事件是INSERT時(shí),取值為TRUE,否則為FALSE。

2)。 UPDATING [(column_1,column_2,…,column_x)]: 當(dāng)觸發(fā)事件是UPDATE????? 時(shí),假設(shè)改動(dòng)了column_x列,則取值為TRUE,否則為FALSE。當(dāng)中column_x是可選的。

3)。 DELETING: 當(dāng)觸發(fā)事件是DELETE時(shí),則取值為TRUE,否則為FALSE。

解發(fā)對(duì)象: 指定觸發(fā)器是創(chuàng)建在哪個(gè)表、視圖上。

l???????? 觸發(fā)類型: 是語(yǔ)句級(jí)還是行級(jí)觸發(fā)器。

l???????? 觸發(fā)條件: 由WHEN子句指定一個(gè)邏輯表達(dá)式, 僅僅同意在行級(jí)觸發(fā)器上指定觸發(fā)條件,指定 UPDATING 后面的列的列表 。

?

問題:當(dāng)觸發(fā)器被觸發(fā)時(shí),要使用被插入、更新或刪除的記錄中的列值,有時(shí)要使用操作前、??????? 后列的值.

實(shí)現(xiàn):??:NEW?修飾符訪問操作完畢后列的值

???????:OLD?修飾符訪問操作完畢前列的值

?

特性

INSERT

UPDATE

DELETE

OLD

NULL

實(shí)際值

實(shí)際值

NEW

實(shí)際值

實(shí)際值

NULL

?

例1: 建立一個(gè)觸發(fā)器, 當(dāng)職工表 emp 表被刪除一條記錄時(shí),把被刪除記錄寫到職工表刪除日志表中去。

?

CREATE ? TABLE ?emp_his? AS ? SELECT ? * ? FROM ?EMP? WHERE ? 1 = 2 ;?
CREATE ? OR ? REPLACE ? TRIGGER ?tr_del_emp?
???BEFORE?
DELETE ? -- 指定觸發(fā)時(shí)機(jī)為刪除操作前觸發(fā)
??? ON ?scott.emp?
???
FOR ?EACH?ROW??? -- 說明創(chuàng)建的是行級(jí)觸發(fā)器 ?
BEGIN
???
-- 將改動(dòng)前數(shù)據(jù)插入到日志記錄表 ?del_emp?, 以供監(jiān)督使用。
??? INSERT ? INTO ?emp_his(deptno?,?empno,?ename?,?job?,mgr?,?sal?,?comm?,?hiredate?)
???????
VALUES (?:old.deptno,?:old.empno,?:old.ename?,?:old.job,:old.mgr,?:old.sal,?:old.comm,?:old.hiredate?);
END ;
DELETE ?emp? WHERE ?empno = 7788 ;
DROP ? TABLE ?emp_his;
DROP ? TRIGGER ?del_emp;

?

例2: 限制對(duì)Departments表改動(dòng)(包含INSERT,DELETE,UPDATE)的時(shí)間范圍,即不同意在非工作時(shí)間改動(dòng)departments表。

?

CREATE ? OR ? REPLACE ? TRIGGER ?tr_dept_time
BEFORE?
INSERT ? OR ? DELETE ? OR ? UPDATE ?
ON ?departments
BEGIN
?
IF ?(TO_CHAR(sysdate, 'DAY' )? IN ?( ' 星期六 ' ,? ' 星期日 ' ))? OR ?(TO_CHAR(sysdate,? 'HH24:MI' )? NOT ? BETWEEN ? '08:30' ? AND ? '18:00' )? THEN
?????RAISE_APPLICATION_ERROR(
- 20001 ,? ' 不是上班時(shí)間,不能改動(dòng) departments ' );
?
END ? IF ;
END ;

?

例3: 限定僅僅對(duì)部門號(hào)為80的記錄進(jìn)行行觸發(fā)器操作。

?

CREATE ? OR ? REPLACE ? TRIGGER ?tr_emp_sal_comm
BEFORE?
UPDATE ? OF ?salary,?commission_pct
???????
OR ? DELETE
ON ?HR.employees
FOR ?EACH?ROW
WHEN ?(old.department_id? = ? 80 )
BEGIN
?
CASE
?????
WHEN ?UPDATING?( 'salary' )? THEN
????????
IF ?:NEW.salary? < ?:old.salary? THEN

???????????RAISE_APPLICATION_ERROR(
- 20001 ,? ' 部門 80 的人員的工資不能降 ' );
????????
END ? IF ;
?????
WHEN ?UPDATING?( 'commission_pct' )? THEN

????????
IF ?:NEW.commission_pct? < ?:old.commission_pct? THEN
???????????RAISE_APPLICATION_ERROR(
- 20002 ,? ' 部門 80 的人員的獎(jiǎng)金不能降 ' );
????????
END ? IF ;
?????
WHEN ?DELETING? THEN
??????????RAISE_APPLICATION_ERROR(
- 20003 ,? ' 不能刪除部門 80 的人員記錄 ' );
?????
END ? CASE ;
END ;?

/*
實(shí)例:
UPDATE?employees?SET?salary?=?8000?WHERE?employee_id?=?177;
DELETE?FROM?employees?WHERE?employee_id?in?(177,170);
*/

?

例4: 利用行觸發(fā)器實(shí)現(xiàn)級(jí)聯(lián)更新。在改動(dòng)了主表regions中的region_id之后(AFTER),級(jí)聯(lián)的、自己主動(dòng)的更新子表countries表中原來在該地區(qū)的國(guó)家的region_id。

?

?

CREATE ? OR ? REPLACE ? TRIGGER ?tr_reg_cou
AFTER?
update ? OF ?region_id
ON ?regions
FOR ?EACH?ROW
BEGIN
?DBMS_OUTPUT.PUT_LINE(
' 舊的 region_id 值是 ' || :old.region_id
??????????????????
|| ' 、新的 region_id 值是 ' || :new.region_id);
?
UPDATE ?countries? SET ?region_id? = ?:new.region_id
?
WHERE ?region_id? = ?:old.region_id;
END ;

例5: 在觸發(fā)器中調(diào)用過程。

?

CREATE ? OR ? REPLACE ? PROCEDURE ?add_job_history
?(?p_emp_id??????????job_history.employee_id
% type
???,?p_start_date??????job_history.start_date
% type
??,?p_end_date????????job_history.end_date
% type
???,?p_job_id??????????job_history.job_id
% type
???,?p_department_id???job_history.department_id
% type
???)
IS
BEGIN
?
INSERT ? INTO ?job_history?(employee_id,?start_date,?end_date,
???????????????????????????job_id,?department_id)
??
VALUES (p_emp_id,?p_start_date,?p_end_date,?p_job_id,?p_department_id);
END ?add_job_history;

-- 創(chuàng)建觸發(fā)器調(diào)用存儲(chǔ)過程 ...
CREATE ? OR ? REPLACE ? TRIGGER ?update_job_history
?AFTER?
UPDATE ? OF ?job_id,?department_id? ON ?employees
?
FOR ?EACH?ROW
BEGIN
?add_job_history(:old.employee_id,?:old.hire_date,?sysdate,
??????????????????:old.job_id,?:old.department_id);
END ;

?

8.2.3 創(chuàng)建替代(INSTEAD OF)觸發(fā)器

?

創(chuàng)建觸發(fā)器的一般語(yǔ)法是:

?

CREATE ? [OR?REPLACE] ? TRIGGER ?trigger_name
INSTEAD?
OF
{
INSERT ? | ? DELETE ? | ? UPDATE ? [OF?column?[,?column?…] ]}
[OR?{INSERT?|?DELETE?|?UPDATE?[OF?column?[,?column?…] ]}...]
ON ? [schema.] ?view_name? -- 僅僅能定義在視圖上
[REFERENCING?{OLD?[AS] ?old? | ?NEW? [AS] ?new | ?PARENT? as ?parent}]
[FOR?EACH?ROW?] ? -- 由于 INSTEAD?OF 觸發(fā)器僅僅能在行級(jí)上觸發(fā) , 所以沒有必要指定
[WHEN?condition]
PL
/ SQL_block? | ?CALL?procedure_name;

?

當(dāng)中:

???????????INSTEAD OF 選項(xiàng)使ORACLE激活觸發(fā)器,而不運(yùn)行觸發(fā)事件。 僅僅能對(duì)視圖和對(duì)象視圖建立INSTEAD OF觸發(fā)器,而不能對(duì)表、模式和數(shù)據(jù)庫(kù)建立INSTEAD OF 觸發(fā)器。

???????????FOR EACH ROW選項(xiàng)說明觸發(fā)器為行觸發(fā)器。行觸發(fā)器和語(yǔ)句觸發(fā)器的差別表如今:行觸發(fā)器要求當(dāng)一個(gè)DML語(yǔ)句操走影響數(shù)據(jù)庫(kù)中的多行數(shù)據(jù)時(shí),對(duì)于當(dāng)中的每一個(gè)數(shù)據(jù)行,僅僅要它們符合觸發(fā)約束條件,均激活一次觸發(fā)器;而語(yǔ)句觸發(fā)器將整個(gè)語(yǔ)句操作作為觸發(fā)事件,當(dāng)它符合約束條件時(shí),激活一次觸發(fā)器。當(dāng)省略FOR EACH ROW 選項(xiàng)時(shí),BEFORE 和AFTER 觸發(fā)器為語(yǔ)句觸發(fā)器,而INSTEAD OF 觸發(fā)器則為行觸發(fā)器。

???????????REFERENCING 子句說明相關(guān)名稱,在行觸發(fā)器的PL/SQL塊和WHEN 子句中能夠使用相關(guān)名稱參照當(dāng)前的新、舊列值,默認(rèn)的相關(guān)名稱分別為OLD和NEW。觸發(fā)器的PL/SQL塊中應(yīng)用相關(guān)名稱時(shí),必須在它們之前加冒號(hào)(:),但在WHEN子句中則不能加冒號(hào)。

WHEN 子句說明觸發(fā)約束條件。Condition 為一個(gè)邏輯表達(dá)時(shí),當(dāng)中必須包括相關(guān)名稱,而不能包括查詢語(yǔ)句,也不能調(diào)用PL/SQL 函數(shù)。WHEN 子句指定的觸發(fā)約束條件僅僅能用在BEFORE 和AFTER 行觸發(fā)器中,不能用在INSTEAD OF 行觸發(fā)器和其他類型的觸發(fā)器中。

?

??? INSTEAD_OF 用于對(duì)視圖的DML觸發(fā),因?yàn)橐晥D有可能是由多個(gè)表進(jìn)行聯(lián)結(jié)(join)而成,因而并不是是全部的聯(lián)結(jié)都是可更新的。但能夠依照所需的方式運(yùn)行更新,比如以下情況:

例1:

?

CREATE ? OR ? REPLACE ? VIEW ?emp_view? AS ?
SELECT ?deptno,? count ( * )?total_employeer,? sum (sal)?total_salary?
FROM ?emp? GROUP ? BY ?deptno;

?

在此視圖中直接刪除是非法:

SQL > DELETE ? FROM ?emp_view? WHERE ?deptno = 10 ;
DELETE ? FROM ?emp_view? WHERE ?deptno = 10

??????????

ERROR 位于第 1 行:

ORA-01732: 此視圖的數(shù)據(jù)操縱操作非法

?

可是我們能夠創(chuàng)建INSTEAD_OF觸發(fā)器來為 DELETE 操作運(yùn)行所需的處理,即刪除EMP表中全部基準(zhǔn)行:

?

CREATE ? OR ? REPLACE ? TRIGGER ?emp_view_delete
???INSTEAD?
OF ? DELETE ? ON ?emp_view? FOR ?EACH?ROW
BEGIN
???
DELETE ? FROM ?emp? WHERE ?deptno = ?:old.deptno;
END ?emp_view_delete;?

DELETE ? FROM ?emp_view? WHERE ?deptno = 10 ;?

DROP ? TRIGGER ?emp_view_delete;

DROP ? VIEW ?emp_view;?

?

例2: 創(chuàng)建復(fù)雜視圖,針對(duì)INSERT操作創(chuàng)建INSTEAD OF觸發(fā)器,向復(fù)雜視圖插入數(shù)據(jù)。

l???????? 創(chuàng)建視圖:

?

CREATE ? OR ? REPLACE ?FORCE? VIEW ?"HR"."V_REG_COU"?("R_ID",?"R_NAME",?"C_ID",?"C_NAME")
AS
?
SELECT ?r.region_id,
????r.region_name,
????c.country_id,
????c.country_name
?
FROM ?regions?r,
????countries?c
?
WHERE ?r.region_id? = ?c.region_id;

?

l????????創(chuàng)建觸發(fā)器:

?

CREATE ? OR ? REPLACE ? TRIGGER ?"HR"."TR_I_O_REG_COU"?INSTEAD? OF
?
INSERT ? ON ?v_reg_cou? FOR ?EACH?ROW? DECLARE ?v_count? NUMBER ;
BEGIN
?
SELECT ? COUNT ( * )? INTO ?v_count? FROM ?regions? WHERE ?region_id? = ?:new.r_id;
?
IF ?v_count? = ? 0 ? THEN
????
INSERT ? INTO ?regions
??????(region_id,?region_name
??????)?
VALUES
??????(:new.r_id,?:new.r_name
??????);
?
END ? IF ;

?
SELECT ? COUNT ( * )? INTO ?v_count? FROM ?countries? WHERE ?country_id? = ?:new.c_id;
?
IF ?v_count? = ? 0 ? THEN
????
INSERT
????
INTO ?countries
??????(
????????country_id,
????????country_name,
????????region_id
??????)
??????
VALUES
??????(
????????:new.c_id,
????????:new.c_name,
????????:new.r_id
??????);
?
END ? IF ;
END ;

?

創(chuàng)建INSTEAD OF觸發(fā)器須要注意下面幾點(diǎn):

l???????? 僅僅能被創(chuàng)建在視圖上,而且該視圖沒有指定WITH CHECK OPTION選項(xiàng)。

l???????? 不能指定BEFORE 或 AFTER選項(xiàng)。

l???????? FOR EACH ROW子但是可選的,即INSTEAD OF觸發(fā)器僅僅能在行級(jí)上觸發(fā)、或僅僅能是行級(jí)觸發(fā)器,沒有必要指定。

l???????? 沒有必要在針對(duì)一個(gè)表的視圖上創(chuàng)建INSTEAD OF觸發(fā)器,僅僅要?jiǎng)?chuàng)建DML觸發(fā)器就能夠了。

?

8.2.3 創(chuàng)建系統(tǒng)事件觸發(fā)器

??? ORACLE10G提供的系統(tǒng)事件觸發(fā)器能夠在DDL或數(shù)據(jù)庫(kù)系統(tǒng)上被觸發(fā)。DDL指的是數(shù)據(jù)定義語(yǔ)言,如CREATE 、ALTER及DROP 等。而數(shù)據(jù)庫(kù)系統(tǒng)事件包含數(shù)據(jù)庫(kù)server的啟動(dòng)或關(guān)閉,用戶的登錄與退出、數(shù)據(jù)庫(kù)服務(wù)錯(cuò)誤等。創(chuàng)建系統(tǒng)觸發(fā)器的語(yǔ)法例如以下:?

創(chuàng)建觸發(fā)器的一般語(yǔ)法是:

?

CREATE ? OR ? REPLACE ? TRIGGER ? [sachema.] trigger_name
{BEFORE
| AFTER}?
{ddl_event_list?
| ?database_event_list}
ON ?{? DATABASE ? | ? [schema.] SCHEMA ?}
[WHEN?condition]
PL
/ SQL_block? | ?CALL?procedure_name;

?

當(dāng)中: ddl_event_list:一個(gè)或多個(gè)DDL 事件,事件間用 OR 分開;

????????database_event_list:一個(gè)或多個(gè)數(shù)據(jù)庫(kù)事件,事件間用 OR 分開;

?

???????????系統(tǒng)事件觸發(fā)器既能夠建立在一個(gè)模式上,又能夠建立在整個(gè)數(shù)據(jù)庫(kù)上。當(dāng)建立在模式(SCHEMA)之上時(shí),僅僅有模式所指定用戶的DDL操作和它們所導(dǎo)致的錯(cuò)誤才激活觸發(fā)器, 默認(rèn)時(shí)為當(dāng)前用戶模式。當(dāng)建立在數(shù)據(jù)庫(kù)(DATABASE)之上時(shí),該數(shù)據(jù)庫(kù)全部用戶的DDL操作和他們所導(dǎo)致的錯(cuò)誤,以及數(shù)據(jù)庫(kù)的啟動(dòng)和關(guān)閉均可激活觸發(fā)器。要在數(shù)據(jù)庫(kù)之上建立觸發(fā)器時(shí),要求用戶具有ADMINISTER DATABASE TRIGGER權(quán)限。

?

以下給出系統(tǒng)觸發(fā)器的種類和事件出現(xiàn)的時(shí)機(jī)(前或后):

事件

同意的時(shí)機(jī)

說明

STARTUP

AFTER

啟動(dòng)數(shù)據(jù)庫(kù)實(shí)例之后觸發(fā)

SHUTDOWN

BEFORE

關(guān)閉數(shù)據(jù)庫(kù)實(shí)例之前觸發(fā)(非正常關(guān)閉不觸發(fā))

SERVERERROR

AFTER

數(shù)據(jù)庫(kù)server錯(cuò)誤發(fā)生之后觸發(fā)

LOGON

AFTER

成功登錄連接到數(shù)據(jù)庫(kù)后觸發(fā)

LOGOFF

BEFORE

開始斷開數(shù)據(jù)庫(kù)連接之前觸發(fā)

CREATE

BEFORE,AFTER

在運(yùn)行CREATE語(yǔ)句創(chuàng)建數(shù)據(jù)庫(kù)對(duì)象之前、之后觸發(fā)

DROP

BEFORE,AFTER

在運(yùn)行DROP語(yǔ)句刪除數(shù)據(jù)庫(kù)對(duì)象之前、之后觸發(fā)

ALTER

BEFORE,AFTER

在運(yùn)行ALTER語(yǔ)句更新數(shù)據(jù)庫(kù)對(duì)象之前、之后觸發(fā)

DDL

BEFORE,AFTER

在運(yùn)行大多數(shù)DDL語(yǔ)句之前、之后觸發(fā)

GRANT

BEFORE,AFTER

運(yùn)行GRANT語(yǔ)句授予權(quán)限之前、之后觸發(fā)

REVOKE

BEFORE,AFTER

運(yùn)行REVOKE語(yǔ)句收權(quán)限之前、之后觸犯發(fā)

RENAME

BEFORE,AFTER

運(yùn)行RENAME語(yǔ)句更改數(shù)據(jù)庫(kù)對(duì)象名稱之前、之后觸犯發(fā)

AUDIT / NOAUDIT

BEFORE,AFTER

運(yùn)行AUDIT NOAUDIT進(jìn)行審計(jì)或停止審計(jì)之前、之后觸發(fā)

?

?

8.2.4 系統(tǒng)觸發(fā)器事件屬性

?

事件屬性\事件

Startup/Shutdown

Servererror

Logon/Logoff

DDL

DML

事件名稱

ü*

ü*

ü*

ü*

數(shù)據(jù)庫(kù)名稱

ü*

?

?

?

?

數(shù)據(jù)庫(kù)實(shí)例號(hào)

ü*

?

?

?

?

錯(cuò)誤號(hào)

?

ü*

?

?

?

username

?

?

ü*

?

模式對(duì)象類型

?

?

?

ü*

模式對(duì)象名稱

?

?

?

ü*

?

?

?

?

ü*

?

除DML語(yǔ)句的列屬性外,其余事件屬性值可通過調(diào)用ORACLE定義的事件屬性函數(shù)來讀取。

函數(shù)名稱

數(shù)據(jù)類型

說??? 明

Ora_sysevent

VARCHAR2(20)

激活觸發(fā)器的事件名稱

Instance_num

NUMBER

數(shù)據(jù)庫(kù)實(shí)例名

Ora_database_name

VARCHAR2(50)

數(shù)據(jù)庫(kù)名稱

Server_error(posi)

NUMBER

錯(cuò)誤信息棧中posi指定位置中的錯(cuò)誤號(hào)

?

?

Is_servererror(err_number)

?

?

BOOLEAN

檢查err_number指定的錯(cuò)誤號(hào)是否在錯(cuò)誤信息棧中,假設(shè)在則返回TRUE,否則返回FALSE。在觸發(fā)器內(nèi)調(diào)用此函數(shù)能夠推斷是否發(fā)生指定的錯(cuò)誤。

Login_user

VARCHAR2(30)

登陸或注銷的username稱

Dictionary_obj_type

VARCHAR2(20)

DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象類型

Dictionary_obj_name

VARCHAR2(30)

DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象名稱

Dictionary_obj_owner

VARCHAR2(30)

DDL語(yǔ)句所操作的數(shù)據(jù)庫(kù)對(duì)象全部者名稱

Des_encrypted_password

VARCHAR2(2)

正在創(chuàng)建或改動(dòng)的經(jīng)過DES算法加密的用戶口令

?

例1: 創(chuàng)建觸發(fā)器,存放有關(guān)事件信息。

DESC ?ora_sysevent
DESC ?ora_login_user

-- 創(chuàng)建用于記錄事件用的表

CREATE ? TABLE ?ddl_event
(crt_date?
timestamp ? PRIMARY ? KEY ,
?event_name?
VARCHAR2 ( 20 ),?
?
user_name ? VARCHAR2 ( 10 ),
?obj_type?
VARCHAR2 ( 20 ),
?obj_name?
VARCHAR2 ( 20 ));

-- 創(chuàng)建觸犯發(fā)器
CREATE ? OR ? REPLACE ? TRIGGER ?tr_ddl
AFTER?DDL?
ON ? SCHEMA
BEGIN
???
INSERT ? INTO ?ddl_event? VALUES
???(systimestamp,ora_sysevent,?ora_login_user,?
????ora_dict_obj_type,?ora_dict_obj_name);
END ?tr_ddl;

?

例2: 創(chuàng)建登錄、退出觸發(fā)器。

?

CREATE ? TABLE ?log_event
(
user_name ? VARCHAR2 ( 10 ),
?address?
VARCHAR2 ( 20 ),?
?logon_date?
timestamp ,
?logoff_date?
timestamp );?

-- 創(chuàng)建登錄觸發(fā)器
CREATE ? OR ? REPLACE ? TRIGGER ?tr_logon
AFTER?LOGON?
ON ? DATABASE
BEGIN
???
INSERT ? INTO ?log_event?( user_name ,?address,?logon_date)
???
VALUES ?(ora_login_user,?ora_client_ip_address,?systimestamp);
END ?tr_logon;
-- 創(chuàng)建退出觸發(fā)器
CREATE ? OR ? REPLACE ? TRIGGER ?tr_logoff
BEFORE?LOGOFF?
ON ? DATABASE
BEGIN
???
INSERT ? INTO ?log_event?( user_name ,?address,?logoff_date)
???
VALUES ?(ora_login_user,?ora_client_ip_address,?systimestamp);
END ?tr_logoff;

?

8.2.5 使用觸發(fā)器謂詞

??? ORACLE 提供三個(gè)參數(shù)INSERTING, UPDATING,DELETING 用于推斷觸發(fā)了哪些操作。

謂詞

行為

INSERTING

假設(shè)觸發(fā)語(yǔ)句是 INSERT 語(yǔ)句,則為TRUE,否則為FALSE

UPDATING

假設(shè)觸發(fā)語(yǔ)句是 UPDATE語(yǔ)句,則為TRUE,否則為FALSE

DELETING

假設(shè)觸發(fā)語(yǔ)句是 DELETE 語(yǔ)句,則為TRUE,否則為FALSE

?

8.2.6 又一次編譯觸發(fā)器

假設(shè)在觸發(fā)器內(nèi)調(diào)用其他函數(shù)或過程,當(dāng)這些函數(shù)或過程被刪除或改動(dòng)后,觸發(fā)器的狀態(tài)將被標(biāo)識(shí)為無效。當(dāng)DML語(yǔ)句激活一個(gè)無效觸發(fā)器時(shí),ORACLE將又一次編譯觸發(fā)器代碼,假設(shè)編譯時(shí)發(fā)現(xiàn)錯(cuò)誤,這將導(dǎo)致DML語(yǔ)句運(yùn)行失敗。

在PL/SQL程序中能夠調(diào)用ALTER TRIGGER語(yǔ)句又一次編譯已經(jīng)創(chuàng)建的觸發(fā)器,格式為:???????????

ALTER ? TRIGGER ? [schema.] ?trigger_name?COMPILE? [?DEBUG]

?????? 當(dāng)中:DEBUG 選項(xiàng)要器編譯器生成PL/SQL 程序條使其所使用的調(diào)試代碼。

8.3?刪除和使能觸發(fā)器

l???????? 刪除觸發(fā)器:

DROP ? TRIGGER ?trigger_name;

當(dāng)刪除其它用戶模式中的觸發(fā)器名稱,須要具有DROP ANY TRIGGER系統(tǒng)權(quán)限,當(dāng)刪除建立在數(shù)據(jù)庫(kù)上的觸發(fā)器時(shí),用戶須要具有ADMINISTER DATABASE TRIGGER系統(tǒng)權(quán)限。

此外,當(dāng)刪除表或視圖時(shí),建立在這些對(duì)象上的觸發(fā)器也隨之刪除。?

l???????? 禁用或啟用觸發(fā)器

數(shù)據(jù)庫(kù)TRIGGER 的狀態(tài):

有效狀態(tài)(ENABLE):當(dāng)觸發(fā)事件發(fā)生時(shí),處于有效狀態(tài)的數(shù)據(jù)庫(kù)觸發(fā)器TRIGGER 將被觸發(fā)。

無效狀態(tài)(DISABLE):當(dāng)觸發(fā)事件發(fā)生時(shí),處于無效狀態(tài)的數(shù)據(jù)庫(kù)觸發(fā)器TRIGGER 將不會(huì)被觸發(fā),此時(shí)就跟沒有這個(gè)數(shù)據(jù)庫(kù)觸發(fā)器(TRIGGER) 一樣。

數(shù)據(jù)庫(kù)TRIGGER的這兩種狀態(tài)能夠互相轉(zhuǎn)換。格式為:

ALTER ?TIGGER?trigger_name? [DISABLE?|?ENABLE?] ;

-- 例: ALTER?TRIGGER?emp_view_delete?DISABLE;

???????????

???????????ALTER TRIGGER語(yǔ)句一次僅僅能改變一個(gè)觸發(fā)器的狀態(tài),而 ALTER TABLE 語(yǔ)句則一次可以改變與指定表相關(guān)的全部觸發(fā)器的使用狀態(tài) 。格式為:?????????????

ALTER ? TABLE ? [schema.] table_name?{ENABLE | DISABLE}? ALL ?TRIGGERS;

-- 例:使表 EMP? 上的全部 TRIGGER? 失效:
ALTER ? TABLE ?emp?DISABLE? ALL ?TRIGGERS;?

?

8.4?觸發(fā)器和數(shù)據(jù)字典

相關(guān)數(shù)據(jù)字典: USER_TRIGGERS ALL_TRIGGERS 、 DBA_TRIGGERS ?

SELECT ?TRIGGER_NAME,?TRIGGER_TYPE,?TRIGGERING_EVENT,
?TABLE_OWNER,?BASE_OBJECT_TYPE,?REFERENCING_NAMES,
?STATUS,?ACTION_TYPE
?
FROM ?user_triggers;

?

8.5?? 數(shù)據(jù)庫(kù)觸發(fā)器的應(yīng)用舉例

例1: 創(chuàng)建一個(gè)DML語(yǔ)句級(jí)觸發(fā)器,當(dāng)對(duì)emp表運(yùn)行INSERT, UPDATE, DELETE 操作時(shí),它自己主動(dòng)更新dept_summary 表中的數(shù)據(jù)。因?yàn)樵赑L/SQL塊中不能直接調(diào)用DDL語(yǔ)句,所以,利用ORACLE內(nèi)置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它運(yùn)行DDL語(yǔ)句創(chuàng)建觸發(fā)器。

?

CREATE ? TABLE ?dept_summary(
?Deptno?
NUMBER ( 2 ),
?Sal_sum?
NUMBER ( 9 ,? 2 ),
?Emp_count?
NUMBER );?

INSERT ? INTO ?dept_summary(deptno,?sal_sum,?emp_count)
?
SELECT ?deptno,? SUM (sal),? COUNT ( * )?
FROM ?emp?
GROUP ? BY ?deptno;

-- 創(chuàng)建一個(gè) PL/SQL 過程 disp_dept_summary
--
在觸發(fā)器中調(diào)用該過程顯示 dept_summary 標(biāo)中的數(shù)據(jù)。
CREATE ? OR ? REPLACE ? PROCEDURE ?disp_dept_summary
IS
?Rec?dept_summary
% ROWTYPE;
?
CURSOR ?c1? IS ? SELECT ? * ? FROM ?dept_summary;
BEGIN
?
OPEN ?c1;
?
FETCH ?c1? INTO ?REC;
?DBMS_OUTPUT.PUT_LINE(
'deptno????sal_sum????emp_count' );
?DBMS_OUTPUT.PUT_LINE(
'-------------------------------------' );
?
WHILE ?c1 % FOUND?LOOP
????DBMS_OUTPUT.PUT_LINE(RPAD(rec.deptno,?
6 ) ||
??????To_char(rec.sal_sum,?
'$999,999.99' ) ||
??????LPAD(rec.emp_count,?
13 ));
????
FETCH ?c1? INTO ?rec;
?
END ?LOOP;
?
CLOSE ?c1;
END ;
BEGIN
?DBMS_OUTPUT.PUT_LINE(
' 插入前 ' );
?Disp_dept_summary();
?DBMS_UTILITY.EXEC_DDL_STATEMENT(
'
????CREATE?OR?REPLACE?TRIGGER?trig1
??????AFTER?INSERT?OR?DELETE?OR?UPDATE?OF?sal?ON?emp
????BEGIN
??????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig1? 觸發(fā)器 …'');
??????DELETE?FROM?dept_summary;
??????INSERT?INTO?dept_summary(deptno,?sal_sum,?emp_count)
??????SELECT?deptno,?SUM(sal),?COUNT(*)?
??????FROM?emp?GROUP?BY?deptno;
????END;
?'
);


?
INSERT ? INTO ?dept(deptno,?dname,?loc)?
?
VALUES ( 90 ,?‘demo_dept’,?‘none_loc’);
?
INSERT ? INTO ?emp(ename,?deptno,?empno,?sal)
?
VALUES ( USER ,? 90 ,? 9999 ,? 3000 );

?DBMS_OUTPUT.PUT_LINE(
' 插入后 ' );
?Disp_dept_summary();

?
UPDATE ?emp? SET ?sal = 1000 ? WHERE ?empno = 9999 ;
?DBMS_OUTPUT.PUT_LINE(
' 改動(dòng)后 ' );
?Disp_dept_summary();

?
DELETE ? FROM ?emp? WHERE ?empno = 9999 ;
?
DELETE ? FROM ?dept? WHERE ?deptno = 90 ;

?DBMS_OUTPUT.PUT_LINE(
' 刪除后 ' );
?Disp_dept_summary();?
?DBMS_UTILITY.EXEC_DDL_STATEMENT(‘
DROP ? TRIGGER ?trig1’);
EXCEPTION
???
WHEN ?OTHERS? THEN
??????DBMS_OUTPUT.PUT_LINE(SQLCODE
|| '---' || SQLERRM);

END ;

?

例2: 創(chuàng)建DML語(yǔ)句行級(jí)觸發(fā)器。當(dāng)對(duì)emp表運(yùn)行INSERT, UPDATE, DELETE 操作時(shí),它自己主動(dòng)更新dept_summary 表中的數(shù)據(jù)。因?yàn)樵赑L/SQL塊中不能直接調(diào)用DDL語(yǔ)句,所以,利用ORACLE內(nèi)置包DBMS_UTILITY中的EXEC_DDL_STATEMENT過程,由它運(yùn)行DDL語(yǔ)句創(chuàng)建觸發(fā)器。

?

BEGIN
??DBMS_OUTPUT.PUT_LINE(
' 插入前 ' );
??Disp_dept_summary();
??DBMS_UTILITY.EXEC_DDL_STATEMENT(
????
'CREATE?OR?REPLACE?TRIGGER?trig2_update
??????AFTER?UPDATE?OF?sal?ON?emp
??????REFERENCING?OLD?AS?old_emp?NEW?AS?new_emp
??????FOR?EACH?ROW
??????WHEN?(old_emp.sal?!=?new_emp.sal)
????BEGIN
??????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2_update? 觸發(fā)器 …'');
??????DBMS_OUTPUT.PUT_LINE(''sal?
舊值: ''||?:old_emp.sal);
??????DBMS_OUTPUT.PUT_LINE(''sal?
新值: ''||?:new_emp.sal);
??????UPDATE?dept_summary
????????SET?sal_sum=sal_sum?+?:new_emp.sal?-?:old_emp.sal
????????WHERE?deptno?=?:new_emp.deptno;
????END;'

??);
??
??DBMS_UTILITY.EXEC_DDL_STATEMENT(
????
'CREATE?OR?REPLACE?TRIGGER?trig2_insert
??????AFTER?INSERT?ON?emp
??????REFERENCING?NEW?AS?new_emp
??????FOR?EACH?ROW
????DECLARE
??????I?NUMBER;
????BEGIN
??????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2_insert? 觸發(fā)器 …'');
??????SELECT?COUNT(*)?INTO?I?
??????FROM?dept_summary?WHERE?deptno?=?:new_emp.deptno;
??????IF?I?>?0?THEN
????????UPDATE?dept_summary?
????????SET?sal_sum=sal_sum+:new_emp.sal,
????????Emp_count=emp_count+1
????????WHERE?deptno?=?:new_emp.deptno;
??????ELSE
????????INSERT?INTO?dept_summary
????????VALUES?(:new_emp.deptno,?:new_emp.sal,?1);
??????END?IF;
????END;'

??);

??DBMS_UTILITY.EXEC_DDL_STATEMENT(
????
'CREATE?OR?REPLACE?TRIGGER?trig2_delete
??????AFTER?DELETE?ON?emp
??????REFERENCING?OLD?AS?old_emp
??????FOR?EACH?ROW
????DECLARE
??????I?NUMBER;
????BEGIN
??????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2_delete? 觸發(fā)器 …'');
??????SELECT?emp_count?INTO?I?
??????FROM?dept_summary?WHERE?deptno?=?:old_emp.deptno;
??????IF?I?>1?THEN
????????UPDATE?dept_summary?
????????SET?sal_sum=sal_sum?-?:old_emp.sal,
????????Emp_count=emp_count?-?1
????????WHERE?deptno?=?:old_emp.deptno;
??????ELSE
????????DELETE?FROM?dept_summary?WHERE?deptno?=?:old_emp.deptno;
??????END?IF;
????END;'

??);

??
INSERT ? INTO ?dept(deptno,?dname,?loc)?
????
VALUES ( 90 ,? 'demo_dept' ,? 'none_loc' );
??
INSERT ? INTO ?emp(ename,?deptno,?empno,?sal)
????
VALUES ( USER ,? 90 ,? 9999 ,? 3000 );
??
INSERT ? INTO ?emp(ename,?deptno,?empno,?sal)
????
VALUES ( USER ,? 90 ,? 9998 ,? 2000 );
??DBMS_OUTPUT.PUT_LINE(
' 插入后 ' );
??Disp_dept_summary();

??
UPDATE ?emp? SET ?sal? = ?sal * 1.1 ? WHERE ?deptno = 90 ;
??DBMS_OUTPUT.PUT_LINE(
' 改動(dòng)后 ' );
??Disp_dept_summary();

??
DELETE ? FROM ?emp? WHERE ?deptno = 90 ;
??
DELETE ? FROM ?dept? WHERE ?deptno = 90 ;
??DBMS_OUTPUT.PUT_LINE(
' 刪除后 ' );
??Disp_dept_summary();

??DBMS_UTILITY.EXEC_DDL_STATEMENT(
'DROP?TRIGGER?trig2_update' );
??DBMS_UTILITY.EXEC_DDL_STATEMENT(
'DROP?TRIGGER?trig2_insert' );
??DBMS_UTILITY.EXEC_DDL_STATEMENT(
'DROP?TRIGGER?trig2_delete' );
EXCEPTION
???
WHEN ?OTHERS? THEN
??????DBMS_OUTPUT.PUT_LINE(SQLCODE
|| '---' || SQLERRM);
END ;

?

例3: 利用ORACLE提供的條件謂詞INSERTING、UPDATING和DELETING創(chuàng)建與例2具有同樣功能的觸發(fā)器。

?

BEGIN
????DBMS_OUTPUT.PUT_LINE(
' 插入前 ' );
????Disp_dept_summary();
????DBMS_UTILITY.EXEC_DDL_STATEMENT(
????????
'CREATE?OR?REPLACE?TRIGGER?trig2
????????????AFTER?INSERT?OR?DELETE?OR?UPDATE?OF?sal
ON?emp
????????????REFERENCING?OLD?AS?old_emp?NEW?AS?new_emp
????????????FOR?EACH?ROW
????????DECLARE
????????????I?NUMBER;
????????BEGIN
????????????IF?UPDATING?AND?:old_emp.sal?!=?:new_emp.sal?THEN
????????????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2? 觸發(fā)器 …'');
????????????????DBMS_OUTPUT.PUT_LINE(''sal?
舊值: ''||?:old_emp.sal);
????????????????DBMS_OUTPUT.PUT_LINE(''sal?
新值: ''||?:new_emp.sal);
????????????????UPDATE?dept_summary
????????????????????SET?sal_sum=sal_sum?+?:new_emp.sal?-?:old_emp.sal
????????????????WHERE?deptno?=?:new_emp.deptno;
????????????ELSIF?INSERTING?THEN
????????????????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2 觸發(fā)器 …'');
????????????????SELECT?COUNT(*)?INTO?I?
????????FROM?dept_summary?
????????WHERE?deptno?=?:new_emp.deptno;
????????????????IF?I?>?0?THEN
????????????????????UPDATE?dept_summary?
??????????SET?sal_sum=sal_sum+:new_emp.sal,
??????????????Emp_count=emp_count+1
??????????WHERE?deptno?=?:new_emp.deptno;
????????????ELSE
??????????INSERT?INTO?dept_summary
????????????VALUES?(:new_emp.deptno,?:new_emp.sal,?1);
????????END?IF;
??????ELSE
????????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig2 觸發(fā)器 …'');
????????SELECT?emp_count?INTO?I?
????????FROM?dept_summary?WHERE?deptno?=?:old_emp.deptno;
??????IF?I?>?1?THEN
????????UPDATE?dept_summary?
????????SET?sal_sum=sal_sum?-?:old_emp.sal,
????????Emp_count=emp_count?-?1
????????WHERE?deptno?=?:old_emp.deptno;
??????ELSE
??????????DELETE?FROM?dept_summary?
??????????WHERE?deptno?=?:old_emp.deptno;
??????END?IF;
????END?IF;
????END;'

??);

??
INSERT ? INTO ?dept(deptno,?dname,?loc)?
????
VALUES ( 90 ,? 'demo_dept' ,? 'none_loc' );
??
INSERT ? INTO ?emp(ename,?deptno,?empno,?sal)
????
VALUES ( USER ,? 90 ,? 9999 ,? 3000 );
??
INSERT ? INTO ?emp(ename,?deptno,?empno,?sal)
????
VALUES ( USER ,? 90 ,? 9998 ,? 2000 );
??DBMS_OUTPUT.PUT_LINE(
' 插入后 ' );
??Disp_dept_summary();

??
UPDATE ?emp? SET ?sal? = ?sal * 1.1 ? WHERE ?deptno = 90 ;
??DBMS_OUTPUT.PUT_LINE(
' 改動(dòng)后 ' );
??Disp_dept_summary();

??
DELETE ? FROM ?emp? WHERE ?deptno = 90 ;
??
DELETE ? FROM ?dept? WHERE ?deptno = 90 ;
??DBMS_OUTPUT.PUT_LINE(
' 刪除后 ' );
??Disp_dept_summary();

??DBMS_UTILITY.EXEC_DDL_STATEMENT(
'DROP?TRIGGER?trig2' );
EXCEPTION
???
WHEN ?OTHERS? THEN
??????DBMS_OUTPUT.PUT_LINE(SQLCODE
|| '---' || SQLERRM);
END ;

?

例4: 創(chuàng)建INSTEAD OF 觸發(fā)器。首先創(chuàng)建一個(gè)視圖myview,因?yàn)樵撘晥D是復(fù)合查詢所產(chǎn)生的視圖,所以不能運(yùn)行DML語(yǔ)句。依據(jù)用戶對(duì)視圖所插入的數(shù)據(jù)推斷須要將數(shù)據(jù)插入到哪個(gè)視圖基表中,然后對(duì)該基表運(yùn)行插入操作。

?

DECLARE
????No?
NUMBER ;
????Name?
VARCHAR2 ( 20 );
BEGIN
????DBMS_UTILITY.EXEC_DDL_STATEMENT(
'
????????CREATE?OR?REPLACE?VIEW?myview?AS
????????????SELECT?empno,?ename,?''E''?type?FROM?emp
????????????UNION
????????????SELECT?dept.deptno,?dname,?''D''?FROM?dept
????'
);
????
--? 創(chuàng)建 INSTEAD?OF? 觸發(fā)器 trigger3;
????DBMS_UTILITY.EXEC_DDL_STATEMENT( '
????????CREATE?OR?REPLACE?TRIGGER?trig3
????????????INSTEAD?OF?INSERT?ON?myview
????????????REFERENCING?NEW?n
????????????FOR?EACH?ROW
????????DECLARE
????????????Rows?INTEGER;
????????BEGIN
????????????DBMS_OUTPUT.PUT_LINE(''
正在運(yùn)行 trig3 觸發(fā)器 …'');
????????????IF?:n.type?=?''D''?THEN
????????????????SELECT?COUNT(*)?INTO?rows
????????????????????FROM?dept?WHERE?deptno?=?:n.empno;
????????????????IF?rows?=?0?THEN
????????????????????DBMS_OUTPUT.PUT_LINE(''
dept 表中插入數(shù)據(jù) …'');
????????????????????INSERT?INTO?dept(deptno,?dname,?loc)
????????????????????????VALUES?(:n.empno,?:n.ename,?''none’’);
????????????????ELSE
????????????????????DBMS_OUTPUT.PUT_LINE(''
編號(hào)為 ''||?:n.empno||
?????????????????????''
的部門已存在,插入操作失??! '');
?????????????????END?IF;
????????????ELSE
????????????????SELECT?COUNT(*)?INTO?rows
????????????????????FROM?emp?WHERE?empno?=?:n.empno;
????????????????IF?rows?=?0?THEN
????????????????????DBMS_OUTPUT.PUT_LINE('
emp 表中插入數(shù)據(jù) …’’);
????????????????????
INSERT ? INTO ?emp(empno,?ename)
????????????????????????
VALUES (:n.empno,?:n.ename);
????????????????
ELSE
????????????????????DBMS_OUTPUT.PUT_LINE(
'' 編號(hào)為 '' || ?:n.empno ||
??????????????????????
'' 的人員已存在,插入操作失敗 ! '' );
????????????????
END ? IF ;
????????????
END ? IF ;
????????
END ;
????
');

????INSERT?INTO?myview?VALUES?(70,?'
demo ',?' D ');
????INSERT?INTO?myview?VALUES?(9999,?USER,?'
E ');
????SELECT?deptno,?dname?INTO?no,?name?FROM?dept?WHERE?deptno=70;
????DBMS_OUTPUT.PUT_LINE('
員工編號(hào): '||TO_CHAR(no)||' 姓名: '||name);
????SELECT?empno,?ename?INTO?no,?name?FROM?emp?WHERE?empno=9999;
????DBMS_OUTPUT.PUT_LINE('
部門編號(hào): '||TO_CHAR(no)||' 姓名: '||name);
??DELETE?FROM?emp?WHERE?empno=9999;
??DELETE?FROM?dept?WHERE?deptno=70;
????DBMS_UTILITY.EXEC_DDL_STATEMENT('
DROP ? TRIGGER ?trig3 ');
END;

?

例5: 利用ORACLE事件屬性函數(shù),創(chuàng)建一個(gè)系統(tǒng)事件觸發(fā)器。首先創(chuàng)建一個(gè)事件日志表eventlog,由它存儲(chǔ)用戶在當(dāng)前數(shù)據(jù)庫(kù)中所創(chuàng)建的數(shù)據(jù)庫(kù)對(duì)象,以及用戶的登陸和注銷、數(shù)據(jù)庫(kù)的啟動(dòng)和關(guān)閉等事件,之后創(chuàng)建trig4_ddl、trig4_before和trig4_after觸發(fā)器,它們調(diào)用事件屬性函數(shù)將各個(gè)事件記錄到eventlog數(shù)據(jù)表中。

?

BEGIN
????
--? 創(chuàng)建用于記錄事件日志的數(shù)據(jù)表
????DBMS_UTILITY.EXEC_DDL_STATEMENT( '
????????CREATE?TABLE?eventlog(
????????????Eventname?VARCHAR2(20)?NOT?NULL,
????????????Eventdate?date?default?sysdate,
????????????Inst_num?NUMBER?NULL,
????????????Db_name?VARCHAR2(50)?NULL,
????????????Srv_error?NUMBER?NULL,
????????????Username?VARCHAR2(30)?NULL,
????????????Obj_type?VARCHAR2(20)?NULL,
????????????Obj_name?VARCHAR2(30)?NULL,
????????????Obj_owner?VARCHAR2(30)?NULL
????????)
????'
);

????
--? 創(chuàng)建 DDL 觸發(fā)器 trig4_ddl
????DBMS_UTILITY.EXEC_DDL_STATEMENT( '
????????CREATE?OR?REPLACE?TRIGGER?trig4_ddl
????????????AFTER?CREATE?OR?ALTER?OR?DROP?
ON?DATABASE
????????DECLARE
????????????Event?VARCHAR2(20);
????????????Typ?VARCHAR2(20);
????????????Name?VARCHAR2(30);
????????????Owner?VARCHAR2(30);
????????BEGIN
????????????--?
讀取 DDL 事件屬性
????????????Event?:=?SYSEVENT;
????????????Typ?:=?DICTIONARY_OBJ_TYPE;
????????????Name?:=?DICTIONARY_OBJ_NAME;
????????????Owner?:=?DICTIONARY_OBJ_OWNER;
????????????--
將事件屬性插入到事件日志表中
????????????INSERT?INTO?scott.eventlog(eventname,?obj_type,?obj_name,?obj_owner)
????????????????VALUES(event,?typ,?name,?owner);
????????END;
????'
);

????
--? 創(chuàng)建 LOGON 、 STARTUP SERVERERROR? 事件觸發(fā)器
????DBMS_UTILITY.EXEC_DDL_STATEMENT( '
????????CREATE?OR?REPLACE?TRIGGER?trig4_after
????????????AFTER?LOGON?OR?STARTUP?OR?SERVERERROR?
??????ON?DATABASE
????????DECLARE
????????????Event?VARCHAR2(20);
????????????Instance?NUMBER;
????????????Err_num?NUMBER;
????????????Dbname?VARCHAR2(50);
????????????User?VARCHAR2(30);
????????BEGIN
????????????Event?:=?SYSEVENT;
????????????IF?event?=?''LOGON''?THEN
????????????????User?:=?LOGIN_USER;
????????????????INSERT?INTO?eventlog(eventname,?username)
????????????????????VALUES(event,?user);
????????????ELSIF?event?=?''SERVERERROR''?THEN
????????????????Err_num?:=?SERVER_ERROR(1);
????????????????INSERT?INTO?eventlog(eventname,?srv_error)
????????????????????VALUES(event,?err_num);
????????????ELSE
????????????????Instance?:=?INSTANCE_NUM;
????????????????Dbname?:=?DATABASE_NAME;
????????????????INSERT?INTO?eventlog(eventname,?inst_num,?db_name)
????????????????????VALUES(event,?instance,?dbname);
??????END?IF;
????END;
??'
);

??
--? 創(chuàng)建 LOGOFF SHUTDOWN? 事件觸發(fā)器
??DBMS_UTILITY.EXEC_DDL_STATEMENT( '
????CREATE?OR?REPLACE?TRIGGER?trig4_before
??????BEFORE?LOGOFF?OR?SHUTDOWN?
??????ON?DATABASE
????DECLARE
??????Event?VARCHAR2(20);
??????Instance?NUMBER;
??????Dbname?VARCHAR2(50);
??????User?VARCHAR2(30);
????BEGIN
??????Event?:=?SYSEVENT;
??????IF?event?=?''LOGOFF''?THEN
????????User?:=?LOGIN_USER;
????????INSERT?INTO?eventlog(eventname,?username)
??????????VALUES(event,?user);
??????ELSE
????????Instance?:=?INSTANCE_NUM;
????????Dbname?:=?DATABASE_NAME;
????????INSERT?INTO?eventlog(eventname,?inst_num,?db_name)
??????????VALUES(event,?instance,?dbname);
??????END?IF;
????END;
??'
);
END ;

CREATE ? TABLE ?mydata(mydate? NUMBER );
CONNECT?SCOTT
/ TIGER

COL?eventname?FORMAT?A10
COL?eventdate?FORMAT?A12
COL?username?FORMAT?A10
COL?obj_type?FORMAT?A15
COL?obj_name?FORMAT?A15
COL?obj_owner?FORMAT?A10
SELECT ?eventname,?eventdate,?obj_type,?obj_name,?obj_owner,?username,?Srv_error
??
FROM ?eventlog;

DROP ? TRIGGER ?trig4_ddl;
DROP ? TRIGGER ?trig4_before;
DROP ? TRIGGER ?trig4_after;
DROP ? TABLE ?eventlog;
DROP ? TABLE ?mydata;

?

8.6?? 數(shù)據(jù)庫(kù)觸發(fā)器的應(yīng)用實(shí)例

用戶能夠使用數(shù)據(jù)庫(kù)觸發(fā)器實(shí)現(xiàn)各種功能:

l???????? 復(fù)雜的審計(jì)功能;

例:將EMP 表的變化情況記錄到AUDIT_TABLE和AUDIT_TABLE_VALUES中。

?

CREATE ? TABLE ?audit_table(
????Audit_id?????
NUMBER ,
????
User_name ? VARCHAR2 ( 20 ),
????Now_time?DATE,
????Terminal_name?
VARCHAR2 ( 10 ),
????Table_name?
VARCHAR2 ( 10 ),
????Action_name?
VARCHAR2 ( 10 ),
????Emp_id?
NUMBER ( 4 ));

CREATE ? TABLE ?audit_table_val(
????Audit_id?
NUMBER ,
????Column_name?
VARCHAR2 ( 10 ),
????Old_val?
NUMBER ( 7 , 2 ),
????New_val?
NUMBER ( 7 , 2 ));

CREATE ?SEQUENCE?audit_seq
????START?
WITH ? 1000
????INCREMENT?
BY ? 1
????NOMAXVALUE
????NOCYCLE?NOCACHE;

CREATE ? OR ? REPLACE ? TRIGGER ?audit_emp
????AFTER?
INSERT ? OR ? UPDATE ? OR ? DELETE ? ON ?emp
????
FOR ?EACH?ROW
DECLARE
????Time_now?DATE;
????Terminal?
CHAR ( 10 );
BEGIN ?
????Time_now:
= sysdate;
????Terminal:
= USERENV( 'TERMINAL' );
????
IF ?INSERTING? THEN
????????
INSERT ? INTO ?audit_table
????
VALUES (audit_seq.NEXTVAL,? user ,?time_now,?
???????????terminal,?
'EMP' ,? 'INSERT' ,?:new.empno);
????ELSIF?DELETING?
THEN
????????
INSERT ? INTO ?audit_table
????
VALUES (audit_seq.NEXTVAL,? user ,?time_now,?
???????????terminal,?
'EMP' ,? 'DELETE' ,?:old.empno);
????
ELSE
????????
INSERT ? INTO ?audit_table
????
VALUES (audit_seq.NEXTVAL,? user ,?time_now,?
???????????terminal,?
'EMP' ,? 'UPDATE' ,?:old.empno);
????????
IF ?UPDATING( 'SAL' )? THEN
????????????
INSERT ? INTO ?audit_table_val
????????????????
VALUES (audit_seq.CURRVAL,? 'SAL' ,?:old.sal,?:new.sal);
????????
ELSE ?UPDATING( 'DEPTNO' )?
????????????
INSERT ? INTO ?audit_table_val
????????????????
VALUES (audit_seq.CURRVAL,? 'DEPTNO' ,?:old.deptno,?:new.deptno);
????????
END ? IF ;
????
END ? IF ;
END ;

?

l???????? 增強(qiáng)數(shù)據(jù)的完整性管理;

例:改動(dòng)DEPT表的DEPTNO列時(shí),同一時(shí)候把EMP表中對(duì)應(yīng)的DEPTNO也作對(duì)應(yīng)的改動(dòng);

?

CREATE ?SEQUENCE?update_sequence?
????INCREMENT?
BY ? 1
????START?
WITH ? 1000
????MAXVALUE?
5000 ?CYCLE;

ALTER ? TABLE ?emp
????
ADD ?update_id? NUMBER ;

CREATE ? OR ? REPLACE ?PACKAGE?integritypackage? AS
????Updateseq?
NUMBER ;
END ?integritypackage;

CREATE ? OR ? REPLACE ?PACKAGE?BODY?integritypackage? AS
END ?integritypackage;

CREATE ? OR ? REPLACE ? TRIGGER ?dept_cascade1
????BEFORE?
UPDATE ? OF ?deptno? ON ?dept
DECLARE ?
????
Dummy ? NUMBER ;
BEGIN ?
????
SELECT ?update_sequence.NEXTVAL? INTO ? dummy ? FROM ?dual;
????Integritypackage.updateseq:
= dummy ;
END ;

CREATE ? OR ? REPLACE ? TRIGGER ?dept_cascade2
????AFTER?
DELETE ? OR ? UPDATE ? OF ?deptno? ON ?dept
????
FOR ?EACH?ROW
BEGIN
????
IF ?UPDATING? THEN
????????
UPDATE ?emp? SET ?deptno = :new.deptno,?
?????update_id
= integritypackage.updateseq
????????
WHERE ?emp.deptno = :old.deptno? AND ?update_id? IS ? NULL ;
????
END ? IF ;
????
IF ?DELETING? THEN
????????
DELETE ? FROM ?emp
????????????
WHERE ?emp.deptno = :old.deptno;
????
END ? IF ;
END ;

CREATE ? OR ? REPLACE ? TRIGGER ?dept_cascade3
????AFTER?
UPDATE ? OF ?deptno? ON ?dept?
BEGIN
????
UPDATE ?emp? SET ?update_id = NULL
????????
WHERE ?update_id = integritypackage.updateseq;
END ;

SELECT ? * ? FROM ?EMP? ORDER ? BY ?DEPTNO;
UPDATE ?dept? SET ?deptno = 25 ? WHERE ?deptno = 20 ;

?

l???????? 幫助實(shí)現(xiàn)安全控制;

例:保證對(duì)EMP表的改動(dòng)僅在工作日的工作時(shí)間;

?

CREATE ? TABLE ?company_holidays( day ?DATE);

INSERT ? INTO ?company_holidays?
????
VALUES (sysdate);
INSERT ? INTO ?company_holidays?
VALUES (TO_DATE( '21-10 -01' ,? 'DD-MON-YY' ));

CREATE ? OR ? REPLACE ? TRIGGER ?emp_permit_change
????BEFORE?
INSERT ? OR ? DELETE ? OR ? UPDATE ? ON ?emp
DECLARE
????
Dummy ? NUMBER ;
????Not_on_weekends?EXCEPTION;
????Not_on_holidays?EXCEPTION;
????Not_working_hours?EXCEPTION;
BEGIN
????
/*?check?for?weekends?*/
IF ?TO_CHAR(SYSDATE,? 'DAY' )? IN ?( ' 星期六 ' ,? ' 星期日 ' )? THEN
????RAISE?not_on_weekends;
END ? IF ;
????
/*?check?for?company?holidays?*/
SELECT ? COUNT ( * )? INTO ? dummy ? FROM ?company_holidays
????
WHERE ?TRUNC( day ) = TRUNC(SYSDATE);
IF ? dummy ? > 0 ? THEN
????RAISE?not_on_holidays;
END ? IF ;
????
/*?check?for?work?hours(8:00?AM?to?18:00?PM?*/
IF ?(TO_CHAR(SYSDATE, 'HH24' ) < 8 ? OR ?TO_CHAR(SYSDATE,? 'HH24' ) > 18 )? THEN
??RAISE?not_working_hours;
END ? IF ;
EXCEPTION
??
WHEN ?not_on_weekends? THEN
????RAISE_APPLICATION_ERROR(
- 20324 ,?
'May?not?change?employee?table?during?the?weekends' );?
??
WHEN ?not_on_holidays? THEN
????RAISE_APPLICATION_ERROR(
- 20325 ,?
'May?not?change?employee?table?during?a?holiday' );?
??
WHEN ?not_working_hours? THEN
????RAISE_APPLICATION_ERROR(
- 20326 ,?
'May?not?change?employee?table?during?no_working?hours' );?
END ;

?


ORACLE觸發(fā)器具體解釋


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。?!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 国产精品女在线观看 | 91国内在线国内在线播放 | 欧美国产成人免费观看永久视频 | 久久狠狠第一麻豆婷婷天天 | 中文字幕一区二区三区 精品 | 国产精品午夜在线观看 | 久久机热一这里只精品 | 欧美另类色| 天天天天躁天天天天碰 | 国产日韩亚洲欧洲一区二区三区 | 国产精品国产精品国产专区不卡 | 四影虎库最新2021 | 日日噜噜噜夜夜爽爽狠狠 | 我我色综合 | 久久91精品国产91久久跳舞 | 亚洲乱亚洲乱妇无码 | 亚洲精品一区二区三区在 | 免费观看成人碰视频公开 | 国产性较精品视频免费 | 中文国产成人精品少久久 | 99久久精品国产高清一区二区 | 国产午夜亚洲精品国产 | 亚洲精品日韩精品一区 | 一级片亚洲| 福利不卡| 九九99靖品 | 香蕉视频97| 26uuu精品一区二区 | 日日日日人人人夜夜夜2017 | 亚洲精品久久成人福利 | 国产精品久久久久久亚洲伦理 | 亚洲一区二区三区福利在线 | 日本视频一区二区三区 | 2020国产免费久久精品99 | 日本一级一片免费 | 亚洲 欧美 日韩 在线 | 一本色道久久88加勒比—综合 | 99久久综合狠狠综合久久 | 亚洲成年人视频 | 99久久精品国产国产毛片 | 亚洲欧美精品天堂久久综合一区 |