ヘッダーをスキップ
Oracle® Databaseオブジェクト・リレーショナル開発者ガイド
12cリリース1 (12.1)
B72963-02
  目次へ移動
目次
索引へ移動
索引

前
 
次
 

A オブジェクト・リレーショナル機能を使用したサンプル・アプリケーション

この付録では、ユーザー定義データ型(Oracleオブジェクト)の作成および使用方法の概要を説明するサンプル・アプリケーションを示します。まず、アプリケーションの開発にリレーショナル・モデルを使用し、次にオブジェクト・リレーショナル・モデルを使用します。

内容は次のとおりです。

サンプル・アプリケーションの概要

ユーザー定義型とは、アプリケーション内のデータ構造および操作を形式化したスキーマ・オブジェクトのことです。

この付録の例では、オブジェクト型の定義、使用および進化における最も重要な点を説明します。オブジェクト型の作業で重要な点の1つは、オブジェクトに対して操作を実行するメソッドを作成することです。この例では、オブジェクト型メソッドの定義でPL/SQL言語を使用します。型の定義など、オブジェクト型の使用にかかわるそれ以外の部分ではSQLを使用します。

例では、顧客の発注書を管理するアプリケーションについて、様々なバージョンのデータベース・スキーマを作成します。まず、純粋なリレーショナル・バージョンを示し、次に同等のオブジェクト・リレーショナル・バージョンを示します。いずれのバージョンでも、顧客、発注書、明細項目などの同じ基本エンティティが使用されています。ただし、オブジェクト・リレーショナル・バージョンでは、これらのエンティティに対してオブジェクト型を作成し、それぞれのオブジェクト型のインスタンスをインスタンス化することによって、特定の顧客および発注書のデータを管理します。

PL/SQLおよびJavaを使用すると、特にコレクション要素のアクセスおよび操作の点で、この付録に示す以上の機能が実現できます。

OCI、Pro*C/C++、またはOracle Data Providers for .NET (ODP.NET)を使用するクライアント・アプリケーションでは、その広範な機能を利用してオブジェクトおよびコレクションにアクセスし、それらをクライアント上で操作できます。


関連項目:

  • ユーザー定義型のSQL構文および使用方法の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

  • PL/SQLの機能の詳細は、『Oracle Database PL/SQL言語リファレンス』を参照してください。

  • Javaの詳細は、『Oracle Database Java開発者ガイド』を参照してください。

  • 『Oracle Call Interfaceプログラマーズ・ガイド』

  • 『Pro*C/C++プログラマーズ・ガイド』

  • Oracle Data Provider for .NET開発者ガイドfor Microsoft Windows


リレーショナル・モデルに基づくスキーマの実装

この項では、図A-1に示す発注書スキーマのリレーショナル・バージョンを実装します。

エンティティとリレーションシップ

この例における基本エンティティは、次のとおりです。

  • 顧客

  • 販売する製品の在庫

  • 発注書

図A-1のように、顧客には担当情報があるため、所在地と電話番号セットはその顧客に対して排他的です。アプリケーションでは、同じ所在地または電話番号に他の顧客を関連付けることはできません。顧客が所在地を変更すると、前の所在地は存在しなくなります。ユーザーが顧客でなくなった場合は、関連所在地が存在しなくなります。

顧客は、発注書に対して1対多リレーションシップを持ちます。1件の顧客が多数の発注を行うことはできますが、特定の発注を行う顧客は1件のみです。顧客は発注前に定義できるため、リレーションシップは必須ではなくオプションです。

同様に、発注書は在庫品目に対して多対多リレーションシップを持ちます。このリレーションシップはどの発注書にどの在庫品目が記載されているかを示すものではないため、E-Rには明細項目の表記法が使用されます。発注書には、1つ以上の明細項目を含める必要があります。各明細項目は、1件の発注書にのみ関連付けられています。明細項目と在庫品目の間には、在庫品目は明細書に記載しないか、1件または多数の明細項目に記載できますが、各明細項目が参照する在庫品目は1つのみであるというリレーションシップがあります。

図A-1 発注書アプリケーションのエンティティ関連ダイアグラム

図A-1の説明
「図A-1 発注書アプリケーションのエンティティ関連ダイアグラム」の説明

リレーショナル・モデルに基づく表の作成

リレーショナル・アプローチでは、すべてが表に正規化されます。表名は、Customer_reltabPurchaseOrder_reltabおよびStock_reltabです。

所在地の各部はCustomer_reltab表の列となります。電話番号を列として構造化することで、顧客が使用できる電話番号の数について任意の制限が設定されます。

リレーショナル・アプローチでは、明細項目が発注書から分離され、それぞれがPurchaseOrder_reltabおよびLineItems_reltabという固有の表に格納されます。

図A-1のように、明細項目は発注書および在庫品目の両方とリレーションシップを持ちます。これらは、PurchaseOrder_reltabおよびStock_reltabへの外部キーを使用してLineItems_reltab表に列として実装されます。


注意:

この項の表記規則では、リレーショナル表の名前に接尾辞_reltabが追加されています。このようにわかりやすい表記法を使用することで、コードを容易にメンテナンスできます。

表(_tab)と型(_typ)を区別すると便利です。それ以外でも、必要な名前を選択できます。オブジェクト・リレーショナル構造のメリットの1つは、対応する現実のオブジェクトを密接にモデル化する名前を指定できることです。


以降の各項では、リレーショナル・アプローチに基づいて作成された表について説明します。

Customer_reltab

Customer_reltab表の定義は、次のとおりです。

例A-1 Customer_reltab表の作成

CREATE TABLE Customer_reltab (
  CustNo                NUMBER NOT NULL,
  CustName              VARCHAR2(200) NOT NULL,
  Street                VARCHAR2(200) NOT NULL,
  City                  VARCHAR2(200) NOT NULL,
  State                 CHAR(2) NOT NULL,
  Zip                   VARCHAR2(20) NOT NULL,
  Phone1                VARCHAR2(20),
  Phone2                VARCHAR2(20),
  Phone3                VARCHAR2(20),
  PRIMARY KEY (CustNo));

この表Customer_reltabには、顧客に関するすべての情報、つまり、顧客固有の情報(NOT NULL制約で定義)と重要でない情報がすべて格納されます。この表の定義によると、アプリケーションでは各顧客に出荷先所在地が必要です。

エンティティ関連(E-R)ダイアグラムは発注する顧客を示していましたが、この表では顧客と発注書の間のリレーションシップは許可されていません。このリレーションシップは発注書別に管理する必要があります。

PurchaseOrder_reltab

PurchaseOrder_reltab表の定義は、次のとおりです。

例A-2 PurchaseOrder_reltab表の作成

CREATE TABLE PurchaseOrder_reltab (    
   PONo        NUMBER, /* purchase order no */  
   Custno      NUMBER references Customer_reltab, /*  Foreign KEY referencing 
                                                      customer */
   OrderDate   DATE, /*  date of order */  
   ShipDate    DATE, /* date to be shipped */    
   ToStreet    VARCHAR2(200), /* shipto address */    
   ToCity      VARCHAR2(200),    
   ToState     CHAR(2),    
   ToZip       VARCHAR2(20),    
   PRIMARY KEY(PONo));   
  

PurchaseOrder_reltabでは、Customer_reltabCustNoキーを参照する外部キー(FK)列CustNoを使用して顧客と発注書とのリレーションシップを管理します。PurchaseOrder_reltab表には、関連明細項目に関する情報は含まれていません。次項の明細項目表では、発注番号を使用して明細項目を親発注書に関連付けます。

Stock_reltab

Stock_reltab表の定義は、次のとおりです。

例A-3 Stock_reltab表の作成

CREATE TABLE Stock_reltab (
  StockNo      NUMBER PRIMARY KEY,
  Price        NUMBER,
  TaxRate      NUMBER);

LineItems_reltab

LineItems_reltab表の定義は、次のとおりです。

例A-4 LineItems_reltab表の作成

CREATE TABLE LineItems_reltab (
  LineItemNo           NUMBER,
  PONo                 NUMBER REFERENCES PurchaseOrder_reltab,
  StockNo              NUMBER REFERENCES Stock_reltab,
  Quantity             NUMBER,
  Discount             NUMBER,
  PRIMARY KEY (PONo, LineItemNo));

注意:

Stock_reltab表とPurchaseOrder_reltab表は、LineItems_reltab表の前に作成する必要があります。

表名には、この表に明細項目の集合が保持されることをコードを読むユーザーに対して強調するために、複数形であるLineItems_reltabが使用されています。

エンティティ関連ダイアグラムに示すように、明細項目のリストは発注書および在庫品目の両方とのリレーションシップを持ちます。これらのリレーションシップは、LineItems_reltabで次の2つの外部キー列を使用して管理されます。

  • PONo: PurchaseOrder_reltabPONo列を参照します。

  • StockNo: Stock_reltabStockNo列を参照します。

リレーショナル・モデルに基づく値の挿入

アプリケーションでは、次のような文を使用して表にデータを挿入します。

例A-5 在庫の設定

INSERT INTO Stock_reltab VALUES(1004, 6750.00, 2);
INSERT INTO Stock_reltab VALUES(1011, 4500.23, 2);
INSERT INTO Stock_reltab VALUES(1534, 2234.00, 2);
INSERT INTO Stock_reltab VALUES(1535, 3456.23, 2);

例A-6 顧客の登録

INSERT INTO Customer_reltab
  VALUES (1, 'Jean Nance', '2 Avocet Drive',
         'Redwood Shores', 'CA', '95054',
         '415-555-0102', NULL, NULL);

INSERT INTO Customer_reltab
  VALUES (2, 'John Nike', '323 College Drive',
         'Edison', 'NJ', '08820',
         '609-555-0190', '201-555-0140', NULL);

例A-7 発注処理

INSERT INTO PurchaseOrder_reltab
  VALUES (1001, 1, SYSDATE, '10-MAY-1997',
          NULL, NULL, NULL, NULL);

INSERT INTO PurchaseOrder_reltab
  VALUES (2001, 2, SYSDATE, '20-MAY-1997',
         '55 Madison Ave', 'Madison', 'WI', '53715');

例A-8 明細項目の詳細入力

INSERT INTO LineItems_reltab VALUES(01, 1001, 1534, 12,  0);
INSERT INTO LineItems_reltab VALUES(02, 1001, 1535, 10, 10);
INSERT INTO LineItems_reltab VALUES(01, 2001, 1004,  1,  0);
INSERT INTO LineItems_reltab VALUES(02, 2001, 1011,  2,  1);

リレーショナル・モデルに基づくデータの問合せ

アプリケーションでは、次のような問合せを実行できます。

例A-9 特定の発注書に関する顧客および明細項目データの取得

SELECT   C.CustNo, C.CustName, C.Street, C.City, C.State,
         C.Zip, C.phone1, C.phone2, C.phone3,
         P.PONo, P.OrderDate,
         L.StockNo, L.LineItemNo, L.Quantity, L.Discount
 FROM    Customer_reltab C,
         PurchaseOrder_reltab P,
         LineItems_reltab L
 WHERE   C.CustNo = P.CustNo
  AND    P.PONo = L.PONo
  AND    P.PONo = 1001;

例A-10 発注書の合計値の取得

SELECT     P.PONo, SUM(S.Price * L.Quantity)
 FROM      PurchaseOrder_reltab P,
           LineItems_reltab L,
           Stock_reltab S
 WHERE     P.PONo = L.PONo
  AND      L.StockNo = S.StockNo
 GROUP BY P.PONo;

例A-11 在庫品目1004に関する発注書および明細項目データの取得

SELECT    P.PONo, P.CustNo,
          L.StockNo, L.LineItemNo, L.Quantity, L.Discount
 FROM     PurchaseOrder_reltab P,
          LineItems_reltab     L
 WHERE    P.PONo = L.PONo
   AND    L.StockNo = 1004;

リレーショナル・モデルに基づくデータの更新

アプリケーションでは、次のような文を実行してデータを更新できます。

例A-12 発注書1001および在庫品目1534の数量の更新

UPDATE LineItems_reltab
   SET      Quantity = 20
   WHERE    PONo     = 1001
   AND      StockNo  = 1534;

リレーショナル・モデルに基づくデータの削除

アプリケーションでは、例A-13のような文を実行してデータを削除できます。

例A-13 リレーショナル・モデルに基づく発注書1001の削除

DELETE
   FROM   LineItems_reltab
   WHERE  PONo = 1001;

DELETE
   FROM   PurchaseOrder_reltab
   WHERE  PONo = 1001;

オブジェクト・リレーショナル・モデルに基づくスキーマの実装

オブジェクト・リレーショナル・アプローチも、「エンティティとリレーションシップ」に示したのと同じE-Rから始まります。これらのE-Rを次のクラス図のようにオブジェクト指向の観点から見ると、実社会のさらに多くの構造をデータベース・スキーマに変換できます。

図A-2 発注書アプリケーションのクラス図

図A-2の説明
「図A-2 発注書アプリケーションのクラス図」の説明

オブジェクト・リレーショナル・アプローチでは、所在地や複数の電話番号を関連性のないリレーショナル表列に分割するかわりに、所在地全体および電話番号リスト全体を表す型を定義します。同様に、このアプローチでは明細項目と発注書を個別に格納するかわりに、ネストした表を使用して格納します。

主なエンティティ(顧客、在庫および発注書)はオブジェクト型になります。オブジェクト型間のなんらかのリレーションシップを表すためにオブジェクト参照が使用されます。コレクション型(VARRAYおよびネストした表)は、複数値属性をモデル化するために使用されます。


注意:

この付録では、新しいオブジェクト・リレーショナル・スキーマを作成してオブジェクト・リレーショナル・インタフェースを実装します。このアプローチでは、データ格納用のオブジェクト表を作成します。または、オブジェクト表のかわりにオブジェクト・ビューを使用して、リレーショナル表に格納されている既存のデータへのオブジェクト・リレーショナル・インタフェースを実装することもできます。オブジェクト・ビューについては、第6章を参照してください。

型の定義

オブジェクト型は、CREATE TYPE文で作成します。たとえば、次の文では、StockItem_objtyp型が作成されます。

例A-14 StockItem_objtypオブジェクトの作成

CREATE TYPE StockItem_objtyp AS OBJECT (
  StockNo    NUMBER,
  Price      NUMBER,
  TaxRate    NUMBER
  );
/

StockItem_objtyp型のインスタンスは、顧客が発注する在庫品目を表すオブジェクトです。これらは3つの数値属性を持ちます。StockNoは主キーです。

型を定義する順序によって違いが生じる場合があります。参照先の型を定義してから、その型を参照する他の型を定義するのが理想的です。

たとえば、LineItem_objtyp型はStockItem_objtypのオブジェクトへのREFである属性を含むことでStockItem_objtypを参照(前提と)します。このことは、LineItem_objtyp型を作成する文で確認できます。

例A-15 LineItem_objtypオブジェクトの作成

CREATE TYPE LineItem_objtyp AS OBJECT (
  LineItemNo   NUMBER,
  Stock_ref    REF StockItem_objtyp,
  Quantity     NUMBER,
  Discount     NUMBER
  );
/

LineItem_objtyp型のインスタンスは、明細項目を表すオブジェクトです。これらは3つの数値属性と1つのREF属性を持ちます。LineItem_objtypにより明細項目エンティティがモデル化され、対応する在庫オブジェクトへのオブジェクト参照が含まれます。

型間の参照が入り組んでいるために、ある型を作成する前にその前提となる型をすべて作成することが困難な場合や不可能な場合があります。このような状況に対処するために、不完全型と呼ばれる型を作成し、参照先として作成する他の型のプレースホルダとして使用できます。これにより、他の型を作成した後に、不完全型に戻って完全型で置き換えることができます。

たとえば、StockItem_objtypの作成前にLineItem_objtypを作成する必要がある場合は、次のような文を使用してLineItem_objtypを不完全型として作成します。

CREATE TYPE LineItem_objtyp;

不完全型の作成に使用されるCREATE TYPE文の書式には、AS OBJECT句がなく、属性の指定もありません。

不完全型を完全な定義で置き換えるには、次の例に示すようにOR REPLACE句を挿入します。

例A-16 LineItem_objtypオブジェクトの置換

CREATE OR REPLACE TYPE LineItem_objtyp AS OBJECT (
  LineItemNo   NUMBER,
  Stock_ref    REF StockItem_objtyp,
  Quantity     NUMBER,
  Discount     NUMBER
  );
/

OR REPLACE句は、置き換える不完全型がない場合も使用できます。

ここで、スキーマに必要な残りの型を作成します。次の文では、電話番号リストの配列型を定義します。

例A-17 PhoneList_vartyp型の作成

CREATE TYPE PhoneList_vartyp AS VARRAY(10) OF VARCHAR2(20);
/

PhoneList_vartyp型のデータ単位(インスタンス)は、それぞれがVARCHAR2型のデータ項目で表される最大10個の電話番号のVARRAYです。

VARRAYまたはネストした表を使用して、電話番号リストを格納できます。この場合のリストは、1件の顧客の連絡先電話番号のセットです。次の理由から、VARRAYの方がネストした表よりも適しています。

  • 番号の順序が重要な場合があります。VARRAYは順序付きですがネストした表には順序がありません。

  • 特定の顧客の電話番号は少数です。VARRAYの場合は、最大要素数(この例で10)を事前に指定する必要があります。特別なサイズ制限のないネストした表よりも、記憶域が効率的に使用されます。

  • ネストした表の問合せはできますが、VARRAYの問合せはできません。ただし、電話番号リストを問い合せる理由はないため、ネストした表を使用してもメリットはありません。

通常、順序付けと境界が設計上の重要課題でなければ、設計者は次の経験則に従ってVARRAYとネストした表のどちらを使用するかを選択できます。つまり、コレクションの問合せが必要な場合はネストした表を使用し、コレクション全体を取り出す意図がある場合はVARRAYを使用します。


関連項目:

VARRAYとネストした表に関する設計上の考慮点の詳細は、第9章「Oracleオブジェクトの設計上の考慮点」を参照してください。

次の文では、所在地を表すオブジェクト型Address_objtypを定義します。

例A-18 Address_objtypオブジェクトの作成

CREATE TYPE Address_objtyp AS OBJECT (
  Street         VARCHAR2(200),
  City           VARCHAR2(200),
  State          CHAR(2),
  Zip            VARCHAR2(20)
  ) 
/

所在地の属性はすべて、単純化された郵送先住所の各部を表す文字列です。

次の文では、他のオブジェクト型を構築ブロックとして使用するオブジェクト型Customer_objtypを定義します。

例A-19 Customer_objtypオブジェクトの作成

CREATE TYPE Customer_objtyp AS OBJECT (
  CustNo           NUMBER,
  CustName         VARCHAR2(200),
  Address_obj      Address_objtyp,
  PhoneList_var    PhoneList_vartyp,

  ORDER MEMBER FUNCTION
    compareCustOrders(x IN Customer_objtyp) RETURN INTEGER
) NOT FINAL;
/

Customer_objtyp型のインスタンスは、特定の顧客に関する情報ブロックを表すオブジェクトです。Customer_objtypオブジェクトの属性は、数値、文字列、Address_objtypオブジェクトおよびPhoneList_vartyp型のVARRAYです。

NOT FINAL句を使用すると、必要に応じて後からcustomer型のサブタイプを作成できます。デフォルトでは、型はFINALとして作成されますが、これは、その型がサブタイプを導出することでそれ以上特化できないことを意味します。より特殊な顧客に関するCustomer_objtypのサブタイプの定義については、この付録で後述します。

Customer_objtypオブジェクトには、2種類の比較メソッドの一方であるオーダー・メソッドも関連付けられています。Oracleでは、2つのCustomer_objtypオブジェクトの比較を必要とする場合に、暗黙的にcompareCustOrdersメソッドが起動されて比較が実行されます。


注意:

比較メソッドを実装するPL/SQLについては、「compareCustOrdersメソッド」を参照してください。

2種類の比較メソッドとは、マップ・メソッドとオーダー・メソッドです。このアプリケーションでは、説明のためにそれぞれ1つずつ使用します。

ORDERメソッドは比較対象となる2つのオブジェクトごとにコールする必要がありますが、マップ・メソッドはオブジェクトごとに1回コールされます。通常、一連のオブジェクトをソートする場合、ORDERメソッドのコール回数の方がマップ・メソッドのコール回数よりも多くなります。


関連項目:


次の文では、ネストした明細項目表の型を定義します。各発注書では、このネストした表の型のインスタンスを使用して、その発注書の明細項目が格納されます。

例A-20 LineItemList_ntabtyp型の作成

CREATE TYPE LineItemList_ntabtyp AS TABLE OF LineItem_objtyp;
/

この型のインスタンスは、各行にLineItem_objtyp型のオブジェクトが含まれるネストした表オブジェクト(ネストした表)です。次の理由から、複数値明細項目リストを表すには、LineItem_objtypオブジェクトのVARRAYよりもネストした明細項目表の方が適しています。

  • ほとんどのアプリケーションでは、明細項目の内容を問い合せる必要があります。明細項目がネストした表に格納されていれば、この問合せにSQLを使用できますが、VARRAYに格納されている場合はSQLを使用できません。

  • アプリケーションで明細項目データに索引付けする必要がある場合、ネストした表は使用できますが、VARRAYは使用できません。

  • 明細項目の格納順序は重要でないと推定される場合、問合せ時に必要に応じて明細項目番号で順序付けできます。

  • 発注書の明細項目数には、事実上の上限はありません。VARRAYを使用するには、要素数の任意の上限を指定する必要があります。

次の文では、オブジェクト型PurchaseOrder_objtypを定義します。

例A-21 PurchaseOrder_objtypオブジェクトの作成

CREATE TYPE PurchaseOrder_objtyp AUTHID CURRENT_USER AS OBJECT (
  PONo                 NUMBER,
  Cust_ref             REF Customer_objtyp,
  OrderDate            DATE,
  ShipDate             DATE,
  LineItemList_ntab    LineItemList_ntabtyp,
  ShipToAddr_obj       Address_objtyp,

  MAP MEMBER FUNCTION
    getPONo RETURN NUMBER,

  MEMBER FUNCTION
    sumLineItems RETURN NUMBER
  );
/

PurchaseOrder_objtyp型のインスタンスは、発注書を表すオブジェクトです。この種のオブジェクトには、Customer_objtypへのREFAddress_objtypオブジェクト、LineItem_objtyp型に基づくLineItemList_ntabtyp型のネストした表など、6つの属性があります。PONoは主キーで、Cust_refは外部キーです。

PurchaseOrder_objtyp型のオブジェクトには、2つのメソッドgetPONoおよびsumLineItemsがあります。一方のgetPONoは、2種類の比較メソッドの1つであるマップ・メソッドです。マップ・メソッドは、オブジェクト内のレコードの順序における指定されたレコードの相対位置を戻します。そのため、Oracleでは、2つのPurchaseOrder_objtypオブジェクトの比較が必要になると、暗黙的にgetPONoメソッドがコールされて比較が実行されます。

2つのプラグマ宣言は、2つのメソッドに必要なデータベースへのアクセス・タイプ情報をPL/SQLに提供します。

文には、メソッドgetPONoおよびsumLineItemsを実装する実際のPL/SQLプログラムは含まれていません。「メソッド定義」を参照してください。

メソッド定義

型にメソッドがない場合、その定義はCREATE TYPE文のみで構成されます。ただし、メソッドを持つ型の場合は、型の定義を完了するために型本体も定義する必要があります。そのためにはCREATE TYPE BODY文を使用します。CREATE TYPEの場合と同様に、OR REPLACE句を挿入できます。既存の型本体を新規の型本体で置き換える場合は、メソッドを変更するために、この句を挿入する必要があります。

次の文では、PurchaseOrder_objtyp型の本体を定義します。この文は、型のメソッドを実装するPL/SQLプログラムを提供します。

例A-22 PurchaseOrder_objtyp型本体の作成

CREATE OR REPLACE TYPE BODY PurchaseOrder_objtyp AS 

MAP MEMBER FUNCTION getPONo RETURN NUMBER is   
   BEGIN  
      RETURN PONo;   
   END;    
   
MEMBER FUNCTION sumLineItems RETURN NUMBER is  
      i             INTEGER;  
      StockVal      StockItem_objtyp;  
      Total         NUMBER := 0;  
   
   BEGIN  
      FOR i in 1..SELF.LineItemList_ntab.COUNT LOOP  
         UTL_REF.SELECT_OBJECT(LineItemList_ntab(i).Stock_ref,StockVal);  
         Total := Total + SELF.LineItemList_ntab(i).Quantity * StockVal.Price;  
      END LOOP;  
      RETURN Total;
   END;
END;
/

getPONoメソッド

getPONoメソッドは、単にこのメソッドをコールするPurchaseOrder_objtyp型のインスタンスのPONo属性の値(発注書番号)を戻します。この種のgetメソッドを使用すると、内部表現に変更があった場合にも、オブジェクトを使用するコードを再加工する必要がありません。

sumLineItemsメソッド

sumLineItemsメソッドは、多数のオブジェクト・リレーショナル機能を使用します。

  • 前述のように、sumLineItemsメソッドの基本機能は、関連PurchaseOrder_objtypオブジェクトの明細項目の値の合計を戻すことです。各ファンクションのパラメータとして暗黙的に作成されるSELFキーワードにより、そのオブジェクトを参照できます。

  • COUNTキーワードは、PL/SQL表または配列の要素数のカウントを示します。この例ではLOOPと併用されており、アプリケーションはコレクション内の全要素(この場合は発注書の項目)を反復します。この方法で、SELF.LineItemList_ntab.COUNTはネストした表内でPurchaseOrder_objtypオブジェクトのLineItemList_ntab属性(ここではSELFで表現)と一致する要素の数をカウントします。

  • 実装内ではパッケージUTL_REFのメソッドが使用されます。Oracleでは、PL/SQLプログラム内でのREFの暗黙的な参照解除はサポートされないため、UTL_REFメソッドが必要です。UTL_REFパッケージは、オブジェクト参照を操作するメソッドを提供します。ここでは、Stock_refに対応するStockItem_objtypオブジェクトを取得するためにSELECT_OBJECTメソッドがコールされます。

AUTHID CURRENT_USER構文では、PurchaseOrder_objtypが実行者権限を使用して定義されていることが指定されます。つまり、メソッドは型を定義したユーザーの権限ではなく現行のユーザーの権限で実行されます。

  • PL/SQL変数StockValStockItem_objtyp型です。この変数は、UTL_REF.SELECT_OBJECTにより次を参照するオブジェクトに設定されます。

    (LineItemList_ntab(i).Stock_ref)

    このオブジェクトは、現在選択されている明細項目で参照される実際の在庫品目です。

  • 問題の在庫品目を取り出した後、次のステップは原価を計算することです。プログラムでは、在庫品目の原価をStockItem_objtypオブジェクトのPrice属性であるStockVal.Priceとして参照します。ただし、品目の原価を計算するために品目の受注数量も知る必要があります。アプリケーションでは、条件LineItemList_ntab(i).Quantityは現在選択されているLineItem_objtypオブジェクトのQuantity属性を表します。

メソッド・プログラムの残りの部分は、明細項目の値の合計を計算するループです。メソッドは合計を戻します。

compareCustOrdersメソッド

次の文では、Customer_objtypオブジェクト型の型本体でcompareCustOrdersメソッドを定義します。

例A-23 Customer_objtyp型本体の作成

CREATE OR REPLACE TYPE BODY Customer_objtyp AS
  ORDER MEMBER FUNCTION
  compareCustOrders (x IN Customer_objtyp) RETURN INTEGER IS
  BEGIN
    RETURN CustNo - x.CustNo;
  END;
END;
/

前述のように、オーダー・メソッドcompareCustOrdersの操作では、2つの顧客発注に関する情報が比較されます。このメソッドは入力引数としてCustomer_objtypオブジェクトをもう1つ取り、2つのCustNo番号の違いを戻します。戻り値は次のとおりです。

  • 固有のオブジェクトのCustNo値の方が小さい場合は負の数。

  • 固有のオブジェクトのCustNo値の方が大きい場合は正の数。

  • 2つのオブジェクトのCustNo値が同一の場合は0 (ゼロ)。この場合は、両方の発注が同じ顧客に関連付けられています。

戻り値が正、負または0(ゼロ)のいずれであるかは、顧客番号の相対順序を示します。たとえば、通常、小さい番号は大きい番号よりも前に作成されます。ORDERメソッドの入力引数(SELFと明示的引数)がNULLの場合、ORDERメソッドはコールされず、結果は単にNULLとして処理されます。

これで、発注書スキーマのオブジェクト・リレーショナル・バージョンについて、すべてのオブジェクト型を定義したことになります。実際の発注書データを含めるこれらの型のインスタンスは作成しておらず、この種のデータを格納する表も作成していません。次の項では、この作成方法について説明します。

オブジェクト表の作成

オブジェクト型の作成は、表の作成とは異なります。型を作成する場合は、論理構造を定義するのみで、記憶域は作成しません。データへのオブジェクト・リレーショナル・インタフェースを使用するには、データをオブジェクト表に格納するか、リレーショナル表に残しておいてオブジェクト・ビューからアクセスするかに関係なく、オブジェクト型を作成する必要があります。オブジェクト・ビューとオブジェクト表は、どちらもオブジェクト型を前提とします。オブジェクト表またはオブジェクト・ビューは、常に特定のオブジェクト型の表またはビューです。この点では、常に指定のデータ型を持つリレーショナル列に似ています。


関連項目:

オブジェクト・ビューの詳細は、第6章「オブジェクト・モデルのリレーショナル・データへの適用」を参照してください。

リレーショナル列と同様に、オブジェクト表に含めることができるのは、表と同一の宣言された型のオブジェクト・インスタンスなど、1種類の行のみです。(また、表が代入可能な場合は、宣言された型のサブタイプのインスタンスも含めることができます。)

オブジェクト表の各行は1つのオブジェクト・インスタンスです。そのため、ある意味では、オブジェクト表は宣言されたオブジェクト型の1列のみで構成されることになります。ただし、これはリレーショナル表の場合と異なるわけではありません。リレーショナル表の各行も、理論上は1つのエンティティ(リレーショナルCustomers表内の顧客など)を表します。リレーショナル表の各列には、このエンティティの属性データが格納されます。

同様に、オブジェクト表では、オブジェクト型の属性は挿入および選択可能な列にマップされます。主な違いは、オブジェクト表の場合、データが表の型で定義された構造で格納される(および取出し可能である)ため、きわめて単純な問合せでデータのマルチレベル構造全体を取り出すことができるという点にあります。

オブジェクト表Customer_objtab

次の文では、Customer_objtyp型のオブジェクトを保持するオブジェクト表Customer_objtabを定義します。

例A-24 Customer_objtab表の作成

CREATE TABLE Customer_objtab OF Customer_objtyp (CustNo PRIMARY KEY) 
   OBJECT IDENTIFIER IS PRIMARY KEY;

リレーショナル表とは異なり、オブジェクト表の作成時には、データ型(格納されるオブジェクトの型)を指定します。

表には、次のようにCustomer_objtypの属性ごとに1列があります。


CustNo NUMBER /* Primary key */
CustName VARCHAR2(200)
Address_obj Address_objtyp
PhoneList_var PhoneList_vartyp

これらの型の定義は、例A-18「Address_objtypオブジェクトの作成」および例A-17「PhoneList_vartyp型の作成」を参照してください。

図A-3 表Customer_objtabのオブジェクト・リレーショナル表現

図A-3の説明
「図A-3 表Customer_objtabのオブジェクト・リレーショナル表現」の説明

オブジェクト表のテンプレートとしてのオブジェクト・データ型

Customer_objtyp型があるため、同じ型の多数のオブジェクト表を作成できます。たとえば、同じCustomer_objtyp型のオブジェクト表Customer_objtab2を作成できます。

複数の表を作成する場合は、バリエーションを導入できます。Customer_objtabの作成に使用した文では、CustNo列に主キー制約を定義しました。この制約は、このオブジェクト表にのみ適用されます。同じ型の別のオブジェクト表には、この制約がない場合があります。

オブジェクト識別子および参照

Customer_objtabには、行オブジェクトとして表される顧客オブジェクトが含まれています。Oracleでは、行オブジェクトを参照可能にでき、これは、他の行オブジェクトまたはリレーショナル行からオブジェクト識別子(OID)を使用して参照できることを意味します。たとえば、発注書行オブジェクトでは、オブジェクト参照を使用して顧客行オブジェクトを参照できます。オブジェクト参照は、REF型で表されるシステム生成値であり、行オブジェクトの一意OIDに基づきます。

Oracleでは、各行オブジェクトに一意OIDが必要です。一意OID値をシステム生成として指定するか、行オブジェクトの主キーを一意OIDとして機能するように指定できます。この指定を行うには、CREATE TABLE文の実行時にOBJECT IDENTIFIER IS PRIMARY KEYまたはOBJECT IDENTIFIER IS SYSTEM GENERATEDを指定します。後者がデフォルトです。主キー値がデフォルトである16バイトのシステム生成識別子より小さい場合は、オブジェクト識別子として主キーを使用する方が効率的です。この例では、行オブジェクト識別子として主キーを使用しています。

埋込みオブジェクトを持つオブジェクト表

Customer_objtabAddress_obj列にAddress_objtypオブジェクトが含まれていることに注意してください。この図が示すように、オブジェクト型は、それ自体がオブジェクト型である属性を持つことができます。オブジェクト表について宣言された型のオブジェクト・インスタンスが行オブジェクトと呼ばれるのは、1つのオブジェクト・インスタンスが表の行全体を占めるためです。ただし、Address_obj列にあるような埋込みオブジェクトは、列オブジェクトと呼ばれます。この種のオブジェクトは、行全体を占めないという点が行オブジェクトとは異なります。そのため参照不可であり、REFのターゲットにすることはできません。また、NULLにすることができます。

Address_objtypオブジェクトの属性は組込み型です。複合型ではなくスカラーである(つまり、固有の属性を持つオブジェクト型ではない)ため、ブランチの終了を表すことを反映するためにリーフ・レベル属性と呼ばれます。Address_objtypオブジェクトとその属性の列は、オブジェクト表Customer_objtabに作成されます。これらの列の参照またはナビゲートには、ドット表記法を使用できます。たとえば、Zip列に索引を作成する場合は、Address.Zipとして参照できます。

PhoneList_var列には、PhoneList_vartyp型のVARRAYが含まれています。PhoneList_vartyp型の各オブジェクトは、それぞれがVARCHAR2型のデータ項目で表される最大10個の電話番号のVARRAYとして定義してあります。例A-17を参照してください。

PhoneList_vartyp型の各VARRAYには最大200文字(10x20)と少量のオーバーヘッドを格納できるため、OracleではVARRAYがPhoneList_var列に1つのデータ単位として格納されます。4000バイト以内のVARRAYはインラインのBLOBに格納され、これは、VARRAY値の一部が表の外部に格納される可能性があることを意味します。

オブジェクト表Stock_objtab

次の文では、StockItem_objtypオブジェクトのオブジェクト表を作成します。

例A-25 Stock_objtab表の作成

CREATE TABLE Stock_objtab OF StockItem_objtyp (StockNo PRIMARY KEY)
   OBJECT IDENTIFIER IS PRIMARY KEY;

この表の各行は、次の3つの数値属性を持つStockItem_objtypオブジェクトです。


StockNo NUMBER
Price NUMBER
TaxRate NUMBER

Oracleでは、属性ごとに1列が作成されます。CREATE TABLE文では、StockNo列に主キー制約が適用され、主キーを行オブジェクトの識別子として使用するように指定されます。

オブジェクト表PurchaseOrder_objtab

次の文では、PurchaseOrder_objtypオブジェクトのオブジェクト表を定義します。

例A-26 PurchaseOrder_objtab表の作成

CREATE TABLE PurchaseOrder_objtab OF PurchaseOrder_objtyp (  /* Line 1 */
   PRIMARY KEY (PONo),                                       /* Line 2 */
   FOREIGN KEY (Cust_ref) REFERENCES Customer_objtab)        /* Line 3 */
   OBJECT IDENTIFIER IS PRIMARY KEY                          /* Line 4 */
   NESTED TABLE LineItemList_ntab STORE AS PoLine_ntab (     /* Line 5 */
     (PRIMARY KEY(NESTED_TABLE_ID, LineItemNo))              /* Line 6 */
     ORGANIZATION INDEX COMPRESS)                            /* Line 7 */
   RETURN AS LOCATOR                                         /* Line 8 */
/   

このCREATE TABLE文では、PurchaseOrder_objtabオブジェクト表が作成されます。各行の内容は次のとおりです。

行1:


CREATE TABLE PurchaseOrder_objtab OF PurchaseOrder_objtyp (

この行は、表の各行がPurchaseOrder_objtypオブジェクトであることを示します。PurchaseOrder_objtypオブジェクトの属性は次のとおりです。


PONo NUMBER
Cust_ref REF Customer_objtyp
OrderDate DATE
ShipDate DATE
LineItemList_ntab LineItemList_ntabtyp
ShipToAddr_obj Address_objtyp

これらの型の定義は、例A-19「Customer_objtypオブジェクトの作成」および例A-20「LineItemList_ntabtyp型の作成」を参照してください。

図A-4 表PurchaseOrder_objtabのオブジェクト・リレーショナル表現

図A-4の説明
「図A-4 表PurchaseOrder_objtabのオブジェクト・リレーショナル表現」の説明

行2:

PRIMARY KEY (PONo),

この行は、PONo属性を表の主キーとして指定します。

行3:

FOREIGN KEY (Cust_ref) REFERENCES Customer_objtab)

この行は、Cust_ref列の参照制約を指定します。この参照制約は、リレーショナル表に対して指定される制約と同様です。制約がない場合、REF列では任意の行オブジェクトを参照できます。ただし、この場合、Cust_ref REFで参照できるのはCustomer_objtabオブジェクト表の行オブジェクトのみです。

行4:

OBJECT IDENTIFIER IS PRIMARY KEY

この行は、PurchaseOrder_objtabオブジェクト表の主キーが行のOIDとして使用されることを示します。

行5から8:


NESTED TABLE LineItemList_ntab STORE AS PoLine_ntab (
(PRIMARY KEY(NESTED_TABLE_ID, LineItemNo))
ORGANIZATION INDEX COMPRESS)
RETURN AS LOCATOR

この4行は、ネストした表の列LineItemList_ntabの記憶域の指定とプロパティに関係しています。ネストした表の行は、別の記憶表に格納されます。この記憶表をユーザーが直接問い合せることはできませんが、メンテナンスのためにDDL文で参照することはできます。記憶表の非表示列NESTED_TABLE_IDでは、各行が対応する親行と一致します。特定の親に属するネストした表の全要素は同じNESTED_TABLE_ID値を持ちます。たとえば、PurchaseOrder_objtabの特定の行のネストした表の要素は、すべて同じNESTED_TABLE_ID値を持ちます。PurchaseOrder_objtabの別の行に属するネストした表の要素は、異なるNESTED_TABLE_ID値を持ちます。

前述のCREATE TABLEの例では、行5はネストした表LineItemList_ntabの行が別の表(記憶表)PoLine_ntabに格納されることを示します。STORE AS句で記憶表に対する制約と記憶域を指定することもできます。この例で、行7は記憶表が索引構成表(IOT)であることを示します。通常、ネストした表の行をIOTに格納すると、同じ親に属している行がクラスタ化されるというメリットがあります。IOTCOMPRESSが指定されているため、記憶域が節約されますが、その理由は、COMPRESSを指定しなければ、IOTのキーのNESTED_TABLE_ID部分が親の行オブジェクトの行ごとに繰り返されるためです。ただし、COMPRESSを指定すると、NESTED_TABLE_IDは親の行オブジェクトごとに一度のみ格納されます。


関連項目:

ネストした表をIOTとして構成するメリット、ネストした表の圧縮を指定するメリット、ネストした表の記憶域全般の詳細は、「ネストした表の記憶域」を参照してください。

行6では、NESTED_TABLE_IDおよびLineItemNo属性が2つの目的で記憶表の主キーとして指定されています。第1の目的はIOTのキーを指定すること、第2の目的はネストした表のLineItemNo列の一意性を親表の各行内で規定することです。この文では、LineItemNo列をキーに含めることで、LineItemNo列に各発注書内の個別値が確実に含まれるようになります。

行8は、ネストした表LineItemList_ntabが取得時にロケータ形式で戻されることを示します。LOCATORを指定しなければ、デフォルトはVALUEで、ロケータのみでなくネストした表全体が戻されます。ネストした表のコレクションに多数の要素が含まれている場合は、それらを含んでいる行オブジェクトまたは列が選択されたときに、ネストされた表全体を戻すのは非効率的です。

ネストした表のロケータが戻されるように指定すると、Oracleでは実際のコレクション値へのロケータのみをクライアントに送信できます。アプリケーションでは、OCICollIsLocatorまたはUTL_COLL.IS_LOCATORインタフェースをコールして、フェッチされたネストした表がロケータ形式であるか値形式であるかを検出できます。ロケータが戻されることを確認した後、アプリケーションでは問合せにロケータを使用して、ネストした表の行要素のうち必要なサブセットのみをフェッチできます。このような、ネストした表の行のロケータ・ベースの取得は、ネストした表の値またはコピー・セマンティクスを保つために、オリジナルの文のスナップショットに基づきます。つまり、ネストした表の行要素のサブセットをフェッチするためにロケータが使用される場合、ネストした表のスナップショットには、ロケータが最初に取得されたときのネストした表が反映されます。

ここで再び「メソッド定義」に示したPurchaseOrder_objtypsumLineItemsメソッドの実装を考えてみます。その実装は、ネストした表LineItemList_ntabVALUEとして戻されることを前提としていました。大きいネストした表を効率的に処理し、PurchaseOrder_objtabのネストした表がロケータとして戻されるという事実を活用するには、sumLineItemsメソッドを次のように書き換える必要があります。

例A-27 PurchaseOrder_objtyp型本体の置換

CREATE OR REPLACE TYPE BODY PurchaseOrder_objtyp AS 

   MAP MEMBER FUNCTION getPONo RETURN NUMBER is   
      BEGIN  
         RETURN PONo;   
      END;   
    
   MEMBER FUNCTION sumLineItems RETURN NUMBER IS  
      i          INTEGER;  
      StockVal   StockItem_objtyp;  
      Total      NUMBER := 0;
  
   BEGIN
      IF (UTL_COLL.IS_LOCATOR(LineItemList_ntab)) -- check for locator
         THEN
            SELECT SUM(L.Quantity * L.Stock_ref.Price) INTO Total
            FROM   TABLE(CAST(LineItemList_ntab AS LineItemList_ntabtyp)) L;
      ELSE
         FOR i in 1..SELF.LineItemList_ntab.COUNT LOOP  
            UTL_REF.SELECT_OBJECT(LineItemList_ntab(i).Stock_ref,StockVal);  
            Total := Total + SELF.LineItemList_ntab(i).Quantity * 
                                                            StockVal.Price;  
         END LOOP;  
      END IF;  
   RETURN Total;  
   END;  
END;     
/

書き換えたsumLineItemsメソッドは、UTL_COLL.IS_LOCATORファンクションを使用して、ネストした表の属性LineItemList_ntabがロケータとして戻されるかどうかをチェックします。条件がTRUEに評価されると、ネストした表のロケータはTABLE式を使用して問合せされます。


注意:

SQLコンパイル・エンジンにコレクション属性(またはパラメータまたは変数)の実際の型を指示して問合せをコンパイルできるように、現在、TABLE式にはCAST式が必要です。

ネストした表のロケータを問い合せる方が、発注書の大きい明細項目リストを効率的に処理できます。LineItemList_ntabを反復する前述のコードは、ネストした表がVALUEとして戻される場合に対処するために残されています。

表の作成後に、ALTER TABLE文が発行され、REFに対するSCOPE FOR制約が追加されます。REFに対するSCOPE FOR制約は、CREATE TABLE文では指定できません。Stock_refで参照できるのがオブジェクト表Stock_objtabのみであることを指定するには、PoLine_ntab記憶表に対して次のALTER TABLE文を発行します。

例A-28 SCOPE FOR制約の追加

ALTER TABLE PoLine_ntab
   ADD (SCOPE FOR (Stock_ref) IS stock_objtab) ;

この文では、ネストした表のStock_ref列の有効範囲をStock_objtabに制限するように指定します。これは、この列に格納される値がStock_objtab内の行オブジェクトへの参照である必要があることを示します。参照制約とは異なり、SCOPE制約には参照先オブジェクトへの依存性がありません。たとえば、Stock_objtab内で参照されている行オブジェクトは、ネストした表のStock_ref列で参照されていても削除できます。このような削除により、ネストした表内の対応する参照はDANGLING REFとなります。

図A-5 ネストした表LineItemList_ntabのオブジェクト・リレーショナル表現

図A-5の説明
「図A-5 ネストした表LineItemList_ntabのオブジェクト・リレーショナル表現」の説明

Oracleでは、記憶表に対する参照制約の指定はサポートされていません。この場合は、REF列に対するSCOPE句を指定すると有効です。通常、REF列に対して有効範囲または参照制約を指定すると、次のようなメリットがあります。

  • Oracleで行オブジェクトの一意識別子のみを列にREF値として格納できるため、記憶域の節約になります。

  • 記憶表のREF列に索引を作成できます。

  • Oracleでは、これらのREFの参照解除を含む問合せを、参照表が関係する結合としてリライトできます。

この時点で、発注書アプリケーションのすべての表が作成されました。次の項では、これらの表の操作方法について説明します。

図A-6 表PurchaseOrder_objtabのオブジェクト・リレーショナル表現

図A-6の説明
「図A-6 表PurchaseOrder_objtabのオブジェクト・リレーショナル表現」の説明

値の挿入

ここでは、前にリレーショナル表に挿入したのと同じデータをオブジェクト表に挿入する方法について説明します。型のインスタンスを作成するために、一部の値によりオブジェクト型のコンストラクタへのコールが取り込まれていることに注意してください。

例A-29 Stock_objtabへの値の挿入

INSERT INTO Stock_objtab VALUES(1004, 6750.00, 2) ;
INSERT INTO Stock_objtab VALUES(1011, 4500.23, 2) ;
INSERT INTO Stock_objtab VALUES(1534, 2234.00, 2) ;
INSERT INTO Stock_objtab VALUES(1535, 3456.23, 2) ;

例A-30 Customer_objtabへの値の挿入

INSERT INTO Customer_objtab
  VALUES (
    1, 'Jean Nance',
    Address_objtyp('2 Avocet Drive', 'Redwood Shores', 'CA', '95054'),
    PhoneList_vartyp('415-555-0102')
    ) ;

INSERT INTO Customer_objtab
  VALUES (
    2, 'John Nike',
    Address_objtyp('323 College Drive', 'Edison', 'NJ', '08820'),
    PhoneList_vartyp('609-555-0190','201-555-0140')
    ) ;

例A-31 PurchaseOrder_objtabへの値の挿入

INSERT INTO PurchaseOrder_objtab
  SELECT  1001, REF(C),
          SYSDATE, '10-MAY-1999',
          LineItemList_ntabtyp(),
          NULL
   FROM   Customer_objtab C
   WHERE  C.CustNo = 1 ;

この文では、次の属性を持つPurchaseOrder_objtypオブジェクトが構成されます。


PONo 1001
Cust_ref REF to customer number 1
OrderDate SYSDATE
ShipDate 10-MAY-1999
LineItemList_ntab an empty LineItem_ntabtyp
ShipToAddr_obj NULL

この文では、問合せを使用して、CustNo値が1であるCustomer_objtabオブジェクト表の行オブジェクトへのREFが構成されます。

次の文では、TABLE式を使用して、ネストした表が挿入先として識別されます。これは、PONo値が1001であるPurchaseOrder_objtab表の行オブジェクトのLineItemList_ntab列にあるネストした表です。

例A-32 LineItemList_ntabへの値の挿入

INSERT INTO TABLE (
  SELECT  P.LineItemList_ntab
   FROM   PurchaseOrder_objtab P
   WHERE  P.PONo = 1001
  )
  SELECT  01, REF(S), 12, 0
   FROM   Stock_objtab S
   WHERE  S.StockNo = 1534 ;

この文では、TABLE式で識別されたネストした表に明細項目が1つ挿入されます。挿入される明細項目には、Stock_objtabオブジェクト表内でStockNo値が1534の行オブジェクトへのREFが含まれています。

次の文は、前述の文と同じパターンに従います。

例A-33 PurchaseOrder_objtabおよびLineItemList_ntabへの値の挿入

INSERT INTO PurchaseOrder_objtab
  SELECT  2001, REF(C),
          SYSDATE, '20-MAY-1997',
          LineItemList_ntabtyp(),
          Address_objtyp('55 Madison Ave','Madison','WI','53715')
   FROM   Customer_objtab C
   WHERE  C.CustNo = 2 ;

INSERT INTO TABLE (
  SELECT  P.LineItemList_ntab
   FROM   PurchaseOrder_objtab P
   WHERE  P.PONo = 1001
  )
  SELECT  02, REF(S), 10, 10
   FROM   Stock_objtab S
   WHERE  S.StockNo = 1535 ;

INSERT INTO TABLE (
  SELECT  P.LineItemList_ntab
   FROM   PurchaseOrder_objtab P
   WHERE  P.PONo = 2001
  )
  SELECT  10, REF(S), 1, 0
   FROM   Stock_objtab S
   WHERE  S.StockNo = 1004 ;

INSERT INTO TABLE (
  SELECT  P.LineItemList_ntab
   FROM   PurchaseOrder_objtab P
   WHERE  P.PONo = 2001
  )
  VALUES(11, (SELECT REF(S)
    FROM  Stock_objtab S
    WHERE S.StockNo = 1011), 2, 1) ;

問合せ

次の問合せ文は、暗黙的に比較メソッドを起動します。この例は、OracleでPurchaseOrder_objtyp型のオブジェクトがその型の比較メソッドを使用して順序付けされる方法を示しています。

例A-34 発注書の問合せ

SELECT  p.PONo
 FROM   PurchaseOrder_objtab p
 ORDER BY VALUE(p) ;

Oracleは、選択されたPurchaseOrder_objtypオブジェクトごとにマップ・メソッドgetPONoを起動します。このメソッドはオブジェクトのPONo属性を戻すため、選択により発注書番号の昇順リストが生成されます。

次の問合せは、リレーショナル・モデルに基づいて実行された問合せに対応します。

例A-35 発注書1001の顧客および明細項目データの問合せ

SELECT  DEREF(p.Cust_ref), p.ShipToAddr_obj, p.PONo, 
        p.OrderDate, LineItemList_ntab
 FROM   PurchaseOrder_objtab p
 WHERE  p.PONo = 1001 ;

例A-36 各発注書の合計値の問合せ

SELECT   p.PONo, p.sumLineItems()
 FROM    PurchaseOrder_objtab p ;

例A-37 在庫品目1004に関する発注書および明細項目データの問合せ

SELECT   po.PONo, po.Cust_ref.CustNo,
         CURSOR (
           SELECT  *
            FROM   TABLE (po.LineItemList_ntab) L
            WHERE  L.Stock_ref.StockNo = 1004
           )
 FROM    PurchaseOrder_objtab po ; 

前述の問合せでは、ネストした表から選択されたLineItem_objオブジェクトのセットについてネストしたカーソルが戻されます。アプリケーションでは、ネストしたカーソルからフェッチして個別のLineItem_objオブジェクトを取得できます。この問合せは、ネストしたセットを外側の結果からネスト解除して表すこともできます。

SELECT   po.PONo, po.Cust_ref.CustNo, L.*
 FROM    PurchaseOrder_objtab po, TABLE (po.LineItemList_ntab) L
 WHERE   L.Stock_ref.StockNo = 1004 ;

前述の問合せでは、結果セットが平滑化された形式(第1標準形式)で戻されます。このタイプの問合せは、リレーショナル・ツールやODBCなどのAPIからOracleコレクション列にアクセスする際に役立ちます。前述のネスト解除例では、LineItemList_ntab行を持つPurchaseOrder_objtabオブジェクト表の行のみが戻されます。対応するLineItemList_ntab内の行の有無に関係なくPurchaseOrder_objtab表の行をすべてフェッチするには、(+)演算子が必要です。

SELECT   po.PONo, po.Cust_ref.CustNo, L.*
 FROM    PurchaseOrder_objtab po, TABLE (po.LineItemList_ntab) (+) L
 WHERE   L.Stock_ref.StockNo = 1004 ;

例A-38の要求では、すべてのPurchaseOrder_objtab行のすべてのネストした表LineItemList_ntabの行を問い合せる必要があります。ネスト解除も必要です。

例A-38 すべての発注書のすべての明細項目の平均値引の問合せ

SELECT AVG(L.DISCOUNT)
  FROM PurchaseOrder_objtab po, TABLE (po.LineItemList_ntab) L ;

削除

次の例には、例A-13に示したリレーショナル・モデルの場合に必要だった2つの削除と同じ効果があります。例A-39では、明細項目を含めて発注書オブジェクト全体が1回のSQL操作で削除されます。リレーショナル・モデルの場合は、発注書の明細項目を明細項目表から削除し、発注書を発注書表から個別に削除する必要があります。


注意:

このサンプルのSQL文を実行する場合、発注書は以降の例に必要になるため、例A-39のDELETE文を実行しないでください。

例A-39 オブジェクト・リレーショナル・モデルに基づく発注書1001の削除

DELETE
 FROM   PurchaseOrder_objtab
 WHERE  PONo = 1001 ;

オブジェクト型の進化

完成して完全にビルドされたアプリケーションであっても、未完成となる傾向があります。要件が変化したり、基礎となるオブジェクト・モデルやスキーマを新しい状況にあわせて変更せざるを得なくなったり、当初意図していたものよりも適切にジョブを実行するためにオブジェクト・モデルを改良する方法があったりする場合があります。

オブジェクト・リレーショナル・アプリケーションの運用開始後しばらくして、設計を改良する方法が明らかになったとします。特に、ユーザーは顧客レコードを表示するときに、ほぼ常に購入履歴を確認しようとすることが判明したとします。これを現行のオブジェクト・モデルで実行するには、顧客情報と発注書情報が保持される2つの表Customer_objtabおよびPurchaseOrder_objtabの結合が必要です。そこで、関連発注書に関するデータに顧客表から直接アクセスできるように設計を改良することになったとします。

その方法の1つは、顧客の発注書情報がその顧客を表すオブジェクト・インスタンスに適切に組み込まれるように、Customer_objtypを変更することです。つまり、発注書情報の属性をCustomer_objtypに追加する必要があります。複数の発注書に関する情報を保持するには、属性をコレクション型(ネストした表)にする必要があります。

属性の追加は、オブジェクト型を変更または進化させる方法の1つです。型を進化させると、Oracleでは変更内容が型自体およびすべての依存スキーマ・オブジェクト(型のサブタイプ、変更された型を属性として持つ他のオブジェクト型および変更された型の表と列)に適用されます。

Customer_objtypを変更して発注書のネストした表の属性を追加するには、次のように複数の手順が必要です。

  1. 発注書のネストした表に使用する新規の型を作成します。

  2. Customer_objtypを変更して新規の型の新規の属性を追加します。

  3. Customer_objtabオブジェクト表で、新規に追加したネストした表の記憶表の名前と有効範囲を指定します。

    • 新規の属性にあわせてCustomer_objtabオブジェクト表をアップグレードすると、発注書自体に明細項目のネストした表が含まれるため、一方の内側に他方が含まれる2レベルのネストした表が実際に追加されます。

    • 発注書のネストした表と明細項目のネストした表の両方に主キー・ベースのREFを使用できるように、両方について有効範囲を指定する必要があります。この詳細は、次の項を参照してください。

図A-7 顧客オブジェクト型のネストした表

図A-7の説明
「図A-7 顧客オブジェクト型のネストした表」の説明

前述の手順を完了すると、モデルにおける顧客情報と発注書情報の論理的な関連性が明確になり、顧客、発注書および明細項目に関するすべての情報について顧客表を問合せできるようになります。また、顧客表に対する1つのINSERT文で新規顧客の新規発注書を挿入できます。

Customer型への属性の追加

発注書のネストした表をCustomer_objtypの属性として追加する前に、この種のネストした表の型を定義する必要があります。そのためには次の文を使用します。

例A-40 PurchaseOrderList_ntabtypの作成

CREATE TYPE PurchaseOrderList_ntabtyp AS TABLE OF PurchaseOrder_objtyp;
/

これで、ALTER TYPE文を使用して、この型の属性をCustomer_objtypに追加できます。

例A-41 Customer_objtypの変更

ALTER TYPE Customer_objtyp
  ADD ATTRIBUTE (PurchaseOrderList_ntab PurchaseOrderList_ntabtyp)
  CASCADE;

変更する型に依存型または依存表がある場合は、型に対するALTER TYPE文でCASCADEまたはINVALIDATEを指定して、依存オブジェクトに対する変更内容の適用方法を指定する必要があります。

  • CASCADEを指定すると、型変更の適用前に依存オブジェクトの妥当性チェックが実行されます。これらのチェックでは、変更により、表のパーティション・キーとして使用中の属性が削除されるなどの不正動作が発生しないことが確認されます。依存オブジェクトが検証に失敗すると、型の変更は異常終了します。これに対して、すべての依存オブジェクトが検証に成功すると、システムは型の変更を伝播するために必要なメタデータおよびデータの変更に進みます。これには、列の追加と削除、ネストした表の記憶表の作成などが自動的に含まれる場合があります。

  • INVALIDATEオプションを指定すると、予備的な妥当性チェックはスキップされ、型の変更が依存オブジェクトに直接適用されます。それらのオブジェクトは、次回のアクセス時に検証されます。このようにして型を変更すると検証所要時間が短縮されますが、後にユーザーがアクセスを試行した依存表を検証できない場合は、表が検証に合格するまでそのデータにアクセスできなくなります。

Customer_objtab表に追加される発注書と明細項目の新規のネストした表ごとに、REF列の有効範囲を追加する必要があります。便宜上、まず新規の表の名前をシステム生成名から認識可能な名前に変更します。次に、指定した名前を使用して記憶表を変更し、そのREF列の有効範囲を追加できます。

この操作を実行する必要があるのは、主キー・ベースのオブジェクト識別子を持つ表の列にオブジェクトへのREFを格納するには、列の有効範囲としてその表を指定するか、参照制約を適用する必要があるためです。列の有効範囲を特定の表に制限するには、その列のすべてのREFがその表のオブジェクトへのREFであると宣言します。この宣言が必要となるのは、主キー・ベースのオブジェクト識別子の一意性が保証されるのが特定の表のコンテキスト内のみであり、すべての表にまたがって一意であるとはかぎらないためです。主キー・ベースのREFまたはユーザー定義REFを有効範囲なしの列に挿入しようとすると、次のようなエラーが戻されます。

オブジェクト・ビューREFまたはユーザー定義REFを挿入できません。

明細項目には、オブジェクト識別子に表の主キーが使用されているStock_objtab表内のオブジェクトへのREFが含まれます。このため、PurchaseOrder_objtab表を作成した後で、その表内の明細項目のネストした表の記憶表にREF列の有効範囲を追加する必要がありました。次に、これと同じ操作を表Customer_objtab内の明細項目の新規のネストした表について実行する必要があります。

さらに、Customer_objtab表に追加する発注書の新規のネストした表について、同じ操作を実行する必要があります。発注書ではCustomer_objtab表の顧客が参照され、この表のオブジェクト識別子も主キー・ベースです。

次の文を使用して、名前を変更できるようにシステム生成表の名前を確認します。

SELECT table_name, parent_table_name, parent_table_column FROM user_nested_tables;

出力は次のようになります。

TABLE_NAME                    PARENT_TABLE_NAME             PARENT_TABLE_COLUMN
----------------------------- ----------------------------- ----------------------
SYSNTQOFArJyBTHu6iOMMKU4wHw== CUSTOMER_OBJTAB               PURCHASEORDERLIST_NTAB
POLINE_NTAB                   PURCHASEORDER_OBJTAB          LINEITEMLIST_NTAB
SYSNTZqu6IQItR++UAtgz1rMB8A== SYSNTQOFArJyBTHu6iOMMKU4wHw== LINEITEMLIST_NTAB

便宜上、システム生成のネストした表を適切な名前に変更します。たとえば、前述のサンプル出力のシステム生成名を使用して、次のように変更します。

ALTER TABLE "SYSNTQOFArJyBTHu6iOMMKU4wHw==" RENAME TO PO_List_nt;
ALTER TABLE "SYSNTZqu6IQItR++UAtgz1rMB8A==" RENAME TO Items_List_nt;

システム生成のネストした表の名前を変更するプロセスは、次のPL/SQLプロシージャを使用して自動的に実行することもできます。

DECLARE 
  nested_table_1 VARCHAR2(30);
  nested_table_2 VARCHAR2(30);
  cust_obj_table VARCHAR2(30) := 'CUSTOMER_OBJTAB';
BEGIN 
 EXECUTE IMMEDIATE ' SELECT table_name FROM user_nested_tables
    WHERE parent_table_name = :1 ' INTO nested_table_1 USING cust_obj_table;
 EXECUTE IMMEDIATE ' SELECT table_name FROM user_nested_tables
    WHERE parent_table_name = :1 ' INTO nested_table_2 USING nested_table_1;
 EXECUTE IMMEDIATE 'ALTER table "'|| nested_table_1 ||'" RENAME TO PO_List_nt';
 EXECUTE IMMEDIATE 'ALTER table "'|| nested_table_2 ||'" RENAME TO Items_List_nt';
END; 
/

新規の記憶表の名前はPO_List_ntおよびItems_List_ntです。次の文では、この2つの表のREF列の有効範囲を特定の表に限定します。

例A-42 ネストした表へのREFの有効範囲の追加

ALTER TABLE PO_List_nt ADD (SCOPE FOR (Cust_Ref) IS Customer_objtab);
ALTER TABLE Items_List_nt ADD (SCOPE FOR (Stock_ref) IS Stock_objtab);

Customer_objtabに顧客の発注書を挿入する前に、もう1つ必要な操作があります。PurchaseOrderList_ntabtypの実際のネストした表を、表内の顧客ごとにインスタンス化する必要があります。

新規の属性に関する列が表に追加されると、既存の行の列値がNULLに初期化されます。これは、既存の各顧客のネストした発注書表がアトミックNULLに設定されること(そこには実際にネストした表は存在せず、空の表もないこと)を意味します。顧客ごとにネストした表をインスタンス化するまでは、発注書を挿入しようとすると次のようなエラーが戻されます。

NULLの表値を参照しています。

次の文では、実際のネストした表のインスタンスを含むように各行を更新して、発注書を保持する列を準備します。

例A-43 Customer_objtabの更新

UPDATE Customer_objtab c
  SET c.PurchaseOrderList_ntab = PurchaseOrderList_ntabtyp();

この文で、PurchaseOrderList_ntabtyp()はネストした表の型のコンストラクタ・メソッドへのコールです。このコールでは、発注書は指定されておらず、空のネストした表が作成されます。

マルチレベル・コレクションの操作

この時点までに、Customer_objtyp型を進化させて発注書のネストした表を追加し、ネストした表に発注書を格納できるように表Customer_objtabを設定しました。これで、発注書をCustomer_objtabに挿入する準備が完了したことになります。

PurchaseOrder_objtabには、すでに2件の発注書があります。次の2つの文では、これらの発注書をCustomer_objtabにコピーします。

例A-44 Customer_objtabへの発注書の挿入

INSERT INTO TABLE (
  SELECT   c.PurchaseOrderList_ntab
    FROM   Customer_objtab c
    WHERE  c.CustNo = 1
  )
  SELECT VALUE(p)
    FROM PurchaseOrder_objtab p
    WHERE p.Cust_Ref.CustNo = 1;

INSERT INTO TABLE (
  SELECT   c.PurchaseOrderList_ntab
    FROM   Customer_objtab c
    WHERE  c.CustNo = 2
  )
  SELECT VALUE(p)
    FROM PurchaseOrder_objtab p
    WHERE p.Cust_Ref.CustNo = 2;

ネストした表への挿入

前述の各INSERT文には2つの主要部分があります。挿入操作のターゲット表を指定するTABLE式と、挿入するデータを取得するSELECTです。各部分のWHERE句では、発注書を(TABLE式で)受け取る顧客オブジェクトと、発注書の(発注書を取得する副問合せでの)選択対象となる顧客を選択します。

副問合せのWHERE句では、ドット表記法を使用してCustNo属性p.Cust_Ref.CustNoにナビゲートします。ドット表記法を使用する場合は、表の別名pが必須であることに注意してください。別名を省略してかわりにCust_Ref.CustNoを指定すると、エラーになります。

このWHERE句のドット表記法については、発注書のCust_Ref REF属性を介して顧客のCustNo属性にナビゲートできることにも注意する必要があります。PL/SQLではなくSQLは、この方法でドット表記法に使用されているREFを暗黙的に参照解除します。

INSERT文の最初の部分のTABLE式は、式から戻されるコレクションを表として処理するようにシステムに対して指示します。ここでは、式は特定の顧客に関する発注書のネストした表を挿入ターゲットとして選択するために使用されています。

INSERT文の第2の部分では、VALUE()ファンクションが選択した行をオブジェクトとして戻します。この場合、各行は発注書オブジェクトであり、明細項目の固有のコレクションで終了します。発注書の各行は、PurchaseOrder_objtyp型のある表から選択され、その型の別の表に挿入されます。

前述のINSERT文では、PurchaseOrder_objtypの顧客参照属性を使用して、既存の各発注書が属している顧客を識別しています。ただし、この時点で、古い発注書がすべて発注書表からアップグレード後のCustomer_objtabにコピーされ、発注書のこの顧客参照属性は使用されなくなります。これで、発注書は顧客オブジェクト自体に格納されるようになります。

次のALTER TYPE文では、顧客参照属性を削除するためにPurchaseOrder_objtypを進化させます。この文では、出荷先所在地が常に顧客所在地と同一であるとみなし、ShipToAddr_obj属性も冗長として削除します。

例A-45 PurchaseOrder_objtypの変更

ALTER TYPE PurchaseOrder_objtyp
    DROP ATTRIBUTE Cust_ref,
    DROP ATTRIBUTE ShipToAddr_obj
    CASCADE;

この時点で、CASCADEオプションを使用して、システムで検証を実行し、依存型および依存表に対して必要な変更をすべて行えるようになりました。

明細項目を含む新規発注書の挿入

前述したINSERTの例は、VALUE()ファンクションを使用して、既存の発注書オブジェクトを選択し、発注書のネストした表に挿入して、明細項目の固有のネストした表で完了する方法を示していました。次の例は、発注書オブジェクトとしてインスタンス化されていない新規発注書の挿入方法を示しています。この場合、発注書の明細項目のネストした表を、各明細項目オブジェクトおよびデータとともにインスタンス化する必要があります。参照しやすいように明細番号を左側に示してあります。

例A-46 VALUE()を使用したLineItemList_ntabtypへの挿入

INSERT INTO TABLE (                                             /* Line 1  */
  SELECT c.PurchaseOrderList_ntab                               /* Line 2  */
    FROM Customer_objtab c                                      /* Line 3  */
    WHERE c.CustName = 'John Nike'                              /* Line 4  */
   )                                                            /* Line 5  */
  VALUES (1020, SYSDATE, SYSDATE + 1,                           /* Line 6  */
    LineItemList_ntabtyp(                                       /* Line 7  */
      LineItem_objtyp(1, MAKE_REF(Stock_objtab, 1004), 1, 0),   /* Line 8  */
      LineItem_objtyp(2, MAKE_REF(Stock_objtab, 1011), 3, 5),   /* Line 9  */
      LineItem_objtyp(3, MAKE_REF(Stock_objtab, 1535), 2, 10)   /* Line 10 */
      )                                                         /* Line 11 */
);                                                              /* Line 12 */

行1から5では、TABLE式を使用して、挿入対象となるネストした表(顧客John Nikeに関する発注書のネストした表など)を選択します。

VALUES句(行6から12)には、次のように新規発注書の各属性の値が含まれています。


PONo
OrderDate
ShipDate
LineItemList_ntab

INSERT文の行6では、3つの発注書属性PONoOrderDateおよびShipDateの値を指定します。

属性値のみが指定され、発注書コンストラクタは指定されていません。ネストした表は発注書のネストした表として宣言されているため、発注書コンストラクタを明示的に指定して発注書インスタンスをネストした表内でインスタンス化する必要はありません。発注書コンストラクタを省略すると、システムにより自動的に発注書がインスタンス化されます。ただし、必要な場合はコンストラクタを指定でき、その場合、VALUES句は次のようになります。

INSERT INTO TABLE (
  SELECT c.PurchaseOrderList_ntab
    FROM Customer_objtab c
    WHERE c.CustName = 'John Nike'
   )
VALUES (
  PurchaseOrder_objtyp(1025, SYSDATE, SYSDATE + 1,
    LineItemList_ntabtyp(
      LineItem_objtyp(1, MAKE_REF(Stock_objtab, 1004), 1, 0),
      LineItem_objtyp(2, MAKE_REF(Stock_objtab, 1011), 3, 5),
      LineItem_objtyp(3, MAKE_REF(Stock_objtab, 1535), 2, 10)
     )
  )
)

行7から11では、明細項目のネストした表をインスタンス化してデータを提供します。コンストラクタ・メソッドLineItemList_ntabtyp(…)では、3つの明細項目を含むネストした表のインスタンスを作成します。

明細項目コンストラクタLineItem_objtyp()では、各明細項目のオブジェクト・インスタンスを作成します。明細項目属性の値は、コンストラクタの引数として指定します。

MAKE_REFファンクションでは、明細項目のStock_ref属性のREFを作成します。MAKE_REFの引数は、在庫表の名前と、参照する在庫品目の主キー値です。ここでMAKE_REFを使用できるのは、在庫表内のオブジェクト識別子が主キーに基づいているためです。主キーに基づいていない場合は、副問合せでREFファンクションを使用して在庫表の行へのREFを取得する必要があります。

マルチレベルのネストした表の問合せ

他の(埋込みではなく)トップレベルの列または属性のように、名前をSELECT構文のリストに指定すると、トップレベルのネストした表列を問い合せることができますが、結果はあまり読みやすくありません。たとえば、次の問合せでは、John Nikeに関する発注書のネストした表を選択します。

例A-47 顧客John Nikeに関するCustomer_objtabの問合せ

SELECT c.PurchaseOrderList_ntab
   FROM Customer_objtab c
   WHERE CustName = 'John Nike';

この問合せでは、次のような結果が生成されます。

PURCHASEORDERLIST_NTAB(PONO, ORDERDATE, SHIPDATE, LINEITEMLIST_NTAB(LINEITEMNO,
--------------------------------------------------------------------------------
PURCHASEORDERLIST_NTABTYP(PURCHASEORDER_OBJTYP(2001, '25-SEP-01', '20-MAY-97', L
INEITEMLIST_NTABTYP(LINEITEM_OBJTYP(10, 00004A038A00468ED552CE6A5803ACE034080020
B8C8340000001426010001000100290000000000090600812A00078401FE0000000B03C20B050000
...

インスタンス・データをネストしていない形式で表示し、REFをまったく表示しない方が読みやすくなります。TABLE式を(この場合は問合せのFROM句に)使用すると、読みやすい形式で表示できます。

たとえば、例A-48の問合せでは、John Nikeに属している全発注書の発注書番号、発注日および出荷日を選択します。

例A-48 TABLE式を使用したCustomer_objtabの問合せ

SELECT p.PONo, p.OrderDate, p.Shipdate
    FROM Customer_objtab c, TABLE(c.PurchaseOrderList_ntab) p
    WHERE c.CustName = 'John Nike';

PONO ORDERDATE SHIPDATE
------- --------- ---------
2001 25-SEP-01 26-SEP-01
1020 25-SEP-01 26-SEP-01

TABLE式は引数としてコレクションを取り、SQL文のSQL表と同様に使用できます。前述の問合せでは、FROM句のTABLE式に発注書のネストした表をリストすると、通常の表列の場合と同様にネストした表の列を選択できます。各列は、使用する表別名pにより、ネストした表に属する列として識別されます。この例に示すとおり、FROM句のTABLE式にも、式自身の表別名を持たせることが可能です。

TABLE式では、ネストした表は顧客表Customer_objtab自体の表別名cにより、顧客表の列として識別されます。FROM句では、Customer_objtab表がそれを参照するTABLE式の前に指定されていることに注意してください。このように、TABLE式でFROM句の左辺に指定されている表別名を使用する機能を、左相関といいます。これを使用すると、表とTABLE式をデイジー・チェーン化できます(これには、別のTABLE式の表別名を使用するTABLE式が含まれます)。実際には、この方法を使用して、他のネストした表に埋め込まれているネストした表の列を選択できます。

たとえば、次の例に発注書番号1020に関するすべての明細項目情報を選択する問合せを示します。

例A-49 発注書1020に関するCustomer_objtabの問合せ

SELECT p.PONo, i.LineItemNo, i.Stock_ref.StockNo, i.Quantity, i.Discount
  FROM Customer_objtab c, TABLE(c.PurchaseOrderList_ntab) p,
    TABLE(p.LineItemList_ntab) i
  WHERE p.PONo = 1020;
 

PONO LINEITEMNO STOCK_REF.STOCKNO QUANTITY DISCOUNT
----- ---------- ----------------- ---------- ----------
1020 1 1004 1 0
1020 2 1011 3 5
1020 3 1535 2 10

この問合せでは2つのTABLE式を使用し、第2の式で第1の式を参照しています。明細項目情報は、外側のネストした表内の発注書番号1020に属している内側のネストした表から選択されます。

顧客表の列は、SELECT構文のリストにもWHERE句にも指定されていないことに注意してください。顧客表は、ネストした表へのアクセス開始点を指定するためにのみFROM句に指定されています。

次に前述の問合せのバリエーションを示します。このバージョンは、ワイルド・カード*を使用してTABLE式のコレクションの列をすべて指定できることを示しています。

SELECT p.PONo, i.*
  FROM Customer_objtab c, TABLE(c.PurchaseOrderList_ntab) p,
    TABLE(p.LineItemList_ntab) i
  WHERE p.PONo = 1020;

型の継承と代入可能な列

会計マネージャを介して大口の標準顧客を多数処理するとします。会計マネージャのIDに使用するフィールドを、これらの顧客の顧客レコードに追加する必要があります。

前述の例では、発注書のネストした表の属性を追加する必要が生じたときに、顧客の型自体を進化させました。会計マネージャIDの属性を追加する際には、前回と同様の方法を使用するか、顧客型のサブタイプを作成し、サブタイプにのみ属性を追加できます。どちらの方法を使用するのが適切であるかを考えてみます。

この種の決定を行うには、提示された新規属性をベース型のすべてのインスタンス(すべての顧客)に有意かつ有効に適用できるか、またはベース型で識別可能なサブクラスにのみ適用できるかを検討する必要があります。

前述の例では、すべての顧客が発注書を持っているため、型自体を変更してその属性を追加する方法が適切でした。ただし、すべての顧客に会計マネージャがいるとはかぎらず、実際に会計マネージャがいるのは法人顧客のみです。そのため、顧客型を進化させて一般顧客には意味のない属性を追加するかわりに、識別した特殊な種類の顧客用に新規のサブタイプを作成し、そこに新規の属性を追加する方が適切です。

サブタイプの作成

ベース型に基づいてサブタイプを作成できるのは、ベース型でサブタイプが許可されている場合のみです。型のサブタイプを作成できるかどうかは、その型のFINALプロパティに応じて異なります。デフォルトでは、新規の型はFINALとして作成されます。これは、それがシリーズの最後の型であり、サブタイプを作成できないことを意味します。サブタイプを作成できる型を作成するには、顧客型を作成したときと同様にCREATE TYPE文でNOT FINALを指定する必要があります。

サブタイプを定義するには、CREATE TYPE文でUNDERキーワードを使用します。次の文では、Customer_objtypに新規のサブタイプCorp_Customer_objtypを作成します。この型はNOT FINALとして作成されるため、後で追加が必要になった場合にサブタイプを持たせることができます。

例A-50 Corp_Customer_objtypの作成

CREATE TYPE Corp_Customer_objtyp UNDER Customer_objtyp
            (account_mgr_id     NUMBER(6) ) NOT FINAL;
/

CREATE TYPE文を使用して新規サブタイプを作成する際には、新規に追加する属性およびメソッドのみを指定します。サブタイプはベース型から既存の属性とメソッドをすべて継承するため、指定する必要はありません。新規の属性およびメソッドは、継承された属性およびメソッドの後に追加されます。たとえば、新規Corp_Customer_objtypサブタイプの属性の完全リストは次のようになります。


CustNo
CustName
Address_obj
Phonelist_var
PurchaseOrderList_ntab
Account_mgr_id

デフォルトでは、サブタイプのインスタンスは、対応する任意のベース型の任意の列またはオブジェクト表に格納できます。このように、ベース型のスロットにサブタイプのインスタンスを格納する機能を、代入性といいます。列と表は、明示的にNOT SUBSTITUTABLEとして宣言されていないかぎり代入可能です。サブタイプ属性用の新規列と、各行に格納されるインスタンスの型ID用の非表示列が、システムにより自動的に追加されます。

実際には、FINAL型のサブタイプを作成できますが、最初にALTER TYPE文を使用して型をFINAL型からNOT FINAL型に進化させておく必要があります。変更した型の既存の列および表に新規サブタイプのインスタンスを格納可能にするには、ALTER TYPE文でCASCADEオプションCONVERT TO SUBSTITUTABLEを指定します。「型進化」を参照してください。

サブタイプの挿入

列またはオブジェクト表が代入可能な場合は、その列または表の宣言された型のインスタンスのみでなく、宣言された型の任意のサブタイプのインスタンスも挿入できます。Customer_objtab表の場合、これは表を使用してあらゆる種類(標準と法人の両方)の顧客に関する情報を格納できることを意味します。ただし、サブタイプの場合、情報の挿入方法には1つ重要な違いがあります。つまり、サブタイプのコンストラクタを明示的に指定する必要があります。コンストラクタの使用がオプションとなるのは、列または表の宣言された型のインスタンスの場合のみです。

たとえば、次の文では新規の標準顧客William Kiddを挿入します。

例A-51 標準顧客に関するデータの挿入

INSERT INTO Customer_objtab
  VALUES (
    3, 'William Kidd',
    Address_objtyp('43 Harbor Drive', 'Redwood Shores', 'CA', '95054'),
    PhoneList_vartyp('650-555-0188'),
    PurchaseOrderList_ntabtyp()
  );

VALUES句には各Customer_objtyp属性のデータが含まれていますが、Customer_objtypコンストラクタは省略されています。ここでコンストラクタがオプションなのは、表の宣言された型がCustomer_objtypであるためです。ネストした表の属性の場合、コンストラクタPurchaseOrderList_ntabtyp()では空のネストした表が作成されますが、発注書のデータは指定されていません。

次の例に、同じ表に新規の法人顧客を挿入する文を示します。コンストラクタCorp_Customer_objtyp()が使用され、会計マネージャIDのデータ値531が指定されていることに注意してください。

例A-52 法人顧客に関するデータの挿入

INSERT INTO Customer_objtab
  VALUES (
    Corp_Customer_objtyp(   -- Subtype requires a constructor
      4, 'Edward Teach',
      Address_objtyp('65 Marina Blvd', 'San Francisco', 'CA', '94777'),
      PhoneList_vartyp('415-555-0198', '415-555-0199'),
      PurchaseOrderList_ntabtyp(), 531
    )
  );

次の文では、2つの新規顧客それぞれについて1件の発注書を挿入します。新規顧客を挿入する文とは異なり、発注書を挿入する2つの文の構造は同じですが、発注書の明細項目数が異なっています。

例A-53 標準顧客に関する発注書の挿入

INSERT INTO TABLE (
  SELECT c.PurchaseOrderList_ntab
    FROM Customer_objtab c
    WHERE c.CustName = 'William Kidd'
   )
  VALUES (1021, SYSDATE, SYSDATE + 1,
    LineItemList_ntabtyp(
      LineItem_objtyp(1, MAKE_REF(Stock_objtab, 1535), 2, 10),
      LineItem_objtyp(2, MAKE_REF(Stock_objtab, 1534), 1, 0)
     )
   );

例A-54 法人顧客に関する発注書の挿入

INSERT INTO TABLE (
  SELECT c.PurchaseOrderList_ntab
    FROM Customer_objtab c
    WHERE c.CustName = 'Edward Teach'
   )
  VALUES (1022, SYSDATE, SYSDATE + 1,
    LineItemList_ntabtyp(
      LineItem_objtyp(1, MAKE_REF(Stock_objtab, 1011), 1, 0),
      LineItem_objtyp(2, MAKE_REF(Stock_objtab, 1004), 3, 0),
      LineItem_objtyp(3, MAKE_REF(Stock_objtab, 1534), 2, 0)
     )
   );

代入可能な列の問合せ

代入可能な列または表には、複数のデータ型のデータを含めることができます。これにより、たとえば顧客表の1回の問合せで全種類の顧客に関する情報を取得できます。ただし、特定の種類の顧客に関する情報のみ、またはその顧客の特定の属性に関する情報のみを取得することもできます。

次の例に、代入可能な表または列から必要な情報を取得する際に役立つ方法をいくつか示します。

例A-55に示す問合せでは、IS OF条件を含むWHERE句を使用して、ある種の法人顧客でない顧客を除外します。つまり、この問合せでは、法人顧客はすべて戻されますが、他の種類の顧客のインスタンスは戻されません。

例A-55 すべての法人顧客とそのサブタイプの選択

SELECT c.*
  FROM Customer_objtab c
  WHERE VALUE(c) IS OF (Corp_Customer_objtyp);

例A-56に示す問合せは前述の例と似ていますが、Corp_Customer_objtypのサブタイプを除外するためにIS OF条件にONLYキーワードが追加されています。最も限定的な型がCorp_Customer_objtypであるインスタンスの行のみが戻されます。

例A-56 サブタイプのないすべての法人顧客の選択

SELECT p.PONo
  FROM Customer_objtab c, TABLE(c.PurchaseOrderList_ntab) p
  WHERE VALUE(c) IS OF (ONLY Corp_Customer_objtyp);

例A-57に示す問合せでは、TABLE式を使用して発注書番号を(発注書のネストした表から)取得します。全種類の顧客がこの属性を持っていますが、WHERE句により検索対象が法人顧客のみに限定されています。

例A-57 法人顧客のみの発注書番号の選択

SELECT p.PONo
  FROM Customer_objtab c, TABLE(c.PurchaseOrderList_ntab) p
  WHERE VALUE(c) IS OF (Corp_Customer_objtyp);

例A-58に示す問合せでは、会計マネージャIDのデータが戻されます。これは法人顧客サブタイプでのみ保持される属性であり、表の宣言された型には欠落しています。問合せでは、サブタイプ属性Account_mgr_idにアクセスするために、TREAT()ファンクションを使用して各顧客を法人顧客として処理させています。

例A-58 TREATファンクションを使用したサブタイプ属性の選択

SELECT CustName, TREAT(VALUE(c) AS Corp_Customer_objtyp).Account_mgr_id
  FROM Customer_objtab c
  WHERE VALUE(c) IS OF (ONLY Corp_Customer_objtyp);

例A-58TREAT()が必要なのは、Account_mgr_idが表の宣言された型Customer_objtypの属性ではないためです。前述のように属性をSELECT構文のリストで指定するのみの場合、例A-59のような問合せでは「無効な列名」というエラーが戻されます。これは、Corp_Customer_objtypのインスタンスを除く全インスタンスを除外するWHERE句を使用した場合も同じです。WHERE句は結果から行を除外するのみであるため、ここでは不十分です。

例A-59 TREATファンクションを使用しないサブタイプ属性の選択

-- Following statement returns error, invalid column name for Account_mgr_id
SELECT CustName, Account_mgr_id
  FROM Customer_objtab c
  WHERE VALUE(c) IS OF (ONLY Corp_Customer_objtyp);

代入可能な各列またはオブジェクト表には、各行のインスタンスの型を識別する非表示の型ID列が関連付けられています。型の型IDは、USER_TYPESカタログ・ビューで確認できます。

ファンクションSYS_TYPEID()は、特定のインスタンスの型IDを戻します。例A-60に示す問合せでは、USER_TYPESカタログ・ビューでSYS_TYPEID()および結合を使用して、表Customer_objtab内の各顧客インスタンスの型名を戻します。

例A-60 各インスタンスの型の検出

SELECT c.CustName, u.TYPE_NAME
  FROM Customer_objtab c, USER_TYPES u
  WHERE SYS_TYPEID(VALUE(c)) = u.TYPEID;

--------------------------------- ---------------------
Jean Nance CUSTOMER_OBJTYP
John Nike CUSTOMER_OBJTYP
William Kidd CUSTOMER_OBJTYP
Edward Teach CORP_CUSTOMER_OBJTYP

SYS_TYPEID()VALUE()およびTREAT()の詳細は、「オブジェクトに便利なファンクションおよび演算子」を参照してください。