ヘッダーをスキップ
Oracle® Databaseオブジェクト・リレーショナル開発者ガイド
11gリリース2 (11.2)
E94920-01
  目次へ移動
目次
索引へ移動
索引

前
 
次
 

3 PL/SQLでのオブジェクト型の使用

この章では、PL/SQLでのオブジェクト型の使用方法を説明します。

内容は次のとおりです。

PL/SQLでのオブジェクトの宣言と初期化

PL/SQLのブロック、サブプログラムまたはパッケージでオブジェクト型を使用する場合、2段階の操作を行います。

  1. SQL*Plusまたはそれと同種のプログラムでは、CREATE TYPE SQL文を使用してオブジェクト型を定義する必要があります。

    オブジェクト型がスキーマに定義およびインストールされたら、その型はすべてのPL/SQLブロック、サブプログラムまたはパッケージにおいて使用できます。

  2. その後、PL/SQLで、先の手順で定義したユーザー定義型またはADTをデータ型に持つ変数を宣言します。

オブジェクトおよびADTには、通常の有効範囲とインストール・ルールが適用されます。

オブジェクト型の定義

例3-1に、2つのオブジェクト型と、オブジェクト型の1つの表を示します。続く各例に、PL/SQLでこれらのオブジェクト型の変数を宣言し、これらのオブジェクトが関連するその他の操作を実行する方法を示します。

例3-1 オブジェクト型を使用した作業

CREATE TYPE address_typ AS OBJECT ( 
   street          VARCHAR2(30),
   city            VARCHAR2(20),
   state           CHAR(2),
   postal_code     VARCHAR2(6) );
/
CREATE TYPE employee_typ AS OBJECT (
  employee_id       NUMBER(6),
  first_name        VARCHAR2(20),
  last_name         VARCHAR2(25),
  email             VARCHAR2(25),
  phone_number      VARCHAR2(20),
  hire_date         DATE,
  job_id            VARCHAR2(10),
  salary            NUMBER(8,2),
  commission_pct    NUMBER(2,2),
  manager_id        NUMBER(6),
  department_id     NUMBER(4),
  address           address_typ,
  MAP MEMBER FUNCTION get_idno RETURN NUMBER,
  MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) );
/
CREATE TYPE BODY employee_typ AS
  MAP MEMBER FUNCTION get_idno RETURN NUMBER IS
  BEGIN
    RETURN employee_id;
  END;
  MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(first_name || ' '  || last_name);
    DBMS_OUTPUT.PUT_LINE(address.street);
    DBMS_OUTPUT.PUT_LINE(address.city || ', '  || address.state || ' ' ||
                         address.postal_code);   
  END;
END;
/
CREATE TABLE employee_tab OF employee_typ;

PL/SQLブロックでのオブジェクトの宣言

オブジェクトとADTは、CHARNUMBERなどの組込み型が使用できるところであればどこでも使用できます。

例3-2では、employee_typ型のempオブジェクトを宣言しています。次に、オブジェクト型employee_typのコンストラクタによりオブジェクトが初期化されています。

例3-2 PL/SQLブロックでのオブジェクトの宣言

-- Requires Ex. 3-1
DECLARE
  emp employee_typ; -- emp is atomically null
BEGIN
-- call the constructor for employee_typ
  emp := employee_typ(315, 'Francis', 'Logan', 'FLOGAN',
        '415.555.0100', '01-MAY-04', 'SA_MAN', 11000, .15, 101, 110, 
         address_typ('376 Mission', 'San Francisco', 'CA', '94222'));
  DBMS_OUTPUT.PUT_LINE(emp.first_name || ' ' || emp.last_name); -- display details
  emp.display_address();  -- call object method to display details
END;
/

PL/SQLサブプログラムの仮パラメータは、ユーザー定義型のデータ型を持つことができます。したがって、ストアド・サブプログラムにオブジェクトを渡したり、あるサブプログラムから別のサブプログラムへとオブジェクトを渡したりすることができます。次の例では、オブジェクト型employee_typが仮パラメータのデータ型を指定しています。

PROCEDURE open_acct (new_acct IN OUT employee_typ) IS ...

次の例では、オブジェクト型employee_typがファンクションの戻り型を指定しています。

FUNCTION get_acct (acct_id IN NUMBER) RETURN employee_typ IS ...

PL/SQLでの未初期化オブジェクトの処理規則

ユーザー定義型は、コレクションと同様、オブジェクト型のコンストラクタをコールしてオブジェクトを初期化するまではアトミックNULLです。つまり、オブジェクトの属性のみではなく、オブジェクト自体がNULLになります。

NULLオブジェクトを他の任意のオブジェクトと比較すると、結果は常にNULLになります。また、アトミックNULLのオブジェクトを別のオブジェクトに代入した場合は、そのオブジェクトもアトミックNULLになります(再初期化する必要があります)。同様に、値のないNULLをオブジェクトに代入した場合も、オブジェクトはアトミックNULLになります。

式の中では、未初期化オブジェクトの属性はNULLとして評価されます。IS NULL比較演算子は、未初期化オブジェクトまたはその属性に適用された場合、TRUEを返します。

NULLオブジェクト、およびNULLを持つオブジェクトについては、例2-1を参照してください。

PL/SQLでのオブジェクトの操作

この項では、PL/SQLでオブジェクトの属性とメソッドを操作する方法について説明します。

この項の内容は次のとおりです。

ドット表記法によるオブジェクト属性へのアクセス

属性は名前で参照します。属性にアクセスしたり属性値を変更する際には、ドット表記法を使用します。属性名を連鎖させれば、ネストされたオブジェクト型の属性にアクセスできます。

例3-3では、ドット表記を使用して例3-2と同じ出力を生成します。

例3-3 オブジェクトの属性へのアクセス

-- Requires Ex. 3-1
DECLARE
  emp employee_typ;
BEGIN
  emp := employee_typ(315, 'Francis', 'Logan', 'FLOGAN',
        '415.555.0100', '01-MAY-04', 'SA_MAN', 11000, .15, 101, 110, 
         address_typ('376 Mission', 'San Francisco', 'CA', '94222'));
  DBMS_OUTPUT.PUT_LINE(emp.first_name || ' '  || emp.last_name);
  DBMS_OUTPUT.PUT_LINE(emp.address.street);
  DBMS_OUTPUT.PUT_LINE(emp.address.city || ', '  ||emp. address.state || ' ' ||
                       emp.address.postal_code);   
END;
/

オブジェクトのコンストラクタとメソッドに対するコール

コンストラクタは、ファンクション・コールが使用できるところであればどこからでもコールできます。例3-3および例3-4に示すように、コンストラクタはすべてのファンクションと同様、式の一部としてコールされます。

例3-4 オブジェクト表への行の挿入

-- Requires Ex. 3-1
DECLARE
  emp employee_typ;
BEGIN
  INSERT INTO employee_tab VALUES (employee_typ(310, 'Evers', 'Boston', 'EBOSTON',
   '617.555.0100', '01-AUG-04', 'SA_REP', 9000, .15, 101, 110, 
    address_typ('123 Main', 'San Francisco', 'CA', '94111')) );
  INSERT INTO employee_tab VALUES (employee_typ(320, 'Martha', 'Dunn', 'MDUNN',
    '650.555.0150', '30-SEP-04', 'AC_MGR', 12500, 0, 101, 110,
    address_typ('123 Broadway', 'Redwood City', 'CA', '94065')) );
END;
/
SELECT VALUE(e) from employee_tab e;

コンストラクタにパラメータを渡してコールした場合は、インスタンス化するオブジェクトの属性に初期値が代入されます。デフォルト・コンストラクタをコールする場合、すべての属性に値を入れるには、すべての属性に対してパラメータを指定する必要があります。定数や変数とは異なり、属性はデフォルト値を持つことができません。コンストラクタのコールには、位置表記法のかわりに名前表記法を使用することもできます。

メソッドは、パッケージ・サブプログラムと同様、ドット表記法を使用してコールします。例3-5では、オブジェクトの属性を表示するためにdisplay_addressメソッドをコールしています。オブジェクトの値を返すVALUEファンクションの使用方法に注目してください。VALUEは、相関変数を引数にとります。ここでいう相関変数とは、オブジェクト表内の行に関連付けられた行変数または表別名です。

例3-5 オブジェクト・メソッドへのアクセス

-- Requires Ex. 3-1 and Ex. 3-4
DECLARE
  emp employee_typ;
BEGIN
  SELECT VALUE(e) INTO emp FROM employee_tab e WHERE e.employee_id = 310;
  emp.display_address();
END;
/

SQL文では、パラメータのないメソッドをコールするには空のパラメータ・リストが必要です。プロシージャ文では、コールを連鎖しないかぎり空のパラメータ・リストは任意であり、連鎖する場合は、最後のコールを除くすべてのコールにパラメータ・リストが必要です。また、2つのファンクション・コールを連鎖する場合は、1つ目のファンクションが2つ目のファンクションに渡すためのオブジェクトを戻す必要があります。

DML操作中にADTコンストラクタのかわりにPL/SQLファンクションが使用される場合は、ファンクションがDML実行の一部として複数回実行されることがあります。ファンクションを出現ごとに1回のみ実行するには、決定的ファンクションにする必要があります。

静的メソッドをコールする場合は、型のインスタンスを指定するのではなく、表記法type_name.method_nameを使用します。

サブタイプのインスタンスを使用してメソッドをコールする場合、型階層内のどのメソッドが実際に実行されるかは、宣言の内容によって異なります。サブタイプがスーパータイプから継承したメソッドをオーバーライドしている場合は、サブタイプの実装が使用されます。それ以外の場合は、スーパータイプの実装が使用されます。この機能を動的メソッド・ディスパッチといいます。

オブジェクトの更新および削除

PL/SQLブロック内から、オブジェクト表の行を変更または削除できます。

例3-6 オブジェクト表の行の更新および削除

-- Requires Ex. 3-1 and 3-4
DECLARE
  emp employee_typ;
BEGIN
  INSERT INTO employee_tab VALUES (employee_typ(370, 'Robert', 'Myers', 'RMYERS',
   '415.555.0150', '07-NOV-04', 'SA_REP', 8800, .12, 101, 110, 
    address_typ('540 Fillmore', 'San Francisco', 'CA', '94011')) );
  UPDATE employee_tab e SET e.address.street = '1040 California' 
     WHERE e.employee_id = 370;
  DELETE FROM employee_tab e WHERE e.employee_id = 310;
END;
/
SELECT VALUE(e) from employee_tab e;

REF修飾子によるオブジェクトの操作

REFの取得には、REFファンクションを使用できます。REFファンクションは、相関変数または別名を引数にとります。

例3-7 REF修飾子を使用したオブジェクト表内の行の更新

-- Requires Ex. 3-1, 3-4, and 3-6
DECLARE
  emp         employee_typ;
  emp_ref REF employee_typ;
BEGIN
  SELECT REF(e) INTO emp_ref FROM employee_tab e WHERE e.employee_id = 370;
  UPDATE employee_tab e 
    SET e.address = address_typ('8701 College', 'Oakland', 'CA', '94321')
    WHERE REF(e) = emp_ref;
END;
/

REFは、変数、パラメータ、フィールドまたは属性として宣言できます。REFはSQLデータ操作文の入力変数または出力変数として使用できます。

PL/SQLでは、REFを使用してナビゲートすることはできません。たとえば、例3-8のようなREFを使用した代入は実行できません。かわりに、DEREFファンクションを使用するか、UTL_REFパッケージをコールしてオブジェクトにアクセスしてください。REFファンクションの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

例3-8 SELECT INTO文でのDEREFの使用(間違った試行)

-- Requires Ex. 3-1, 3-4, and 3-6
DECLARE 
  emp           employee_typ;
  emp_ref   REF employee_typ;
  emp_name      VARCHAR2(50);
BEGIN
  SELECT REF(e) INTO emp_ref FROM employee_tab e WHERE e.employee_id = 370;
  -- the following assignment raises an error, not allowed in PL/SQL  emp := DEREF(emp_ref);  --  cannot use DEREF in procedural statements
  emp_name := emp.first_name || ' ' || emp.last_name;
  DBMS_OUTPUT.PUT_LINE(emp_name);
END;
/

この代入により、後述のようなエラーが発生します。

not allowed in PL/SQL
-- emp_name := emp_ref.first_name || ' ' || emp_ref.last_name;
-- emp := DEREF(emp_ref); not allowed, cannot use DEREF in procedural statements

DEREFファンクションの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

継承を使用するPL/SQLでのオーバーロードの使用

オーバーロードを使用すると、サブタイプの値を仮パラメータ、つまりスーパータイプに代入できます。この機能を代入性といいます。この項では、オーバーロードのこの局面について説明します。

代入のルール

オーバーロードされたプロシージャの複数のインスタンスがプロシージャ・コールに一致する場合、次の代入ルールによって、コールされるプロシージャが判断されます。

  • オーバーロードされたプロシージャのシグネチャの唯一の違いが、一部のパラメータが同じスーパータイプ・サブタイプ階層のオブジェクト・タイプである場合、最も近い一致が使用されます。最も近い一致とは、サブタイプとスーパータイプの間の継承の深さによって判断され、少なくともすべてのパラメータが他のオーバーロードされたインスタンスと同程度に近く、少なくとも1つのパラメータがより近いことをいいます。

  • オーバーロードされた2つのメソッドのインスタンスが一致したが、1つ目のオーバーロードされたプロシージャにおいていくつかの引数の型が近く、2つ目のプロシージャにおいて他の引数の型が近い場合、セマンティック・エラーが発生します。

  • オブジェクト型階層内でいくつかのパラメータの位置が異なり、かつ他のパラメータはデータ型が異なるために暗黙的な変換が必要になる場合も、セマンティック・エラーが発生します。

例3-9では、super_tから始まる3つのレベルを持つ型階層を作成します。パッケージには、ファンクションのオーバーロードされたインスタンスが2つ含まれ、これらのインスタンスは、型階層内の引数型の位置以外は同じです。起動では、型final_tの変数を宣言してから、オーバーロードされたファンクションをコールします。

階層内でsub_tsuper_tよりもfinal_tに近いため、実行されるファンクションのインスタンスは、sub_tパラメータを受け入れるインスタンスです。これは、代入のルールに基づいたものです。

コールするインスタンスはコンパイル時に決定されるため、渡された引数もfinal_tであったという事実は無視されることに注意してください。宣言がv super_t := final_t(1,2,3)であれば、引数がsuper_tのオーバーロードされたファンクションがコールされます。

例3-9 PL/SQLファンクションと継承の解決

CREATE OR REPLACE TYPE super_t AS OBJECT
  (n NUMBER) NOT final;
/
CREATE OR REPLACE TYPE sub_t UNDER super_t
  (n2 NUMBER) NOT final;
/
CREATE OR REPLACE TYPE final_t UNDER sub_t
  (n3 NUMBER);
/
CREATE OR REPLACE PACKAGE p IS
   FUNCTION func (arg super_t) RETURN NUMBER;
   FUNCTION func (arg sub_t) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY p IS
   FUNCTION func (arg super_t) RETURN NUMBER IS BEGIN RETURN 1; END;
   FUNCTION func (arg sub_t) RETURN NUMBER IS BEGIN RETURN 2; END;
END;
/

DECLARE
  v final_t := final_t(1,2,3);
BEGIN
  DBMS_OUTPUT.PUT_LINE(p.func(v));  -- prints 2
END;
/

例3-10では、ファンクションは型階層のメンバー・ファンクションをオーバーライドしているため、コールするインスタンスは実行時に決定されます。これは、「動的メソッドのディスパッチ」で説明されている動的メソッド・ディスパッチです。

vsuper_tのインスタンスですが、final_tの値がvに割り当てられているため、代入のルールに従ってファンクションのsub_tインスタンスがコールされます。

例3-10 PL/SQLファンクションと継承の動的解決

-- Perform the following drop commands if you created these objects in Ex. 3-9
-- DROP PACKAGE p;
-- DROP TYPE final_t;
-- DROP TYPE _sub_t;
-- DROP TYPE super_t FORCE;
CREATE OR REPLACE TYPE super_t AS OBJECT
  (n NUMBER, MEMBER FUNCTION func RETURN NUMBER) NOT final;
/
CREATE OR REPLACE TYPE BODY super_t AS
 MEMBER FUNCTION func RETURN NUMBER IS BEGIN RETURN 1; END; END;
/
CREATE TYPE sub_t UNDER super_t
  (n2 NUMBER,
   OVERRIDING MEMBER FUNCTION func RETURN NUMBER) NOT final;
/
CREATE OR REPLACE TYPE BODY sub_t AS
 OVERRIDING MEMBER FUNCTION func RETURN NUMBER IS BEGIN RETURN 2; END; END;
/
CREATE OR REPLACE TYPE final_t UNDER sub_t
  (n3 NUMBER);
/

DECLARE
  v super_t := final_t(1,2,3);
BEGIN
  DBMS_OUTPUT.PUT_LINE('answer:'|| v.func); -- prints 2
END;
/

オブジェクトでの動的SQLの使用

動的SQLは、実行時にSQL情報(表名、SQL文のフルテキスト、変数の情報など)を入力できるようにするPL/SQLの機能です。


関連項目:

『Oracle Database PL/SQL言語リファレンス』

例3-11は、動的SQLでのオブジェクトとコレクションの使用方法を示しています。この例では、最初にオブジェクト型person_typVARRAYhobbies_varを定義してから、これらの型を使用するパッケージteamsを定義しています。動的パッケージ・メソッドを実行するためのAUTHID CURRENT_USERが必要です。これがない場合、例3-12を実行すると、これらのメソッドでは権限不足エラーが発生します。

例3-11 オブジェクト型とコレクションに動的SQLを使用するパッケージ

CREATE OR REPLACE TYPE person_typ AS OBJECT (name VARCHAR2(25), age NUMBER);
/
CREATE TYPE hobbies_var AS VARRAY(10) OF VARCHAR2(25);
/
CREATE OR REPLACE PACKAGE teams 

   AUTHID CURRENT_USER AS
   PROCEDURE create_table (tab_name VARCHAR2);
   PROCEDURE insert_row (tab_name VARCHAR2, p person_typ, h hobbies_var);
   PROCEDURE print_table (tab_name VARCHAR2);
END;
/
CREATE OR REPLACE PACKAGE BODY teams AS
   PROCEDURE create_table (tab_name VARCHAR2) IS
   BEGIN
      EXECUTE IMMEDIATE 'CREATE TABLE ' || tab_name ||
                        ' (pers person_typ, hobbs hobbies_var)';
   END;
   PROCEDURE insert_row (
      tab_name VARCHAR2,
      p person_typ,
      h hobbies_var) IS
   BEGIN
      EXECUTE IMMEDIATE 'INSERT INTO ' || tab_name ||
         ' VALUES (:1, :2)' USING p, h;
   END;
   PROCEDURE print_table (tab_name VARCHAR2) IS
      TYPE  refcurtyp IS REF CURSOR;
      v_cur refcurtyp;
      p     person_typ;
      h     hobbies_var;
   BEGIN
      OPEN v_cur FOR 'SELECT pers, hobbs FROM ' || tab_name;
      LOOP
         FETCH v_cur INTO p, h;
         EXIT WHEN v_cur%NOTFOUND;
         -- print attributes of 'p' and elements of 'h'
         DBMS_OUTPUT.PUT_LINE('Name: ' || p.name || ' - Age: ' || p.age);
         FOR i IN h.FIRST..h.LAST 
         LOOP
           DBMS_OUTPUT.PUT_LINE('Hobby(' || i || '): ' || h(i));
         END LOOP;
      END LOOP;
      CLOSE v_cur;
   END;
END;
/

匿名ブロックから、TEAMSパッケージ内のプロシージャをコールすることもできます。

例3-12 TEAMSパッケージ内のプロシージャに対するコール

DECLARE
   team_name VARCHAR2(15);
BEGIN
   team_name := 'Notables';
   TEAMS.create_table(team_name);
   TEAMS.insert_row(team_name, person_typ('John', 31),
      hobbies_var('skiing', 'coin collecting', 'tennis'));
   TEAMS.insert_row(team_name, person_typ('Mary', 28),
      hobbies_var('golf', 'quilting', 'rock climbing', 'fencing'));
   TEAMS.print_table(team_name);
END;
/