编写触发器时,需要注意以下几点:
1.触发器不接受参数。
2.在一个表上的触发器越多,对在该表上的DML操作的性能影响就越大。
3.在触发器的执行部分只能用DML语句(SELECT、INSERT、UPDATE、DELETE),不能使用DDL语句(CREATE、ALTER、DROP)。
4.触发器中不能包含事务控制语句(COMMIT,ROLLBACK,SAVEPOINT)。因为触发器是触发语句的一部分,触发语句被提交、回退时,触发器也被提交、回退了。
(虽然创建触发器的pl/sql有commit时触发器也可以正常创建不会报错,但是对表执行dml操作时会报错ORA-04092: COMMIT 不能在触发器中,导致无法对表执行dml操作)
5.在触发器主体中调用的任何过程、函数,都不能使用事务控制语句。
6.在行触发器的PL/SQL块中可以使用相关名称参照当前的新、旧列值,默认的相关名称分别为OLD和NEW。触发器的PL/SQL块中应用相关名称时,必须在它们之前加冒号(:)
7.无法对sys用户的对象执行触发器,也就是说不能对sys用户的表创建触发器
create [or replace] TRIGGER 触发器名
触发时间
触发事件
on 表名
[for each row]
[WHEN condition]
begin
pl/sql语句
end
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [,column,...]]}
ON table_name
[FOR EACH ROW ]
[WHEN condition]
begin
pl/sql语句
end
--触发时间:指明触发器何时执行,before:表示在数据库动作之前触发器执行;after:表示在数据库动作之后触发器执行。
--触发事件:指明哪些数据库动作会触发此触发器:insert:数据库插入会触发此触发器;update:数据库修改会触发此触发器;delete:数据库删除会触发此触发器。
--UPDATE [(column_1,column_2,…,column_x)]:当触发事件是UPDATE时,如果修改了column_x列,则取值为TRUE,否则为FALSE。其中column_x是可选的。
--FOR EACH ROW选项说明触发器为行触发器。对表的每一行触发器执行一次。如果没有这一选项则只对整个表执行一次。
--WHEN 子句说明触发约束条件
以上都是表级触发器,我们做的登陆监控trigger是事件触发器所以不需要on表名,on database就可以了
CREATE [OR REPLACE] TRIGGER 触发器名
{BEFORE|AFTER }
{DDL事件1 [DDL事件2...]| 数据库事件1 [数据库事件2...]}
ON {DATABASE| [模式名.]SCHEMA }
[WHEN (条件)]
DECLARE
声明部分
BEGIN
主体部分
END;
其中:DATABASE表示创建数据库级触发器,数据库级要给出数据库事件;SCHEMA表示创建模式级触发器,模式级要给出模式事件或DDL事件。
例1: 建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中去。
CREATE TABLE emp_his AS SELECT * FROM EMP WHERE 1=2;
CREATE OR REPLACE TRIGGER tr_del_emp
BEFORE DELETE --指定触发时机为删除操作前触发
ON EMP
FOR EACH ROW --说明创建的是行级触发器
BEGIN
--将修改前数据插入到日志记录表 del_emp ,以供监督使用。
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;
当然表级触发器也可以declare的,但是必须放于for each row后面,begin的前面
CREATE OR REPLACE TRIGGER tr_del_emp
BEFORE DELETE
ON EMP
FOR EACH ROW
declare vv number:=2;
BEGIN
INSERT INTO emp_his(deptno , empno, ename , job ,mgr , sal , comm , hiredate ) VALUES( :old.deptno,vv, :old.ename , :old.job,:old.mgr, :old.sal, :old.comm, :old.hiredate );
END;
例2:限制对Departments表修改(包括INSERT,DELETE,UPDATE)的时间范围,即不允许在非工作时间修改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, '不是上班时间,不能修改departments表');
END IF;
END;
例3:限定只对部门号为80的记录进行行触发器操作。
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的人员的奖金不能降');
END IF;
WHEN DELETING THEN
RAISE_APPLICATION_ERROR(-20003, '不能删除部门80的人员记录');
END CASE;
END;
例4:利用行触发器实现级联更新。在修改了主表regions中的region_id之后(AFTER),级联的、自动的更新子表countries表中原来在该地区的国家的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;
创建trigger限制不在列表中的IP不能登陆DB
先建立一个表,存放IP
CREATE TABLE DBA_CONTROL_RULE
(
RULE_ID NUMBER(8) NOT NULL PRIMARY KEY,
IP_ADDRESS VARCHAR2(15 BYTE)
)
建立一个表,存放登陆记录
CREATE TABLE DBA_ACCESS_LOG
(
SESSION_ID NUMBER(10),
IP_ADDRESS VARCHAR2(15 BYTE),
DB_USER_NAME VARCHAR2(20 BYTE),
OS_USER VARCHAR2(50 BYTE),
MACHINE_NAME VARCHAR2(50 BYTE),
TERMINAL_NAME VARCHAR2(50 BYTE),
PROGRAM_NAME VARCHAR2(50 BYTE),
ACTION_DATE DATE
)
建立限制登陆的触发器
CREATE OR REPLACE TRIGGER SYS.DBA_LOGON_CHECK
AFTER LOGON ON DATABASE
declare
v_session_id number;
v_ip_address varchar2(30);
v_db_user_name varchar2(20);
v_os_user varchar2(20);
v_machine_name varchar2(50);
v_terminal_name varchar2(50);
v_program_name varchar2(50);
v_permit varchar2(1);
v_rule_id number;
v_check varchar2(1) := 'Y';
begin
begin
select nvl(sid,0),nvl(sys_context('USERENV','IP_ADDRESS'),'NONE'),nvl(username,'NONE'),nvl(osuser,'NONE'),nvl(trim(machine),'NONE'),nvl(terminal,'NONE'),nvl(program,'NONE')
into v_session_id,v_ip_address,v_db_user_name,v_os_user,v_machine_name,v_terminal_name,v_program_name
from v$session
where audsid=sys_context('USERENV','SESSIONID') and instr(nvl(program,'NONE'),'(J0')=0;
exception when others then
v_check := 'N';
end;
v_permit := 'D';
if v_session_id>0 and v_check='Y'
then
begin
--Permit Access
select rule_id into v_rule_id from DBA_CONTROL_RULE where nvl(ip_address,v_ip_address)=v_ip_address and rownum=1;
v_permit := 'P';
exception when no_data_found then
null;
end;
insert into dba_access_log(session_id,ip_address,db_user_name,os_user,machine_name,terminal_name,program_name,action_date)
values(v_session_id,v_ip_address,v_db_user_name,v_os_user,v_machine_name,v_terminal_name,v_program_name,sysdate);
if v_permit = 'D' then
raise_application_error(-20001,'You(IP:'||v_ip_address||') have no priviledge to access the DB!please contact with DBA' );
end if;
end if;
end;