北大青鸟oracle学习笔记26 27 28

本文介绍了Oracle数据库中的触发器,包括触发器的创建、类型(如表级、行级、INSTEAD OF触发器)以及使用场景。特别讨论了变异表的概念及其在触发器中的限制,提供了一个限制主修课程学生人数的触发器设计案例,强调了在处理变异表时的注意事项和解决策略。
摘要由CSDN通过智能技术生成

数据库触发器

触发器语句
    制定触发器定时、事件、表名及类型
触发器主体
    是pl/sql快或对过程的调用
触发器限制
    可以通过when子句实现

DML(insert update delete)
DDL(create alter drop)
数据库操作(servererror logon logoff startup shutdown)

create trigger 触发器名
before|after insert|delete|update of 列名
on 表名
[for each row]  --行级触发器
when 条件

触发器谓词
inserting insert操作
updating update操作
deleting delete操作
可以根据这三个谓词判断到底在执行哪个操作
create or replace
Trigger tg_insert
before insert or update on student
for each row
begin
  if updating then
    dbms_output.put_line('before update');
  end if;
  if inserting then
    dbms_output.put_line('before insert');
  end if;
end;

 

触发器类型:   
1、 表级触发器(表级触发器对每个DML语句执行一次)
create or replace Trigger tg_insert
before update on student
begin
  dbms_output.put_line('before update);
end;

4 rows updated
before update

2、 行触发器 (行级触发器对DML语句影响的每个行执行一次)
create or replace
Trigger tg_insert
before update on student
for each row  --每行触发一次
begin
  dbms_output.put_line('before update');
end;

4 rows updated
before update
before update
before update
before update

使用触发器时,两个特殊值:new 新插入的值 :old 原来的值  不能用于表级触发器

 create or replace trigger trig_job
before insert or update of job
on emp
for each row
begin
if inserting then
:new.job:=upper(:new.job);
else
:new.job:=upper(:new.job);
end if;
end;

 

3、INSTEAD OF触发(此触发器是在视图上而不是在表上定义的触发器,它是用来替换所使用实际语句的触发器.) 只能用于行级,并且不能在表上创建,只能在视图中创建,实际中可以将对视图的操作转换为多实体表的操作
create or replace
trigger insteadtg
instead of insert or update on stu_view
for each row
begin
  dbms_output.put_line(:new.stu_name);
  dbms_output.put_line('instead of insert or update');
end;

4、 模式触发器

select * from user_objects

CREATE TABLE dropped_obj (
    obj_name VARCHAR2(30),
    obj_type VARCHAR2(20),
    drop_date DATE);

CREATE OR REPLACE TRIGGER log_drop_obj
AFTER DROP ON SCHEMA
BEGIN
   INSERT INTO dropped_obj
   VALUES( ORA_DICT_OBJ_NAME,
    ORA_DICT_OBJ_TYPE, SYSDATE);
END;

drop trigger trigger_name;---------为了测试可以删除一个不用的trigger或其它数据对象
SELECT * FROM dropped_obj;

 


5、 数据库级触发器

CREATE TABLE system.session_info (
   username   VARCHAR2(30),
   logontime  DATE,
   session_id VARCHAR2(30),
   ip_addr    VARCHAR2(30),
   hostname   VARCHAR2(30),
   auth_type  VARCHAR2(30)
 );

 CREATE OR REPLACE TRIGGER trg_session_info
 BEFORE LOGOFF
 ON DATABASE
 DECLARE
   session_id VARCHAR2(30);
   ip_addr    VARCHAR2(30);
   hostname   VARCHAR2(30);
   auth_type  VARCHAR2(30);
   logontime  date;
 BEGIN
   SELECT sys_context (/'USERENV/', /'SESSIONID/')  -- 会话编号
   INTO session_id FROM dual;
   --  用户登录的客户端IP地址   
   SELECT sys_context (/'USERENV/', /'IP_ADDRESS/')
   INTO ip_addr  FROM dual;
   --  用户登录的客户端主机名    
   SELECT sys_context (/'USERENV/', /'HOST/')
   INTO hostname FROM dual;
   --  登录认证方式,数据库认证或外部认证
   SELECT sys_context (/'USERENV/', /'AUTHENTICATION_TYPE/')
   INTO auth_type FROM dual; 
   INSERT INTO system.session_info
   VALUES (user, sysdate, session_id, 
    ip_addr, hostname,auth_type);
 END;

 

--使用when条件
CREATE OR REPLACE TRIGGER biu_emp_deptno
BEFORE INSERT OR UPDATE OF deptno
ON emp
FOR EACH ROW
WHEN (New.deptno <> 40)
BEGIN
  :New.comm := 0;
END;

变异表
当前正由DML语句修改
声明级联删除

约束表(在触发器中select的表)
对于sql语句,触发语句直接读取
对于声明的引用完整性约束条件,间接读取

表的编译或约束只是对会话而言的

需要明确的是trigger中SQL语句不能进行如下操作:
1)读或修改触发语句的任何变异表,其中包括触发表本身
2)读或修改触发表的约束表中的主关键字,唯一关键字和外部关键字列。
除此之外的其他列都可以修改。

create or replace
TRIGGER TG_DELETE
AFTER DELETE ON STUDENT
FOR EACH ROW
DECLARE

type RefstuCur is ref cursor return student%rowtype;
  stuCur RefstuCur;
  sturow student%rowtype;
  icount INTEGER;
BEGIN
  select count(*) into icount from student;
  dbms_output.put_line(icount);
   open stuCur for select * from student;
   loop
    fetch stuCur into sturow;
    exit when stuCur%NotFound;
    dbms_output.put_line(sturow.stu_name);
  end loop;
END;

SQL Error: ORA-04091: 表 PUF.STUDENT 发生了变化, 触发器/函数不能读它
ORA-06512: 在 "PUF.TG_DELETE", line 7
ORA-04088: 触发器 'PUF.TG_DELETE' 执行过程中出错

这里把for each row去除就可以执行了。

猜想原因可能是在每行更新或删除的过程中,无法修改或读取变异表,而去掉for each row以后在更新或删除操作结束以后,则可以正常修改和读取。详细原因还是不理解。。。

为了更好的阐述变异表考虑下面3个表:students,classes,registered_students 表students和classes都没有依赖关系但是表registered_students有两个外键一个依赖于students表的主键一个依赖于classes的主键

表registered_students 上声明了2个引用完整性约束,因此表students和classes都是registered_students 的约束表,由于存在这种约束所以表students和classes可能会需要使用DML语句进行更新和查询,另外在registered_students表上执行DML语句的时候它就变成一个变异表

触发器中sql语句不能:读取或更新触发语句的任何变异表也包括触发表本身,读取或更新该触发表的约束表的主键列、唯一性键列或外键列但是如果需要也可以更新其他列。这些限制约束适用于所有行级触发器。但有个特例就是如果INSERT只影响一行记录那么定义在这行上的行级BEFORE和AFTER触发器就不会将这个触发表当作变异表。


这里参考了一篇文章:

作为实例可以考虑下面这个触发器即使他更新了students和classes这两个表但是依然是合法的因为更新的列都不是主键列

create or replace trigger cascadersinserts
  before insert on registered_students
  for each row
declare
  v_credits  classes.num_credits%type;
begin
  select num_credits into v_credits from classes where department=:new.department and course=:new.course;
  update students set current_credits=current_credits+v_credits where id=:new.student_id;
  update classes set current_students=current_students+1 where department=:new.department and course=:new.course;
end cascadersinserts;

 

变异表示例

假定希望将每一门主修课程的学生人数限制在5人,可以在students表上定义一个行级触发器来实现这个任务

create or replace trigger limitmajors
  before insert or update of major on students
  for each row
declare
  v_maxstudents constant number:=5;
  v_currentstudents number;
begin
  select count(*) into v_currentstudents from students where major=:new.major;
  if v_currentstudents+1>v_maxstudents then
     raise_application_error(-20000,'too many students in major'||:new.major);
  end if;
end limitmajors;

 

初看之下似乎实现了这个目标但是会产生错误原因是触发器查询了它自己的触发表而该触发表又是一个变异表

那么要想实现就意味这不能在行级触发器里查询该表,但是可以在语句级触发器里查询它。但是不能简单的将它设计成一个语句级触发器因为我们需要在触发器主体中使用:new.major的值。这个问题的解决办法就是创建2个触发器一个行级的一个语句的,在行级里记录:new.major的值但是不查询表,查询在语句里实现。那么怎么记录值呢,就要通过一个包了。

create or replace package studentdata as 
  type t_majors is table of students.major%type index by binary_integer; 
  type t_ids is table of students.id%type index by binary 
  v_studentmajors t_majors; 
  v_studentids    t_ids; 
  v_numbertries   binary_integer:=0; 
end studentsdata;

create or replace trigger rlimitmajors 
  before insert or update of major on students 
  for each row 
begin 
  studentdata.v_numbertries:=studentdata.v_numbertries+1; 
  studentdata.v_studentmajors(studentdata.v_numberies):=:new.major; 
  studentdata.v_studentids(studentdata.v_numberies):=:new.id; 
end rlimitmajors;

create or replace trigger slimitmajors 
  after insert or update of major on students 
declare 
  v_maxstudents constant number:=2; 
  v_currentstudents number; 
  v_studentid student.id%type; 
  v_major students.major%type; 
begin 
  for v_loopindex in 1..studentdata.v_numberies loop 
      v_studentid:=studentdata.v_studentids(v_loopindex); 
      v_major:=studentdata.v_studentmajors(v_loopindex); 
  select count(*) into v_currentstudents from students where major=v_major; 
  if v_currentstudents>v_maxstudents then 
     raise_application_error(-20000,'too many students for major '||v_major||'because of student'||v_studentid); 
  end if; 
  studentdata.v_numberies:=0; 
end slimitmajors;

 

这样就得到了我们想要的结果了!

实际上运用了全局数组保存需要的参数,然后在所有操作执行完以后利用表级触发器最后进行检查,达到发现选课人数过多的情况,从而使编程人员可以进行回滚处理等等。

 

维护:
                             Alter trigger …..disenable;     使得触发器不可用;
                             Alter trigger ……enable;      开启触发器;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值