2. 触发器的种类
(1) 行级触发器和语句级触发器
行级触发器:
触发机制是基于行的,当表中数据改变时,将触发行级触发器,改变一行数据,触发一次;改变N行数据,触发N次。
语句级触发器:
基于语句级的,当一条SQL语句改变数据时,无论这条SQL语句影响多少条记录,语句级触发器都只触发一次。
(2) BEFORE和AFTER触发器
适合于行级触发器和语句级触发器。
(3) 复合触发器
表上的触发器,它有四个时间点,可以让我们针对不同的时间点指定不同的处理动作:
在触发语句执行前
在触发语句执行后
在每行记录被修改之前
在每行记录被修改之后
触发语句必须是DML。
(4) INSTEAD OF触发器
某些视图,我们不能直接对其进行更新操作,但是我们可以再这种视图上面建立触发器,利用触发器对视图的基表进行
更新操作,这种类型的触发器称为"INSTEAD OF触发器".
create or replace view v_stu
as
select sum(student_id) allID, FIRST_NAME
from student_bak
group by first_name;
create or replace trigger tri_view
instead of delete on v_stu
for each row
begin
delete from student_bak
where student_id = 167;
end tri_view;
/
测试1:
SQL> delete from v_stu where ALLID = 169;
1 row deleted
SQL> commit;
Commit complete
SQL> select * from v_stu; --可以看到无法中视图里面删除基表中的记录
ALLID FIRST_NAME
---------- -------------------------
169 Frantz --还会存在,因为基表里面存在这一行
174 Michael
172 Maria
170 P.
173 Oscar
171 Denise
168 Sally
175 Debra
8 rows selected
下面开始通过INSTEAD OF触发器来实现:
测试2:
SQL> select * from student_bak;
STUDENT_ID SALUTATION FIRST_NAME LAST_NAME STREET_ADDRESS ZIP PHONE EMPLOYER REGISTRATION_DATE CREATED_BY CREATED_DATE MODIFIED_BY MODIFIED_DATE
---------- ---------- ------------------------- ------------------------- -------------------------------------------------- ----- --------------- -------------------------------------------------- ----------------- ------------------------------ ------------ ------------------------------ -------------
167 Mr. Jim Joas 53-33 192nd St. 11365 718-555-5555 Gaum, Inc. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
168 Ms. Sally Naso 812 79th St. 07047 201-555-5555 Motors National 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
169 Mr. Frantz McLean 23-08 Newtown Ave. 11102 718-555-5555 Guenther Soehner 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
170 Ms. P. Balterzar 30 Carriage Rd. 11576 718-555-5555 Parton Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
171 Ms. Denise Brownstein 104-36 196th St. 11412 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
172 Ms. Maria Arias Box 216 11426 718-555-5555 Lising Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
173 Mr. Oscar McGill 578 E 40th ST. 11218 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
174 Mr. Michael Brown 265 Hawthorne St #2D 11225 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
175 Ms. Debra Boyce 294 East 98 St. 11212 718-555-5555 Hoare Govett, Inc. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
9 rows selected
SQL> select * from v_stu;
ALLID FIRST_NAME
---------- -------------------------
169 Frantz
174 Michael
172 Maria
170 P.
173 Oscar
171 Denise
168 Sally
167 Jim
175 Debra
9 rows selected
SQL> delete from v_stu;
9 rows deleted
SQL> select * from student_bak;
STUDENT_ID SALUTATION FIRST_NAME LAST_NAME STREET_ADDRESS ZIP PHONE EMPLOYER REGISTRATION_DATE CREATED_BY CREATED_DATE MODIFIED_BY MODIFIED_DATE
---------- ---------- ------------------------- ------------------------- -------------------------------------------------- ----- --------------- -------------------------------------------------- ----------------- ------------------------------ ------------ ------------------------------ -------------
168 Ms. Sally Naso 812 79th St. 07047 201-555-5555 Motors National 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
169 Mr. Frantz McLean 23-08 Newtown Ave. 11102 718-555-5555 Guenther Soehner 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
170 Ms. P. Balterzar 30 Carriage Rd. 11576 718-555-5555 Parton Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
171 Ms. Denise Brownstein 104-36 196th St. 11412 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
172 Ms. Maria Arias Box 216 11426 718-555-5555 Lising Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
173 Mr. Oscar McGill 578 E 40th ST. 11218 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
174 Mr. Michael Brown 265 Hawthorne St #2D 11225 718-555-5555 Nearst Corp. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
175 Ms. Debra Boyce 294 East 98 St. 11212 718-555-5555 Hoare Govett, Inc. 2003-2-2 BROSENZWEIG 2007-2-2 BROSENZW 2007-2-2
8 rows selected
SQL> select * from v_stu;
ALLID FIRST_NAME
---------- -------------------------
169 Frantz
174 Michael
172 Maria
170 P.
173 Oscar
171 Denise
168 Sally
175 Debra
8 rows selected
(5) 系统事件触发器
基于数据库系统的触发器,系统事件触发器与表,视图没有关系。
(6) 用户事件触发
3. 触发动作的编码
触发动作,也称触发主体,PL/SQL语句块,或者对一个子程序(包括PL/SQL子程序和java子程序)的调用(如call 存储过程)
create trigger tri_p
before insert or update of student_id on student_bak
for each row
when (new.student_id <> 2002)
call check_id(new.student_id);
在PL/SQL主体中访问列在值:
:NEW.列名 引用新值
:OLD.列名 引用旧值
注:insert没有旧值,只有新值。delete没有新值,只有旧值。
:NEW和:OLD只可以用于行级触发器。
create table tb_tri(name varchar2(20));
create or replace trigger tb_tri_test
after update on tb_tri
for each row
begin
dbms_output.put_line('old value:' || :OLD.NAME);
dbms_output.put_line('new value:' || :NEW.NAME);
end;
/
测试:
SQL> insert into tb_tri values('胡总');
SQL> commit;
SQL> update tb_tri set name='OK' where name = '胡总';
old value:胡总
new value:OK
SQL> select * from tb_tri;
NAME
--------------------
OK
NEW和OLD的重命名
为了防止命名冲突,可以使用referencing进行重命名
create or replace trigger tri_rename
before update on tb_tri
referencing new as new_one --对NEW进行重命名
old as old_one --对OLD进行重命名
for each row
begin
v_new varchar2(20);
v_old varchar2(20);
begin
v_new := :new_one.name;
v_old := :old_one.name;
...
end;
判断触发的SQL语句类型(inserting,deleting,updating)
Oracle提供了三个判断条件(inserting,deleting,updating)
create or replace trigger tri_dml
after insert or update or delete on tb_tri
begin
if updating
then
...
elsif deleting
then
...
elsif inserting
then
...
end if;
end;
4. 触发器的维护
(1) 创建触发器
自己模式下创建触发器:create trigger权限
其他模式下创建触发器:create any trigger权限
创建系统触发器:administer database trigger权限
创建行级触发器:
create or replace trigger tri2
before insert on tt.ct
for each row
begin
null;
end;
创建语句级触发器:
create or replace trigger tri1
before insert on tt.ct
begin
null;
end;
创建before触发器:
create or replace trigger tri_before
before delete or insert or udpate on tb_tri
begin
null;
end;
****创建复合触发器:****
create or replace trigger tr_stuinfor
for update of name on tb_tri compound trigger --compound trigger指创建复合触发器
a int;
before statement is --时间点1,触发语句执行前
begin
...
end before statement;
before each row is --时间点2,每条记录修改前
begin
null;
...
end before each row;
after each row is --时间点2,每条记录修改后
begin
null;
...
end after each row;
after statement is --时间点2,触发语句执行后
begin
null;
...;
end after statement;
end tr_stuinfor;
创建INSTEAD OF触发器
见前面
创建用户事件触发器
create or replace trigger tri4
before create on tt.SCHEMA --创建触发器的用户 SCHEMA表示创建的是用户事件触发器
begin
raise_application_error(num => -20001,msg => 'Cannot Create object');
end;
创建系统事件触发器
create or replace trigger log_errors
after servererror on database --当数据库发生错误后,点燃该触发器 database表示系统事件触发器
begin
...
null;
end;
(2) 禁用和启用触发器
alter trigger login1 diable;
alter trigger login1 enable;
(3) 删除触发器
drop trigger login1;
5. 阻止触发语句的执行
编写触发器,如果插入的ID值小于50,可以顺利插入,如果插入的ID值大于50,则触发语句将被阻止执行。
create or replace trigger tri_auto
before insert on student_bak
for each row
declare
sql_string varchar2(100);
begin
if :NEW.student_id > 10
then
raise_application_error(-20001,'can not insert user with id > 10');
end if;
end tri_auto;
/
SQL> insert into student_bak select * from student where rownum < 10;
insert into student_bak select * from student where rownum < 10
ORA-20001: can not insert user with id > 10
ORA-06512: 在 "JIANG.TRI_AUTO", line 6
ORA-04088: 触发器 'JIANG.TRI_AUTO' 执行过程中出错
SQL> select * from student_bak; --没有数据,插入失败
STUDENT_ID SALUTATION FIRST_NAME LAST_NAME STREET_ADDRESS ZIP PHONE EMPLOYER REGISTRATION_DATE CREATED_BY CREATED_DATE MODIFIED_BY MODIFIED_DATE
---------- ---------- ------------------------- ------------------------- -------------------------------------------------- ----- --------------- -------------------------------------------------- ----------------- ------------------------------ ------------ ------------------------------ -------------
6. DDL语句的语句的救星--动态SQL
案例:
利用静态SQL在存储过程中创建表失败
SQL> create or replace procedure create_table
2 as
3 Pstring varchar2(2000);
4 begin
5 null;
6 create table tp (id int,name varchar2(20)); --DDL语句的问题
7 end;
8 /
Warning: Procedure created with compilation errors
SQL> show error;
Errors for PROCEDURE JIANG.CREATE_TABLE:
LINE/COL ERROR
-------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
6/5 PLS-00103: 出现符号 "CREATE"在需要下列之一时: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with
利用动态SQL在存储过程中创建表成功
create or replace procedure create_table
as
Pstring varchar2(2000);
begin
Pstring := 'create table tp (id int,name varchar2(20))';
execute immediate Pstring; --使用execute immediate执行动态SQL创建表
end;
/
SQL> execute create_table;
PL/SQL procedure successfully completed
注:静态SQL不能执行DDL语句,而动态可以。
(1) 动态SQL用法
两种方法用来书写动态SQL
使用本地动态SQL,使用execute immediate命令执行动态SQL语句 --性能较高
包DBMS_SQL执行动态SQL
(2) execute immediate使用
占位符:是SQL语句中的一个位置,绑定变量将为该位置提供值。每个占位符必须有一个相对的绑定变量。
execute immediate SQL_string
{into {变量...| record}}
{using [in | out | in out ] 绑定参数...]};
没有占位符,则指execute immediate 没有using子句。
处理DDL语句:
create or replace procedure CreateTable(tablename varchar2)
is
begin
execute immediate 'create table ' || tablename || ' (name varchar(20))';
end;
/
SQL> execute CreateTable('TEST1');
如果使用绑定参数来动态传递表的名字,是无法创建表的。
绑定参数不能使模式对象的元素(如表名,列名,数据类型等),绑定参数只能是值,变量或者表达式。用DDL语句动态创建
对象时,推荐使用连接运算符(||),不推荐使用绑定参数。
处理TCL语句:
可以在PL/SQL直接执行commit和rollback等事务控制语句,也可以动态执行SQL。
declare
SQLstring varchar2(1000);
begin
SQLstring := 'commit';
execute immediate 'insert into phone values(''E68'',''苹果'',555)';
execute immediate SQLstring;
end;
处理DML语句:
执行普通的DML语句---不带占位符(不带using子句)
begin
execute immediate 'insert into phone values(''E68'',''苹果'',555)';
commit;
end;
有特殊的DML语句---带占位符(使用using子句给DML语句传入值)
declare
v_phone_name varchar2(20) := 'C510';
v_producer varchar2(20) := '爱立信';
v_price number := 1201;
begin
execute immediate 'insert into phone values(:a, :b, :c)'
using v_phone_name, v_producer, v_price;
commit;
end;
执行有返回值的DML
declare
SQLstring varchar2(1000);
v_phone_name varchar2(20);
v_producer varchar2(20);
v_price number := 0;
begin
--phone_name,producer,price为列
SQLstring := 'update phone set price = 1202 where price = :a returning phone_name,producer,price into :b,:c,:d';
execute immediate SQLstring using v_price returning into v_phone_name, v_producer, v_price;
commit;
dbms_output.put_line(v_phone_name || ' ' || v_producer || ' ' || v_price);
end;
(3) 在动态SQL中调用存储过程
create or replace procedure p_call(p1 in varchar2 := NULL)
as
begin
dbms_output.put_line(p1 || ' is from procedure p_call');
end p_call;
/
动态SQL调用存储过程:
begin
execute immediate 'Call p_call(''Hello'')';
end;
(4) 在动态SQL中调用函数
调用函数一定有返回值
create or replace function F_Call(p1 in varchar2)
return varchar2
as
begin
return p1 || ' is from function F_Call';
end F_Call;
动态SQL调用函数:
declare
v_return varchar2(50);
begin
execute immediate 'call F_Call(''Hello'') into :function_result' using out v_return;
dbms_output.put_line(v_return);
end;
处理单行查询
通过into将查询结果保存到变量或者记录中
declare
v_maxprice number;
v_producer varchar2(20) := '苹果';
begin
execute immediate 'select max(price) from phone where producer = :a' into v_maxprice
using v_producer;
dbms_output.put_line(v_maxprice);
end;
*********处理多行查询*********
如果动态SQL语句是一个查询语句,并且返回多行记录,可以使用下面两种方法进行处理:
使用带有子句bulk collect into 的 execute immediate语句
使用open-for,fetch,close 语句
***使用bulk collect可以将查询结果一次性地加载到集合中,而不是通过游标一条一条地处理,可以在select into,
fetch into , returning into 语句中使用 bulk collect.
注:在使用bulk collect时,所有的into 变量都必须是集合类型
***实例:使用bulk collect
step 1:
desc dept;
--SQL> desc dept
--Name Type Nullable Default Comments
------ ------------ -------- ------- --------
--DEPTNO NUMBER(2)
--DNAME VARCHAR2(14) Y
--LOC VARCHAR2(13) Y
--根据表dept的结构创建对象类型dept_type
create or replace type dept_type as object --如果将删除dept_type时,必须先删除dept_tab.
(
DEPTNO NUMBER(2),
DNAME VARCHAR2(14),
LOC VARCHAR2(13)
)
/
--创建表类型dept_tab
create or replace type dept_tab as table of dept_type;
/
--将查询结果整体放入到变量v_rec中
declare
v_rec dept_tab; --定义表类型变量
begin
--如果不指定dept_type(DEPTNO,DNAME,LOC)中的dept_type数据类型转换,则会报类型不一致错误
--oracle@NOAS252:~> oerr ora 00932
--00932, 00000, "inconsistent datatypes: expected %s got %s"
execute immediate 'select dept_type(DEPTNO,DNAME,LOC) from dept' bulk collect into v_rec;
--输出v_rec内容
for i in v_rec.first..v_rec.last
loop
dbms_output.put_line(v_rec(i).DEPTNO || ' ' || v_rec(i).DNAME || ' ' || v_rec(i).LOC);
end loop;
end;
/
***使用open-for,fetch,close语句处理多行查询***
declare
type ref_type is ref cursor; --定义游标指针
v_cursor ref_type; --定义游标变量
v_record dept%ROWTYPE;
sqlstring varchar2(200);
begin
sqlstring := 'select * from dept where deptno > :p';
open v_cursor for sqlstring --打开游标,执行动态SQL
using 30;
loop
fetch v_cursor into v_record;
exit when v_cursor%NOTFOUND;
dbms_output.put_line(v_record.deptno || ' ' || v_record.dname || ' ' || v_record.loc);
end loop;
close v_cursor;
end;
(5) ***********DBMS_SQL使用****************
DBMS_SQL包提供一个接口,用于执行动态SQL(包括DDL和DML)。DBMS_SQL定义了一个实体叫做
游标ID,游标ID是一个PL/SQL整型数,通过游标ID,可以对游标进行操作。
包DBMS_SQL提供了一系列的过程和函数
函数:
open_cursor:打开一个动态游标,并返回一个整数
execute (c in integer):执行游标,并处理返回的函数
fetch_rows(c in integer):循环从游标中取数据
过程:
close_cursor(c in out integer):关闭游标
parse(c in integer,statement in varchar2,language_flag in integer):--c指游标,statement为SQL语句,language_flag为解析SQL语句所用的版本,一般有V6,native,V7
bind_variable (c in integer,name in varchar2,value):定义动态SQL语句中所对应字段的值,c为游标,name为字段名,value为字段的值
define_column(c in integer, position in integer, column any datatype,[column_size in integer]):定义从游标中选出的列
column_value(c in integer, position in integer, value):将所取得的游标数据赋值到相应的变量
***利用DBMS_SQL执行DDL语句***
打开游标->SQL解析->关闭游标
create or replace procedure CreateTable(tablename varchar2)
is
SQL_string varchar2(1000);
v_cur integer; --定义整型变量,用于存放游标
begin
SQL_string := 'create table ' || tablename || ' ( name varchar2(20))';
v_cur := dbms_sql.open_cursor; --打开游标
dbms_sql.parse(v_cur,SQL_string,DBMS_SQL.native); --解析并执行SQL语句
dbms_sql.close_cursor(v_cur); --关闭游标
end;
/
SQL> execute CreateTable('jiang');
***利用DBMS_SQL执行select语句***
过程:打开游标-解析动态SQL-绑定输入的参数-定义列-执行动态SQL-从游标中取数据-把结果值放入到变量中-关闭游标
declare
v_cursor number; --游标ID
sqlstring varchar2(200);
v_deptno NUMBER := 30;
v_dname varchar2(14);
v_loc varchar2(13);
v_count int; --存放函数返回值
begin
sqlstring := 'select * from dept where deptno > :p';
v_cursor := dbms_sql.open_cursor; --打开游标
dbms_sql.parse(v_cursor, sqlstring, dbms_sql.native); --解析动态SQL
dbms_sql.bind_variable(v_cursor, ':p', v_deptno); --绑定输入参数,v_deptno的值传给:p
dbms_sql.define_column(v_cursor,1,v_deptno); --定义列,v_deptno为select中的第一列
dbms_sql.define_column(v_cursor,2,v_dname,14);
dbms_sql.define_column(v_cursor,3,v_loc,13);
v_count := dbms_sql.execute(v_cursor); --执行动态SQL语句
loop
exit when dbms_sql.fetch_rows(v_cursor) <= 0;
dbms_sql.column_value(v_cursor,1,v_deptno); --函数column_value()将缓冲区的列的值读入相应的变量中
dbms_sql.column_value(v_cursor,2,v_dname);
dbms_sql.column_value(v_cursor,3,v_loc);
dbms_output.put_line(v_deptno || ' ' || v_dname || ' ' || v_loc);
end loop;
dbms_sql.close_cursor(v_cursor);
end;
***利用DBMS_SQL执行DML语句***
过程:打开游标-解析动态SQL-绑定输入参数-执行动态SQL-关闭游标
declare
v_cursor number; --游标ID
sqlstring varchar2(200);
v_deptno NUMBER := 30;
v_dname varchar2(14);
v_loc varchar2(13);
v_count int; --被DML语句影响的行数
begin
sqlstring := 'insert into dept values(:a,:b,:c)';
v_deptno := 50;
v_dname := 'MATH';
v_loc := 'NAN JING';
v_cursor := dbms_sql.open_cursor; --打开游标
dbms_sql.parse(v_cursor, sqlstring, dbms_sql.native); --解析动态SQL
dbms_sql.bind_variable(v_cursor, ':a', v_deptno); --绑定输入参数,v_deptno的值传给:p
dbms_sql.bind_variable(v_cursor, ':b', v_dname); --绑定输入参数,v_deptno的值传给:p
dbms_sql.bind_variable(v_cursor, ':c', v_loc); --绑定输入参数,v_deptno的值传给:p
v_count := dbms_sql.execute(v_cursor); --执行动态SQL语句
dbms_sql.close_cursor(v_cursor);
dbms_output.put_line('Insert ' || to_char(v_count) || ' row');
commit;
end;
****利用存储过程返回一个结果集****
SQL> create or replace package pack_return_result --定义包头
2 as
3 type type_cursor is ref cursor;
4 procedure pro_getresult(p in out type_cursor);
5 end pack_return_result;
6
7 /
Package created.
SQL> create or replace package body pack_return_result --定义包体
2 as
3 procedure pro_getresult(p in out type_cursor)
4 as
5 begin
6 open p for select deptno,dname,loc from jiang.dept where deptno >20;
7 end;
8 end;
9 /
Package body created.
SQL> set serveroutput on
SQL> variable v_bind_ref refcursor; --定义游标绑定变量,用于批量处理PL/SQL中的select语句的结果。variable为SQL*Plus命令
SQL> execute pack_return_result.pro_getresult(:v_bind_ref);
PL/SQL procedure successfully completed.
SQL> print v_bind_ref --打印游标绑定变量的内容 print为SQL*Plus命令
DEPTNO DNAME LOC
---------- -------------- -------------
50 MATH NAN JING
30 SALES CHICAGO
40 OPERATIONS BOSTON