PL/SQL笔记3

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      <<     continue close current delete fetch lock insert open rollback     savepoint set sql execute commit forall merge pipe purge 

利用动态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

你可能感兴趣的:(Oracle)