PL_SQL模块学习之十四、包

在一个大型项目中,可能有很多模块,而每个模块又有自己的过程、函数等。而这些过程、函数默认是放在一起的(如在PL/SQL中,过程默认都是放在一起 的,即Procedures中),这些非常不方便查询和维护,甚至会发生误删除的事件。

PL/SQL为了满足程序模块化的需要,引入了包的构造。通过使用包就可以分类管理过程和函数等。

(1)包是一种数据库对象,用来存储相关程序结构的对象,它存储于数据字典中,相当于一个容器。将逻辑上相关的过程、函数、变量、常量和游标组合成一个更大的单位。用户可以从其他 PL/SQL 块中对其进行引用,对整个包的访问权只需一次授予。

(2)包类似于C++和JAVA语言中的类,其中变量相当于类中的成员变量,过程和函数相当于类方法。把相关的模块归类成为包,每个包又是相互独立的。在不同的包中,过程、函数都可以重名,这解决了在同一个用户环境中命名的冲突问题。

(3)包由两个分离的部分组成:包头(PACKAGE)和包体(PACKAGE BODY)。包头是包的说明部分,是对外的操作接口,对应用是可见的;包体是包的代码和实现部分,对应用来说是不可见的黑盒。所以包具有信息隐蔽性(informationhiding),仅在算法和数据结构设计有关层可见。

(4)当程序首次调用包内函数或过程时,ORACLE将整个包调入内存,当再次访问包内元素时,ORACLE直接从内存中读取,而不需要进行磁盘I/O操作,从而使程序执行效率得到提高。且在同一个会话中,公用变量的值将被保留,直到会话结束

以上参考《Oracle包和包体》《oracle之包的详解》《Oracle—包和包体》

1. 包的创建

包由包头和包体两部分组成,包的创建应该先创建包头部分,然后创建包体部分。创建、删除和编译包的权限同创建、删除和编译存储过程的权限相同。
创建包的语法规范:

PACKAGE pkg_name
    Is
        [变量或者类型声明]
        [游标声明]
        [主对象声明(函数、过程等)]
    END pkg_name

创建包体的语法规范:

PACKAGE BODY pkg_name
    Is
        [变量或者游标声明]
        [游标相关SELECT语句声明]
        [主对象的body声明]
    END pkg_name;

Pkg_name.element_name来调用包中元素。
示例:
创建一个包含全局变量、游标这两个过程的包,并将两个同名过程实现重载

SQL> create or replace
  2  package emp_pkg is
  3  var_empno number; --公有变量
  4     procedure add_emp(e_number number,e_name varchar2,e_date date);
  5     procedure del_emp(e_number number);
  6     procedure del_emp(e_name varchar2);
  7     procedure raise_sal(e_number number,salary number);
  8  end;
  9  /

程序包已创建。
SQL> l
  1  create or replace
  2  package body emp_pkg is
  3  var_inner_number number:= 0;
  4      procedure add_emp(e_number number,e_name varchar2,e_date date) is
  5      begin
  6           insert into emp (empno,ename,hiredate) values(e_number,e_name,sysdate);
  7      end add_emp;
  8      procedure del_emp(e_number number) is
  9      begin
 10          delete from emp where empno = e_number;
 11      end del_emp;
 12      procedure del_emp(e_name varchar2) is
 13          begin
 14              delete from emp where ename=e_name;
 15          end del_emp;
 16      procedure raise_sal(e_number number,salary number) is
 17      begin
 18          update emp set sal=salary where empno=e_number;
 19      end raise_sal;
 20
 21      begin
 22           select empno into var_empno from emp
 23          where hiredate = (select min(hiredate) from emp);
 24*     end;
SQL> /

程序包体已创建。

2. 包的调用及过程重载

调用包emp_pkg的过程并验证

SQL> exec emp_pkg.add_emp(1001,'dannel',sysdate);

PL/SQL 过程已成功完成。

SQL> select ename,hiredate from emp where empno=1001;

ENAME      HIREDATE
---------- --------------
dannel     17-3-20

SQL> exec emp_pkg.del_emp(1001);

PL/SQL 过程已成功完成。

SQL> select ename,hiredate from emp where empno=1001;

未选定行

3. 包的私有过程与函数

私有过程是指只能在包内部调用的过程。
示例:
重新编译包emp_kg的包体,增加两个私有元素:私有函数emp_count、私有过程show_emp_count

SQL> create or replace
  2  package body emp_pkg is
  3  var_inner_number number:= 0;
  4      procedure add_emp(e_number number,e_name varchar2,e_date date) is
  5      begin
  6          insert into emp (empno,ename,hiredate) values(e_number,e_name,sysdate);
  7      end add_emp;
  8      procedure del_emp(e_number number) is
  9      begin
 10          delete from emp where empno = e_number;
 11      end del_emp;
 12      procedure del_emp(e_name varchar2) is
 13          begin
 14              delete from emp where ename=e_name;
 15          end del_emp;
 16      procedure raise_sal(e_number number,salary number) is
 17      begin
 18          update emp set sal=salary where empno=e_number;
 19      end raise_sal;
 20	#添加私有函数emp_count
 21      function emp_count
 22          return number
 23          is
 24          var_count number;
 25          begin
 26              select count(*) into var_count
 27                  from emp;
 28              return var_count;
 29          exception
 30              when others
 31                  then
 32                      return(0);
 33          end emp_count;
 
 34	#添加私有过程show_emp_count,并调用私有函数emp_count
 35      procedure show_emp_count
 36      is
 37          var_count number;
 38      begin
 39          var_count := emp_count;
 40          dbms_output.put_line
 41              ('the total number is :'||var_count);
 42          end show_emp_count;
 
 43
 44      begin
 45          select empno into var_empno from emp
 46          where hiredate = (select min(hiredate) from emp);
 47      end;
 48  /

程序包体已创建。

因为没有在包声明中创建的函数或者过程,而是在包体创建时定义的函数或者过程称为私有对象只能在包内部被调用,而其他任何程序都无法调用,否则报错。

SQL> declare
  2  var_count number;
  3  begin
  4  var_count := emp_pkg.emp_count;
  5  dbms_output.put_line(var_count);
  6  end;
  7  /
var_count := emp_pkg.emp_count;
                     *4 行出现错误:
ORA-06550: 第 4,22 列:
PLS-00302: 必须声明 'EMP_COUNT' 组件
ORA-06550: 第 4,1 列:
PL/SQL: Statement ignored


SQL> exec emp_pkg.show_emp_count;
BEGIN emp_pkg.show_emp_count; END;

              *1 行出现错误:
ORA-06550: 第 1,15 列:
PLS-00302: 必须声明 'SHOW_EMP_COUNT' 组件
ORA-06550: 第 1,7 列:
PL/SQL: Statement ignored

tips:
如果在创建包时已经声明一个对象,在创建包体时却没有定义,编译时将
报错


查看包emp_pkg的结构定义

SQL> desc emp_pkg;
PROCEDURE ADD_EMP
参数名称                       类型                    输入/输出默认值?
------------------------------ ----------------------- ------ --------
 E_NUMBER                       NUMBER                  IN
 E_NAME                         VARCHAR2                IN
 E_DATE                         DATE                    IN
PROCEDURE DEL_EMP
参数名称                       类型                    输入/输出默认值?
------------------------------ ----------------------- ------ --------
 E_NUMBER                       NUMBER                  IN
PROCEDURE DEL_EMP
参数名称                       类型                    输入/输出默认值?
------------------------------ ----------------------- ------ --------
 E_NAME                         VARCHAR2                IN
PROCEDURE RAISE_SAL
参数名称                       类型                    输入/输出默认值?
------------------------------ ----------------------- ------ --------
 E_NUMBER                       NUMBER                  IN
 SALARY                         NUMBER                  IN

4. 包的变量和游标

4.1 包的变量

  • 包变量分为私有变量和公有变量
  • 在包规范创建的变量称为公有变量,可在包外被其他对象调用
  • 在包体创建时定义的变量是私有变量,只有包内的函数或者过程可以调用

示例:
创建一个包,包含公有变量var_date过程change_sal函数get_empname

SQL> create or replace package emp_api as
  2     var_date date;
  3  procedure change_sal;
  4  function get_empname
  5     return emp.ename%type;
  6  end emp_api;
  7  /

程序包已创建。

创建包体:
包过程change_sal的作用是:将7698的所有员工工资减少5%。
函数get_empname的作用是:查询表emp中工资最高的员工姓名,并打印到屏幕。

SQL> create or replace package body emp_api as
  2      procedure change_sal
  3      is
  4          begin
  5              update emp
  6                  set sal = sal*0.95
  7              where mgr=7698;
  8      end change_sal;
  9      function get_empname
 10          return emp.ename%type
 11          is
 12              var_empname emp.ename%type;
 13          begin
 14              select ename into var_empname
 15                  from emp
 16              where sal=(select max(sal) from emp);
 17              return var_empname;
 18              exception
 19                  when others
 20                      then
 21                          dbms_output.put_line('error....');
 22          end get_empname;
 23          begin
 24              select sysdate into var_date from dual;
 25          end emp_api;
 26  /

查询emp表中工资最高的员工姓名


SQL> select emp_api.get_empname from dual;

GET_EMPNAME
----------------------------
tom

执行emp_api的change_sal过程

#首先查看部门7698的员工工资
SQL> select sal from emp where mgr=7698;

       SAL
----------
      1600
      1250
      1250
      1500
      1150
#再执行过程
SQL> exec emp_api.change_sal;
old value is  1600
new value is 1520
old value is  1250
new value is 1187.5
old value is  1250
new value is 1187.5
old value is  1500
new value is 1425
old value is  1150
new value is 1092.5
#不用继续查看也能发现工资均已减少5%
PL/SQL 过程已成功完成。

4.2 包的游标

游标的具体使用可回顾:《PL_SQL模块学习之十、游标》

  • 在创建包规范时,可使用游标变量调用游标。
  • 游标变量在运行时会动态绑定到对应的select语句,
  • 为了创建游标变量,需先声明游标变量的对应记录,该纪录的数据类型与select语句中使用的结果集相同。

示例:
通过输入的员工id,获得员工相应的信息,并使用游标获取这个结果集合,如果为空,则赋予要显示的属性的对应信息。

首先创建包规范:

create or replace package emp_pkg as
    -- 定义一个记录emp_record_type,
    -- 该纪录的结果集数据类型与表emp的对应列数据类型一致
    type emp_record_type is record 
        (ename emp.ename%type,
        job emp.job%type,
        sal emp.sal%type,
        deptno emp.deptno%type);
    -- 声明一个ref cursor类型的游标变量,
    -- 该变量返回数据类型未定义的记录emp_record_type
    type emp_cursor is ref cursor  [return emp_record_type];
    procedure get_emp_infor
        (emp_id number,
         emp_cur in out emp_cursor);
    end emp_pkg;

上述包规范中,声明了一个
记录类型emp_redord_type:用于定义游标变量使用的select语句获得的结果集合;
游标变量emp_cursor:类型是REF CURSOR
过程get_emp_infor根据输入的参数值不同返回不同的结果集的游标变量,这些结果集的记录类型都是emp_record_type

创建包体:

create or replace package body emp_pkg as
  procedure get_emp_infor
      (emp_id number,
       emp_cur in out emp_cursor)
      is
      begin
      -- 根据emp_id 的不同值使用select语句填充游标变量
          if emp_id is null
              then
                  open emp_cur for  -- open for 为不同查询打开相同游标变量
                      select 'ename' ename,
                              null job,
                              null sal,
                              null deptno from dual;
          elsif emp_id is not null then
              open emp_cur for    -- open for 为不同查询打开相同游标变量
                  select ename ename,
                          job job,
                          sal sal,
                          deptno deptno
                      from emp
                      where empno=emp_id;
          end if;
      end get_emp_infor;
end emp_pkg;

验证过程的执行结果:

-- 首先声明一个游标变量类型的变量
SQL> var emp_cr refcursor;
-- 在pl/sql块中用冒号加变量名表示进行引用
SQL> exec emp_pkg.get_emp_infor(7879,:emp_cr);

PL/SQL 过程已成功完成。

SQL> print emp_cr;

ENAME      JOB              SAL     DEPTNO
---------- --------- ---------- ----------
tom        MANAGER        10000

SQL> exec emp_pkg.get_emp_infor(null,:emp_cr);

PL/SQL 过程已成功完成。
-- emp_id为null时,会执行select ... from dual填充游标,而不是从实际表中获取数据
SQL> print emp_cr;

ENAME J S D
----- - - -
发布了94 篇原创文章 · 获赞 24 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_32392597/article/details/104927514