`
will_lp
  • 浏览: 6874 次
  • 性别: Icon_minigender_1
  • 来自: 无锡
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论
阅读更多
PL/SQL
易忘记的:set serveroutput on;
1. 什么是pl/sql : Procedure Language & Structured Query Language 的缩写, PL/SQL是对SQL语言存储过程语言的扩展
2. pl/sql有什么优点:
有利于客户/服务器环境应用的运行 
PL/SQL是对SQL语言存储过程语言的扩展 
PL/SQL是ORACLE系统的核心语言,现在ORACLE的许多部件都是由PL/SQL写成
3. PL/SQL块的结构如下:
DECLARE
/* 声明部分: 在此声明PL/SQL用到的变量,类型及游标,以及局部的存储过程和函数 */
BEGIN
/*  执行部分:  过程及SQL 语句, 即程序的主要部分 */
EXCEPTION
   /* 执行异常部分: 错误处理  */
END;
其中,执行部分 即 begin 和 end 块是必须的。
4. 变量定义:
a) 定义变量:
i. v_name varchar2(15) :='zhangsan';
ii. v_ename emp.ename%type;
--v_ename的数据类型与emp表的ename列相同
b) 定义变量一般应该以v_开头,变量名不能和数据库或表的表示符相同;
c) 标示符可以使用的符号:a-z,A-Z,0-9,$,_,#
d) 变量的赋值: :=
5. 定义常量:c_shcoolName constant varchar2(20) not null :='无锡niit';--定义常量时,要使用constant关键字,常量名一般以c_开头。如果使用了not null,则必须要赋初始值。常量的声明和赋值语句必须是在同一行
6. 使用变量:
a) select ename into v_ename from emp
where empno = &no;
--将查询出来的值赋值给变量v_ename
b) declare
  v_deptno dept.deptno%type;
begin
select deptno into v_deptno from dept where dname = 'SALES';
dbms_output.put_line('变量值为:'||v_ deptno);
delete from emp1 where deptno = v_deptno;
--使用变量
end;
7. 条件结构
a) 简单if结构
if v_deptno = 30 then –指定if条件
        update emp set sal = sal*1.2 where deptno = v_deptno;
end if; --if语句块结束
b) 双重if结构
i. if v_deptno = 30 then
         update emp set sal = sal *1.1 where deptno = v_deptno;
else
         dbms_output.put_line('哈哈,你有点不太幸运,加工资没有你的份');
end if;
c) 多重if结构
i. if v_deptno = 10 then
        update emp set sal = sal * 1.1;
elsif v_deptno = 20 then –注意elsif的写法
        update emp set sal = sal *1.2;
elsif v_deptno = 30 then
        update emp set sal = sal*1.3;
else
        dbms_output.put_line('不是一个幸运儿');
end if;
ii. declare
   v_empno emp.empno%type;
   v_sal emp.sal%type;
   v_deptno emp.deptno%type;
begin
    v_empno := &no;
   select sal ,deptno into v_sal, v_deptno from emp where empno = v_empno;
    if v_sal<2000 and v_deptno = 10 then
            update emp set sal = sal*1.1 where empno = v_empno;
            dbms_output.put_line('你的工资提升了10%');
    elsif v_sal<2000 and v_deptno = 20 then
            update emp set sal = sal *1.2 where empno = v_empno;
            dbms_output.put_line('你的工资提升了20%');
    else
            dbms_output.put_line('谢谢参与');
    end if;
end;
d) 嵌套if结构
declare
v_empno emp.empno%type;
v_sal emp.sal%type;
v_deptno emp.deptno%type;
begin
  v_empno := &no;
   select sal,deptno into v_sal,v_deptno from emp where empno = v_empno;
   if v_sal < 2000 then
           if v_deptno = 10 then
                   update emp set sal = sal * 1.1 where empno = v_empno;
                   dbms_output.put_line('本次加薪10%');
           elsif v_deptno = 20 then
                   update emp set sal = sal *1.2 where empno = v_empno;
                   dbms_output.put_line('本次加薪20%');
           else
                   dbms_output.put_line('你所在的部门不参与本次加薪');
          end if;
   else
           dbms_output.put_line('你的工资已经够高了,不需要加薪');
   end if;
end;
8. 循环结构
a) loop –循环开始,loop类似于do… while循环,至少要执行一次
declare
    v int :=1;
begin
    loop
          if v>10
                then exit;--循环退出
          else
                 dbms_output.put_line(v);
                 v := v+1;
          end if;
    end loop;--循环结束
end;
练习:建一个表,有一个id列,number类型,使用循环插入20行数据
b) while循环:可以一次,也不执行
示例:
declare
i int := 1;
begin
   while i<21  loop
           insert into tempt values (i);
           i := i+1;
   end loop;
end;
c) for循环:for i in [reverse] 1..20 loop  end loop;--循环变量不需要手动声明,由编译器自动创建
示例:
begin
   for i in [reverse] 1..20 loop –如果使用reverse,则变量递减,否则是递加
           insert into tempt values(i);
   end loop;
end;
d) 嵌套循环:
示例
begin
   for i in 6..14 loop
           for j in 5..20 loop
                   dbms_output.put_line(i*j);
           end loop;
   end loop;
end;
e) 使用goto 和 标签
9. 复合数据类型: 自定义数据类型
a) 记录类型 record : 自定义记录类型命名规范:name_record_type
i. 示例1:
declare
   type stu_record_type is record(
    id number(4),
    name varchar2(20),
birth date); --自定义记录类型 stu_record_type
    stu1 stu_record_type; --声明记录类型的变量
begin
    stu1.id := 1001;
    stu1.name := 'lulu';
    stu1.birth := to_date('1986-6-25','yyyy-mm-dd');
    dbms_output.put_line(stu1.name||'''s birth is '||stu1.birth);
end;
ii. 示例2:
declare
    type emp_record_type is record(
    empno emp.empno%type,
    ename emp.ename%type,
    sal emp.sal%type);
    emp1 emp_record_type;
begin
    emp1.empno := 1001;
    emp1.ename := 'guofucheng';
    emp1.sal := 5000;
    insert into emp (empno,ename,sal)
values (emp1.empno,emp1.ename,emp1.sal);
end;
iii. 示例3:
declare
    emp1 emp%rowtype;
begin
    emp1.empno := 1003;
    insert into emp (empno) values (emp1.empno);
end;
b) 索引表类型:与高级语言的数组相似,有索引,区别是索引可以为负数,并且长度没有限制。索引表只能有一列,这一列的数据类型由of 子句决定,索引的数据类型可以为binary_integer,nls_intege,varchar2
示例1:
declare
    type ename_table_type is table of varchar2(18)
    index by binary_integer; --定义一个索引表,索引类型为binary_integer
    ename_table ename_table_type; --定义一个索引表类型变量
begin
    select ename into ename_table(-18) from emp where empno = 7788;
    dbms_output.put_line(ename_table(-18));
end;
c) 嵌套表:与索引表的相同点时都是一列,但嵌套表的索引是从1,嵌套表有构造方法,嵌套表的变量在使用前必须要使用构造方法初始化,嵌套表的索引是不需要定义的
示例1:
declare
   type ename_table_type is table of varchar2(20); --定义嵌套表类型
   ename_table ename_table_type; -- 声明一个嵌套表类型的变量
begin
   ename_table := ename_table_type('a','a','a'); --给这个变量初始化,这是必须的
   select ename into ename_table(2) from emp where empno = 7788; --使用变量
   dbms_output.put_line(ename_table(2));
end;
d) 记录表:和索引表、嵌套表只能存贮一列多行不同的是,它能够存储多行多列,和索引表相同的是它也有索引,不需要初始化
示例
declare
   type emp_table_type is table of emp%rowtype
   index by binary_integer; --定义记录表,注意有索引,数据类型是表的一行
   emp_table emp_table_type; --声明一个变量
begin
   select * into emp_table(1) from emp where empno = 7788; --给变量的一个元素赋值,要指定元素的索引
   dbms_output.put_line(emp_table(1).ename||'''s salary is '||emp_table(1).sal);
end; --输出变量元素的值
e) 变长数组:与嵌套表相似,只能存贮单列,不用定义索引,都要使用构造方法进行初始化,在使用前都要用构造方法初始化,差别是有长度限制
示例1:
declare
    type ename_varray_type is varray(3) of emp.ename%type; --定义变长数组类型,要注意数组长度、数组数据类型
    ename_varray ename_varray_type; --定义变量
begin
    ename_varray := ename_varray_type('a','a','a');--使用构造方法初始化
    select ename into ename_varray(1) from emp where empno = 7788;--给数组变量元素赋值
    dbms_output.put_line(ename_varray(1));--输出数组变量元素的值
end;
f) 集合的常用属性和方法
i. exists:判断某一个元素是否存在
1. 示例:
declare
   type enames_table_type is table of emp.ename%type;
   enames_table enames_table_type ;
begin
   if enames_table.exists(1) then –判断元素1是否存在
           null; --null是一个站位,表示这里什么都不用做,主要是方便阅读
   else
           dbms_output.put_line('table has not been initialized');
   end if;
end;
ii. count:返回集合中元素的个数
iii. first : 返回集合第一个元素的索引
iv. last :返回集合最后一个元素的索引
v. 示例:
declare
   type sal_table_type is table of emp.sal%type
   index by emp.ename%type;
   sal_table sal_table_type;
   v_sal emp.sal%type;
   v_ename emp.ename%type;
   cursor emp_cursor
   is
   select sal,ename from emp;
begin
   open emp_cursor;
   loop
           fetch emp_cursor into v_sal,v_ename;
           exit when emp_cursor%notfound;
           sal_table(v_ename) := v_sal;
   end loop;
   close emp_cursor;
   dbms_output.put_line(sal_table.count); --显示集合的元素数量
   if sal_table.exists('FORD') then --判断元素FORD是否存在
   dbms_output.put_line(sal_table('FORD'));
   end if;
   dbms_output.put_line(sal_table.first); --显示第一个元素的索引
   dbms_output.put_line(sal_table.last); --显示最后一个元素的索引
end;

10. 游标 CURSOR:
a) 什么是游标:oracle在执行insert、delete、update、select时会分配一个上下文区,使用游标指向这个上下文区。游标可以分为显式游标和隐式游标,用户只需要处理显式游标。显式游标的使用分为四个步骤:定义、打开、使用、关闭
b) 示例:
declare
   cursor emp_cursor
   is
   select ename from emp; --定义游标
   v_ename emp.ename%type;
begin
   open emp_cursor; --打开游标
   fetch emp_cursor into v_ename; --提取游标的值
   dbms_output.put_line(v_ename);
   close emp_cursor; --关闭游标
end;
c) 游标的属性:
i. %isopen
declare
   cursor emp_cursor
   is
   select ename from emp;
   v_ename emp.ename%type;
begin
   if emp_cursor%isopen then
           fetch emp_cursor into v_ename;
           dbms_output.put_line(v_ename);
           close emp_cursor;
   else
   dbms_output.put_line('please open the cursor');
   end if;
end;
ii. %found : 如果从游标中提取数据后,找到数据了,返回true
iii. %notfound : 从游标中提取数据后,没找到数据,返回true
declare
   cursor emp_cursor
   is
   select ename from emp;
   v_ename emp.ename%type;
begin
   open emp_cursor;
   if emp_cursor%isopen then
   loop
           fetch emp_cursor into v_ename;
           exit when emp_cursor%notfound;
           dbms_output.put_line(v_ename);
   end loop;
   else
   dbms_output.put_line('please open the cursor');
   end if;
   close emp_cursor;
end;
iv. %rowcount : 表示当前游标已经提取了多少行数据,与实际查询的结果行数无关
d) for游标:专门适用于需要进行循环处理的游标,不需要定义接受变量,不需要显式打开、提取、关闭游标
i. 示例:
declare
   cursor emp_cursor
   is
   select ename,sal from emp;
begin
  for e in emp_cursor loop
   dbms_output.put_line(e.ename||'''s salary is '||e.sal);
  end loop;
end;
e) 带参游标:当游标的select语句需要参数时,应该定义带参游标
i. 示例:
declare
    cursor emp_cursor(no number) –在定义时定义参数
    is
    select sal from emp where empno = no;
    v_sal emp.sal%type;
begin
    open emp_cursor(7788); --在打开时传递参数值
    fetch emp_cursor into v_sal;
    dbms_output.put_line(v_sal);
    close emp_cursor;
end;
ii. 练习:编写带参的for游标,根据指定的部门编号,查询这个部门的薪水,将游标中的值打印出来
declare
    cursor emp_cursor(no number)
    is
    select sal from emp where deptno= no;
begin
    for e in emp_cursor(10) loop
    dbms_output.put_line(e.sal);
    end loop;
end;
f) 隐式游标
i. 示例
begin
   for e in (select sal from emp where deptno= &no) loop –这个游标没有显式定义
   dbms_output.put_line(e.sal);
   end loop;
end;
g) 使用游标删除记录
i. 示例:
declare
    cursor emp1_cursor
    is
    select hiredate from emp1 for update nowait; --for update 在游标上加更新锁
    v_hiredate date; --nowait 如果资源被占用,不等待,但会抛出异常

begin
    open emp1_cursor;
    loop
            fetch emp1_cursor into v_hiredate;
            exit when emp1_cursor%notfound;
            if v_hiredate > to_date('1987-01-01','yyyy-mm-dd') then
                    delete from emp1 where current of emp1_cursor;
            end if;
    end loop;
    close emp1_cursor;
end;
ii. 练习:
h) 使用游标更新记录
i. 示例:
    declare
       cursor emp1_cursor
       is
       select sal from emp1 for update nowait;
       v_sal emp1.sal%type;
    begin
       open emp1_cursor;
       loop
              fetch emp1_cursor into v_sal;
              exit when emp1_cursor%notfound;
              if v_sal<1500 then
                      update emp1 set sal = sal + 200 where current of emp1_cursor;
              elsif v_sal < 2000 then
                      update emp1 set sal = sal + 100 where current of emp1_cursor;
              else
                      update emp1 set sal = sal + 50 where current of emp1_cursor;
              end if;
      end loop;
  end;
ii. 练习:查询emp1表的薪水,如果薪水低于1500块加薪200,低于2000加薪100,其他加薪50
i) 游标变量:同一个游标变量可以为多个查询使用
i. 示例:
  declare
     type emp_cursor is ref cursor; --定义一个游标类型
     e_c emp_cursor; --定义一个emp_cursor类型的游标变量
     v_ename emp.ename%type;
  begin
     open e_c for select ename from emp; --使用这个变量打开特定查询
     loop
             fetch e_c into v_ename;
             exit when e_c%notfound;
             dbms_output.put(v_ename||'  ');
     end loop;
     close e_c;
end;
ii. 练习:定一个游标变量,在同一个pl/sql块中使用两次
j) 游标与集合 :fetch … bulk collect into
i. 示例
declare
   cursor emp_cursor
   is
   select ename from emp;
   type emp_table_type is table of emp.ename%type
   index by binary_integer;
   emp_table emp_table_type;
begin
   open emp_cursor;
   fetch emp_cursor bulk collect into emp_table;
   for i in 1 .. emp_table.count loop
           dbms_output.put_line(emp_table(i));
   end loop;
   close emp_cursor;
end;
ii. 练习:定义一个游标,查询emp表中所有的数据,存贮到一个记录表中,将这个记录表中的每个人的姓名、薪水打印出来
一、 异常处理
a) 分类:
i. 预定义异常:系统视为错误,有异常码和异常名称,只需要捕获异常
1. no_data_found --没有直到数据
2. too_many_rows --返回多行
3. invalid_cursor--无效游标,可能是游标没有打开
4. cursor_already_open--游标已经打开
5. dup_val_on_index –重复索引或主键
6. collection_is_null –集合为空
7. access_into_null –对象为空
8. case_not_found –没有对应的case
9. 使用步骤:
a) 捕获异常
ii. 非预定义异常:系统视为错误,有异常码,但没有异常名称,
1. 使用步骤:
a) 定义异常变量,并将异常变量与异常码关联
b) 并捕获异常
iii. 自定义异常:系统并不视为错误,但如果希望给用户提供更详细的提示时,可以进行自定义异常,
1. 使用步骤:
a) 定义异常变量,不需要将变量与异常码关联,因为本来就没有异常码
b) 在需要触发的执行码位置触发异常,if sql%notfound then raise e_error_empno
c) 捕获异常

b) 预定义异常
i. 示例:
declare
    v_ename emp.ename%type;
begin
    select ename into v_ename from emp where empno = &noo;
    dbms_output.put_line(v_ename);
exception
    when no_data_found then –处理预定义异常
            dbms_output.put_line('please input correct empno');
end;
ii. 练习:使用too_many_rows 预定义异常编码
c) 非预定义异常:
i. 示例
declare
    e_integrity exception; --定义异常变量
    pragma exception_init(e_integrity,-02291); --将异常变量与特定的异常码关联
begin
    update emp set deptno= &dno where empno = &eno;
exception
    when e_integrity then --处理非预定义异常
            dbms_output.put_line('parent key not found');
end;
ii. 练习:使用非预定义异常的方式对too_many_rows类型的异常进行处理
d) 自定义异常:
示例:
declare
    e_error_empno exception; --声明一个异常变量,与预定义异常不同的是,不需要与相应的错误码关联
begin
    update emp set deptno = & dno where empno = &no;
    if sql%notfound then
            raise e_error_empno; --在你想要触发异常的地方抛出异常
    end if;
exception
    when e_error_empno then
            dbms_output.put_line('wrong empno');
end;练习:使用自定义异常处理 编码为-06530类型的异常
e) 异常函数:
i. sqlcode
ii. sqlerrm
iii. raise_application_error
二、 函数:与存储过程不同的是,函数有返回语句
a) 示例
create or replace function fun_count
(dno in number)
return number ----一定要有return 语句
is
    v_count number;
begin
    select count(*) into v_count from emp where deptno= dno;
    return v_count; --在执行部分至少要有一个返回语句
exception
    when others then
            dbms_output.put_line(sqlerrm);
end;
b) 调用
c) 练习:编写函数,统计emp表的全部员工平均工资。查看文本。删除函数
三、 存储过程:如果有一段pl/sol在程序中可能需要多次执行,可以定义成存储过程
a) 示例:
create or replace procedure pro_out_time –声明部分
is
begin
  dbms_output.put_line(systimestamp);
end;
调用:exec pro_out_time;
调用:call pro_out_time();--注意使用call时要小括号
b) 练习:定义一个存储过程,在这个过程中,将emp表中的所有薪水的和打印出来,并调用
c) 如何显示编译错误:show errors
d) 带参存储过程:
i. 示例:
create or replace procedure pro_add_dept
(dno in dept.deptno%type,name in dept.dname%type,location dept.loc%type default 'wuxi') –参数可以有两种模式:输入( in )和输出( out )及输入输入(in out),默认为in, default 提供默认值,当没有为这个参数提供值时,自动使用默认值。所有的输入参数,如果没有提供默认值,则在调用过程时,必须要提供值
is
begin
   insert into dept values(dno,name,location);
exception
   when dup_val_on_index then
           dbms_output.put_line('dump primary key value');
   when others then
           dbms_output.put_line('input correct parameters');
end;
ii. 调用测试:
1. exec pro_add_dept(60,'instude','suzhou'); --按位置提供正确的输入参数
2. exec pro_add_dept(60,'instude','suzhou'); --提供重复的主键值
3. exec pro_add_dept(61,'instude') –使用默认值
4. exec pro_add_dept(dno=>70,name=>'study') –使用名字提供参数
iii. 使用out
1. 示例:
create or repalce procedure pro_compute
(n1 in number,n2 in number,n3 out number)
is
begin
    n3 := n1+n2;
end;
2. 调用:
var n3 number;
exec pro_compute(1,2,:n3);
iv. 使用in out参数
1. 示例
create or replace procedure pro_compute
(n1 in number,n2 in out number)
is
begin
    n2 := n1*n2;
end;
2. 调用:
var n1 number;
var n2 number;
exec :n1 := 3;
exec :n2 := 4;
exec pro_compute(:n1,:n2);
v. 查看创建的存储过程的文本:
select text from user_source where name =upper('pro_compute')
vi. 删除存储过程;
drop procedure pro_compute
e) 练习:编译一个存储过程,根据部门编号,返回部门人数。查看存储过程的文本。删除存储过程。
四、 包
a) 为什么要使用包:
i. 可以将相关的数据库对象:过程、函数、索引表、嵌套表、记录表、变长数组等放在一个逻辑相关的包里
ii. 可以通过包对对象可见性进行控制
b) 如何使用包:
i. 声明包规范:包规范类型于java接口,可以在规范中定义共有的组件:变量、常量、函数、过程、游标
create or replace  package pk_emp
is
    g_deptno emp.deptno%type;
procedure add_emp(p_empno emp.empno%type,p_ename emp.ename%type,p_deptno emp.deptno%type);
function getSal(f_empno emp.empno%type) return emp.sal%type;
end pk_emp;
ii. 定义包体:在包规范中没有声明过,只在包体中定义的元素都是私有的,只能在包内可见
   create or replace package body pk_emp
  is
       v_p_sal emp.sal%type; --定义一个私有的变量

      function getAvg(dno emp.deptno%type) –定义一个私有的函数
     return emp.sal%type
       is
        v_avg number(6,2);
      begin
                    select avg(sal) into v_avg from emp where deptno=dno;
               return v_avg;
      exception
              when others then
               dbms_output.put_line(sqlerrm);
      end getAvg;

     function existsDeptno(f_deptno emp.deptno%type) –定义一个私有的函数
     return boolean
       is
      v_count int;
      begin
      select count(*)  into v_count from dept where deptno=f_deptno;
      if v_count=1 then
             return true;
      else return false;
     end if;
     end existsDeptno;

     procedure add_emp(p_empno emp.empno%type,p_ename emp.ename%type,p_deptno emp.deptno%type) –实现包规范中定义的过程
is
      begin
     if existsDeptno(p_deptno) then –调用包内的私有函数
                     insert into emp(empno,ename,deptno)
                     values(p_empno,p_ename,p_deptno);
     else
             raise_application_error(-20001,'部门编号不存在');
     end if;
     exception
     when dup_val_on_index then
                raise_application_error(-20002,'员工编号已存在');
               when others then
               dbms_output.put_line(sqlerrm);
   end add_emp;


    function getSal     (f_empno emp.empno%type) –实现包规范中定义的函数
      return emp.sal%type
     is
        v_sal emp.sal%type;
     begin
     select sal into v_sal from emp where empno=f_empno;
     return v_sal;
                  exception
     when no_data_found then
      dbms_output.put_line('员工编号不正确');
      when others then
               dbms_output.put_line(sqlerrm);
              end getSal;
  end pk_emp;
iii. 练习:创建一个包emp_package,含有的公有变量员工编号、公有函数getMinSal、公有存储过程根据员工信息(empno,sal)修改emp表,定一个私有的函数,根据empno判断员工是否存在。调用公有函数、存储过程、私有函数,查询包定义文本,删除包
iv. 访问其他用户的包的函数:
conn sysdba/tiger as sysdba;
var min number;
exec :min :=scott.emp_package.getMinSal(10);
print min;
c) 包重载:在同一个包在中,函数和存储过程有相同的名称,但参数列表不同
i. 示例:
create or replace package emp_package
is
  function getSal(v_empno emp.empno%type) return emp.sal%type; --根据员工编号获取员工薪水
function getSal(v_ename emp.ename%type) return emp.sal%type; --根据员工姓名获取员工薪水
end emp_package;

create or replace package body emp_package
is
function getSal(v_empno emp.empno%type)
return emp.sal%type --根据员工编号获取员工薪水
    is
             v_sal emp.sal%type;
    begin
             select sal  into v_sal from emp where empno = v_empno;
             return v_sal;
    exception
             when no_data_found then
                  dbms_output.put_line('错误的员工编号'); 
    end getSal;                             
    function getSal(v_ename emp.ename%type)
return emp.sal%type --根据员工姓名获取员工薪水
    is
             v_sal emp.sal%type;
    begin
             select sal  into v_sal from emp where lower(ename) = lower(v_ename);
             return v_sal;
    exception
             when no_data_found then
                  dbms_output.put_line('错误的员工编号'); 
    end getSal;
end emp_package;
ii. 练习:使用重载创建两个存储过程,一个是根据员工编号更新薪水,另一个是根据员工姓名更新薪水
d) 包构造过程:作用是对包的变量进行初始化,在同一个会话中,构造过程在第一次调用函数或过程时被调用,并且在同一个会话中只被调用一次;
i. 如何定义构造过程:在包的结尾,以begin … end 语句块形式定义
create or replace package emp_p
is
       --定义两个全局变量,新加入的员工工资应该处于这两个变量之间
       g_minSal emp.sal%type;
      g_maxSal emp.sal%type;
       procedure addEmp(v_empno emp.empno%type,v_ename emp.ename%type,v_sal emp.sal%type);
end emp_p;

create or replace package body emp_p
is
     procedure addEmp(v_empno emp.empno%type,v_ename emp.ename%type,v_sal emp.sal%type)
       is
       begin
                 if v_sal>= g_minSal and v_sal <= g_maxSal then
                    insert into emp (empno,ename,sal) values (v_empno,v_ename,v_sal);
                else
                    raise_application_error(-20000,'薪水不在合理的范围内');
                 end if;    
       exception
                   when others then
                       dbms_output.put_line(sqlerrm); 
      end addEmp;
        --构造过程,对g_maxSal、g_minSal进行初始化
       begin
           dbms_output.put_line('构造过程被调用');
           select min(sal) into g_minSal from emp;
           select max(sal) into g_maxSal from emp;
       end;   --此后不能再有代码
ii. 练习:编写一个存储过程,根据员工的编号和薪水对emp表进行更新,更新的薪水不得低于平均工资,不得高于最高工资的120%
e) 包纯度级别:可以对包的函数或过程禁止读、写数据库的数据或包的变量
i. 示例:pragma restrict_references(updateEmp, wnds) –禁止updateEmp写数据库
ii. 示例:pragma restrict_references(updateEmp, rnds) –禁止updateEmp读数据库
iii. 示例:pragma restrict_references(updateEmp, rnps) –禁止updateEmp读包的变量
iv. 示例:pragma restrict_references(updateEmp, wnps) –禁止updateEmp写包的变量
五、 触发器
a) 什么是触发器:就是一个由事件触发而自动调用的存储过程
b) 触发器分类:DML触发器,DDL触发器,系统触发器
c) DML触发器:
i. 示例:
create or replace trigger tri_forbidOperateOnWeekend –创建一个触发器
  before insert or update or delete on emp  --定义事件、触发时机
begin
    if to_char(sysdate,'day') in ('星期六','星期日') then
        raise_application_error(-20000,'不能在休息时间对emp进行操作');
     end if;  
end forbidOperateOnWeekend;
ii. 练习:模仿上例,禁止在休息时间对emp表进行操作,要求代码纯手写
iii. 查看触发器的编译错误:show errors trigger tri_forbidOperateOnWeekend
iv. 使用谓词:inserting、updating、deleting
1. 示例:
create or replace trigger tri_forbidOperateOnWeekend
before insert or update or delete on emp 
begin
    if to_char(sysdate,'dy') in ('星期六','星期日') then
        case
            when inserting then                                        
                 raise_application_error(-20000,'不能在周末新增雇员');
            when updating then                                        
                 raise_application_error(-20000,'不能在周末修改雇员信息');
            when deleting then                                        
                 raise_application_error(-20000,'不能在周末解雇雇员');
end case;
     end if;  
end forbidOperateOnWeekend;
v. 使用of子句:在更新时可以更精确的控制触发
1. 示例:
create or replace trigger tri_updateSal
before update of sal on emp  --of 子句对列进行精确控制
for each row –表示这个触发器是一个行级触发器
begin –业务逻辑是如果更新的值比以前的值要高20%,则禁止更新
    if :new.sal <ld.sal*0.7 then -- :new.sal表示更新的值 ld.sal表示之前的值
          raise_application_error(-20000,'一次性提高工资不得超出原工资的20 %');
    end if;
end;
2. 练习:创建一个行级触发器,要求对sal列进行更新时,工资不得低于原工资的70%
vi. 使用after
1. 示例
create or replace trigger tri_sal_update_record
after update of sal on emp –after 表示在事件发生后启动触发器
for each row
begin
      insert into salUpdateRecord
      values (:old.ename,:old.sal,:new.sal,sysdate);
end tri_sal_update_record;
2. 练习:创建一个触发器,要求对工资提升20%的记录到salUpdateRecord
vii. 触发器条件:
1. 示例:
create or replace trigger tri_sal_update_record
after update of sal on emp –after 表示在事件发生后启动触发器
for each row
when (new.sal >= old.sal*1.2) –触发器启动条件,如果条件返回为true,则执行触发器,否则不执行
begin
      insert into salUpdateRecord
      values (:old.ename,:old.sal,:new.sal,sysdate);
end tri_sal_update_record;
viii. 定义变量:
1. 示例:
create or replace trigger tri_sal_update_record
after update of sal on emp
for each row
when ( new.sal >= old.sal*1.2)
declare
     v_sal number :=2500 ;   --定义变量
begin   
     if :new.sal > v_sal then
      insert into salUpdateRecord
      values (:old.ename,:old.sal,:new.sal,sysdate);
     end if;
end tri_sal_update_record;
ix. 使用dml触发器不能从基表在中查询数据:
create or replace trigger tri_sal_update_record
after update of sal on emp
for each row
when ( new.sal >= old.sal*1.2)
declare
    v_sal number :=2500 ;  
begin  
    select sal into v_sal from emp where empno = 7788;
    if :new.sal > v_sal then
        insert into salUpdateRecord
        values (:old.ename,:old.sal,:new.sal,sysdate);
     end if;
end tri_sal_update_record;
这段代码在编译时能通过,但在执行时会报错,原因是在触发器中对基表进行了查询
d) 如何查询触发器:select trigger_name from user_triggers;
e) instead of触发器:如果对复杂视图进行DML操作,需要创建instead of
i. 示例:
create or replace trigger tri_insert_v_dept_emp
  instead of insert on v_dept_emp  --instead of
for each row
begin
--假定用户插入的部门编号和员工编号在数据库中不存在
  insert into dept(deptno,dname) values(:new.deptno,:new.dname);
  insert into emp(empno,ename) values(:new.empno,:new.ename);
end tri_insert_v_dept_emp;
ii. 练习:创建一个触发器,在对v_dept_emp插入数据,判断所插入的主键是否存在,如果存在,则执行更新,否则,执行插入
f) 系统触发器:在特定的系统事件(例程启动、关闭、用户登陆、登出)发生时触发,系统触发器只能由特定的用户创建(sys/tiger as sysdba)
i. 示例:
conn sys/tiger as sysdba;
create table sysevent(
event varchar2(30),
eventtime timestamp);
create or replace trigger tri_sysevent_startup
after startup on database
begin
insert into sysevent values(ora_sysevent,sysdate);
end;
create or replace trigger tri_sysevent_shutdown
after shutdown on database
begin
insert into sysevent values(ora_sysevent,sysdate);
end;
--创建用户日志表,记载用户登陆 或登出
create table user_log(
username varchar2(30),
event varchar2(30),
eventtime timestamp);
--创建用户登入触发器
create or replace trigger tri_user_logon
after logon on database
begin
   insert into user_log values(ora_login_user,ora_sysevent,sysdate);
end;
--创建用户等处触发器
create or replace trigger tri_user_logoff
before logoff on database
begin
   insert into user_log values(ora_login_user,ora_sysevent,sysdate);
end;
g) DDL触发器:由DDL语句触发
i. 示例:
conn sys/tiger as sysdba;
--创建数据表
create table ddl_log(
username varchar2(30),
time timestamp,
objectname varchar2(30),
objecttype varchar2(30)
);
--创建触发器
create or replace trigger tri_ddl_log
after ddl on scott.schema
begin
insert into ddl_log values(ora_login_user,sysdate,ora_dict_obj_name,ora_dict_obj_type);
end;
--创建存储过程,以便验证
create or replace procedure pro_getSal
(eno in emp.empno%type,v_sal out emp.sal%type)
is
begin
    select sal into v_sal from emp where empno = eno;
end;
--查看记录表,存储过程的定义已经记录到表中,说明触发器创建成功
select * from ddl_log;
六、 用户管理
a) conn sys/tiger as sysdba;
b) create user smith
identified by "tiger"; --创建用户,
c) grant connect to s; --授予用户创建会话的权限
d) grant select, insert ,update,delete
on scott.emp to smith; --授予smith对scott的emp表的select、insert、update、delete的权限
e) revoke select,insert,update,delete
on scott.emp from smith; --收权
七、 文件导入导出
八、 触发器常见应用
a) oralce使用序列和触发器为表创建自动增长的主键值
--建表
create table book(
  bid int primary key,
  name varchar2(30),
  price number(6,1));
--创建序列
create sequence sq_book;
--创建触发器
create or replace trigger tri_book
before insert on book
for each row
begin
   select sq_book.nextval into :new.bid from dual;    
end;

--插入数据
insert into book (name,price) values ('oracle expert',99);
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics