1. 程式人生 > >Oracle入門(十四.22)之建立DDL和資料庫事件觸發器

Oracle入門(十四.22)之建立DDL和資料庫事件觸發器

一、什麼是DDL和資料庫事件觸發器?

DDL語句觸發DDL觸發器:CREATE,ALTER或DROP。
資料庫事件觸發器由資料庫中的非SQL事件觸發,例如:
•使用者連線到資料庫或與資料庫斷開連線。
•DBA啟動或關閉資料庫。

•使用者會話中引發了特定的異常。

(1)在DDL語句中建立觸發器語法

•ON DATABASE在資料庫中的所有模式上觸發DDL

•ON SCHEMA僅針對您自己的模式中的物件觸發DDL

CREATE [OR REPLACE] TRIGGER trigger_name
Timing
[ddl_event1 [OR ddl_event2 OR ...]]
ON {DATABASE|SCHEMA}
trigger_body

(2)DDL觸發器的示例

每次在模式中建立新的資料庫物件時,都希望寫入日誌記錄:

CREATE OR REPLACE TRIGGER log_create_trigg
AFTER CREATE ON SCHEMA
BEGIN
 INSERT INTO log_table
 VALUES (USER, SYSDATE);
END;
只要有任何(型別)的物件被建立,觸發器就會觸發。 您不能建立引用特定資料庫物件的DDL觸發器。

(3)DDL觸發器的第二個例子

防止從模式中刪除任何物件。

CREATE OR REPLACE TRIGGER prevent_drop_trigg
BEFORE DROP ON SCHEMA
BEGIN
 RAISE_APPLICATION_ERROR (-20203, 'Attempted drop – failed');
END;

只要有任何(型別)的物件被刪除,觸發器就會觸發。 同樣,您不能建立引用特定資料庫物件的DDL觸發器。

(4)在資料庫事件語法上建立觸發器

•ON DATABASE觸發資料庫中所有會話的事件觸發器。
•ON SCHEMA僅為您自己的會話觸發觸發器。
CREATE [OR REPLACE] TRIGGER trigger_name
timing
[database_event1 [OR database_event2 OR ...]]
ON {DATABASE|SCHEMA}
trigger_body

二、LOGON、LOGOFF和SERVERERROR

示例1:LOGON和LOGOFF觸發器

CREATE OR REPLACE TRIGGER logon_trig
AFTER LOGON ON SCHEMA
BEGIN
INSERT INTO log_trig_table(user_id,log_date,action)
 VALUES (USER, SYSDATE, 'Logging on');
END;
CREATE OR REPLACE TRIGGER logoff_trig
BEFORE LOGOFF ON SCHEMA
BEGIN
INSERT INTO log_trig_table(user_id,log_date,action)
 VALUES (USER, SYSDATE, 'Logging off');
END;

示例2:SERVERERROR觸發器
想保留會話中發生的任何ORA-00942錯誤的日誌:

CREATE OR REPLACE TRIGGER servererror_trig
AFTER SERVERERROR ON SCHEMA
BEGIN
IF (IS_SERVERERROR (942)) THEN
 INSERT INTO error_log_table ...
END IF;
END;

三、觸發器中的CALL語句

沒有結束;語句,並且在CALL語句結尾處沒有分號。

語法:

CREATE [OR REPLACE] TRIGGER trigger_name
timing
event1 [OR event2 OR event3]
ON table_name
[REFERENCING OLD AS old | NEW AS new]
[FOR EACH ROW]
[WHEN condition]
CALL procedure_name

例子:

CREATE OR REPLACE TRIGGER log_employee
BEFORE INSERT ON EMPLOYEES
 CALL log_execution

四、突變表和行觸發器

    突變表是一個當前正在由DML語句修改的表。

    行觸發器不能從變異表中選擇,因為它會看到不一致的資料集(當觸發器嘗試讀取資料時,表中的資料將會改變)。 但是,如果需要,行觸發器可以從不同的表中進行選擇。

此限制不適用於DML語句觸發器,僅適用於DML行觸發器。

突變表:例子

CREATE OR REPLACE TRIGGER check_salary
 BEFORE INSERT OR UPDATE OF salary, job_id ON employees
 FOR EACH ROW
DECLARE
 v_minsalary employees.salary%TYPE;
 v_maxsalary employees.salary%TYPE;
BEGIN
 SELECT MIN(salary), MAX(salary)
     INTO v_minsalary, v_maxsalary
     FROM employees
 WHERE job_id = :NEW.job_id;
 IF :NEW.salary < v_minsalary OR
     :NEW.salary > v_maxsalary THEN
     RAISE_APPLICATION_ERROR(-20505,'Out of range');
 END IF;
END;
UPDATE employees
SET salary = 3400
WHERE last_name = 'Davies';
出錯:
ORA-04091: table USVA_TEST_SQL01_T01_EMPLOYEES is mutating,
trigger/function may not
see it
ORA-06512: at “USVA_TEST_SQL01_T01.CHECK_SALARY”, line 5
ORA-04088: error during execution of trigger
‘USVA_TEST_SQL01_T01.CHECK_SALARY’
3. WHERE last_name – ‘Davies’;

五、觸發器的更多可能用途

    不應該建立觸發器來執行某些可以通過其他方式輕鬆完成的操作,例如通過檢查約束或適當的物件許可權。 但是有時你必須建立一個觸發器,因為沒有其他方法可以做需要的事情。
    以下示例只顯示了必須建立觸發器的三種情況。 還有更多!

(1)第一個例子

資料庫安全性(誰可以做什麼)通常由系統和物件許可權控制。 例如,使用者SCOTT需要更新EMPLOYEES行:

GRANT UPDATE ON employees TO scott;

但是,SCOTT被允許這樣做時,單憑許可權無法控制。 為此,我們需要一個觸發器:

CREATE OR REPLACE TRIGGER weekdays_emp
 BEFORE UPDATE ON employees
BEGIN
IF (TO_CHAR (SYSDATE, 'DY') IN ('SAT','SUN')) THEN
 RAISE_APPLICATION_ERROR(-20506,'You may only change data during normal business hours.');
END IF;
END;

(2)第二個例子

資料庫完整性(允許DML)通常由約束條件控制。 例如,每個員工的工資必須至少為500美元:

ALTER TABLE employees ADD CONSTRAINT ck_salary CHECK (salary >= 500);

如果一條業務規則指出員工的薪水可以提高但不降低,這個限制並不能阻止員工的薪水從700美元降低到600美元。 為此,我們需要一個行觸發器。此程式碼顯示在下一張幻燈片中。

現在我們不再需要約束了。

CREATE OR REPLACE TRIGGER check_salary
 BEFORE UPDATE OF salary ON employees
 FOR EACH ROW
 WHEN (NEW.salary < OLD.salary
 OR NEW.salary < 500)
BEGIN
 RAISE_APPLICATION_ERROR (-20508,
 'Do not decrease salary.');
END;

(3)第三個例子

您需要建立一個顯示部門總工資單的報表。 你可以宣告和使用這個遊標:

...
CURSOR tot_sals IS
 SELECT SUM(salary)
 FROM employees
 WHERE department_id = p_dept_id;
...
但是,如果在一個大型組織中,該部門有10,000名員工呢? 從EMPLOYEES表中抽取10,000行可能太慢。 下面展示了一個更快的方法來做到這一點。

首先,我們在DEPARTMENTS表中新增一個新列以儲存每個部門的總工資單:

ALTER TABLE DEPARTMENTS ADD (total_salary NUMBER(12,2));

接下來,只填寫當前總工資單的這一欄:

UPDATE departments d
 SET total_salary = (SELECT SUM(salary) FROM employees
 WHERE department_id = d.department_id);

現在,我們必須在更改工資時保持這一新列。 這是通過使用DML行觸發器完成的。

CREATE OR REPLACE PROCEDURE increment_salary
 (p_id IN NUMBER, p_new_sal IN NUMBER) IS
BEGIN
 UPDATE departments
 SET total_salary = total_salary + NVL(p_new_sal,0)
 WHERE department_id = p_id;
END increment_salary;
CREATE OR REPLACE TRIGGER compute_salary
AFTER INSERT OR UPDATE OF salary OR DELETE
ON employees FOR EACH ROW
BEGIN
IF DELETING THEN increment_salary
 (:OLD.department_id,(:OLD.salary * -1));
ELSIF UPDATING THEN increment_salary
 (:NEW.department_id,(:NEW.salary - :OLD.salary));
ELSE increment_salary
 (:NEW.department_id,:NEW.salary);
END IF;
END;