对象类型

在 PL/SQL 中,面向对象的程序设计是基于对象类型来完成的,对象类型是用户自定义的一种复合数据类型,它封装了数据结构和用于操纵这些数据结构的过程和函数。

对象类型的组成

对象类型由两部分组成:对象类型规范和对象类型体

对象类型规范用于定义对象类型的公有属性和方法;
对象类型体用于实现对象类型规范所定义的公有方法。

对象类型属性

定义对象类型最少要包含一个属性,最多可包含 1000 个属性,定义时必须提供属性名和数据类型,但不能指定默认值和 NOT NULL,数据类型不能包括 LONG、LONG RAW、ROWID、UROWID 和 PL/SQL 特有类型,如:BOOLEAN、%TYPPE、%ROWTYPE、REF CURSOR 等。

对象类型的方法

定义对象类型可以包含也可以不包含方法,可以定义构造方法、member 方法、static 方法、map 方法和 order 方法。

  • 构造方法

当定义了一个对象类型后,系统会提供一个接收与每个属性相对应的参数的构造函数,因此大多数情况下不需要自己再编写构造函数,不过我们也可以出于如下的目的来自定义构造函数:   为对象提供初始化功能,以避免许多具有特别用途的过程只初始化对象的不同部分,可以通过构造函数进行统一初始化;
  可以在构造函数中为某些属性提供默认值;
  出于维护性的考虑,在新的属性添加到对象中时,避免要更改调用构造函数的应用程序中的代码,这样可以使已经存在的构造函数调用继续工作。
自定义构造函数使用 CONSTRUCTOR 关键字进行声明:

--定义对象类型规范
SQL> create or replace type salary_obj as object (
  --定义对象属性
  percent      number(10,4),       
  sal          number(10,2),
  --自定义构造函数
  constructor function salary_obj(p_sal number) return self as result
)
instantiable         --可实例化对象
final;               --不可以继承


--定义对象类型体
SQL> create or replace type body salary_obj
as
  --实现重载的构造函数
  constructor function salary_obj (p_sal number)
    return self as result
  as
  begin
    self.sal := p_sal;          --设置属性值
    self.percent := 1.12;       --为属性指定初值
    return;
  end;
end;
  • member 方法

用于访问对象实例的数据。当使用 member 方法时,可以使用内置参数 self 访问当前对象实例。
当定义 member 方法时,无论是否定义 self 参数,它都会被作为第一个参数传递给 member 方法,但如果要定义参数 self,那么其类型必须要使用当前对象类型,member 方法只能由对象实例调用,而不能由对象类型调用。

  • static 方法

用于访问对象类型,可以在对象类型上执行全局操作,而不需要访问特定对象实例的数据,因此 static 方法引用 self 参数。
static 方法只能由对象类型调用,不能由对象实例调用。

  • map 方法

maporder 方法不能同时定义,使用原则是可不用则均不用;
map 函数会将对象实例根据一定的调用规则返回 DATE、NUMBER、VARCHAR2 类型的标量类型,在映射对象类型为标量类型后,就可以通过对标量函数的比较来得到结果了。

--定义一个对象类型规范,该规范中包含map方法
SQL> create or replace type emp_map as object (
  --定义对象类型属性
  empno    number (4),
  sal      number (10, 2),
  comm     number (10, 2),
  deptno   number (4),
  map member function convert return real   --定义一个map方法
)
not final;  


--定义一个对象类型体,实现map函数
SQL> create or replace type body emp_map as
  map member function convert return real  is   --定义一个map方法
  begin
     return sal + comm;                         --返回标量类型的值
  end;
end;

在定义了 map 函数后,PL/SQL 会隐式地通过调用 map 函数在多个对象间进行排序或比较。
例如下面创建了一个 emp_map_tab 的对象表,向这个对象表插入多个对象,然后就可以对这个对象表进行对象的排序:

--创建emp_map类型的对象表
SQL> create table emp_map_tab of emp_map;


--向对象表中插入员工薪资信息对象。
SQL> insert into emp_map_tab(empno, sal, comm, deptno) values(7123, 3000, 200, 20);
SQL> insert into emp_map_tab(empno, sal, comm, deptno) values(7124, 2000, 800, 20);
SQL> insert into emp_map_tab(empno, sal, comm, deptno) values(7125, 5000, 800, 20);
SQL> insert into emp_map_tab(empno, sal, comm, deptno) values(7129, 3000, 400, 20);

SQL> commit;


SQL> select value(e) val, e.sal + e.comm from emp_map_tab e order by 1;

VAL(EMPNO, SAL, COMM, DEPTNO)            E.SAL+E.COMM
---------------------------------------- ------------
EMP_MAP(7124, 2000, 800, 20)                     2800
EMP_MAP(7123, 3000, 200, 20)                     3200
EMP_MAP(7129, 3000, 400, 20)                     3400
EMP_MAP(7125, 5000, 800, 20)                     5800
  • order 方法

order 方法只能对两个对象进行比较,且必须是返回数值型结果的函数,根据结果返回正数、负数或0。该方法只有两个参数,SELF 和另一个要比较的对象类型,如果传递该参数为 NULL,则返回 NULL。

--定义一个对象规范,该规范中包含order方法
SQL> create or replace type emp_order as object (
  --定义对象类型属性
  empno    number (4),
  sal      number (10, 2),
  comm     number (10, 2),
  deptno   number (4),
  order member function match(r emp_order) return integer   --定义一个order方法
)
not final; 


--定义一个对象类型体,实现order函数
SQL> create or replace type body emp_order as
  order member function match(r emp_order) return integer is   
  begin
     if ((self.sal + self.comm) < (r.sal + r.comm)) then
        return -1;      --可为任何负数
     elsif ((self.sal + self.comm) > (r.sal + r.comm)) then
        return 1;       --可为任何正数
     else 
        return 0;       --如果相等则为0
     end if;
  end match;
end;

定义了ORDER函数后,就可以对两个对象进行比较:

declare
   emp1 emp_order := emp_order(7112, 3000, 200, 20);    --定义员工1
   emp2 emp_order := emp_order(7113, 3800, 100, 20);    --定义员工2
begin
   --对员工1和2进行比较,获取返回结果
   if emp1 > emp2 then
      dbms_output.put_line('员工1的薪资加提成比员工2大!');
   elsif emp1 < emp2 then
      dbms_output.put_line('员工1的薪资加提成比员工2小!');
   else
      dbms_output.put_line('员工1的薪资加提成与员工2相等!');
   end if;
end;
/
员工1的薪资加提成比员工2小!

PL/SQL 过程已成功完成。

同样,使用 order 成员方法的对象也可以插入到对象表中,使用对象表的排序功能利用 order 成员方法进行排序:

--创建emp_order类型的对象表
SQL> create table emp_order_tab of emp_order;


--向对象表中插入员工薪资信息对象。
SQL> insert into emp_order_tab(empno, sal, comm, deptno) values(7123, 3000, 200, 20);
SQL> insert into emp_order_tab(empno, sal, comm, deptno) values(7124, 2000, 800, 20);
SQL> insert into emp_order_tab(empno, sal, comm, deptno) values(7125, 5000, 800, 20);
SQL> insert into emp_order_tab(empno, sal, comm, deptno) values(7129, 3000, 400, 20);

SQL> commit;


SQL> select value(e) val, e.sal + e.comm from emp_order_tab e order by 1;

VAL(EMPNO, SAL, COMM, DEPTNO)            E.SAL+E.COMM
---------------------------------------- ------------
EMP_ORDER(7124, 2000, 800, 20)                   2800
EMP_ORDER(7123, 3000, 200, 20)                   3200
EMP_ORDER(7129, 3000, 400, 20)                   3400
EMP_ORDER(7125, 5000, 800, 20)                   5800

对象类型的基本应用

基本应用也属于最常规、最简单的应用,讲述如何建立和使用独立的并且与其他对象类型无关的对象类型。 包括语法、建立带方法和不带方法的对象类型。

  • 建立和使用不包含任何方法的对象类型

定义对象类型规范 DEPT_OBJ

SQL> create or replace type dept_obj as object(deptno number, dname varchar2(10));

类型已创建。

创建列对象表:

SQL> create table dept_obj_tab(dept dept_obj, loc varchar2(10));

表已创建。

写入数据:

SQL> insert into dept_obj_tab(dept, loc) values(dept_obj(10, 'Sales'), 'CHINA');

已创建 1 行。

SQL> commit;
  • 建立和使用包含方法的对象类型

定义对象类型规范 EMP_OBJ

SQL> create or replace type emp_obj as object (
  --声明对象类型属性
  empno   number(4),
  ename   varchar2(20),
  sal     number(10,2),
  deptno  number(4),
  --声明对象类型实例方法
  member procedure change_sal(p_empno number, p_sal number),
  member procedure change_deptno(p_empno number, p_deptno number),
  member function get_sal(p_empno number) return number,
  member function get_deptno(p_empno number) return integer,
  --声明对象类型静态方法
  static function get_ename (p_empno number) return varchar2
);

定义对象类型体:

SQL> create or replace type body emp_obj
as
  --定义对象成员方法,更改员工薪资
  member procedure change_sal (p_empno number, p_sal number)
  is
  begin
    update emp
      set sal = p_sal
    where empno = p_empno;
  end;
  --定义对象成员方法,更改员工部门
  member procedure change_deptno (p_empno number, p_deptno number)
  is
  begin
    update emp
      set deptno = p_deptno
    where empno = p_empno;
  end;
  --定义对象成员方法,获取员工薪资
  member function get_sal (p_empno number)
    return number
  is
    v_sal   number (10, 2);
  begin
    select sal
      into v_sal
    from emp where empno = p_empno;
    return v_sal;
  end;
  --定义对象成员方法,获取员工部门
  member function get_deptno (p_empno number)
    return integer
  is
    v_deptno   int;
  begin
    select deptno
      into v_deptno
    from emp where empno = p_empno;
    return v_deptno;
  end;
  --声明对象类型静态方法
  static function get_ename (p_empno number)
  return varchar2
  is
    v_ename   varchar2(50);
  begin
    select ename
      into v_ename
    from emp where empno = p_empno;
    return v_ename;
  end;
end;

建立行对象表:

SQL> create table emp_obj_tab of emp_obj;

写入数据:

SQL> insert into emp_obj_tab(empno, ename, sal, deptno) values(7123, 'King', 200, 10);
SQL> insert into emp_obj_tab(empno, ename, sal, deptno) values(7124, 'Scott', 800, 20);
SQL> insert into emp_obj_tab(empno, ename, sal, deptno) values(7125, 'John', 800, 20);
SQL> insert into emp_obj_tab(empno, ename, sal, deptno) values(7129, 'Smith', 1000, 30);

SQL> commit;

对象类型的高级应用

  • 嵌套对象类型

嵌套对象类型是指在一个对象中嵌入另一个对象类型。

--定义地址对象类型规范
SQL> create or replace type address_type as object (
  street_addr1  varchar2(25),   --街道地址1
  street_addr2  varchar2(25),   --街道地址2
  city          varchar2(30),   --城市
  state         varchar2(20),    --省份
  zip_code      number,         --邮政编码
  --实例方法,返回地址字符串
  member function tostring return varchar2,
  --map方法提供地址比较函数
  map member function mapping_function return varchar2
);


--定义地址对象类型体,实现实例方法与map函数
SQL> create or replace type body address_type
as
  --将地址属性转换为字符形式显示
  member function tostring
    return varchar2                    
  is
  begin
    if (street_addr2 is not null) then
      return street_addr1 || chr(10) || 
             street_addr2 || chr(10) || 
             city || ',' || state || ' ' || zip_code;
    else
      return street_addr1 || chr (10) || 
             city || ',' || state || ' ' || zip_code;
    end if;
  end;
  --定义地址对象map函数的实现,返回varchar2类型
  map member function mapping_function    
    return varchar2
  is
  begin
    return to_char(nvl(zip_code, 0), 'fm00000') || 
           lpad(nvl(city, ''), 30) || 
           lpad(nvl(street_addr1, ''), 25) || 
           lpad(nvl(street_addr2, ''), 25);
  end;
end;
--定义一个对象类型规范,该规范中包含地址对象类型的属性
SQL> create or replace type employee_addr as object (
  empno    number (4),
  sal      number (10, 2),
  comm     number (10, 2),
  deptno   number (4),
  addr     address_type,
  member function get_emp_info return varchar2   
)
not final; 


--定义对象类型体,实现get_emp_info方法
SQL> create or replace type body employee_addr
as
  member function get_emp_info
    return varchar2                    --返回员工的详细信息
  is
  begin
    return '员工' || self.empno || '的地址为:' || self.addr.tostring;
  end;
end;
--调用
declare
  o_address address_type;
  o_emp employee_addr; 
begin
  o_address := address_type('玉兰一街', '二巷', '深圳', 'DG', 523343);
  o_emp := employee_addr(7369, 5000, 800, 20, o_address); 
  dbms_output.put_line('员工信息为' || o_emp.get_emp_info);
end;
/
员工信息为员工7369的地址为:玉兰一街
二巷
深圳,DG 523343

PL/SQL 过程已成功完成。
  • 对象继承

为了从一个父对象中继承,在子对象中使用 UNDER 关键字,指定一个父对象名称,该父对象必须是使用 NOT FINAL 关键字定义的对象。

--创建父对象
SQL> create or replace type person_obj as object (
  person_name varchar (20),   
  gender      varchar2 (10),          
  birthdate   date,                 
  address     varchar2 (50),         
  member function get_info return varchar2                 
)
not final;                            

SQL> create or replace type body person_obj   
as
  member function get_info
    return varchar2
  is
  begin
    return '姓名:' || person_name || ', 家庭住址:' || address;
  end;
end;
--对象类型使用under语句从person_obj中继承
SQL> create or replace type under_person_obj under person_obj (
  empno   number (6),
  sal     number (10, 2),
  job     varchar2 (10),
  member function get_emp_info return varchar2
);


SQL> create or replace type body under_person_obj as
  member function get_emp_info  return varchar2 is
  begin
    --在对象类型体中可以直接访问在父对象中定义的属性
    return '员工编号:' || self.empno ||' 员工名称:' ||
           self.person_name || ' 职位:' || self.job;
  end;
end;
--调用
declare
   o_emp under_person_obj;         --定义员工对象类型的变量
begin
   --使用构造函数实例化员工对象
   o_emp := under_person_obj('张小五', 'F', to_date('1983-01-01','YYYY-MM-DD'),
                             '中信', 7981, 5000, 'Programmer');
   dbms_output.put_line(o_emp.get_info);          --输出父对象的人员信息
   dbms_output.put_line(o_emp.get_emp_info);      --输出员工对象中的员工信息
end;
/
姓名:张小五, 家庭住址:中信
员工编号:7981 员工名称:张小五 职位:Programmer

PL/SQL 过程已成功完成。

可以看到,由于 under_person_obj 合并了 person_obj 对象,因此在构造函数中初始化对象时,必须先对父对象中的属性进行初始化,然后初始化子对象类型中的属性。

  • 方法重载

所谓的重载就是定义多个具有同名的函数或过程,但是参数类型或个数不同,由编译器根据调用参数确定执行哪一个子程序。这种重载方式也称为静态多态。在使用对象继承时,也可以使用方法重载,这种重载使用了动态方法调用的能力,也称为动态多态或运行时多态。
对象方法的重载使用 OVERRIDING 关键字,不是根据参数的个数或类型来决定调用哪一个方法,而是根据优先级进行调用,也就是总是先调用子类的方法。

--对象类型使用under语句从person_obj中继承
SQL> create or replace type overriding_person_obj under person_obj (
  empno  number(6),
  sal    number(10, 2),
  job    varchar2(10),
  member function get_emp_info return varchar2,
  --定义重载方法
  overriding member function get_info return varchar2
)


SQL> create or replace type body overriding_person_obj as
  member function get_emp_info  return varchar2 is
  begin
    --在对象类型体中可以直接访问在父对象中定义的属性
    return '员工编号:' || self.empno || ' 员工名称:' ||
           self.person_name || ' 职位:' || self.job;
  end;
  --实现重载方法
  overriding member function get_info return varchar2 as
  begin
    return '员工编号:' || self.empno || ' 员工名称:' ||
           self.person_name || ' 职位:' || self.job; 
  end;          
end;

对象类型的维护

  • 显示对象类型信息
SQL> select type_name, attributes, final from user_types;
  • 增删对象类型的属性
SQL> alter type under_person_obj add attribute email varchar2(50) cascade;

SQL> alter type under_person_obj drop attribute email cascade;

cascade 关键字级联更新依赖对象类型的对象类型和对象表。

管道化表函数

表函数是从 Oracle 9i 开始提供的函数,所谓表函数就是能够产生一组作为输出行集合的函数,其核心思想是: 将一个运算的结果直接输送到下一运算中,不需要创建临时关系表来保存中间结果。表函数可以直接用 SQL 语句进行查询,如: SELECT * FROM table(func()); 等,使用起来就好像是一个真正的数据库表,但是由于在内存中,速度比表要快很多。
管道函数其实就是表函数的一种形式,它必须返回一个集合,在函数中,PIPE ROW 语句被用来返回该集合的单个元素,该函数必须以一个空的 RETURN 语句结束,以表明它已经完成,一旦创建了上述函数,就可以使用 TABLE 操作符从 SQL 查询中调用它。

  • 定义对象类型
SQL> create or replace type employee_obj as object(
  f_id number,
  f_empno number,
  vc_ename varchar2(50),
  vc_job varchar2(50),
  d_hiredate date,
  f_sal number, 
  f_deptno number
);

类型已创建。
  • 创建TABLE类型
SQL> create or replace type employee_obj_tab as table of employee_obj;

类型已创建。
  • 创建管道化函数
create or replace function f_emp_pipe(i_deptno in number)
return employee_obj_tab   --返回table类型
pipelined as
  v_deptno            number := i_deptno;
  v_employee_obj      employee_obj;  --记录类型变量
begin
  for x in (
    select
      rownum f_id,
      empno f_empno,
      ename vc_ename,
      job vc_job,
      hiredate d_hiredate,
      sal f_sal,
      deptno f_deptno
    from emp
    where deptno = v_deptno
    order by empno
  ) loop

    v_employee_obj := employee_obj(
      x.f_id,
      x.f_empno,
      x.vc_ename,
      x.vc_job,
      x.d_hiredate,
      x.f_sal,
      x.f_deptno
    );

    pipe row(v_employee_obj);

  end loop;

  return;

exception
  when no_data_needed then
    dbms_output.put_line('运行结束,未输出管道所有记录。');
    return;
end;
  • 调用函数
SQL> select * from table(f_emp_pipe(20));

      F_ID    F_EMPNO VC_ENAME   VC_JOB     D_HIREDATE      F_SAL   F_DEPTNO
---------- ---------- ---------- ---------- ---------- ---------- ----------
         1       7369 Python     CLERK      1980-12-17        800         20
         2       7566 JONES      MANAGER    1981-04-02       2975         20
         3       7788 SCOTT      ANALYST    1987-04-19       3000         20
         4       7876 ADAMS      CLERK      1987-05-23       1100         20
         5       7902 FORD       ANALYST    1981-12-03       3000         20


SQL> select * from table(f_emp_pipe(100));

未选定行         

参考资料

https://blog.csdn.net/bbliutao/article/details/9765469
https://blog.csdn.net/indexman/article/details/27580517
https://blog.csdn.net/lianjiww/article/details/81592293

原创文章,转载请注明出处:http://www.opcoder.cn/article/20/