この付録では、潜在的に意味の曖昧なプロシージャ文およびSQL文で、名前への参照をPL/SQLがどのように解決するかについて説明します。
ここでのトピック:
コンパイルの際に、PL/SQLコンパイラは、PL/SQLサブプログラムの各名前と関連付けるオブジェクトを判別します。 名前は、ローカル変数、表、パッケージ、サブプログラム、スキーマなどを参照する場合があります。 オブジェクトが作成または削除されている場合、サブプログラムが再コンパイルされると、その関連付けが変更されることがあります。
内部有効範囲における宣言または定義で、外部有効範囲における別の宣言または定義が隠される可能性があります。 PL/SQLの名前では大/小文字が区別されないため、例B-1では、client変数の宣言によってClientデータ型の定義が隠されています。
例B-1 グローバル変数名およびローカル変数名の解決
BEGIN
<<block1>>
DECLARE
TYPE Client IS RECORD (
first_name VARCHAR2(20), last_name VARCHAR2(25));
TYPE Customer IS RECORD (
first_name VARCHAR2(20), last_name VARCHAR2(25));
BEGIN
DECLARE
client Customer;
-- hides definition of type Client in outer scope
-- lead1 Client;
-- not allowed; Client resolves to the variable client
lead2 block1.Client;
-- OK; refers to type Client
BEGIN
-- no processing, just an example of name resolution
NULL;
END;
END;
END;
/
ブロック・ラベルblock1で参照を修飾すると、Clientデータ型を参照できます。
次のCREATE TYPE文では、2つ目の文によって警告が生成されます。 managerという名前の属性を作成すると、managerという名前の型が隠されるため、2つ目の属性の宣言は適切に実行されません。
CREATE TYPE manager AS OBJECT (dept NUMBER);
/
CREATE TYPE person AS OBJECT (manager NUMBER, mgr manager)
-- raises a warning;
/
名前解決の際、コンパイラは、単なる未修飾の名前、ドットで区切られて連鎖した識別子、コレクションの索引付きのコンポーネントなど、様々な種類の参照に遭遇する可能性があります。 例B-2を参照してください。
例B-2 ドット表記法を使用した名前の修飾
CREATE OR REPLACE PACKAGE pkg1 AS
m NUMBER;
TYPE t1 IS RECORD (a NUMBER);
v1 t1;
TYPE t2 IS TABLE OF t1 INDEX BY PLS_INTEGER;
v2 t2;
FUNCTION f1 (p1 NUMBER) RETURN t1;
FUNCTION f2 (q1 NUMBER) RETURN t2;
END pkg1;
/
CREATE OR REPLACE PACKAGE BODY pkg1 AS
FUNCTION f1 (p1 NUMBER) RETURN t1 IS
n NUMBER;
BEGIN
-- (1) unqualified name
n := m;
-- (2) dot-separated chain of identifiers
-- (package name used as scope qualifier
-- followed by variable name)
n := pkg1.m;
-- (3) dot-separated chain of identifiers
-- (package name used as scope
-- qualifier followed by function name
-- also used as scope qualifier
-- followed by parameter name)
n := pkg1.f1.p1;
-- (4) dot-separated chain of identifiers
-- (variable name followed by
-- component selector)
n := v1.a;
-- (5) dot-separated chain of identifiers
-- (package name used as scope
-- qualifier followed by variable name
-- followed by component selector)
n := pkg1.v1.a;
-- (6) indexed name followed by component selector
n := v2(10).a;
-- (7) function call followed by component selector
n := f1(10).a;
-- (8) function call followed by indexing followed by
-- component selector
n := f2(10)(10).a;
-- (9) function call (which is a dot-separated
-- chain of identifiers, including schema name used
-- as scope qualifier followed by package name used
-- as scope qualifier followed by function name)
-- followed by component selector of the returned
-- result followed by indexing followed by component selector
n := hr.pkg1.f2(10)(10).a;
-- (10) variable name followed by component selector
v1.a := p1;
RETURN v1;
END f1;
FUNCTION f2 (q1 NUMBER) RETURN t2 IS
v_t1 t1;
v_t2 t2;
BEGIN
v_t1.a := q1;
v_t2(1) := v_t1;
RETURN v_t2;
END f2;
END pkg1;
/
ファンクション本体で宣言されているプライベート変数への外部参照は、無効です。 たとえば、ファンクションf2のhr.pkg1.f1.nなど、ファンクションf1で宣言されている変数nへの外部参照によって例外が発生します。 「PL/SQLパッケージのプライベート項目とパブリック項目」を参照してください。
ドット表記法は、レコード・フィールド、オブジェクト属性、およびパッケージや他のスキーマ内の項目を識別するために使用します。 これらの項目を組み合せる際、複数レベルのドットを含む式を使用する必要がある場合がありますが、その場合は各ドットが示すものがわかりにくくなることもあります。 組合せの例を次に示します。
ファンクションの戻り値のフィールドまたは属性の例:
func_name().field_name func_name().attribute_name
別のスキーマが所有するスキーマ・オブジェクトの例:
schema_name.table_name schema_name.procedure_name() schema_name.type_name.member_name()
別のユーザーが所有するパッケージ・オブジェクトの例:
schema_name.package_name.procedure_name() schema_name.package_name.record_name.field_name
オブジェクト型を含むレコードの例:
record_name.field_name.attribute_name record_name.field_name.member_name()
PL/SQLとSQLの名前解決ルールはよく似ています。 取得回避規則に従っている場合は、いくつかの違いは回避できます。 互換性のため、SQLルールはPL/SQLと比較して、より許容性が高くなっています。 そのほとんどが状況依存なSQLルールでは、PL/SQLルールで認識されるよりも多くの状況とDML文が、有効なものと認識されます。
PL/SQLコンパイラがDML文などのSQL文を処理する場合、PL/SQLでは、SQLと同じ名前解決ルールが使用されます。 たとえば、HR.JOBSという名前の場合、SQLでは、まずHRスキーマでオブジェクトが検索され、次に現行スキーマでパッケージ、型、表およびビューが検索されます。
PL/SQLでは、代入やサブプログラムのコールなどのPL/SQL文の名前は、異なる順序で解決されます。 HR.JOBSという名前の場合、PL/SQLでは、まず現行スキーマでHRという名前のパッケージ、型、表およびビューが検索され、次にHRスキーマでオブジェクトが検索されます。
SQLネーミング規則の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
別の有効範囲における宣言または型の定義が参照の正常な解決の妨げになる場合、その宣言または定義が参照を「取得する」と呼びます。 通常、取得は移行またはスキーマのアップグレードの結果として行われます。 取得には、内部、同一有効範囲および外部の3種類があります。 内部および同一有効範囲の取得はSQLスコープにのみ適用されます。
ここでのトピック:
内部取得が発生するのは、内部有効範囲に含まれる名前が外部有効範囲のエンティティを参照しておらず、次のような状態が発生した場合です。
内部有効範囲に含まれるエンティティに名前が解決される場合。
識別子の一部が内部有効範囲に取得され、参照を完全に解決することができず、プログラムでエラーが発生する場合。
参照が別の有効な名前を指す場合、プログラムが目的と異なる動作を行う理由を認識できない可能性があります。
次の例では、内側のSELECT文におけるcol2への参照は、表tab2がcol2という名前の列を持たないため、表tab1の列col2にバインドされます。
CREATE TABLE tab1 (col1 NUMBER, col2 NUMBER);
INSERT INTO tab1 VALUES (100, 10);
CREATE TABLE tab2 (col1 NUMBER);
INSERT INTO tab2 VALUES (100);
CREATE OR REPLACE PROCEDURE proc AS
CURSOR c1 IS SELECT * FROM tab1
WHERE EXISTS (SELECT * FROM tab2 WHERE col2 = 10);
BEGIN
NULL;
END;
/
この例で、次に示すように、表tab2に列col2を追加した場合を考えます。
ALTER TABLE tab2 ADD (col2 NUMBER);
この場合には、プロシージャprocは無効となり、次に使用する際に、自動的に再コンパイルされます。 ただし、再コンパイルの際に、tab2は内部有効範囲にあるため、内側のSELECT文のcol2はtab2の列col2にバインドされます。 したがって、表tab2への列col2の追加によって、col2への参照は取得されます。
コレクションやオブジェクト型を使用することによって、さらに多くの内部取得が発生する可能性があります。 次の例では、hr.tab2.aへの参照は、問合せの外部有効範囲で参照することのできる表の別名hrを経由して、表tab1の列tab2の属性aに解決されます。
CREATE TYPE type1 AS OBJECT (a NUMBER); / CREATE TABLE tab1 (tab2 type1); INSERT INTO tab1 VALUES ( type1(10) ); CREATE TABLE tab2 (x NUMBER); INSERT INTO tab2 VALUES ( 10 ); -- in the following, -- alias tab1 with same name as schema name, -- which is not a good practice -- but is used here for illustration purpose -- note lack of alias in second SELECT SELECT * FROM tab1 hr WHERE EXISTS (SELECT * FROM hr.tab2 WHERE x = hr.tab2.a);
この例で、内側の副問合せに現れる表hr.tab2に列名aを追加することを考えます。 問合せが処理されると、hr.tab2.aへの参照がスキーマhr内の表tab2の列aに解決されるため、内部取得が発生します。 内部取得を防止するには、「DML文の内部取得の回避」で説明するルールに従います。 これらのルールに従って、この問合せを次のように書き換えます。
SELECT * FROM hr.tab1 p1 WHERE EXISTS (SELECT * FROM hr.tab2 p2 WHERE p2.x = p1.tab2.a);
SQLスコープで、同一有効範囲の取得が発生するのは、結合に使用される2つの表のどちらかに列が追加され、どちらの表にも同じ列名が存在する場合です。 結合問合せのその列名を事前に参照できます。 エラーを回避するには、列名を表名で修飾する必要があります。
外部取得が発生するのは、過去に内部有効範囲内のエンティティに解決されていた内部有効範囲内の名前が、外部有効範囲に解決された場合です。 SQLとPL/SQLは、外部取得を防止する設計になっています。 この状態を回避するためのアクションは必要ありません。
次のルールを遵守することによって、DML文における内部取得を防止できます。
DML文内の各表に対して別名を指定します。
DML文の全体を通じて、表の別名を一意に保ちます。
問合せ内で使用されているスキーマ名と一致する表の別名の使用を避けます。
列の参照を表の別名で修飾します。
文がユーザー定義のオブジェクト型の列を持つ表を参照している場合には、schema_name.table_nameで参照を修飾しても内部取得は防止できません。
ユーザー定義のオブジェクト型の列によって、より多くの内部取得が発生する可能性があります。 問題を最小限に抑えるため、表の別名の使用に関する次のルールが名前解決アルゴリズムに含まれています。
ここでのトピック:
属性およびメソッドへのすべての参照が、表の別名によって修飾されている必要があります。 表を参照する場合、その表に格納されているオブジェクトの属性やメソッドを参照するときには、表名に別名を添付する必要があります。 次の例に示すとおり、属性またはメソッドへの列修飾された参照は、その参照に接頭辞として表名が使用されている場合は使用できません。
CREATE TYPE t1 AS OBJECT (x NUMBER); / CREATE TABLE tb1 (col1 t1); BEGIN -- following inserts are allowed without an alias -- because there is no column list INSERT INTO tb1 VALUES ( t1(10) ); INSERT INTO tb1 VALUES ( t1(20) ); INSERT INTO tb1 VALUES ( t1(30) ); END; / BEGIN UPDATE tb1 SET col1.x = 10 WHERE col1.x = 20; -- error, not allowed END; / BEGIN UPDATE tb1 SET tb1.col1.x = 10 WHERE tb1.col1.x = 20; -- not allowed END; / BEGIN UPDATE hr.tb1 SET hr.tb1.col1.x = 10 WHERE hr.tb1.col1.x = 20; -- not allowed END; / BEGIN -- following allowed with table alias UPDATE hr.tb1 t set t.col1.x = 10 WHERE t.col1.x = 20; END; / DECLARE y NUMBER; BEGIN -- following allowed with table alias SELECT t.col1.x INTO y FROM tb1 t WHERE t.col1.x = 30; END; / BEGIN DELETE FROM tb1 WHERE tb1.col1.x = 10; -- not allowed END; / BEGIN -- following allowed with table alias DELETE FROM tb1 t WHERE t.col1.x = 10; END; /
行の式は、表の別名への参照として解決する必要があります。 行の式をREFおよびVALUEに渡したり、UPDATE文のSET句に行の式を使用できます。 次に例を示します。
CREATE TYPE t1 AS OBJECT (x number); / CREATE TABLE ot1 OF t1; BEGIN -- following inserts are allowed without an alias -- because there is no column list INSERT INTO ot1 VALUES ( t1(10) ); INSERT INTO ot1 VALUES ( 20 ); INSERT INTO ot1 VALUES ( 30 ); END; / BEGIN UPDATE ot1 SET VALUE(ot1.x) = t1(20) WHERE VALUE(ot1.x) = t1(10); -- not allowed END; / BEGIN -- following allowed with table alias UPDATE ot1 o SET o = (t1(20)) WHERE o.x = 10; END; / DECLARE n_ref REF t1; BEGIN -- following allowed with table alias SELECT REF(o) INTO n_ref FROM ot1 o WHERE VALUE(o) = t1(30); END; / DECLARE n t1; BEGIN -- following allowed with table alias SELECT VALUE(o) INTO n FROM ot1 o WHERE VALUE(o) = t1(30); END; / DECLARE n NUMBER; BEGIN -- following allowed with table alias SELECT o.x INTO n FROM ot1 o WHERE o.x = 30; END; / BEGIN DELETE FROM ot1 WHERE VALUE(ot1) = (t1(10)); -- not allowed END; / BEGIN -- folowing allowed with table alias DELETE FROM ot1 o WHERE VALUE(o) = (t1(20)); END; /