この付録では、PL/SQLの名前解決(識別子に対するあいまいな参照を、PL/SQLコンパイラがどのように解決するか)について説明します。
コンパイル単位内で識別子を変更(識別子の追加、名前変更または削除)すると、あいまいでない識別子参照があいまいになる可能性があります。
注意:
ストアドPL/SQLユニットのAUTHID
プロパティは、実行時にユニットによって発行されるSQL文の名前解決に影響します。詳細は、「実行者権限および定義者権限(AUTHIDプロパティ)」を参照してください。
ここでのトピック
ある名前付き項目が別の名前付き項目に属する場合は、ドット表記法を使用して「子」項目の名前を「親」項目の名前で修飾できます(場合よっては必須)。次に例を示します。
参照する項目 | 修飾に使用する必要がある項目 | 使用する構文 |
---|---|---|
レコードのフィールド |
レコードの名前 |
|
コレクションのメソッド |
コレクションの名前 |
|
疑似列 |
順序の名前 |
|
疑似列 |
順序の名前 |
|
識別子が名前付きPL/SQLユニットで宣言されている場合、次の構文を使用して単純名(宣言に含まれる名前)をユニット(ブロック、サブプログラムまたはパッケージ)の名前で修飾できます。
unit_name.simple_identifier_name
識別子を参照できない場合は、識別子の名前を修飾する必要があります(「識別子の有効範囲と可視性」を参照)。
識別子が別のスキーマに属する場合は、次の構文を使用して識別子の名前をスキーマの名前で修飾する必要があります。
schema_name.package_name
例B-1に示すとおり、単純名は複数の名前で修飾できます。
あいまいになる可能性のある修飾名の例をいくつか次に示します。
ファンクションの戻り値のフィールドまたは属性の例:
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
ADTを含むレコードの例:
record_name.field_name.attribute_name record_name.field_name.member_name()
例B-1 修飾名
CREATE OR REPLACE PACKAGE pkg1 AUTHID DEFINER 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 n := m; -- Unqualified variable name n := pkg1.m; -- Variable name qualified by package name n := pkg1.f1.p1; -- Parameter name qualified by function name, -- which is qualified by package name n := v1.a; -- Variable name followed by component name n := pkg1.v1.a; -- Variable name qualified by package name -- and followed by component name n := v2(10).a; -- Indexed name followed by component name n := f1(10).a; -- Function invocation followed by component name n := f2(10)(10).a; -- Function invocation followed by indexed name -- and followed by component name n := hr.pkg1.f2(10)(10).a; -- Schema name, package name, -- function invocation, index, component name 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; /
ローカル変数または仮パラメータのいずれかと列の両方に属する名前をSQL文で参照すると、列名が優先されます。
注意:
変数名またはパラメータ名が列名として解釈されると、データが誤って削除、変更または挿入されることがあります。
例B-2では、last_name
という名前がローカル変数と列の両方に属しています(名前は大/小文字が区別されません)。そのため、WHERE
句に含まれるlast_name
の参照は両方とも列に解決され、すべての行が削除されます。
例B-3では、変数に異なる名前を付けることで、例B-2の問題を解決しています。
例B-4では、ブロックにラベルを付け、変数名をブロック名で修飾することで、例B-2の問題を解決しています。
例B-5のファンクションdept_name
には、表DEPARTMENTS
の列名が名前になっている仮パラメータとローカル変数があります。パラメータおよび変数の名前をファンクション名で修飾して、列名と区別しています。
例B-2 列名に解釈されるために意図していない結果を招く変数名
DROP TABLE employees2; CREATE TABLE employees2 AS SELECT LAST_NAME FROM employees; DECLARE last_name VARCHAR2(10) := 'King'; BEGIN DELETE FROM employees2 WHERE LAST_NAME = last_name; DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' rows.'); END; /
結果:
Deleted 107 rows.
例B-3 異なる変数名の使用による例B-2の修正
DECLARE
v_last_name VARCHAR2(10) := 'King';
BEGIN
DELETE FROM employees2 WHERE LAST_NAME = v_last_name;
DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' rows.');
END;
/
結果:
Deleted 2 rows.
例B-4 ブロック・ラベルの使用による例B-2の修正
<<main>> DECLARE last_name VARCHAR2(10) := 'King'; BEGIN DELETE FROM employees2 WHERE last_name = main.last_name; DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' rows.'); END; /
結果:
Deleted 2 rows.
例B-5 名前解決でのサブプログラム名
DECLARE FUNCTION dept_name (department_id IN NUMBER) RETURN departments.department_name%TYPE IS department_name departments.department_name%TYPE; BEGIN SELECT department_name INTO dept_name.department_name -- ^column ^local variable FROM departments WHERE department_id = dept_name.department_id; -- ^column ^formal parameter RETURN department_name; END dept_name; BEGIN FOR item IN ( SELECT department_id FROM departments ORDER BY department_name) LOOP DBMS_OUTPUT.PUT_LINE ('Department: ' || dept_name(item.department_id)); END LOOP; END; /
結果:
Department: Accounting Department: Administration Department: Benefits Department: Construction Department: Contracting Department: Control And Credit Department: Corporate Tax Department: Executive Department: Finance Department: Government Sales Department: Human Resources Department: IT Department: IT Helpdesk Department: IT Support Department: Manufacturing Department: Marketing Department: NOC Department: Operations Department: Payroll Department: Public Relations Department: Purchasing Department: Recruiting Department: Retail Sales Department: Sales Department: Shareholder Services Department: Shipping Department: Treasury
PL/SQLとSQLの名前解決ルールはよく似ています。ただし、次のことに注意してください。
PL/SQLルールはSQLルールと比較して、より許容性が低くなっています。
ほとんどのSQLルールが状況依存であるため、PL/SQLルールで認識されるよりも多くの状況が有効なものと認識されます。
PL/SQLとSQLでは、修飾名の解決方法が異なります。
たとえば、HR
.JOBS
という表名を解決する場合は次のとおりです。
PL/SQLでは、まず現行スキーマでHR
という名前のパッケージ、型、表およびビューが検索され、次にパブリック・シノニムが検索され、最後にHR
スキーマでJOBS
という名前のオブジェクトが検索されます。
SQLでは、まずHR
スキーマでJOBS
という名前のオブジェクトが検索され、次に現行スキーマでHR
という名前のパッケージ、型、表およびビューが検索されます。
PL/SQLとSQLの名前解決ルールのわずかな相違点に起因する問題を回避するには、「SELECT文およびDML文の内部取得の回避」の推奨事項に従います。
注意:
PL/SQLコンパイラで静的SQL文を処理する場合は、その文がSQLサブシステムに送信され、そこで文に含まれる名前がSQLルールを使用してSQLサブシステムによって解決されます。詳細は、「静的SQL文における名前解決」を参照してください。
静的SQLの詳細は、「PL/SQLの静的SQL」を参照してください。
PL/SQLコンパイラが静的SQL文を見つけると、次の処理が行われます。
その文がSELECT
文の場合、PL/SQLコンパイラはINTO
句を削除します。
PL/SQLコンパイラは、その文をSQLサブシステムに送信します。
SQLサブシステムで、その文の構文がチェックされます。
構文が正しくない場合、PL/SQLユニットのコンパイルは失敗します。構文が正しい場合は、SQLサブシステムによって表の名前が決定され、そのSQL文の有効範囲でその他の名前の解決が試行されます。
そのSQL文の有効範囲で名前を解決できない場合、SQLサブシステムは、その名前をPL/SQLコンパイラに送り返します。この名前は、エスケープ識別子と呼ばれます。
PL/SQLコンパイラは、エスケープ識別子を解決しようとします。
まず、コンパイラは、PL/SQLユニットの有効範囲で識別子を解決しようとします。それが失敗すると、コンパイラは、スキーマの有効範囲で識別子を解決しようとします。それが失敗すると、PL/SQLユニットのコンパイルは失敗します。
PL/SQLユニットのコンパイルが成功すると、PL/SQLコンパイラは、静的SQL文と等価な通常のSQL文テキストを生成し、生成されたコンピュータ・コードとともにそのテキストを格納します。
実行時に、PL/SQLランタイム・システムによって、通常のSQL文を解析、バインド、実行するルーチンが起動されます。
バインド変数はエスケープ識別子です(手順 4を参照)。
この文がSELECT
文の場合、PL/SQLランタイム・システムでは、手順 1
でPL/SQLコンパイラによって削除されたINTO句に指定されたPL/SQLターゲットに結果が格納されます。
注意:
バインド変数はどのような順序でも評価できます。プログラムにより評価の順序が決定される場合は、プログラムで決定が行われる時点で動作は定義されません。
宣言または定義が、別の有効範囲における参照の正常な解決の妨げになる場合、その宣言または定義が参照を「取得する」と呼びます。通常、取得は移行またはスキーマのアップグレードの結果として行われます。
ここでのトピック
外部取得が発生するのは、内部有効範囲内の項目に解決されていた内部有効範囲内の名前が、外部有効範囲内の項目に解決される場合です。PL/SQLとSQLは両方とも、外部取得を防止する設計になっているので、外部取得の回避に慎重になる必要はありません。
同一有効範囲の取得が発生するのは、結合に使用される2つの表のどちらかに列が追加され、この新しい列の名前が他の表の列名と同じである場合です。その名前の列が1つの表にのみ存在する場合は、名前が未修飾で結合に現れる可能性があります。同一有効範囲の取得を回避するには、その列名が結合に現れるすべての場所で、列名を適切な表名で修飾する必要があります。
内部取得が発生するのは、外部有効範囲内の項目に解決されていた内部有効範囲内の名前が、内部有効範囲内の項目に解決される場合か解決できない場合のいずれかです。最初のケースでは結果が変わる可能性があります。2番目のケースではエラーが発生します。
例B-6では、新しい列は、同じ名前の古い列の参照を取得します。col2
は、新しい列col2
が表tab2
に追加される前はtab1.col2
に解決され、追加された後はtab2.col2
に解決されます。
内部取得を防止するには、「SELECT文およびDML文の内部取得の回避」のルールに従います。
例B-6 列の参照の内部取得
表tab1
にはcol2
という名前の列がありますが、表tab2
にはありません。
DROP TABLE tab1; CREATE TABLE tab1 (col1 NUMBER, col2 NUMBER); INSERT INTO tab1 (col1, col2) VALUES (100, 10); DROP TABLE tab2; CREATE TABLE tab2 (col1 NUMBER); INSERT INTO tab2 (col1) VALUES (100);
そのため、内側のSELECT
文では、col2
への参照が列tab1.col2
に解決されます。
CREATE OR REPLACE PROCEDURE proc AUTHID DEFINER AS
CURSOR c1 IS
SELECT * FROM tab1
WHERE EXISTS (SELECT * FROM tab2 WHERE col2 = 10);
BEGIN
OPEN c1;
CLOSE c1;
END;
/
表tab2
への列col2
の追加:
ALTER TABLE tab2 ADD (col2 NUMBER);
これでプロシージャproc
は無効になりました。次に起動する際に、このプロシージャはデータベースによって自動的に再コンパイルされ、内側のSELECT
文におけるcol2
への参照は、列tab2.col2
に解決されます。
次の推奨事項を遵守することによって、SELECT文、SELECT INTO
文およびDML文における参照の内部取得を防止できます。
文内の各表に対して一意の別名を指定します。
文で参照される項目を所有しているスキーマと同じ名前を表の別名に指定しないようにします。
文内の列の各参照を適切な表の別名で修飾します。
例B-7では、スキーマhr
が表tab1
およびtab2
を所有しています。表tab1
にはtab2
という名前の列があり、その抽象データ型(ADT)は属性a
を持っています。表tab2
にはa
という名前の列はありません。推奨事項に反して、この問合せでは、表tab1
に別名hr
を指定して表tab2
を参照しています。そのため、この問合せ内の参照hr.tab2.a
は、表tab1
、列tab2
、属性a
に解決されます。その後、この例では、列a
を表tab2
に追加しています。今度は、問合せ内の参照hr.tab2.a
は、スキーマhr
、表tab2
、列a
に解決されます。表tab2
の列a
は、表tab1
の列tab2
の属性a
への参照を取得します。
ここでのトピック
例B-7 属性の参照の内部取得
CREATE OR REPLACE TYPE type1 AS OBJECT (a NUMBER); / DROP TABLE tab1; CREATE TABLE tab1 (tab2 type1); INSERT INTO tab1 (tab2) VALUES (type1(10)); DROP TABLE tab2; CREATE TABLE tab2 (x NUMBER); INSERT INTO tab2 (x) VALUES (10); /* Alias tab1 with same name as schema name, a bad practice used here for illustration purpose. Note lack of alias in second SELECT statement. */ SELECT * FROM tab1 hr WHERE EXISTS (SELECT * FROM hr.tab2 WHERE x = hr.tab2.a);
結果:
TAB2(A) --------------- TYPE1(10) 1 row selected.
表tab2
(スキーマhr
に属する)への列a
の追加:
ALTER TABLE tab2 ADD (a NUMBER);
ここでこの問合せを実行すると、hr.tab2.a
は、スキーマhr
、表tab2
、列a
に解決されます。この内部取得を回避するために、問合せに推奨事項を適用します。
SELECT * FROM hr.tab1 p1 WHERE EXISTS (SELECT * FROM hr.tab2 p2 WHERE p2.x = p1.tab2.a);
表の要素の属性やメソッドを参照するには、表に別名を付け、その別名を使用して、属性やメソッドに対する参照を修飾します。
例B-8の表tbl1
には、属性x
を持つADTであるデータ型t1
の列col1
があります。この例は、tbl1.col1.x
への適切な参照と適切でない参照をいくつか示しています。
例B-8 ADT属性の参照の修飾
CREATE OR REPLACE TYPE t1 AS OBJECT (x NUMBER); / DROP TABLE tb1; CREATE TABLE tb1 (col1 t1);
次のINSERT
文の参照では、列リストがないため、別名は不要です。
BEGIN INSERT INTO tb1 VALUES ( t1(10) ); INSERT INTO tb1 VALUES ( t1(20) ); INSERT INTO tb1 VALUES ( t1(30) ); END; /
属性x
への次の参照では、エラーORA-00904が発生します。
UPDATE tb1 SET col1.x = 10 WHERE col1.x = 20; UPDATE tb1 SET tb1.col1.x = 10 WHERE tb1.col1.x = 20; UPDATE hr.tb1 SET hr.tb1.col1.x = 10 WHERE hr.tb1.col1.x = 20; DELETE FROM tb1 WHERE tb1.col1.x = 10;
属性x
への表の別名を使用した次の参照は、適切です。
UPDATE hr.tb1 t SET t.col1.x = 10 WHERE t.col1.x = 20; DECLARE y NUMBER; BEGIN SELECT t.col1.x INTO y FROM tb1 t WHERE t.col1.x = 30; END; / DELETE FROM tb1 t WHERE t.col1.x = 10;
行の式は、表の別名への参照として解決する必要があります。行の式は、UPDATE
文のSET
句で使用したり、SQLファンクションREF
またはVALUE
のパラメータにすることができます。
例B-9の表ot1
は、属性x
を持つADTであるデータ型t1
の要素のスタンドアロンのネストした表です。この例は、行の式への適切な参照と適切でない参照をいくつか示しています。
例B-9 行の式への参照の修飾
CREATE OR REPLACE TYPE t1 AS OBJECT (x number); / DROP TABLE ot1; CREATE TABLE ot1 OF t1; BEGIN INSERT INTO ot1 VALUES (t1(10)); INSERT INTO ot1 VALUES (20); INSERT INTO ot1 VALUES (30); END; /
次の参照では、エラーORA-00904が発生します。
UPDATE ot1 SET VALUE(ot1.x) = t1(20) WHERE VALUE(ot1.x) = t1(10); DELETE FROM ot1 WHERE VALUE(ot1) = (t1(10));
表の別名を使用した次の参照は、適切です。
UPDATE ot1 o SET o = (t1(20)) WHERE o.x = 10; DECLARE n_ref REF t1; BEGIN SELECT REF(o) INTO n_ref FROM ot1 o WHERE VALUE(o) = t1(30); END; / DECLARE n t1; BEGIN SELECT VALUE(o) INTO n FROM ot1 o WHERE VALUE(o) = t1(30); END; / DECLARE n NUMBER; BEGIN SELECT o.x INTO n FROM ot1 o WHERE o.x = 30; END; / DELETE FROM ot1 o WHERE VALUE(o) = (t1(20));