Oracle Database 12c DBA官方手册(第8版)
上QQ阅读APP看书,第一时间看更新

5.3 支持基于抽象数据类型的表

用户定义的数据类型,也称为“抽象数据类型”,是对象-关系数据库应用程序的关键部分。每个抽象数据类型都有相关的构造函数方法,开发人员使用这些方法来操作表中的数据。抽象数据类型定义了数据的结构,例如,ADDRESS_TY数据类型可能包含地址数据的属性,以及操作这种数据的方法。创建ADDRESS_TY数据类型时,Oracle将自动创建名为ADDRESS_TY的构造函数方法。ADDRESS_TY构造函数方法包含匹配数据类型属性的参数,从而便于以数据类型的格式插入新的值。下面将介绍如何创建使用抽象数据类型的表,以及与该实现相关联的大小调整信息和安全性问题。

可创建使用抽象数据类型作为列定义的表。例如,可为地址创建一个抽象数据类型,如下所示:

    create type address_ty as object
    (street   varchar2(50),
    city       varchar2(25),
    state      char(2),
    zip        number);

一旦创建了ADDRESS_TY数据类型,创建表时就可以使用它作为一种数据类型,如下面的程序清单所示:

    create table customer
    (name    varchar2(25),
     address  address_ty);

创建抽象数据类型时,Oracle将创建在插入期间使用的构造函数方法。构造函数方法具有和数据类型相同的名称,它的参数是数据类型的属性。在CUSTOMER表中插入记录时,需要使用ADDRESS_TY数据类型的构造函数方法来插入地址值:

    insert into customer values
      ('Joe',address_ty('My Street', 'Some City', 'NY', 10001));

在这个示例中,INSERT命令调用ADDRESS_TY构造函数方法,从而将值插入到ADDRESS_TY数据类型的属性中。

使用抽象数据类型会增加表的空间需求,每个使用的数据类型将增加8B的空间需求。如果数据类型包含另一个数据类型,则应针对每个数据类型增加8B。

5.3.1 使用对象视图

使用抽象数据类型可能增加开发环境的复杂性。查询抽象数据类型的属性时,必须使用某种语法,这种语法不同于对不包含抽象数据类型的表使用的语法。如果不在所有的表中实现抽象数据类型,则需要将一种语法用于一些表,而将另一些语法用于其他表,并且需要提前知道哪些查询使用抽象数据类型。

例如,CUSTOMER表使用上面介绍的ADDRESS_TY数据类型:

    create table customer
    (name     varchar2(25),
    address    address_ty);

ADDRESS_TY数据类型依次有4个属性:STREET、 CITY、STATE和ZIP。如果希望从CUSTOMER表的ADDRESS列中选择STREET属性值,可编写以下查询:

    select address.street from customer;

然而,该查询不会工作。查询抽象数据类型的属性时,必须使用表名的关联变量。否则,选择的对象就会存在多义性。为了查询STREET属性,使用CUSTOMER表的关联变量(在这种情况下是C),如下面的示例所示:

    select c.address.street from customer c;

如该示例所示,需要在查询抽象数据类型属性时使用关联变量,即使该查询只访问一个表。因此,针对抽象数据类型属性的查询有两个特性:用于访问属性的符号和关联变量需求。为连贯地实现抽象数据类型,可能需要改变SQL标准,以支持百分之百地使用关联变量。即使连贯地使用关联变量,访问属性值所需要的符号可能也会造成问题,因为无法在不使用抽象数据类型的表上使用类似符号。

对象视图为这种不一致性提供了有效的折中解决方案。上例创建的CUSTOMER表假定ADDRESS_TY数据类型已经存在。但如果表已经存在会怎么样?如果在前面创建了关系数据库应用程序,并试图在应用程序中实现对象-关系概念,而没有重新构建和重新创建整个应用程序,这时将会如何?现在需要覆盖面向对象(OO)结构(例如已有关系表上的抽象数据类型)的能力。Oracle提供了“对象视图”作为定义已有关系表中使用的对象的方式。

如果CUSTOMER表已经存在,则可创建ADDRESS_TY数据类型,并使用对象视图将该数据类型关联到CUSTOMER表。在下面的程序清单中,CUSTOMER表创建为一个关系表,它只使用了通常提供的数据类型:

    create table customer
    (name         varchar2(25) primary key,
     street       varchar2(50),
     city         varchar2(25),
     state        char(2),
     zip          varchar2(10));

如果希望创建另一个表或应用程序,用于存储有关人员和地址的信息,可选择创建ADDRESS_TY数据类型。然而,为保持一致性,该数据类型应该也应用于CUSTOMER表。下面的示例将使用前面创建的ADDRESS_TY数据类型。

为创建对象视图,可使用CREATE VIEW命令。在CREATE VIEW命令中,指定将组成视图基础的查询。下面的程序清单是在CUSTOMER表上创建CUSTOMER_OV对象视图的代码:

    create view customer_ov (name, address) as
    select name, address_ty(street, city, state, zip)
    from customer;

CUSTOMER_OV视图将有两列:NAME和ADDRESS列(ADDRESS列定义为ADDRESS_ TY数据类型)。注意,在CREATE VIEW命令中不可以指定OBJECT为选项。

该示例中引入了一些重要的语法问题。根据已有的抽象数据类型构建表时,通过引用列的名称(如Name)而不是构造函数方法来选择表中的列值。然而,创建对象视图时,则引用构造函数方法的名称(如ADDRESS_TY)。同时,可在组成对象视图基础的查询中使用WHERE子句,从而可限制通过对象视图可访问的行。

如果使用对象视图,DBA将需要使用和前面相同的方法管理关系表。此时仍需管理数据类型的权限(查看5.3.2小节了解关于抽象数据类型的安全性管理的信息),但表和索引的结构将与创建抽象数据类型之前相同。使用关系结构将简化管理任务,同时允许开发人员通过表的对象视图访问对象。

也可使用对象视图来模仿行对象使用的引用。行对象是对象表中的行。为创建支持行对象的对象视图,需要首先创建和表具有相同结构的数据类型,如下所示:

    create or replace type customer_ty as object
    (name         varchar2(25),
     street       varchar2(50),
     city         varchar2(25),
     state        char(2),
     zip          varchar2(10));

接下来创建基于CUSTOMER_TY类型的对象视图,同时将OID(对象标识符)值赋予CUSTOMER中的行:

    create view customer_ov of customer_ty
    with object identifier (name) as
    select name, street, city, state, zip
    from customer;

这个CREATE VIEW命令的第一部分为视图指定名称(CUSTOMER_OV),并且告诉Oracle该视图的结构是基于CUSTOMER_TY数据类型的。对象标识符用于标识行对象。在该对象视图中,NAME列将用作OID。

如果有通过外键或主键关系引用CUSTOMER的第二个表,就可以建立包含对CUSTOMER_ OV引用的对象视图。例如,CUSTOMER_CALL表包含CUSTOMER表的外键,如下所示:

CUSTOMER_CALL表的NAME列引用CUSTOMER表中的相同列。因为有基于CUSTOMER的主键的模拟OID(称为pkOID),所以需要创建对这些OID的引用。Oracle提供了名为MAKE_REF的运算符来创建引用(称为pkREF)。在下面的程序清单中,MAKE_REF运算符用于创建从CUSTOMER_CALL的对象视图到CUSTOMER的对象视图的引用:

    create view customer_call_ov as
    select make_ref(customer_ov, name) name,
           call_number,
           call_date
    from customer_call;

在CUSTOMER_CALL_OV视图中,告诉Oracle需要引用的视图名称和组成pkREF的列。通过在Customer_ID列上使用DEREF运算符,现在可从CUSTOMER_CALL_OV中查询CUSTOMER_OV数据:

    select deref(ccov.name)
    from customer_call_ov ccov
    where call_date = trunc(sysdate);
    因此,可从查询中返回CUSTOMER数据,而不需要直接查询CUSTOMER表。在该示例中,CALL_DATE列用作查询返回行的限制条件。

无论使用行对象或列对象,都可以使用对象视图来保护表不受对象关系的影响。表本身不会被修改,并且可以按常用方式管理它们。区别在于,用户现在可访问CUSTOMER的行,如同它们是行对象一样。

从DBA的角度看,对象视图允许继续创建并支持标准的表和索引,同时应用程序开发人员将高级的对象-关系特性实现为这些表上的一个层。

5.3.2 抽象数据类型的安全性

5.3.1小节中的示例假设同一个用户拥有ADDRESS_TY数据类型和CUSTOMER表。如果数据类型的拥有者并不是表的拥有者,又会如何?如果另一个用户希望根据已经创建的数据类型创建一个数据类型,又会如何?在开发环境中,与表和索引一样,应该建立抽象数据类型的所有权和使用权的指导原则。

例如,如果名为ORANGE_GROVE的账户拥有ADDRESS_TY数据类型,并且账户名为CON_K的用户尝试创建PERSON_TY数据类型,这时会如何?先展示类型所有权问题,然后给出一个简单解决方案。例如,CON_K执行下面的命令:

    create type person_ty as object
    (name    varchar2(25),
     address  address_ty);

如果CON_K不拥有ADDRESS_TY抽象数据类型,Oracle将使用下面的消息响应这个CREATE TYPE命令:

    Warning: Type created with compilation errors.

在创建数据类型时,创建构造函数方法的问题造成了这个编译错误。Oracle无法解析对ADDRESS_TY数据类型的引用,因为CON_K不是拥有该数据类型的账户。

CON_K将不能创建PERSON_TY数据类型(包括ADDRESS_TY数据类型),除非ORANGE_GROVE先授予该类型上的EXECUTE权限。下面的程序清单显示了这个GRANT命令:

    grant execute on address_ty to con_k;


注意:

必须将该类型上的EXECUTE权限授予任何将在该表上执行DML操作的用户。


现在已经使用了合适的GRANT命令,那么CON_K就可以创建基于ORANGE_GROVE的ADDRESS_TY数据类型的数据类型了:

    create or replace type person_ty as object
    (name      varchar2(25),
     address  orange_grove.address_ty);

CON_K的PERSON_TY数据类型现在会成功创建,然而,使用基于另一个用户的数据类型的数据类型并不简单。例如,在INSERT操作期间,必须完全指定每种类型的拥有者的名称。CON_K可创建基于他的PERSON_TY数据类型的表(包括ORANGE_GROVE的ADDRESS_TY数据类型),如下面的程序清单所示:

    create table con_k_customers
    (customer_id  number,
     person        person_ty);

如果CON_K拥有PERSON_TY和ADDRESS_TY数据类型,则在CUSTOMER表中执行INSERT命令需使用下面的格式:

    insert into con_k_customers values
    (1,person_ty('John Smith',
    address_ty('522 Main Street','Half Moon Bay','CA','94019-1922')));

这条命令将不会工作。在INSERT期间,使用了ADDRESS_TY构造函数方法,而ORANGE_GROVE拥有该方法。因此,必须修改INSERT命令,指定ORANGE_GROVE为ADDRESS_TY的拥有者。下面的示例显示了正确的INSERT语句,其中以粗体显示了对ORANGE_GROVE的引用:

    insert into con_k_customers values
    (1,person_ty('John Smith',
orange_grove.address_ty('522 Main Street','Half Moon Bay','CA','94019-1922')));

解决此问题很容易:可创建并使用数据类型的公共同义词。继续前面的示例,ORANGE_GROVE可创建一个公共同义词,并授予对此类型的EXECUTE权限,如下所示:

    create public synonym pub_address_ty for address_ty;
    grant execute on address_ty to public;

结果,包括CON_K在内的任何用户现在都可以使用同义词来引用此类型,以创建新的表或类型:

    create or replace type person_ty as object
        (name     varchar2(25),
         address  pub_address_ty);

在Oracle的纯关系实现中,可授予对过程化对象的EXECUTE权限,如过程和程序包。在Oracle的对象-关系实现中,EXECUTE权限也扩展到抽象数据类型,这一点在前面的示例中已看到。使用EXECUTE权限是因为抽象数据类型可以包括方法——即操作数据类型的PL/SQL函数和过程。如果授予某人权限可以使用你自己的数据类型,则授予该用户执行在该数据类型上定义的方法的权限。虽然ORANGE_GROVE尚未在ADDRESS_TY数据类型上定义任何方法,但Oracle会自动创建用于访问数据的构造函数方法。任何使用ADDRESS_TY数据类型的对象(如PERSON_TY)都使用与ADDRESS_TY关联的构造函数方法。

不可以创建公有类型,但可创建类型的公有同义词,这一点在前文中曾介绍过。这有助于解决数据类型管理问题。一种解决方案是使用单一模式名创建所有类型,并创建相应的同义词。引用类型的用户不必知道这些类型的所有者,就可以有效地使用这些类型。

5.3.3 对抽象数据类型属性创建索引

在前面的示例中,基于PERSON_TY数据类型和ADDRESS_TY数据类型创建了CON_K_CUSTOMERS表。如下面的程序清单所示,CON_K_CUSTOMERS表包含一个标量列(非面向对象列)CUSTOMER_ID和通过PERSON_TY抽象数据类型定义的PERSON列:

    create table george_customers
    (customer_id    number,
     person          person_ty);

根据本章前面的数据类型定义,可看到PERSON_TY有一个列NAME,后面跟着由ADDRESS_TY数据类型定义的ADDRESS列。

在查询、更新和删除期间引用抽象数据类型中的列时,应指定数据类型属性的完整路径。例如,以下查询返回CUSTOMER_ID列和NAME列。NAME列是定义PERSON列的数据类型的属性,因此引用该属性为PERSON.NAME,如下所示:

    select c.customer_id, c.person.name
      from con_k_customers c;

通过指定相关列的完整路径,可引用ADDRESS_TY数据类型中的属性。例如,STREET列的引用为PERSON.ADDRESS.STREET,这就完整描述了它在表结构中的位置。下面的示例两次引用CITY列,一次在选择列的列表中,另一次在WHERE子句中:

    select c.person.name,
           c.person.address.city
     from  con_k_customers c
     where c.person.address.city like 'C%';

因为CITY列和WHERE子句中的范围搜索一起使用,所以优化器在解析查询时就能够使用索引。如果在CITY列上有可用的索引,Oracle就可以快速找到CITY值以字母C开头的所有行,如同谓词中所指定的那样。

为在作为抽象数据类型一部分的列上创建索引,需要指定该列的完整路径作为CREATE INDEX命令的一部分。为在CITY列(该列是ADDRESS列的一部分)上创建索引,可执行下面的命令:

    create index i_con_k_customers_city
    on con_k_customers(person.address.city);

该命令将在PERSON.ADDRESS.CITY列上创建名为I_CON_K_CUSTOMER_CITY的索引。在访问CITY列时,优化器将评估用于访问该数据的SQL,并且确定新的索引是否对改进访问的性能有所帮助。

在创建基于抽象数据类型的表时,应该考虑如何访问抽象数据类型中的列。类似于前一个示例中的CITY列,如果某些列常用作查询中限制条件的一部分,则应该索引它们。在这点上,一个抽象数据类型中多个列的表示法可能妨碍应用程序的性能,因为它不易使人注意到需要在数据类型中的特定列上创建索引。

使用抽象数据类型时,习惯于将一组列视为一个实体,如ADDRESS列或PERSON列。重要的是要记住,在评估查询访问路径时,优化器将单独考虑列。因此,需要说明列的索引需求,即使是在使用抽象数据类型时。此外记住,在使用ADDRESS_TY数据类型的表中的CITY列上创建索引并不影响使用ADDRESS_TY数据类型的第二个表中的CITY列。如果还有一个名为BRANCH的表使用ADDRESS_TY数据类型,则不会在它的CITY列上创建索引,除非显式地在它的CITY列上创建索引。另外注意,与非抽象数据类型上的索引类似,对于抽象数据类型上的附加索引,每个附加索引增加3倍开销。