※DML trigger針對增刪改的之前或之後,自動執行一些程式,分成表級和行級
※表級DML trigger
※針對表的trigger
CREATE OR REPLACE TRIGGER trigger_test BEFORE INSERT OR DELETE ON DEPT DECLARE xxx_min NUMBER; xxx_week NUMBER; BEGIN SELECT TO_NUMBER(TRIM(TO_CHAR(SYSDATE, 'D'))), TO_NUMBER(TRIM(TO_CHAR(SYSDATE, 'HH24MI'))) INTO xxx_week, xxx_min FROM DUAL; IF xxx_week = 1 OR xxx_week = 7 THEN RAISE_APPLICATION_ERROR(-20000, '星期六日不允許更動DEPT表'); ELSIF xxx_min < 900 OR xxx_min > 1830 THEN RAISE_APPLICATION_ERROR(-20001, '非上班時間不允許更動DEPT表'); END IF; END;
※執行「INSERT INTO DEPT VALUES(80, 'a', 'b');」因為是BEFORE,所以會在執行insert之前就去判斷能不能新增
※1代表星期日,7代表星期六
※針對表裡的欄位trigger
CREATE OR REPLACE TRIGGER trigger_test BEFORE UPDATE OF LOC ON DEPT DECLARE xxx_hour NUMBER; BEGIN SELECT TO_NUMBER(TRIM(TO_CHAR(SYSDATE, 'HH24'))) INTO xxx_hour FROM DUAL; IF xxx_hour >= 12 THEN RAISE_APPLICATION_ERROR(-20001, '12點之後不允許修改DEPT表的LOC欄位'); END IF; END;
※因為是針對DEPT表的LOC欄位,所以「UPDATE DEPT SET DNAME = 'xxx' WHERE DEPTNO = 80;」可以成功修改,但「UPDATE DEPT SET LOC = 'ooo' WHERE DEPTNO = 80;」如果過12點就會跳出trigger
※子事務
※trigger裡也可以有事務※新增一張表做練習
CREATE TABLE after_emp_sal( EMPNO NUMBER(4), ENMAE VARCHAR2(10), SAL NUMBER(7,2), COMM NUMBER(7,2), AFTER_SAL NUMBER(7,2), CONSTRAINT AFTER_PK PRIMARY KEY(EMPNO) );
※這張表是在做EMP的SAL(薪水)和COMM(佣金)欄位,加總後判斷多少錢,然後依加薪的程度有所不同,最後新增到這張表
※先不開啟子事務
CREATE OR REPLACE TRIGGER add_sal AFTER INSERT OR UPDATE OF SAL, COMM ON EMP DECLARE -- PRAGMA AUTONOMOUS_TRANSACTION; CURSOR xxx_cur IS SELECT * FROM EMP; xxx_emp EMP%ROWTYPE; xxx_sal EMP.SAL%TYPE; xxx_after_sal after_emp_sal.after_sal%type; BEGIN FOR xxx_emp IN xxx_cur LOOP xxx_sal := NVL(xxx_emp.sal, 0) + NVL(xxx_emp.comm, 0); IF xxx_sal < 2000 THEN xxx_after_sal := xxx_sal * 1.3; ELSIF xxx_sal <= 3000 THEN xxx_after_sal := xxx_sal * 1.2; ELSIF xxx_sal <= 4000 THEN xxx_after_sal := xxx_sal * 1.1; END IF; INSERT INTO after_emp_sal values( xxx_emp.empno, xxx_emp.ename, xxx_emp.sal, xxx_emp.comm, xxx_after_sal ); END LOOP; -- COMMIT; END;
※執行「INSERT INTO EMP (empno, ename, sal, comm) VALUES(8888, 'xxx', 2400, 200);」之後就會發現after_emp_sal已經新增了,也包含了8888這筆,但如果開啟另外一個session,就會發現是空的,因為沒有COMMIT,所以COMMIT或ROLLBACK後,就會全看的見或全看不見
※如果只開啟trigger的COMMIT,就會出ORA-04092:cannot COMMIT in a trigger錯誤訊息,說trigger裡,不能有COMMIT,這時再開啟PRAGMA那行的子事務就可以了
※兩者最大的差異在8888新增的這一筆,如果是用子事務,就不會包括
※如果有子事務,然後把INSERT那筆rollback後,after_emp_sal還是有資料(當然沒有8888,要等下次再做一次才有)
※行級DML trigger
只有加上FOR EACH ROW才表示行級DML trigger,也才可以使用:new、:old官網有寫:new和:old的表,新增的:old和刪除的:new都是null
※新增測試
CREATE OR REPLACE TRIGGER line_trig_add BEFORE INSERT ON DEPT FOR EACH ROW DECLARE xxx_count NUMBER; xxx_deptno NUMBER; BEGIN SELECT COUNT(*) INTO xxx_count FROM DEPT WHERE :new.loc IN( SELECT LOC FROM DEPT ); IF xxx_count = 0 THEN RAISE_APPLICATION_ERROR(-20000, '公司沒有這個地點!'); ELSE xxx_deptno := :new.deptno; IF SUBSTR(xxx_deptno, LENGTH(xxx_deptno), 1) != 0 OR xxx_deptno <= 0 THEN RAISE_APPLICATION_ERROR(-20001, '部門號必需是10的倍數!'); END IF; END IF; END;
※INSERT INTO DEPT VALUES(80, 'xxx', 'ooo');
INSERT INTO DEPT VALUES(62, 'xxx', 'CHICAGO');
以上兩條分別出現不一樣的trigger
※修改測試
CREATE OR REPLACE TRIGGER line_trig_update BEFORE UPDATE OF SAL ON EMP FOR EACH ROW DECLARE BEGIN IF ABS(:new.sal - :old.sal) >= 1000 THEN RAISE_APPLICATION_ERROR(-20000, '加薪太過!'); END IF; END;
※如果加薪幅度超過1000就會跳出trigger
※刪除測試
CREATE OR REPLACE TRIGGER line_trig_delete BEFORE DELETE ON DEPT FOR EACH ROW DECLARE BEGIN IF :old.deptno = 10 THEN RAISE_APPLICATION_ERROR(-20000, '無法刪除10部門'); END IF; END;
※只有刪除10部門才會跳出trigger
※注意事項
1.宣告ddd DEPT%ROWTYPE;然後使用ddd := :new;是不可以的,雖然:new或:old很像%ROWTYPE,但是是不可以的
2.宣告ooo NUMBER;
然後使用ooo := :old.sal;這也是不行的,原始值是不能被修改的,但:new是可以的,如上面的新增就有用到
3.上面的程式碼是我自己run成功的結果貼上去的,有一次我自己打開PL/SQL Developer,然後打開一個新的Test Window,然後複製我成功的程式碼,居然出現「ORA-01036: illegal variable name/number」,debug後發現是:new、:old的錯,也就是它認為我不能有「:」開頭的變數,見鬼了,後來我try了好久,發現有這種「:」開頭的,可以先把它刪除,變成new和old然後編譯就會過了,但當然是不能run,所以我去trigger按右鍵Edit或在command輸入edit trigger名稱就會跳出來,然後再將「:」加上去,最後編譯就可以了
4.編譯完trigger後,就不能更改別的表了,如ON DEPT,就不能換成別的,否則會出現「ORA-04095:trigger 'trigger名稱' already exists on another table, cannot replace it」的錯,只能先Drop這個trigger或重新寫一支trigger,名稱不一樣就ok
※自增主鍵
Oracle12c有提供和其他家資料庫一樣,不用再做Sequence,以下先說不是12c時的做法※建立兩張表做練習
CREATE SEQUENCE ttt_sequence; CREATE TABLE trig_test_table( id NUMBER, name VARCHAR2(10), CONSTRAINT ttt_pk PRIMARY KEY(id) ); CREATE TABLE trig_test_table_copy AS SELECT * FROM trig_test_table WHERE 1=0;
※where 1=0是因為假設trig_test_table有10筆資料,而因為where條件不成立,所以只會複製表的結構而已
※trigger
CREATE OR REPLACE TRIGGER trigger_test BEFORE INSERT ON trig_test_table_copy FOR EACH ROW DECLARE xxx_ttt trig_test_table%ROWTYPE; BEGIN DELETE FROM trig_test_table_copy; SELECT ttt_sequence.NEXTVAL INTO :new.id FROM DUAL; xxx_ttt.id := :new.id; xxx_ttt.name := :new.name; INSERT INTO trig_test_table VALUES xxx_ttt; END;
※也可以不用%ROWTYPE,直接用一般的INSERT,如下:
CREATE OR REPLACE TRIGGER trigger_test BEFORE INSERT ON trig_test_table_copy FOR EACH ROW BEGIN DELETE FROM trig_test_table_copy; INSERT INTO VALUES(ttt_sequence.NEXTVAL, :new.name); END;
※因為不用宣告變數,所以連DECLARE也不要了,比較簡潔
※insert into trig_test_table_copy(name) values('xxx');就可以看到trig_test_table新增進去了
※多一張表是因為如果沒有copy那張表,那第一次insert時觸發了Trigger,而Trigger裡面因為沒有copy那張表,是自己的表,那又觸發了Trigger,所以會變成無限迴圈
※Oracle12c的自增主鍵
CREATE TABLE trig_test_table( -- id NUMBER, id NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT BY 1), name VARCHAR2(10), CONSTRAINT ttt_pk PRIMARY KEY(id) );
※註解的部分變成下面那一行,其實也就是將原本的SEQUENCE寫在裡面,和table挷起來而已,圓括號裡的參數可以不打,參數用法都和SEQUENCE一樣
※這時連trigger都不用了
沒有留言:
張貼留言