この章では、PL/SQLでのオブジェクト型の使用方法を説明します。
内容は次のとおりです。
PL/SQLのブロック、サブプログラムまたはパッケージでオブジェクト型を使用する場合、2段階の操作を行います。
SQL*Plusまたはそれと同種のプログラムでは、CREATE TYPE
SQL文を使用してオブジェクト型を定義する必要があります。
オブジェクト型がスキーマに定義およびインストールされたら、その型はすべてのPL/SQLブロック、サブプログラムまたはパッケージにおいて使用できます。
その後、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;
オブジェクトとADTは、CHAR
やNUMBER
などの組込み型が使用できるところであればどこでも使用できます。
例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 ...
ユーザー定義型は、コレクションと同様、オブジェクト型のコンストラクタをコールしてオブジェクトを初期化するまではアトミックNULLです。つまり、オブジェクトの属性のみではなく、オブジェクト自体がNULLになります。
NULLオブジェクトを他の任意のオブジェクトと比較すると、結果は常にNULL
になります。また、アトミックNULLのオブジェクトを別のオブジェクトに代入した場合は、そのオブジェクトもアトミックNULLになります(再初期化する必要があります)。同様に、値のないNULL
をオブジェクトに代入した場合も、オブジェクトはアトミックNULLになります。
式の中では、未初期化オブジェクトの属性はNULL
として評価されます。IS
NULL
比較演算子は、未初期化オブジェクトまたはその属性に適用された場合、TRUE
を返します。
NULLオブジェクト、およびNULLを持つオブジェクトについては、例2-1を参照してください。
この項では、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ファンクションは、相関変数または別名を引数にとります。
例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言語リファレンス』を参照してください。
オーバーロードを使用すると、サブタイプの値を仮パラメータ、つまりスーパータイプに代入できます。この機能を代入性といいます。この項では、オーバーロードのこの局面について説明します。
代入のルール
オーバーロードされたプロシージャの複数のインスタンスがプロシージャ・コールに一致する場合、次の代入ルールによって、コールされるプロシージャが判断されます。
オーバーロードされたプロシージャのシグネチャの唯一の違いが、一部のパラメータが同じスーパータイプ・サブタイプ階層のオブジェクト・タイプである場合、最も近い一致が使用されます。最も近い一致とは、サブタイプとスーパータイプの間の継承の深さによって判断され、少なくともすべてのパラメータが他のオーバーロードされたインスタンスと同程度に近く、少なくとも1つのパラメータがより近いことをいいます。
オーバーロードされた2つのメソッドのインスタンスが一致したが、1つ目のオーバーロードされたプロシージャにおいていくつかの引数の型が近く、2つ目のプロシージャにおいて他の引数の型が近い場合、セマンティック・エラーが発生します。
オブジェクト型階層内でいくつかのパラメータの位置が異なり、かつ他のパラメータはデータ型が異なるために暗黙的な変換が必要になる場合も、セマンティック・エラーが発生します。
例3-9では、super_t
から始まる3つのレベルを持つ型階層を作成します。パッケージには、ファンクションのオーバーロードされたインスタンスが2つ含まれ、これらのインスタンスは、型階層内の引数型の位置以外は同じです。起動では、型final_t
の変数を宣言してから、オーバーロードされたファンクションをコールします。
階層内でsub_t
はsuper_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では、ファンクションは型階層のメンバー・ファンクションをオーバーライドしているため、コールするインスタンスは実行時に決定されます。これは、「動的メソッドのディスパッチ」で説明されている動的メソッド・ディスパッチです。
v
はsuper_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文のフルテキスト、変数の情報など)を入力できるようにするPL/SQLの機能です。
関連項目: 『Oracle Database PL/SQL言語リファレンス』 |
例3-11は、動的SQLでのオブジェクトとコレクションの使用方法を示しています。この例では、最初にオブジェクト型person_typ
とVARRAY
型hobbies_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; /