`
刘金剑
  • 浏览: 147469 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PL\SQL用户指南与参考10.1 转载

阅读更多
第十章 PL/SQL对象类型

一、抽象的角色

抽象是对一个真实世界实体的高级描述或建模。它能排除掉无关的细节内容,使我们的日常生活更有条理。例如,驾驶一辆汽车时,我们是不需要知道它的发动机是如何工作的。由变速排档、方向盘、加速器和刹车组成的接口就能让我们有效地使用它。而其中每一项的详细信息对于日常驾驶来说并不重要。

抽象是编程的核心内容。例如,我们在隐藏一个复杂的算法时只要编写一个过程,然后为它传递参数就可以做到过程化抽象。如果需要改变具体的实现,换一个过程体即可。有了抽象后,那些调用过程的程序就不需要再修改了。

我们在指定变量的数据类型时,可以使用数据抽象。数据类型代表了对于想操作的数值的值集合和操作符集合。比如说一个POSITIVE类型的变量,只能存放正整数,也只能用于加减乘等运算。使用这个变量时,我们不必知道PL/SQL是如何存储整数或是实现算术运算的。

对象类型是大多数编程语言内置类型的一个概括。PL/SQL提供了大量的标量类型和复合类型,每种类型都与一组预定义操作符相关联。标量类型(如 CHAR)是没有内部组成成分的。但复合类型(如RECORD)是有内部组成成分的,并且其中每一个部分都可以被独立操作。同RECORD类型一样,对象类型也是复合类型。但是,它的操作是用户自定义的,而不是预定义的。

目前,我们还不能用PL/SQL定义对象类型。它们必须用CREATE语句创建并存放在Oracle数据库中,这样才能被许多程序所共享。使用对象类型的程序称为客户端程序,它可以声明并操作对象,但并不要求知道对象类型是如何表现数据或实现操作。这就能够让我们分别编写程序和对象类型,即便是在改变了对象实现时也不会影响到程序。因此,对象类型既支持过程化和又支持数据抽象。

二、什么是对象类型

对象类型是一个用户自定义复合类型,它封装了数据结构和操作这个数据结构的函数和过程。数据结构中的变量称为属性,函数和过程称为方法。通常,我们认为对象(如人、车、银行账户)都是有着属性和行为的。例如一个婴儿有性别、年龄和体重这些属性,行为有吃、喝、睡等。对象类型能够让我们把这些内容抽象出来并在应用程序中使用。

使用CREATE TYPE语句创建对象类型的时候,我们实际上是创建了真实世界中某个对象的抽象模板。模板只指定了我们在程序中能用到的属性和行为。比如一个雇员有很多属性,但通常只有他的一部分信息是我们的应用程序所需要的,见下图:

假设我们现在需要编写一个为雇员分发奖金的程序。因为并不是雇员的所有属性都能用于解决这个问题,所以,我们只需设计一个抽象的雇员,拥有与解决问题相关的属性即可:姓名、ID号、部门、职称、工资和级别。然后,设计一些具体的操作方法,例如更改雇员的级别。

下一步就是定义用于数据表现的变量(属性)和用于执行操作的子程序集(方法)。最后,我们把属性和方法封装到对象类型中去。

对象的属性是公有的(对客户端程序可见)。但是,设计良好的程序是不应该直接操作这些属性的,我们应该为这些操作编写相应的方法。这样,雇员数据就能保存在一个合适的状态。

在运行时,我们可以建立抽象雇员的实例(通常称为对象),然后为它的属性赋值。我们可以按照我们的需求创建任意多个实例。每个对象都有姓名、编号、职别等,如下图所示:

三、为什么使用对象类型

对象类型能把大的系统划分为多个逻辑实体,简化系统复杂度。这就使我们可以创建模块化、可维护、可重用的组件。也能让不同小组的程序员并行开发软件组件。

对象类型靠封装数据的操作来把数据维护代码从SQL脚本中分离出来,并把PL/SQL块封装到方法里。使用对象方法可以避免很多在数据访问时带来的负面影响,同时,对象类型隐藏实现细节,更新细节内容时不必修改客户端程序。

对象类型可以为现实数据建模。现实世界中的复杂实体和关系都可以直接映射到对象类型中。并且,对象类型还可以直接映射到面向对象语言(如Java和C++)的类中。

四、对象类型的结构

与包相同,对象类型也有两部分:说明和体,如下图所示。说明部分是应用程序接口;它声明了数据结构(属性集合)和所需的操作(方法)。方法体部分是对已声明方法的实现。

客户端程序要使用到的所有方法都在说明中声明。我们可以把对象说明想象成一个可选的接口,把对象体想象成一个黒盒。我们可以在不改变说明部分的前提下调试,增强或替换对象体,并且不会对客户端程序造成影响。

在一个对象说明中,所有的属性都必须声明在方法之前。只有子程序的声明才需要实现。所以,如果一个对象类型的说明只声明了属性,那么对象类型的体就没有必要了。我们不能在对象体中声明属性。对象说明中的声明都是公有的。

为了能更好的了解结构,请看下面的例子。这是一个复数的对象类型,有实数部分和虚数部分,并有几个与复数操作相关的方法。

CREATE  TYPE  complex AS  OBJECT(
  rpart   REAL ,   -- attribute
  ipart   REAL ,
  MEMBER FUNCTION  plus(x complex)
    RETURN  complex,   -- method
  MEMBER FUNCTION  LESS(x complex)
    RETURN  complex,
  MEMBER FUNCTION  times(x complex)
    RETURN  complex,
  MEMBER FUNCTION  divby(x complex)
    RETURN  complex
);

CREATE  TYPE  BODY  complex AS

  MEMBER FUNCTION  plus(x complex)
    RETURN  complex IS
  BEGIN
    RETURN  complex(rpart + x.rpart, ipart + x.ipart);
  END  plus;

  MEMBER FUNCTION  LESS(x complex)
    RETURN  complex IS
  BEGIN
    RETURN  complex(rpart - x.rpart, ipart - x.ipart);
  END  LESS;

  MEMBER FUNCTION  times(x complex)
    RETURN  complex IS
  BEGIN
    RETURN  complex(rpart * x.rpart - ipart * x.ipart,
                   rpart * x.ipart + ipart * x.rpart);
  END  times;

  MEMBER FUNCTION  divby(x complex)
    RETURN  complex IS
    z   REAL  := x.rpart ** 2 + x.ipart ** 2;
  BEGIN
    RETURN  complex((rpart * x.rpart + ipart * x.ipart) / z,
                   (ipart * x.rpart - rpart * x.ipart) / z);
  END  divby;
END ;

五、对象类型组件

对象类型封装了数据和操作。我们可以在对象类型说明中声明属性和方法,但不能声明常量、异常、游标或类型。我们至少要声明一个属性(最多1000个),方法是可选的。

1、属性

同变量一样,属性也有名称和数据类型。对象类型中的名称必须是唯一的(但在其他的对象类型中可以重用)。除了下面几种类型之外,其他任何Oralce类型都可以使用:

  1. LONG和LONG RAW
  2. ROWID和UROWID
  3. PL/SQL特定类型BINARY_INTEGER及它的子类型、BOOLEAN、PLS_INTEGER、RECORD、REF CURSOR、%TYPE和%ROWTYPE
  4. PL/SQL包内定义的数据类型

我们不能在声明属性的时候用赋值语句或DEFAULT子句为它初始化。同样,也不能对属性应用NOT NULL约束。但是,对象是可以存放到添加了约束的数据表中。

数据结构中的属性集合依赖于真实世界中的对象。例如,为了表现一个分数,我们只需要两个INTEGER类型的变量。另一方面,要是表现一个学生,我们需要几个VARCHAR2来存放姓名、住址、电话号码和状态等,再添加一个VARRAY类型变量用来存储课程和分数。

数据结构可能是复杂的。例如,一个属性的数据类型可能是另外一个对象类型(称为嵌套对象类型)。有些对象类型,像队列、链表和树,都是动态的,它们是随着使用的需要而动态改变存储长度的。递归对象类型能够直接或间接的包含自身类型,这样就能创建出更诡异的数据类型。

2、方法

一般的,方法就是用关键字MEMBER或STATIC声明在对象说明部分的子程序。方法名不能和对象类型名、属性名重复。MEMBER方法只能通过对象实例调用,如:

instance_expression.method()

但是,STATIC方法直接通过对象类型调用,而不是实例,如:

object_type_name.method()

方法的定义规则与打包子程序的相同,也分为说明和体两个部分。说明部分由一个方法名和一个可选的参数列表组成,如果是函数,还需要包含一个返回类型。包体就是一段能执行一个特殊任务的代码。

对于对象类型说明中的每个方法说明,在对象类型体中都必须有与之对应的方法体实现,除非这个方法是用关键字NOT INSTANTIABLE加以限定,它的意思就是方法体的实现只在子类中出现。为了使方法说明和方法体相匹配,PL/SQL编译器采用token-by- token的方式把它们的头部进行比较。头部必须精确匹配。

与属性相同,一个形式参数的声明也是由名称和数据类型组成。但是,参数的类型不能受到大小约束。数据的类型可以是任何Oracle类型,除了那些不适用于属性的类型。这些约束也适用于返回值的类型。

  • 方法实现所允许使用的语言

Oracle允许我们在PL/SQL、Java或C语言中实现对象方法。我们可以在Java或C语言中实现类型方法,只需提供一个调用说明即可。调用说明在Oracle的数据词典中公布了Java方法或外部C函数。它把程序的名称、参数类型和返回值信息映射到对应的SQL中去。

  • SELF参数

MEMBER方法接受一个内置的SELF参数,它代表了对象类型的实例。不论显式或隐式声明,它总是第一个传入MEMBER方法的参数。但是,STATIC方法就不能接受或引用SELF。

在方法体中,SELF指定了被调用方法所属的对象实例。例如,方法transform把SELF声明为IN OUT参数:

CREATE  TYPE  Complex AS  OBJECT (
  MEMBER FUNCTION  transform (SELF IN  OUT  Complex) ...

我们不能把SELF指定成其他数据类型。在MEMBER函数中,如果SELF没有声明的话,它的参数默认为IN。但是,在MEMBER过程中,如果SELF没有什么,那么它的参数模式默认为IN OUT。并且,我们不能把SELF的模式指定为OUT。

如下例所示,方法可以直接引用SELF的属性,并不需要限定修饰词:

CREATE  FUNCTION  gcd(x INTEGER , y INTEGER )
  RETURN  INTEGER  AS
  -- find greatest common divisor of x and y
  ans   INTEGER ;
BEGIN
  IF  (y <= x) AND (x MOD  y = 0) THEN
    ans    := y;
  ELSIF  x < y THEN
    ans    := gcd(y, x);
  ELSE
    ans    := gcd(y, x MOD  y);
  END  IF ;

  RETURN  ans;
END ;

CREATE  TYPE  rational AS  OBJECT(
  num   INTEGER ,
  den   INTEGER ,
  MEMBER PROCEDURE  normalize,
  ...
);

CREATE  TYPE  BODY  rational AS
  MEMBER PROCEDURE  normalize IS
    g   INTEGER ;
  BEGIN
    g      := gcd(SELF.num, SELF.den);
    g      := gcd(num, den);   -- equivalent to previous statement
    num    := num / g;
    den    := den / g;
  END  normalize;
  ...
END ;

如果我们从SQL语句中调用了一个空实例(即SELF为空)的MEMBER方法,方法不会被调用,并且会返回一个空值。如果从过程语句调用的话,PL/SQL就会抛出预定义异常SELEF_IS_NULL。

  • 重载

与打包子程序一样,同种类型的方法(函数或过程)都能被重载。也就是说,我们可以为不同的方法起相同的名字,只要它们的形式参数在数量、顺序或数据类型上有所不同。当我们调用其中一个方法的时候,PL/SQL会把实参列表和形参列表作比较,然后找出合适的方法。

子类型也可以重载它的基类方法。这种情况下,方法必须有完全相同的形式参数。

如果两个方法只是在参数模式上不同的话,我们是不能进行重载操作的。并且,我们不能因两个函数的返回值类型不同而对它们进行重载。

  • MAP和ORDER方法

一个标量类型,如CHAR或REAL的值都有一个预定义的顺序,这样它们之间就能进行比较。但是对象类型的实例没有预定义的顺序。要想对它们进行比较或排序就要调用我们自己实现的MAP函数。在下面的例子中,关键字MAP指明了方法convert()通过把Relational对象影射到REAL型上,来对它们进行排序操作:

CREATE  TYPE  rational AS  OBJECT(
  num   INTEGER ,
  den   INTEGER ,
  MAP MEMBER FUNCTION  CONVERT
    RETURN  REAL ,
  ...
);

CREATE  TYPE  BODY  rational AS
  MAP MEMBER FUNCTION  CONVERT
    RETURN  REAL  IS
  BEGIN
    RETURN  num / den;
  END  CONVERT;
  ...
END ;

PL/SQL使用顺序来计算布尔表达式的值,如x < y,并且可以在DISTINCT,GROUP BY和ORDER BY子句中作比较。MAP方法convert()可以返回一个对象在所有Relation对象中的相对位置。

一个对象类型只能包含一个MAP方法,它接受一个内置参数SELF并返回一个标量类型:DATE、NUMBER、VARCHAR2,或是一个ANSI SQL类型,如CHARACTER或REAL。

另外,我们还可以用ORDER方法。一个对象类型只能有一个ORDER方法,它必须是一个能返回数字结果的函数。在下面的例子中,关键字ORDER表明了方法match()可以对两个对象进行比较操作:

CREATE  TYPE  customer AS  OBJECT(
  ID     NUMBER ,
  NAME   VARCHAR2 (20),
  addr   VARCHAR2 (30),
  ORDER  MEMBER FUNCTION  match(c customer)
    RETURN  INTEGER
);

CREATE  TYPE  BODY  customer AS
  ORDER  MEMBER FUNCTION  match(c customer)
    RETURN  INTEGER  IS
  BEGIN
    IF  ID < c.ID THEN
      RETURN  -1;   -- any negative number will do
    ELSIF  ID > c.ID THEN
      RETURN  1;   -- any positive number will do
    ELSE
      RETURN  0;
    END  IF ;
  END ;
END ;

每个ORDER方法都只能接受两个参数:内置参数SELF和另外一个同类型的对象。如果c1和c2是Customer对象,一个c1 > c2这样的比较就会自动调用方法match。该方法能够返回负数、零或正数,分别代表SELF比另外一个对象小、等或大。如果传到ORDER方法的参数任意一个为空,方法就会返回空值。

知道方针:一个MAP方法就好比一个哈希函数,能把对象值影射到标量值,然后用操作符,如<、=等来进行比较。一个ORDER方法只是简单地将两个对象进行比较。

我们可以声明一个MAP方法或是一个ORDER方法,但不同时声明这两个方法。如果我们声明了其中一个,我们就可以在SQL或过程语句中进行对象比较。但是,如果我们没有声明方法,我们就只能在SQL语句中进行等或不等的比较。(两个同类型的对象只有它们对应的属性值相同的时候才相等。)

在对大量的对象进行排序或合并的时候,可以使用一个MAP方法。一次调用会把所有的对象影射到标量中,然后对标量值进行排序。ORDER方法的效率相对比较低,因为它必须反复地调用(它一次只能比较两个对象)。

  • 构造方法

每个对象类型都有一个构造方法,它是一个名称与对象类型名称相同的函数,用于初始化,并能返回一个对象类型的新的实例。

Oracle会为每个对象类型生成一个构造函数,其中形参与对象类型的属性相匹配。也就是说,参数和属性是一一对应的关系,并且顺序、名称和数据类型都完全相同。

我们也可以定义自己的构造方法,要么覆盖掉系统定义的构造函数,要么定义一个有着不同方法签名的新构造函数。

PL/SQL从来不会隐式地调用构造函数,所以我们必须显式地调用它。

3、更改已存在对象类型的属性和方法

我们可以使用ALTER TYPE语句来添加、修改或删除属性,并可以为已存在的对象类型添加或删除方法:

CREATE  TYPE  person_typ AS  OBJECT(
  NAME      CHAR (20),
  ssn       CHAR (12),
  address   VARCHAR2 (100)
);

CREATE  TYPE  person_nt IS  TABLE  OF  person_typ;

CREATE  TYPE  dept_typ AS  OBJECT(
  mgr    person_typ,
  emps   person_nt
);

CREATE   TABLE  dept OF  dept_typ;
-- Add new attributes to Person_typ and propagate the change
-- to Person_nt and dept_typ
ALTER   TYPE  person_typ ADD ATTRIBUTE (picture BLOB, dob DATE )
  CASCADE NOT  INCLUDING TABLE  DATA;

CREATE  TYPE  mytype AS  OBJECT(
  attr1   NUMBER ,
  attr2   NUMBER
);

ALTER   TYPE  mytype ADD ATTRIBUTE (attr3 NUMBER ),
  DROP  ATTRIBUTE attr2,
  ADD ATTRIBUTE attr4 NUMBER  CASCADE;

在过程编译时,它总是使用当前引用的对象类型版本。在对象类型发生改变时,服务器端引用那个对象类型的过程就变得无效了,在下次过程被调用时它会被自动重新编译。而对于客户端引用被更改过的类型的过程,我们就必须手动编译。

如果从基类删除一个方法,那么也必须修改覆盖被删除方法的子类。我们可以用ALTER TYPE的CASADE选择来判断是否有子类被影响到;如果有子类覆盖了方法,那么语句就会被回滚。为了能成功地从基类删除一个方法,我们可以:

  1. 先从子类删除方法
  2. 从基类删除方法,然后用不带OVERRIDING关键字的ALTER TYPE把它重新添加进去

六、定义对象类型

对象类型可以表现现实世界中的任何实体。例如,一个对象类型能表现学生,银行账户,计算机显示器,有理数或者是像队列,栈,链表这样的数据结构。这一节给出了几个完整的例子,让我们了解如何设计对象类型并编写我们自己的对象类型。

目前我们还不能在PL/SQL块、子程序或包中定义对象类型。但是,我们可以在SQL*Plus中用下面的语法来定义它:

CREATE  [OR  REPLACE] TYPE  type_name
  [AUTHID  {CURRENT_USER | DEFINER}]
  { {IS  | AS } OBJECT | UNDER supertype_name }
(
  attribute_name datatype[, attribute_name datatype]...
  [{MAP | ORDER } MEMBER function_spec,]
  [{FINAL| NOT  FINAL} MEMBER function_spec,]
  [{INSTANTIABLE| NOT  INSTANTIABLE} MEMBER function_spec,]
  [{MEMBER | STATIC} {subprogram_spec | call_spec}
  [, {MEMBER | STATIC} {subprogram_spec | call_spec}]...]
) [{FINAL| NOT  FINAL}] [ {INSTANTIABLE| NOT  INSTANTIABLE}];
[CREATE  [OR  REPLACE] TYPE  BODY  type_name {IS  | AS }
  { {MAP | ORDER } MEMBER function_body;
  | {MEMBER | STATIC} {subprogram_body | call_spec};}
  [{MEMBER | STATIC} {subprogram_body | call_spec};]...
END ;]

1、PL/SQL类型继承一览

PL/SQL支持单继承模式。我们可以定义对象类型的子类型。这些子类型包括父类型(或超类)所有的属性和方法。子类型还可以包括额外的属性和方法,并可以覆盖超类的方法。

我们还可以定义子类是否能继承于某个特定的类型。我们也可以定义不能直接初始化的类型和方法,只有声明它们的子类才可以进行初始化操作。

有些类型属性可以用ALTER TYPE语句动态的改变。当基类发生变化时,无论是用ALTER TYPE语句还是重新定义基类,子类会自动的应用这些改变的内容。我们可以用TREAT操作符只返回某一个指定的子类的对象。

从REF和DEREF函数中产生的值可以代表声明过的表或视图类型,或是一个或多个它的子类型。

  • PL/SQL类继承举例
-- Create a supertype from which several subtypes will be derived.

CREATE  TYPE  person_typ AS  OBJECT(
  ssn       NUMBER ,
  NAME      VARCHAR2 (30),
  address   VARCHAR2 (100)
)
NOT  FINAL;

-- Derive a subtype that has all the attributes of the supertype,
-- plus some additional attributes.

CREATE  TYPE  student_typ UNDER person_typ(
  deptid   NUMBER ,
  major    VARCHAR2 (30)
)
NOT  FINAL;

-- Because Student_typ is declared NOT FINAL, you can derive
-- further subtypes from it.

CREATE  TYPE  parttimestudent_typ UNDER student_typ(
  numhours   NUMBER
)
;

-- Derive another subtype. Because it has the default attribute
-- FINAL, you cannot use Employee_typ as a supertype and derive
-- subtypes from it.

CREATE  TYPE  employee_typ UNDER person_typ(
  empid   NUMBER ,
  mgr     VARCHAR2 (30)
)
;

-- Define an object type that can be a supertype. Because the
-- member function is FINAL, it cannot be overridden in any
-- subtypes.
CREATE  TYPE  T AS  OBJECT (..., MEMBER PROCEDURE  Print(), FINAL MEMBER
FUNCTION  foo(x NUMBER )...) NOT  FINAL;
-- We never want to create an object of this supertype. By using
-- NOT INSTANTIABLE, we force all objects to use one of the subtypes
-- instead, with specific implementations for the member functions.
CREATE  TYPE  Address_typ AS  OBJECT(...) NOT  INSTANTIABLE NOT  FINAL;
-- These subtypes can provide their own implementations of
-- member functions, such as for validating phone numbers and
-- postal codes. Because there is no "generic" way of doing these
-- things, only objects of these subtypes can be instantiated.
CREATE  TYPE  USAddress_typ UNDER Address_typ(...);
CREATE  TYPE  IntlAddress_typ UNDER Address_typ(...);
-- Return REFs for those Person_typ objects that are instances of
-- the Student_typ subtype, and NULL  REFs otherwise.
SELECT  TREAT(REF (p) AS  REF  student_typ)
  FROM  person_v p;
-- Example of using TREAT for assignment...
-- Return REFs for those Person_type objects that are instances of
-- Employee_type or Student_typ, or any of their subtypes.
SELECT  REF (p)
  FROM  person_v p
 WHERE  VALUE(p) IS  OF (employee_typ, student_typ);
-- Similar to above, but do not allow any subtypes of Student_typ.
SELECT  REF (p)
  FROM  person_v p
 WHERE  VALUE(p) IS  OF (ONLY student_typ);
-- The results of REF and DEREF can include objects of Person_typ
-- and its subtypes such as Employee_typ and Student_typ.
SELECT  REF (p)
  FROM  person_v p;
SELECT  DEREF(REF (p))
  FROM  person_v p;

2、对象类型实例:栈

栈是一个有序集合。栈有一个栈顶和一个栈底。栈中的每一项都只能在栈顶添加或删除。所以,最后一个被加入栈的项会被最先删除。(可以把栈想象成自助餐厅中的盘子。)压栈和退栈操作能够对栈进行后进先出(LIFO)更新。

栈能应用在很多地方。例如,它们可以用在系统编程中控制中断优先级并对递归进行管理。最简单的栈实现就是使用整数数组,数组的一端代表了栈顶。

PL/SQL提供了VARRAY数据类型,它能让我们声明变长数组。要声明变长数组属性,必须先定义变长数组类型。但是,我们不能再对象说明中定义类型,所以,我们只能单独的定义变长数组类型,并指定它的最大长度,具体实现如下:

CREATE  TYPE  IntArray AS  VARRAY(25) OF  INTEGER ;

现在我们可以编写对象类型说明了:

CREATE  TYPE  stack AS  OBJECT(
  max_size   INTEGER ,
  top        INTEGER ,
  POSITION   intarray,
  MEMBER PROCEDURE  initialize,
  MEMBER FUNCTION  FULL
    RETURN  BOOLEAN ,
  MEMBER FUNCTION  empty
    RETURN  BOOLEAN ,
  MEMBER PROCEDURE  push(n IN  INTEGER ),
  MEMBER PROCEDURE  pop(n OUT  INTEGER )
);

最后,我们可以编写对象类型体:

CREATE  TYPE  BODY  stack AS
  MEMBER PROCEDURE  initialize IS
  BEGIN
    top         := 0;
    /* Call constructor for varray and set element 1 to NULL. */
    POSITION    := intarray(NULL );
    max_size    := POSITION.LIMIT;   -- get varray size constraint
    POSITION.EXTEND(max_size - 1, 1);   -- copy element 1 into 2..25
  END  initialize;
  MEMBER FUNCTION  FULL
    RETURN  BOOLEAN  IS
  BEGIN
    RETURN (top = max_size);   -- return TRUE if stack is full
  END  FULL;
  MEMBER FUNCTION  empty
    RETURN  BOOLEAN  IS
  BEGIN
    RETURN (top = 0);   -- return TRUE if stack is empty
  END  empty;
  MEMBER PROCEDURE  push(n IN  INTEGERIS
  BEGIN
    IF  NOT  FULL THEN
      top              := top + 1;   -- push integer onto stack
      POSITION(top)    := n;
    ELSE    -- stack is full
      raise_application_error(-20101, 'stack overflow' );
    END  IF ;
  END  push;
  MEMBER PROCEDURE  pop(n OUT  INTEGERIS
  BEGIN
    IF  NOT  empty THEN
      n      := POSITION(top);
      top    := top - 1;   -- pop integer off stack
    ELSE
      -- stack is empty
      raise_application_error(-20102, 'stack underflow' );
    END  IF ;
  END  pop;
END ;

在成员过程push和pop中,我们使用内置过程raise_application_error来关联用户定义的错误消息。这样,我们就能把错误报告给客户端程序而避免把未控制异常传给主环境。客户端程序捕获PL/SQL异常后,可以在OTHERS异常控制句柄中用SQLCODE和SQLERRM 来确定具体的错误信息。下例中,当异常被抛出时,我们就把对应的错误消息输出:

DECLARE
  ...
BEGIN
  ...
EXCEPTION
  WHEN  OTHERS  THEN
    dbms_output.put_line(SQLERRM );
END ;

另外,程序还可以使用编译指示EXCEPTION_INIT把raise_application_error返回的错误编号影射到命名异常中,如下例所示:

DECLARE
  stack_overflow    EXCEPTION ;
  stack_underflow   EXCEPTION ;
  PRAGMA  EXCEPTION_INIT(stack_overflow, -20101);
  PRAGMA  EXCEPTION_INIT(stack_underflow, -20102);
BEGIN
  ...
EXCEPTION
  WHEN  stack_overflow THEN
  ...
END ;

3、对象类型实例:售票处

假如有一个连锁电影院,每个影院有三个银幕。每个影院有一个售票处,销售三种不同电影的影票。所有影票的价格都为三美元。定期检查影票的销售情况,然后及时补充影票。

在定义代表销售处的对象类型之前,我们必须考虑到必要的数据和操作。对于一个简单的售票处来说,对象类型需要票价、当前影票存量和已售影票的收据这些属性。它还需要一些方法:购票、盘存、补充存量和收集收据。

对于收据,我们可以使用含有三个元素的数组。元素1、2和3各自记录电影1、2和3。要声明一个变长数组属性,我们就必须先像下面这样定义它的类型:

CREATE  TYPE  RealArray AS  VARRAY(3) OF  REAL ;

现在,我们可以编写对象类型说明:

CREATE  TYPE  ticket_booth AS  OBJECT(
  price         REAL ,
  qty_on_hand   INTEGER ,
  receipts      realarray,
  MEMBER PROCEDURE  initialize,
  MEMBER PROCEDURE  purchase(movie INTEGER , amount REAL , CHANGE OUT  REAL ),
  MEMBER FUNCTION  inventory
    RETURN  INTEGER ,
  MEMBER PROCEDURE  replenish(quantity INTEGER ),
  MEMBER PROCEDURE  COLLECT (movie INTEGER , amount OUT  REAL )
);

最后,我们可以编写对象类型体:

CREATE  TYPE  BODY  ticket_booth AS
  MEMBER PROCEDURE  initialize IS
  BEGIN
    price          := 3.00;
    qty_on_hand    := 5000;   -- provide initial stock of tickets
    -- call constructor for varray and set elements 1..3 to zero
    receipts       := realarray(0, 0, 0);
  END  initialize;
  MEMBER PROCEDURE  purchase(movie INTEGER , amount REAL , CHANGE OUT  REALIS
  BEGIN
    IF  qty_on_hand = 0 THEN
      raise_application_error(-20103, 'out of stock' );
    END  IF ;

    IF  amount >= price THEN
      qty_on_hand        := qty_on_hand - 1;
      receipts(movie)    := receipts(movie) + price;
      CHANGE             := amount - price;
    ELSE    -- amount is not enough
      CHANGE    := amount;   -- so return full amount
    END  IF ;
  END  purchase;
  MEMBER FUNCTION  inventory
    RETURN  INTEGER  IS
  BEGIN
    RETURN  qty_on_hand;
  END  inventory;
  MEMBER PROCEDURE  replenish(quantity INTEGERIS
  BEGIN
    qty_on_hand    := qty_on_hand + quantity;
  END  replenish;
  MEMBER PROCEDURE  COLLECT (movie INTEGER , amount OUT  REALIS
  BEGIN
    amount             := receipts(movie);   -- get receipts for a given movie
    receipts(movie)    := 0;   -- reset receipts to zero
  END  COLLECT ;
END ;

4、对象类型实例:银行账户

在定义银行账户对象类型之前,我们必须考虑一下要使用的数据和操作。对于一个简单的银行账户来说,对象类型需要一个账号、余额和状态这三个属性。所需的操作有:打开帐户,验证账号,关闭账户,存款,取款和余额结算。

首先,我们要像下面这样编写对象类型说明:

CREATE  TYPE  bank_account AS  OBJECT(
  acct_number   INTEGER (5),
  balance       REAL ,
  status        VARCHAR2 (10),
  MEMBER PROCEDURE  OPEN (amount IN  REAL ),
  MEMBER PROCEDURE  verify_acct(num IN  INTEGER ),
  MEMBER PROCEDURE  CLOSE (num IN  INTEGER , amount OUT  REAL ),
  MEMBER PROCEDURE  deposit(num IN  INTEGER , amount IN  REAL ),
  MEMBER PROCEDURE  withdraw(num IN  INTEGER , amount IN  REAL ),
  MEMBER FUNCTION  curr_bal(num IN  INTEGER )
    RETURN  REAL
);

然后编写对象体:

CREATE  TYPE  BODY  bank_account AS
  MEMBER PROCEDURE  OPEN (amount IN  REALIS
  -- open account with initial deposit
  BEGIN
    IF  NOT  amount > 0 THEN
      raise_application_error(-20104, 'bad amount' );
    END  IF ;

    SELECT  acct_sequence.NEXTVAL
      INTO  acct_number
      FROM  DUAL;

    status     := 'open' ;
    balance    := amount;
  END  OPEN ;
  MEMBER PROCEDURE  verify_acct(num IN  INTEGERIS
  -- check for wrong account number or closed account
  BEGIN
    IF  (num <> acct_number) THEN
      raise_application_error(-20105, 'wrong number' );
    ELSIF (status = 'closed'THEN
      raise_application_error(-20106, 'account closed' );
    END  IF ;
  END  verify_acct;
  MEMBER PROCEDURE  CLOSE (num IN  INTEGER , amount OUT  REALIS
  -- close account and return balance
  BEGIN
    verify_acct(num);
    status    := 'closed' ;
    amount    := balance;
  END  CLOSE ;
  MEMBER PROCEDURE  deposit(num IN  INTEGER , amount IN  REALIS
  BEGIN
    verify_acct(num);

    IF  NOT  amount > 0 THEN
      raise_application_error(-20104, 'bad amount' );
    END  IF ;

    balance    := balance + amount;
  END  deposit;
  MEMBER PROCEDURE  withdraw(num IN  INTEGER , amount IN  REALIS
  -- if account has enough funds, withdraw
  -- given amount; else, raise an exception
  BEGIN
    verify_acct(num);

    IF  amount <= balance THEN
      balance    := balance - amount;
    ELSE
      raise_application_error(-20107, 'insufficient funds' );
    END  IF ;
  END  withdraw;
  MEMBER FUNCTION  curr_bal(num IN  INTEGER )
    RETURN  REAL  IS
  BEGIN
    verify_acct(num);
    RETURN  balance;
  END  curr_bal;
END ;
分享到:
评论

相关推荐

    pl/sql developer 7 中文用户指南

    ### PL/SQL Developer 7 中文用户指南 #### 一、引言 PL/SQL Developer 是一款专为 Oracle 数据库设计的强大开发工具,它能够帮助开发者高效地编写、调试及优化 PL/SQL 代码。本指南旨在为使用 PL/SQL Developer 7 ...

    pl/sql 用户指南中文版

    ### PL/SQL Developer 6.0 用户指南知识点详解 #### 一、介绍 **PL/SQL Developer** 是一款专门用于开发、调试和管理 Oracle 数据库中的 PL/SQL 对象的强大工具。版本 6.0 作为早期的一个版本,虽然可能在某些方面...

    PL_SQL_Developer 7.0用户指南.pdf

    ### PL/SQL Developer 7.0 用户指南知识点详解 #### 一、介绍 **PL/SQL Developer** 是一款专门用于开发、调试以及管理 Oracle 数据库中的 PL/SQL 对象的强大工具。版本 7.0 作为该系列的一个重要版本,为用户提供...

    PL/SQL Developer 7.0

    ### PL/SQL Developer 7.0 用户指南关键知识点解析 #### 一、介绍 **PL/SQL Developer** 是一款专为 Oracle 数据库设计的强大集成开发环境(IDE),它旨在简化和提升 PL/SQL 应用程序的开发效率。版本 7.0 作为该...

    Oracle的PL/SQL编程手册

    本手册介绍的是Oracle 10g Release 1 (10.1)版本下的PL/SQL用户指南和参考文档。自1996年首次发布以来,PL/SQL经历了多个版本的迭代和发展,每一次更新都带来了更多的特性和改进性能。10g Release 1是Oracle在2003年...

    PostgreSQL用户手册10.1版本.rar

    这篇详尽的手册旨在帮助用户更好地理解和操作 PostgreSQL 10.1,特别是对于开发“高斯”这样的项目来说,这将是一个不可多得的参考资料。 1. **新特性与改进** - **并行查询**:PostgreSQL 10 引入了并行查询机制...

    PLSQL开发指南_20100416更新

    - **与SQL紧密结合**:PL/SQL与SQL无缝集成,可以轻松地在两者之间切换。 - **高性能**:PL/SQL的执行效率非常高,尤其适用于处理大量数据的应用场景。 - **执行过程**:PL/SQL引擎可以将程序分解成SQL和PL/SQL两...

    PLSQL开发指南(中文)

    ### PL/SQL开发指南知识点概览 #### 一、序言与特点介绍 - **PL/SQL的应用领域**: - 实现商业规则通过存储过程和数据库触发器。 - 在数据库内生成并管理XML文档。 - 实现Web页面与数据库的无缝集成。 - 自动化...

    精通Oracle.10g.PLSQL编程

    PUSQL基础 3.1 PL/SQL块简介 3.1.1 PL/SQL块结构 3.1.2 PL/SQL块分类 3.2 定义并使用变量 3.2.1 标量变量 3.2.2 复合变量 3.2.3 参照变量 3.2.4 LOB变量 3.2.5 非PL...

    Oracle PLSQL ProGramming 开发指南

    ### Oracle PL/SQL Programming 开发指南 #### 序言 - 特点介绍 PL/SQL(Procedural Language for SQL)作为Oracle数据库的一种内置编程语言,自Oracle 6版本引入以来,便以其强大的功能和高效的性能深受开发者们...

    PLSQL Developer用户指南

    ### PL/SQL Developer 7 用户详细指南 #### 一、介绍 **PL/SQL Developer** 是一款专为 Oracle 数据库设计的集成开发环境(IDE),主要用于开发存储过程、触发器等存储程序单元。该工具提供了丰富的特性来提高开发...

    pl/sql developer 9.0中文版帮助文档(有书签)

    ### PL/SQL Developer 9.0 用户指南知识点详解 #### 一、介绍 **PL/SQL Developer** 是一款专门用于开发、调试、管理和优化 Oracle 数据库应用程序的强大工具。该版本为 **9.0**,发布于 **2011年4月**。此文档...

    SQLPlus User's Guide and Reference

    《SQL*Plus 用户指南与参考》是一本关于 Oracle SQL*Plus 的官方文档,该版本为 Release 10.1,出版时间为2003年12月。SQL*Plus 是 Oracle 数据库的一个非常重要的工具,它不仅是一个强大的命令行界面工具,也是一个...

    plsql 官方文档

    本段内容涉及的是Oracle数据库10g版本的PL/SQL用户指南和参考手册,版本号为10.1,文档部分编号为B10807-01。 文档的发布日期是2003年12月,作者包括John Russell以及其他一些贡献者。文档中明确指出,该文档受版权...

    plsql中文指南.pdf

    #### 八、创建与修改非PL/SQL对象 ##### 8.1 表定义编辑器 - 用于创建和修改表结构。 - 支持定义主键、索引等。 ##### 8.2 序列定义编辑器 - 用于管理和修改序列对象。 - 序列是用于生成唯一标识符的自动递增数字...

Global site tag (gtag.js) - Google Analytics