第十章 PL/SQL对象类型

一、抽象的角色

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

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

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

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

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

二、什么是对象类型

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

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

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

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

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

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

三、为什么使用对象类型

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

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

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

四、对象类型的结构

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

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

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

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

CREATETYPEcomplex ASOBJECT(
rpart   REAL ,   -- attribute
ipart   REAL ,
MEMBER FUNCTIONplus(x complex)
RETURNcomplex,   -- method
MEMBER FUNCTIONLESS(x complex)
RETURNcomplex,
MEMBER FUNCTIONtimes(x complex)
RETURNcomplex,
MEMBER FUNCTIONdivby(x complex)
RETURNcomplex
);

CREATETYPEBODYcomplex AS

MEMBER FUNCTIONplus(x complex)
RETURNcomplex IS
BEGIN
RETURNcomplex(rpart + x.rpart, ipart + x.ipart);
ENDplus;

MEMBER FUNCTIONLESS(x complex)
RETURNcomplex IS
BEGIN
RETURNcomplex(rpart - x.rpart, ipart - x.ipart);
ENDLESS;

MEMBER FUNCTIONtimes(x complex)
RETURNcomplex IS
BEGIN
RETURNcomplex(rpart * x.rpart - ipart * x.ipart,
rpart * x.ipart + ipart * x.rpart);
ENDtimes;

MEMBER FUNCTIONdivby(x complex)
RETURNcomplex IS
z   REAL:= x.rpart ** 2 + x.ipart ** 2;
BEGIN
RETURNcomplex((rpart * x.rpart + ipart * x.ipart) / z,
(ipart * x.rpart - rpart * x.ipart) / z);
ENDdivby;
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参数:

CREATETYPEComplex ASOBJECT (
MEMBER FUNCTIONtransform (SELF INOUTComplex) ...

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

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

CREATEFUNCTIONgcd(x INTEGER , y INTEGER )
RETURNINTEGERAS
-- find greatest common divisor of x and y
ans   INTEGER ;
BEGIN
IF(y <= x) AND (x MODy = 0) THEN
ans    := y;
ELSIFx < y THEN
ans    := gcd(y, x);
ELSE
ans    := gcd(y, x MODy);
ENDIF ;

RETURNans;
END ;

CREATETYPErational ASOBJECT(
num   INTEGER ,
den   INTEGER ,
MEMBER PROCEDUREnormalize,
...
);

CREATETYPEBODYrational AS
MEMBER PROCEDUREnormalize IS
g   INTEGER ;
BEGIN
g      := gcd(SELF.num, SELF.den);
g      := gcd(num, den);   -- equivalent to previous statement
num    := num / g;
den    := den / g;
ENDnormalize;
...
END ;

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

  • 重载

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

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

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

  • MAP和ORDER方法

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

CREATETYPErational ASOBJECT(
num   INTEGER ,
den   INTEGER ,
MAP MEMBER FUNCTIONCONVERT
RETURNREAL ,
...
);

CREATETYPEBODYrational AS
MAP MEMBER FUNCTIONCONVERT
RETURNREALIS
BEGIN
RETURNnum / den;
ENDCONVERT;
...
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()可以对两个对象进行比较操作:

CREATETYPEcustomer ASOBJECT(
ID     NUMBER ,
NAME   VARCHAR2 (20),
addr   VARCHAR2 (30),
ORDERMEMBER FUNCTIONmatch(c customer)
RETURNINTEGER
);

CREATETYPEBODYcustomer AS
ORDERMEMBER FUNCTIONmatch(c customer)
RETURNINTEGERIS
BEGIN
IFID < c.ID THEN
RETURN-1;   -- any negative number will do
ELSIFID > c.ID THEN
RETURN1;   -- any positive number will do
ELSE
RETURN0;
ENDIF ;
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语句来添加、修改或删除属性,并可以为已存在的对象类型添加或删除方法:

CREATETYPEperson_typ ASOBJECT(
NAME      CHAR (20),
ssn       CHAR (12),
address   VARCHAR2 (100)
);

CREATETYPEperson_nt ISTABLEOFperson_typ;

CREATETYPEdept_typ ASOBJECT(
mgr    person_typ,
emps   person_nt
);

CREATETABLEdept OFdept_typ;
-- Add new attributes to Person_typ and propagate the change
-- to Person_nt and dept_typ
ALTERTYPEperson_typ ADD ATTRIBUTE (picture BLOB, dob DATE )
CASCADE NOTINCLUDING TABLEDATA;

CREATETYPEmytype ASOBJECT(
attr1   NUMBER ,
attr2   NUMBER
);

ALTERTYPEmytype ADD ATTRIBUTE (attr3 NUMBER ),
DROPATTRIBUTE attr2,
ADD ATTRIBUTE attr4 NUMBERCASCADE;

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

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

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

六、定义对象类型

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

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

CREATE[ORREPLACE] TYPEtype_name
[AUTHID{CURRENT_USER | DEFINER}]
{ {ISAS } OBJECT | UNDER supertype_name }
(
attribute_name datatype[, attribute_name datatype]...
[{MAP | ORDER } MEMBER function_spec,]
[{FINAL| NOTFINAL} MEMBER function_spec,]
[{INSTANTIABLE| NOTINSTANTIABLE} MEMBER function_spec,]
[{MEMBER | STATIC} {subprogram_spec | call_spec}
[, {MEMBER | STATIC} {subprogram_spec | call_spec}]...]
) [{FINAL| NOTFINAL}] [ {INSTANTIABLE| NOTINSTANTIABLE}];
[CREATE[ORREPLACE] TYPEBODYtype_name {ISAS }
{ {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.

CREATETYPEperson_typ ASOBJECT(
ssn       NUMBER ,
NAME      VARCHAR2 (30),
address   VARCHAR2 (100)
)
NOTFINAL;

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

CREATETYPEstudent_typ UNDER person_typ(
deptid   NUMBER ,
major    VARCHAR2 (30)
)
NOTFINAL;

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

CREATETYPEparttimestudent_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.

CREATETYPEemployee_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.
CREATETYPEASOBJECT (..., MEMBER PROCEDUREPrint(), FINAL MEMBER
FUNCTIONfoo(x NUMBER )...) NOTFINAL;
-- 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.
CREATETYPEAddress_typ ASOBJECT(...) NOTINSTANTIABLE NOTFINAL;
-- 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.
CREATETYPEUSAddress_typ UNDER Address_typ(...);
CREATETYPEIntlAddress_typ UNDER Address_typ(...);
-- Return REFs for those Person_typ objects that are instances of
-- the Student_typ subtype, and NULLREFs otherwise.
SELECTTREAT(REF (p) ASREFstudent_typ)
FROMperson_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.
SELECTREF (p)
FROMperson_v p
WHEREVALUE(p) ISOF (employee_typ, student_typ);
-- Similar to above, but do not allow any subtypes of Student_typ.
SELECTREF (p)
FROMperson_v p
WHEREVALUE(p) ISOF (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.
SELECTREF (p)
FROMperson_v p;
SELECTDEREF(REF (p))
FROMperson_v p;

2、对象类型实例:栈

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

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

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

CREATETYPEIntArray ASVARRAY(25) OFINTEGER ;

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

CREATETYPEstack ASOBJECT(
max_size   INTEGER ,
top        INTEGER ,
POSITION   intarray,
MEMBER PROCEDUREinitialize,
MEMBER FUNCTIONFULL
RETURNBOOLEAN ,
MEMBER FUNCTIONempty
RETURNBOOLEAN ,
MEMBER PROCEDUREpush(n ININTEGER ),
MEMBER PROCEDUREpop(n OUTINTEGER )
);

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

CREATETYPEBODYstack AS
MEMBER PROCEDUREinitialize 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
ENDinitialize;
MEMBER FUNCTIONFULL
RETURNBOOLEANIS
BEGIN
RETURN (top = max_size);   -- return TRUE if stack is full
ENDFULL;
MEMBER FUNCTIONempty
RETURNBOOLEANIS
BEGIN
RETURN (top = 0);   -- return TRUE if stack is empty
ENDempty;
MEMBER PROCEDUREpush(n ININTEGERIS
BEGIN
IFNOTFULL THEN
top              := top + 1;   -- push integer onto stack
POSITION(top)    := n;
ELSE-- stack is full
raise_application_error(-20101, 'stack overflow' );
ENDIF ;
ENDpush;
MEMBER PROCEDUREpop(n OUTINTEGERIS
BEGIN
IFNOTempty THEN
n      := POSITION(top);
top    := top - 1;   -- pop integer off stack
ELSE
-- stack is empty
raise_application_error(-20102, 'stack underflow' );
ENDIF ;
ENDpop;
END ;

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

DECLARE
...
BEGIN
...
EXCEPTION
WHENOTHERSTHEN
dbms_output.put_line(SQLERRM );
END ;

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

DECLARE
stack_overflow    EXCEPTION ;
stack_underflow   EXCEPTION ;
PRAGMAEXCEPTION_INIT(stack_overflow, -20101);
PRAGMAEXCEPTION_INIT(stack_underflow, -20102);
BEGIN
...
EXCEPTION
WHENstack_overflow THEN
...
END ;

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

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

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

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

CREATETYPERealArray ASVARRAY(3) OFREAL ;

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

CREATETYPEticket_booth ASOBJECT(
price         REAL ,
qty_on_hand   INTEGER ,
receipts      realarray,
MEMBER PROCEDUREinitialize,
MEMBER PROCEDUREpurchase(movie INTEGER , amount REAL , CHANGE OUTREAL ),
MEMBER FUNCTIONinventory
RETURNINTEGER ,
MEMBER PROCEDUREreplenish(quantity INTEGER ),
MEMBER PROCEDURECOLLECT (movie INTEGER , amount OUTREAL )
);

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

CREATETYPEBODYticket_booth AS
MEMBER PROCEDUREinitialize 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);
ENDinitialize;
MEMBER PROCEDUREpurchase(movie INTEGER , amount REAL , CHANGE OUTREALIS
BEGIN
IFqty_on_hand = 0 THEN
raise_application_error(-20103, 'out of stock' );
ENDIF ;

IFamount >= 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
ENDIF ;
ENDpurchase;
MEMBER FUNCTIONinventory
RETURNINTEGERIS
BEGIN
RETURNqty_on_hand;
ENDinventory;
MEMBER PROCEDUREreplenish(quantity INTEGERIS
BEGIN
qty_on_hand    := qty_on_hand + quantity;
ENDreplenish;
MEMBER PROCEDURECOLLECT (movie INTEGER , amount OUTREALIS
BEGIN
amount             := receipts(movie);   -- get receipts for a given movie
receipts(movie)    := 0;   -- reset receipts to zero
ENDCOLLECT ;
END ;

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

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

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

CREATETYPEbank_account ASOBJECT(
acct_number   INTEGER (5),
balance       REAL ,
status        VARCHAR2 (10),
MEMBER PROCEDUREOPEN (amount INREAL ),
MEMBER PROCEDUREverify_acct(num ININTEGER ),
MEMBER PROCEDURECLOSE (num ININTEGER , amount OUTREAL ),
MEMBER PROCEDUREdeposit(num ININTEGER , amount INREAL ),
MEMBER PROCEDUREwithdraw(num ININTEGER , amount INREAL ),
MEMBER FUNCTIONcurr_bal(num ININTEGER )
RETURNREAL
);

然后编写对象体:

CREATETYPEBODYbank_account AS
MEMBER PROCEDUREOPEN (amount INREALIS
-- open account with initial deposit
BEGIN
IFNOTamount > 0 THEN
raise_application_error(-20104, 'bad amount' );
ENDIF ;

SELECTacct_sequence.NEXTVAL
INTOacct_number
FROMDUAL;

status     := 'open' ;
balance    := amount;
ENDOPEN ;
MEMBER PROCEDUREverify_acct(num ININTEGERIS
-- 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' );
ENDIF ;
ENDverify_acct;
MEMBER PROCEDURECLOSE (num ININTEGER , amount OUTREALIS
-- close account and return balance
BEGIN
verify_acct(num);
status    := 'closed' ;
amount    := balance;
ENDCLOSE ;
MEMBER PROCEDUREdeposit(num ININTEGER , amount INREALIS
BEGIN
verify_acct(num);

IFNOTamount > 0 THEN
raise_application_error(-20104, 'bad amount' );
ENDIF ;

balance    := balance + amount;
ENDdeposit;
MEMBER PROCEDUREwithdraw(num ININTEGER , amount INREALIS
-- if account has enough funds, withdraw
-- given amount; else, raise an exception
BEGIN
verify_acct(num);

IFamount <= balance THEN
balance    := balance - amount;
ELSE
raise_application_error(-20107, 'insufficient funds' );
ENDIF ;
ENDwithdraw;
MEMBER FUNCTIONcurr_bal(num ININTEGER )
RETURNREALIS
BEGIN
verify_acct(num);
RETURNbalance;
ENDcurr_bal;
END ;

5、对象类型实例:实数

有理数能够表现成两个整数相除的形式,一个分子和一个分母。同大多数语言一样,PL/SQL并没有实数类型或是用于实数操作的预定义操作符。现在让我们就用对象类型来弥补这个缺失。首先,编写下面的对象说明:

CREATETYPErational ASOBJECT(
num   INTEGER ,
den   INTEGER ,
MAP MEMBER FUNCTIONCONVERT
RETURNREAL ,
MEMBER PROCEDUREnormalize,
MEMBER FUNCTIONreciprocal
RETURNrational,
MEMBER FUNCTIONplus(x rational)
RETURNrational,
MEMBER FUNCTIONLESS(x rational)
RETURNrational,
MEMBER FUNCTIONtimes(x rational)
RETURNrational,
MEMBER FUNCTIONdivby(x rational)
RETURNrational,
PRAGMARESTRICT_REFERENCES(DEFAULT , RNDS, WNDS, RNPS, WNPS)
);

PL/SQL不允许操作符重载。所以我们必须定义方法plus(),less()(minus是保留关键字),times()和divby()来替代操作符+、-、*和/。

下一步,创建下面的独立存储函数,它们会被方法normalize()调用:

CREATEFUNCTIONgcd(x INTEGER , y INTEGER )
RETURNINTEGERAS
-- find greatest common divisor of x and y
ans   INTEGER ;
BEGIN
IF(y <= x) AND (x MODy = 0) THEN
ans    := y;
ELSIFx < y THEN
ans    := gcd(y, x);   -- recursive call
ELSE
ans    := gcd(y, x MODy);   -- recursive call
ENDIF ;

RETURNans;
END ;

下面是对象类型体的内容:

CREATETYPEBODYrational AS
MAP MEMBER FUNCTIONCONVERT
RETURNREALIS
-- convert rational number to real number
BEGIN
RETURNnum / den;
ENDCONVERT;
MEMBER PROCEDUREnormalize IS
-- reduce fraction num / den to lowest terms
g   INTEGER ;
BEGIN
g      := gcd(num, den);
num    := num / g;
den    := den / g;
ENDnormalize;
MEMBER FUNCTIONreciprocal
RETURNrational IS
-- return reciprocal of num / den
BEGIN
RETURNrational(den, num);   -- call constructor
ENDreciprocal;
MEMBER FUNCTIONplus(x rational)
RETURNrational IS
-- return sum of SELF + x
r   rational;
BEGIN
r    := rational(num * x.den + x.num * den, den * x.den);
r.normalize;
RETURNr;
ENDplus;
MEMBER FUNCTIONLESS(x rational)
RETURNrational IS
-- return difference of SELF - x
r   rational;
BEGIN
r    := rational(num * x.den - x.num * den, den * x.den);
r.normalize;
RETURNr;
ENDLESS;
MEMBER FUNCTIONtimes(x rational)
RETURNrational IS
-- return product of SELF * x
r   rational;
BEGIN
r    := rational(num * x.num, den * x.den);
r.normalize;
RETURNr;
ENDtimes;
MEMBER FUNCTIONdivby(x rational)
RETURNrational IS
-- return quotient of SELF / x
r   rational;
BEGIN
r    := rational(num * x.den, den * x.num);
r.normalize;
RETURNr;
ENDdivby;
END ;

七、声明并初始化对象

只要对象类型在模式中定义了,我们就可以在任何PL/SQL块、子程序或包中引用它来声明对象。例如,我们可以使用对象类型作为属性、字段、变量、 绑定变量、记录的域、表元的素、形式参数或函数返回值的数据类型。在运行时,对象类型的实例会被创建,也就是对象实例被初始化。

1、定义对象

我们可以在使用内置类型(如CHAR或NUMBER)的地方使用对象类型。在下面的块中,我们声明了Rational类型的对象r。然后调用构造函数初始化对象,把值6和8分别赋给属性num和den。

DECLARE
r   rational;
BEGIN
r    := rational(6, 8);
DBMS_OUTPUT.put_line(r.num);   -- prints 6
END ;

我们还可以把对象作为函数和过程的形式参数。那样就能把对象从一个子程序传递到另一个子程序了。在下面的例子中,我们使用对象类型Account作为形式参数:

DECLARE
...
PROCEDUREopen_acct (new_acct INOUTAccount) IS...

下面,我们把Accout作为函数的返回类型来使用:

DECLARE
...
FUNCTIONget_acct (acct_id ININTEGERRETURNAccount IS...

2、初始化对象

如果不调用构造函数初始化对象,那它会被自动赋上空值。也就是说对象本身为空,不单单指它的属性。如下例:

DECLARE
r   rational;   -- r becomes atomically null
BEGIN
r    := rational(2, 3);   -- r becomes 2/3
END ;

一个空对象总不能等于另一个对象。实际上,拿一个空对象与另一个对象比较结果总是NULL.同样,如果把一个空对象或NULL赋给另一个对象,那么被赋值的对象也为空,示例如下:

DECLARE
r   rational;
BEGIN
r    := rational(1, 2);   -- r becomes 1/2
r    := NULL ;   -- r becomes atomically null
IFISNULLTHEN...   -- condition yields TRUE

一个好的编程习惯就是在声明的时候就为对象初始化,例如:

DECLARE
r Rational := Rational(2,3);   -- r becomes 2/3

3、PL/SQL如何对待未初始化对象

在表达式中,未初始化的对象属性值为NULL。如果为一个未初始化的对象属性赋值,就会引起预定义异常ACCESS_INTO_NULL。当对未初始化的对象或是它的属性使用IS NULL做比较时,结果总为TRUE。

下面的例子就演示了空对象和含有空属性的对象之间的差异:

DECLARE
r   rational;   -- r is atomically null
BEGIN
IFISNULLTHEN...   -- yields TRUE
IFr.num ISNULLTHEN...   -- yields TRUE
r        := rational(NULLNULL );   -- initializes r
r.num    := 4;   -- succeeds because r is no longer atomically null
-- even though all its attributes are null
r        := NULL ;   -- r becomes atomically null again
r.num    := 4;   -- raises ACCESS_INTO_NULL
EXCEPTION
WHENACCESS_INTO_NULL THEN
...;
END ;

调用一个未初始化对象的方法会引起预定义异常NULL_SELF_DISPATCH。如果把未初始化对象的属性作为IN模式参数进行传递时,就跟传递一个NULL参数一样;如果把它作为OUT或IN OUT模式参数传递,并且尝试为其赋值,就会引起异常。

八、访问属性

我们只能按名称来引用属性,不可以使用它的位置来进行引用。在下面的例子中,我们先把属性den赋给变量denominator,然后把变量numerator的值赋给属性num。

DECLARE
r             rational := rational(NULLNULL );
numerator     INTEGER ;
denominator   INTEGER ;
BEGIN
...
denominator    := r.den;
r.num          := numerator;
END ;

下面再看一个稍微复杂一点的对象嵌套例子:

CREATETYPEaddress ASOBJECT(
street     VARCHAR2 (30),
city       VARCHAR2 (20),
state      CHAR (2),
zip_code   VARCHAR2 (5)
);

CREATETYPEstudent ASOBJECT(
NAME           VARCHAR2 (20),
home_address   address,
phone_number   VARCHAR2 (10),
status         varcahr2(10),
advisor_name   VARCHAR2 (20),
...
);

这里要注意的是,zip_code是对象类型Address的一个属性,而Address又是对象Student的属性home_address的数据类型。如果s是Student的对象的话,我们就可以像下面这样访问它的zip_code属性:

s.home_address.zip_code

九、定义构造函数

默认情况下,我们不需要为对象类型定义构造函数,因为系统会提供一个接受与每个属性相对应的参数的构造函数。

我们也许想定义自己的构造函数:

  1. 为某些属性提供默认值,这样就能确保属性值的正确性而不必依赖于调用者所提供的每一个属性值。
  2. 避免许多特殊用途的过程只初始化对象的不同部分。
  3. 当新的属性加到对象类型中时,避免更改调用构造函数的应用程序中的代码。构造函数也许需要一些新的代码,例如把属性设置为空,但我们还需要保持方法签名不变,这样可以使已存在的构造函数调用继续工作。
CREATEORREPLACE TYPErectangle ASOBJECT(
-- The type has 3 attributes.
LENGTH   NUMBER ,
width    NUMBER ,
area     NUMBER ,
-- Define a constructor that has only 2 parameters.
CONSTRUCTOR FUNCTIONrectangle(LENGTH NUMBER , width NUMBER )
RETURNSELF ASRESULT
);
/

CREATEORREPLACE TYPEBODYrectangle AS
CONSTRUCTOR FUNCTIONrectangle(LENGTH NUMBER , width NUMBER )
RETURNSELF ASRESULT AS
BEGIN
SELF.LENGTH    := LENGTH;
SELF.width     := width;
-- We compute the area rather than accepting it as a parameter.
SELF.area      := LENGTH * width;
RETURN ;
END ;
END ;
/

DECLARE
r1   rectangle;
r2   rectangle;
BEGIN
-- We can still call the default constructor, with all 3 parameters.
r1    := NEWrectangle(10, 20, 200);
-- But it is more robust to call our constructor, which computes
-- the AREA attribute. This guarantees that the initial value is OK.
r2    := NEWrectangle(10, 20);
END ;
/

十、调用构造函数

只要是能够调用普通函数的地方,我们就可以调用构造函数。跟所有的函数一样,构造函数也可以作为表达式的一部分而被调用,如下例所示:

DECLARE
r1   rational := rational(2, 3);

FUNCTIONaverage(x rational, y rational)
RETURNrational IS
BEGIN
...
END ;
BEGIN
r1    := average(rational(3, 4), rational(7, 11));

IF(rational(5, 8) > r1) THEN
...
ENDIF ;
END ;

当我们为构造函数传递参数的时候,调用会把对象中需要初始化的属性赋上初始值。如果是调用默认的构造函数,我们就需要为每个属性指定一个初始值;跟常量和变量不同,属性是不可以有默认值的。下例中,第n个参数为第n个属性赋值:

DECLARE
r   rational;
BEGIN
r    := rational(5, 6);   -- assign 5 to num, 6 to den
-- now r is 5/6

十一、调用方法

跟打包子程序一样,方法也是使用点标志来调用的。示例如下:

DECLARE
r   rational;
BEGIN
r    := rational(6, 8);
r.normalize;
DBMS_OUTPUT.put_line(r.num);   -- prints 3
END ;

如下例所示,我们可以连续调用方法。执行顺序从左到右。首先,成员函数reciprocal()会被调用,然后成员过程normalize()被调用。

DECLARE
r   rational := rational(6, 8);
BEGIN
r.reciprocal().normalize;
DBMS_OUTPUT.put_line(r.num);   -- prints 4
END ;

在SQL语句中,调用无参数的方法需要使用一个空的参数列表。在过程化语句中,空的参数列表是可选的,除非我们使用链式调用,这时除了最后一个调用之外其他的都需要空的参数列表。

我们不能把过程作为连续调用的一部分,因为过程是作为语句使用而不是表达式。所以,像下面这样的语句是不允许的:

r.normalize().reciprocal;   -- not allowed

同样,如果连续调用两个函数,第一个函数必须返回一个能传入第二个函数的对象。对于静态方法,我们使用下面的语法:

type_name.method_name

这种调用是不需要对象实例的。从子类实例中调用方法时,实际执行的方法是由类的继承关系决定的。如果子类覆盖了基类的方法,子类的方法就会被调用;否则的话,基类的方法会被调用。这种情况称为动态方法分派。

十二、通过REF修饰符共享对象

在真实世界中的大多数对象都要比有理数类型庞大而且复杂。如果对象比较大的话,把对象副本从一个子程序传递到另一个子程序时效率就可能会很低。这时如果使用对象共享就很有意义了,我们可以使用一个指向对象的引用来引用所需要的对象。

共享有两个重要的好处。首先,避免了不必要的数据重复。其次,在共享的对象内容更新时,任何引用所指向的内容也会被立即更新。如下面的例子:

CREATETYPEhome ASOBJECT(
address      VARCHAR2 (35),
owner        VARCHAR2 (25),
age          INTEGER ,
style        VARCHAR (15),
floor_plan   BLOB,
price        REAL (9, 2),
...
);
/

CREATETABLEhomes OFhome;

修改一下Person,我们就能建立家庭的模型,几个人共享一个家。我们可以使用修饰符REF来声明引用:

CREATETYPEperson ASOBJECT(
first_name     VARCHAR2 (10),
last_name      VARCHAR2 (15),
birthday       DATE ,
home_address   REFhome,   -- can be shared by family
phone_number   VARCHAR2 (15),
ss_number      INTEGER ,
mother         REFperson,   -- family members refer to each other
father         REFperson,
...
);

我们可以把变量、参数、字段或属性声明为引用。而且,我们还可以在SQL数据操作语句中把引用当作输入或输出变量使用。但是,我们不可以通过引用调 用对象的内容。比如表达式x.attribute,其中x是一个引用,PL/SQL无法找到存放对象的数据表。例如,下面的赋值语句就是不允许的:

DECLARE
p_ref      REFperson;
phone_no   VARCHAR2 (15);
BEGIN
phone_no    := p_ref.phone_number;   -- not allowed
END ;

解决办法就是用函数DEREF或调用包UTL_REF来访问对象。

1、向前类型定义

我们只能引用已经存在的模式对象。下例中,第一个CREATE TYPE语句是不允许的,因为它引用的Department并不存在:

CREATETYPEemployee ASOBJECT(
NAME   VARCHAR2 (20),
dept   REFdepartment,   -- not allowed
...
);

CREATETYPEdepartment ASOBJECT(
"number"   INTEGER ,
manager    employee,
...
);

把上面两个CREATE TYPE语句调换位置也不会起作用,因为这两个对象类型是互相依赖的。对象类型Employee有着一个Department类型的属性,而 Department又同样有着一个Employee类型的属性。为了解决这个问题,我们应该使用特殊的CREATE TYPE语句,我称它为向前类型定义,这样我们就可以互相引用两个独立的对象类型。现在使用下面的语句:

要调试上面的例子,我们只要把下面的语句提前即可:

CREATETYPEDepartment;   -- forward type definition
-- at this point, Department is an incomplete object type

向前类型定义创建的对象类型被称为不完全对象类型(incomplete object type),因为它没有属性和方法。一个不纯的不完全对象类型有属性,但编译时会发生错误,因为它引用了一个未确定的类型。例如,下面的CREATE TYPE语句就会因对象类型Address未确定而出错:

CREATETYPECustomer ASOBJECT (
id     NUMBER ,
name   VARCHAR2 (20),
addr   Address,   -- not yet defined
phone  VARCHAR2 (15)
);

如果使用了向前声明,我们就可以推迟对象类型Address的定义,并且,不完全类型Customer也可以被其他应用程序的开发者引用。

十三、操作对象

我们可以在CREATE TABLE语句中把某个字段指定为对象类型。一旦表被建立,我们就可以用SQL语句把对象插入表中,选取它的属性,调用它的方法更新它的状态。

注意:访问远程的或分布式对象都是不允许的。

在下面SQL*Plus脚本中,INSERT语句调用对象类型Rational的构造函数,然后插入resulting对象。SELECT语句检索 属性num的值。UPDATE语句调用成员方法reciprocal(),在交换属性num和den之后,返回Retional的值。要注意的是,在引用 属性或方法时,表别名是必须的。

CREATETABLEnumbers (rn Rational, ...)
/
INSERTINTOnumbers (rn) VALUES(Rational(3, 62))   -- inserts 3/62
/
SELECTn.rn.num INTOmy_num FROMnumbers n ...   -- returns 3
/
UPDATEnumbers n SETn.rn = n.rn.reciprocal() ... --   yields 62/3

用这种方法初始化对象时,对象在数据库表之外是没有标识的。但是,对象类型是独立于表而存在的,可以用其他方式创建对象。

下例中,我们创建一个存放对象类型Retional的表。这样包含对象类型的表称为对象表。一行中的每一列都与对象的属性对应。行与行间的列值是不同的。

CREATETABLErational_nums OFRational;

对象表中每行都有一个对象标识,能够唯一辨识一个存放在数据库中的对象。

1、查询对象

假定我们要在SQL*Plus中运行下面的脚本,创建一个对象类型Person和一个对象表persons,并且我们为该表填充一些数据:

CREATETYPEperson ASOBJECT(
first_name     VARCHAR2 (15),
last_name      VARCHAR2 (15),
birthday       DATE ,
home_address   address,
phone_number   VARCHAR2 (15)
)
/

CREATETABLEpersons OFperson
/

下面子查询能产生一个只包含Person对象属性的结果集:

BEGIN
INSERTINTOemployees   -- another object table of type Person
SELECT*
FROMpersons p
WHEREp.last_name LIKE'%Smith' ;

要返回对象结果集,我们就必须使用VALUE函数。

  • 使用VALUE函数

跟我们所期望的一样,函数VALUE能返回对象值。VALUE会把一个相关的变量作为它的参数。(在这里,相关变量就是行变量或与对象表中的一行相关联的表别名)。例如,要返回Person对象的结果集,要向下面这样使用VALUE:

BEGIN
INSERTINTOemployees
SELECTVALUE(p)
FROMpersons p
WHEREp.last_name LIKE'%Smith' ;

在下面的例子中,我们可以使用VALUE来返回一个特定的Person对象:

DECLARE
p1   person;
p2   person;
...
BEGIN
SELECTVALUE(p)
INTOp1
FROMpersons p
WHEREp.last_name = 'Kroll' ;

p2    := p1;
...
END ;

p1是一个本地的Person对象,它是名为"Kroll"的存储对象的一个副本,而p2是另一个本地Person对象,它是p1的副本。如下例所示,我们可以使用这些变量来访问和更新它们所引用的对象:

BEGIN
p1.last_name    := p1.last_name || ' Jr' ;
END ;

现在,本地的Person对象p1的名字为"Kroll Jr"。

  • 使用REF函数

我们可以用函数REF来检索引用,同VALUE一样,它也把一个相关的变量作为它的参数。下例中,我们检索一个或多个指向Person对象的引用,然后把引用插入表person_refs中:

BEGIN
INSERTINTOperson_refs
SELECTREF (p)
FROMpersons p
WHEREp.last_name LIKE'%Smith' ;
END ;

下面的例子我们同时检索一个引用和一个属性:

DECLARE
p_ref         REFperson;
taxpayer_id   VARCHAR2 (9);
BEGIN
SELECTREF (p), p.ss_number
INTOp_ref, taxpayer_id
FROMpersons p
WHEREp.last_name = 'Parker' ;   -- must return one row
...
END ;

在最后一个例子中,我们更新一个Person对象的属性:

DECLARE
p_ref          REFperson;
my_last_name   VARCHAR2 (15);
BEGIN
SELECTREF (p)
INTOp_ref
FROMpersons p
WHEREp.last_name = my_last_name;

UPDATEpersons p
SETp = person('Jill''Anders''11-NOV-67' , ...)
WHEREREF (p) = p_ref;
END ;
  • 测试dangling引用

如果一个引用所指向的对象被删除了,那么它就会指向一个不存在的对象,我们称这样的引用为dangling引用。要测试这种情况,我们应该使用 SQL的IS DANGLING语句。假设关系表department的manager字段引用了对象表中的Employee对象。我们就可以使用下面的UPDATE语 句来把"dangling"引用转为空值:

UPDATEdepartment
SETmanager = NULL
WHEREmanager ISDANGLING;
  • 使用DEREF函数

我们不可以在PL/SQL过程语句中。(DEREF是dereference的缩写。当我们反引用一个指针时,我们就能获取它所指向的 值。)DEREF把对象的引用作为它的参数值,然后返回这个引用所指向的对象。如果引用是"dangling"的,DEREF就会返回一个空对象。

在下面的例子中,我们可以反引用一个指向Person对象的引用。要注意的是,我们是从dummy表dual中取得引用值的。我们不需要指定对象表和检索条件,因为每个存放于对象表的对象都有唯一的互斥的对象标识,它是每一个指向对象的引用的一部分。

DECLARE
p1      person;
p_ref   REFperson;
NAME    VARCHAR2 (15);
BEGIN
...
/* Assume that p_ref holds a valid reference
to an object stored in an object table. */

SELECTDEREF(p_ref)
INTOp1
FROMDUAL;

NAME    := p1.last_name;
END ;

我们可以在后续的SQL语句中用DEREF进行反引用操作,如下例所示:

CREATETYPEpersonref ASOBJECT(
p_ref   REFperson
)
/

DECLARE
NAME     VARCHAR2 (15);
pr_ref   REFpersonref;
pr       personref;
p        person;
BEGIN
...
/* Assume pr_ref holds a valid reference. */
SELECTDEREF(pr_ref)
INTOpr
FROMDUAL;

SELECTDEREF(pr.p_ref)
INTOp
FROMDUAL;
...
END ;
/

下面是一个我们不能在过程语句中使用DEREF函数的例子:

BEGIN
...
p1 := DEREF(p_ref);   -- not allowed

在SQL语句中,我们可以使用点标志通过一个对象字段来引用它的属性,也可以通过一个对象字段的属性引用另一个属性。如果使用了表别名,那么就还能通过引用字段来访问属性。例如,下面的语法就是有效的:

table_alias.object_column.ref_attribute
table_alias.object_column.ref_attribute.attribute
table_alias.ref_column.attribute

假设我们在SQL*Plus中运行下面的脚本来创建对象类型Address和Person,对象表persons:

CREATETYPEaddress ASOBJECT(
street     VARCHAR2 (35),
city       VARCHAR2 (15),
state      CHAR (2),
zip_code   INTEGER
)
/

CREATETYPEperson ASOBJECT(
first_name     VARCHAR2 (15),
last_name      VARCHAR2 (15),
birthday       DATE ,
home_address   REFaddress,   -- shared with other Person objects
phone_number   VARCHAR2 (15)
)
/

CREATETABLEpersons OFperson
/

引用属性home_address对应对象表persons中的一个引用,该引用指向存放在其他表中的Address对象的字段。填充数据表之后,我们就可以像下面这样反引用它的引用来得到特定的address:

DECLARE
addr1 Address;
addr2 Address;
...
BEGIN
SELECTDEREF(home_address)
INTOaddr1
FROMpersons p
WHEREp.last_name = 'Derringer' ;
END ;

在下面的例子中,我们可以通过引用字段home_address来找到属性street。这种情况下,我们必须要使用表别名。

DECLARE
my_street   VARCHAR2 (25);
...
BEGIN
SELECTp.home_address.street
INTOmy_street
FROMpersons p
WHEREp.last_name = 'Lucas' ;
END ;

2、插入对象

我们可以使用insert语句为对象表添加一条记录。下例中,我们向对象表persons插入一个Person对象:

BEGIN
INSERTINTOpersons
VALUES('Jenifer''Lapidus' , ...);
END ;

另外,我们还可以使用对象类型Person的构造函数来插入记录:

BEGIN
INSERTINTOpersons
VALUES(person('Albert''Brooker' , ...));
END ;

下例中,我们可以使用RETURNING子句把对象Person的引用保存在本地变量中。注意一下这个子句是如何模拟SELECT语句的。我们也可以在UPDATE和DELETE语句中使用RETURNING子句。

DECLARE
p1_ref   REFperson;
p2_ref   REFperson;
BEGIN
INSERTINTOpersons p
VALUES(Person('Paul''Chang' , ...))
RETURNING REF (p)
INTOp1_ref;

INSERTINTOpersons p
VALUES(Person('Ana''Thorne' , ...))
RETURNING REF (p)
INTOp2_ref;
END ;

要往对象表中插入对象,我们还可以利用返回同样对象类型的子查询来进行操作。示例如下:

BEGIN
INSERTINTOpersons2
SELECTVALUE(p)
FROMpersons p
WHEREp.last_name LIKE'%Jones' ;
END ;

拷贝到对象表person2的行被赋予新的对象标识。对象标识是不会从对象表persons中拷贝出来的。

下面的脚本创建一个名为department的关系表,其中有一个类型为Person的字段,然后向表中插入一条记录。注意如何使用构造函数Person()为字段manager提供值。

CREATETABLEdepartment (
dept_name VARCHAR2 (20),
manager person,
LOCATION VARCHAR2 (20))
/
INSERTINTOdepartment
VALUES('Payroll' , Person('Alan''Tsai' , ...), 'Los Angeles' )
/

存放到字段manager中的新Person对象是不能被引用的,因为它是被存放在字段中(不是行中)的,也就没有对象标识。

3、更新对象

如果要修改对象表中的对象属性,我们可以像下面这样使用UPDATE语句:

BEGIN
UPDATEpersons p
SETp.home_address = '341 Oakdene Ave'
WHEREp.last_name = 'Brody' ;

UPDATEpersons p
SETp = person('Beth''Steinberg' , ...)
WHEREp.last_name = 'steinway' ;
END ;

4、删除对象

我们可以用DELETE语句从对象表中删除对象。要有选择的删除对象,我们就得在WHERE子句中进行指定:

BEGIN
DELETEFROMpersons p
WHEREp.home_address = '108 Palm Dr' ;

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. maven打jar包同时添加MANIFEST.MF文件中Class-Path所有需要jar

    转载自: http://heipark.iteye.com/blog/759701有个项目,完成或的打包成了jar包,期间大量引用了开源框架,如果一个个修改MANIFEST.MF文件中Class-Path会死掉,幸好maven提供了这个功能。Xml代码 <plugin> <groupId>org.apache.maven.plugins</groupId>…...

    2024/4/17 13:04:14
  2. TP6报错:当前访问路由未定义或不匹配

    如果是报路由错误,则说明启用了路由控制,那么所有被访问的页面都需要配置路由,否则将无法访问。 例如: 在浏览器中运行http://hml.tp6.com/admin/getlog 报错:当前访问路由未定义或不匹配已设置过路由:原因:当前访问方式是post,在浏览器中无法模拟访问,必须在apipost中…...

    2024/4/17 13:03:03
  3. 中英文url解码vc++源程序

    本文主要讨论中文url解码实现问题,没有详细讲解url编码,utf-8编码.想对编解码问题有更加详细的了解,请查阅相关资料 url编码:实质字符ascii码的十六进制。只是稍微有些变动,需要在前面加上"%"。比如"\",它的ascii码是92,92的十六进制是5c,所以"\&…...

    2024/4/20 4:38:01
  4. 与Java相关的四十个名字--------转载

    与Java相关的四十个名字 十大事件 1990-1994:Java缘起文/孟岩 Larry Wall说,优秀程序员应有的三个特点:懒惰、急躁和傲慢。Java就是诞生在一群懒惰、急躁而傲慢的程序天才之中。1990年12月,Sun的工程师Patrick Naughton被当时糟糕的Sun C++工具折磨的快疯了。他大声抱怨,并…...

    2024/5/7 13:33:58
  5. VS2010下的app.manifest

    在Win7下编程时可能有这样的情况,代码没有问题,但是执行时却会出现异常,如:System.Security.SecurityException: 不允许所请求的注册表访问权。只有以管理员身份运行时才能够正常运行。 这是Win7下UCA在起作用了。那好在VS的安装目录下有这么一个文件,它叫app.manifest. 我…...

    2024/4/17 13:04:01
  6. tp5将路由规则的作用对象改为模块

    在使用tp5的路由是,写了一个路由规则(route.php)本来只是想在index模块使用,突然发现影响到了admin模块,admin模块下的admin/news/xxx 不能正常访问了,原因就是路由规则影响到了。后来查了下文档,说是route.php是针对的应用,单独在模块里定义route.php是无效的,当然,…...

    2024/5/7 14:44:51
  7. php正则,替换内容里图片src路劲

    preg_replace php函数,很好用。 例子如下, 图片太小 可以放大看。很简单的代码如下$str=$r[CONTENT]; 这是 全部内容变量。$img=preg_replace(#src="/#is, src="http://自己的域名或者ip/,$str); echo $img;$img=preg_replace(#src="/#is, src="…...

    2024/4/20 8:44:58
  8. jmeter----将响应数据URL解码处理

    对代码不熟悉,所以请教了下老师:代码如下:String response = prev.getResponseDataAsString(); out=URLDecoder.decode(response, "utf-8"); prev.setResponseData(out.toString());...

    2024/4/25 13:19:36
  9. cocos2d-x 3.10 lua project.manifest + version.manifest

    project.manifest{"engineVersion" : "cocos-lua 3.10","packageUrl" : "http://远程服务器地址(xxx.xxx.xxx.xxx)/test/","remoteManifestUrl" : "http://远程服务器地址(xxx.xxx.xxx.xxx)/test/src/version/project.…...

    2024/4/20 14:51:07
  10. 使用pycharm写代码时检查拼写一直有波浪线

    自从把默认的头注释的author改成自己的名字以后越看越顺眼,但是发现名字下面一直有个波浪线,强迫症简直不能忍。然后当你把鼠标放上去,再点击提示上的“more”,会看到下面的提示: Spellchecker inspection helps locate typos and misspelling in your code, comments and …...

    2024/4/11 15:24:04
  11. 黑龙江省大庆市谷歌高清卫星地图下载

    一、概述   大庆,别称油城、百湖之城,是中国最大的石油石化基地,是黑龙江省地级市,位于黑龙江省西南部,是黑龙江省省域副中心城市 ,哈长城市群区域中心城市 。综合实力位列全国地级城市第11位,中国城市财力50强 新二线城市。  大庆市是中国第一大油田、世界第十大油…...

    2024/5/5 1:31:36
  12. iOS Application Manifest.plist的创建与编辑

    最近在做用JAVA后台分发Apple企业应用,使用XMLPropertyListConfiguration编辑Apple所用的plist文件,被其中的<array><array>标签搞得烦躁。 其中iOS应用的manifest.plist主要内容如下:<?xml version="1.0"?> <!DOCTYPE plist SYSTEM "…...

    2024/4/17 13:03:55
  13. PHP 正则后瞻 匹配是否是域名

    /^([a-z0-9]+([a-z0-9-]*(?:[a-z0-9]+))?\.)?[a-z0-9]+([a-z0-9-]*(?:[a-z0-9]+))?(\.us|\.tv|\.org\.cn|\.org|\.net\.cn|\.net|\.mobi|\.me|\.la|\.info|\.hk|\.gov\.cn|\.edu|\.com\.cn|\.com|\.co\.jp|\.co|\.cn|\.cc|\.biz)$/i 匹配网址:/*** @description 匹配* …...

    2024/5/5 0:46:23
  14. laravel5.5搭建的后台管理 和 api服务 的小程序商城

    基于Laravel5.5 小T商城(微信小程序端)项目地址:https://github.com/sqc157400661/XiaoTShop后台基于Laravel5.5开发前端资源来源于nideshop功能和数据库参考ecshop注意:当前版本功能还在完善中,暂时请勿商用。项目截图Xiaot_img 006.jpgXiaot_img 007.jpgXiaot_img 008.j…...

    2024/5/4 21:39:10
  15. VC6风格转XP风格界面的两种方法

    方法1 无负担的manifest文件 关于让自己的程序界面实现XP风格这个问题,在网上的讨论很多,大多数的作法都是写一个.manifest文件,然后将文件名改一下,比如.exe文件为test.exe,就将这个.manifest文件改名成test.exe.manifest,并将其和test.exe放在同一个目录里,这样test.e…...

    2024/5/4 15:03:26
  16. iOS UTF-8、URL解码与转码

    UTF-8解码NSString* str= [@"%E4%B8%AD%E5%9B%BD" stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];NSLog(@"strAfterDecodeByUTF8AndURI=%@", str); UTF-8转码[@"我是中文" stringByAddingPercentEscapesUsingEncoding:N…...

    2024/4/26 8:34:16
  17. 在信仰的陪伴下前行

    人是有生命的,人的存在是因为人有精神信仰的支撑,以及名以食为天的社会属性。臧克家为纪念鲁迅发表的诗中有这么几句:有的人活着,他已经死了;有的人死了,他还活着。有的人,骑在人民头上:“呵,我多伟大!”有的人,俯下身子给人民当牛马。有的人,把名字刻入石头,想“…...

    2024/5/4 22:18:40
  18. PHP 正则表达式查找字符串

    <?php set_time_limit(0);$url=http://item.taobao.com/auction/item_detail.htm?xid=0db2&item_num_id=4512430274&cm_cat=50000671&pm2=1; $ch = curl_init(); $timeout = 10; curl_setopt ($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_RETURN…...

    2024/4/19 19:07:44
  19. 初识红黑树

    红黑树是一种二叉查找树,它能保证在最坏的情况下,基本的动态操作的时间为O(lgn)。 1. 红黑树的性质 红黑树是一种二叉查找树,与普通二叉查找树不同之处在于以下几个方面: a) 红黑树在每一个结点上增加一个域来存储该结点的颜色,颜色可以是红(RED)的或者是黑(BLACK)的。…...

    2024/5/4 18:39:09
  20. HBase应用Java API的HBaseAdmin来创建和编辑模式

    远程 链接 hbase之前,保证hadoop集群上做HDFS 确认是运行着的,Hbase完全分布式HA环境搭架参考:http://blog.csdn.net/liulihui1988/article/details/75085428HBase 的 Schema 设计模式(Schema) 创建Configuration config = HBaseConfiguration.create(); HBaseAdmin admin…...

    2024/4/19 14:42:03

最新文章

  1. 移动端自适应

    基本实现核心思想 基本原则上是&#xff0c;布局更多地使用flex&#xff0c;然后尺寸使用rem&#xff0c;vw&#xff0c;vh为单位如果是根据不同的屏幕需要有不同的布局了&#xff0c;一般通过检测屏幕尺寸换不同的站点或者媒体查询使用css rem 以html字体太小为1rem的大小&…...

    2024/5/7 20:26:08
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. ChatGPT 赚钱初学者指南(上)

    原文&#xff1a;The Beginner’s Guide to Earning Money Online with ChatGPT 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第一章&#xff1a;理解基础知识 什么是 ChatGPT&#xff1f; 在人工智能与人类对话相遇的数字织锦中&#xff0c;ChatGPT 作为一个突出…...

    2024/5/5 8:51:03
  4. Redis精品案例解析:Redis实现持久化主要有两种方式

    Redis实现持久化主要有两种方式&#xff1a;RDB&#xff08;Redis DataBase&#xff09;和AOF&#xff08;Append Only File&#xff09;。这两种方式各有优缺点&#xff0c;适用于不同的使用场景。 1. RDB持久化 RDB持久化是通过创建一个二进制的dump文件来保存当前Redis数据…...

    2024/5/7 16:47:17
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/7 5:50:09
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/7 14:25:14
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/6 21:42:42
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57