識別子の有効範囲と可視性

識別子の有効範囲とは、その識別子の参照が可能な、PL/SQLユニットの領域です。識別子の可視性とは、修飾なしでその識別子の参照が可能な、PL/SQLユニットの領域です。識別子は、自身が宣言されているPL/SQLユニットに対してローカルです。そのユニットにサブユニットがある場合、識別子はサブユニットに対してグローバルです。

サブユニットでグローバル識別子が再宣言されると、そのサブユニット内では両方の識別子が有効範囲内にあることになりますが、ローカル識別子のみが表示されます。サブユニットでグローバル識別子を参照するには、グローバル識別子が宣言されているユニットの名前でグローバル識別子を修飾する必要があります。そのユニットに名前がない場合、サブユニットでグローバル識別子を参照することはできません。

PL/SQLユニットは、あるユニットから同じレベルの他のユニットで宣言されている識別子への参照はできません。そのような識別子は、そのブロックに対してローカルでもグローバルでもないためです。

同じPL/SQLユニット内で、同じ識別子を2回宣言することはできません。そうすると、重複した識別子を参照するときにエラーが発生します。

2つの異なるユニットであれば、同じ識別子を宣言できます。その識別子で表される2つのオブジェクトは区別されます。片方を変更しても、もう片方には影響しません。

同じ有効範囲内では、混乱や予期しない結果を回避するためにラベルとサブプログラムに一意の名前を付けます。

例3-17 識別子の有効範囲と可視性

この例では、いくつかの識別子の有効範囲と可視性を示します。最初のサブブロックで、グローバル識別子aが再宣言されています。グローバル変数aを参照するには、最初のサブブロックで、外側のブロックの名前を使用して変数を修飾する必要がありますが、外側のブロックには名前がありません。そのため、最初のサブブロックではグローバル変数aを参照することはできず、参照できるのはローカル変数aのみです。サブブロックは同じレベルにあるため、最初のサブブロックはdを参照できず、2番目のサブブロックはcを参照できません。

-- Outer block:
DECLARE
  a CHAR;  -- Scope of a (CHAR) begins
  b REAL;    -- Scope of b begins
BEGIN
  -- Visible: a (CHAR), b
  
  -- First sub-block:
  DECLARE
    a INTEGER;  -- Scope of a (INTEGER) begins
    c REAL;       -- Scope of c begins
  BEGIN
    -- Visible: a (INTEGER), b, c
    NULL;
  END;          -- Scopes of a (INTEGER) and c end

  -- Second sub-block:
  DECLARE
    d REAL;     -- Scope of d begins
  BEGIN
    -- Visible: a (CHAR), b, d
    NULL;
  END;          -- Scope of d ends

-- Visible: a (CHAR), b
END;            -- Scopes of a (CHAR) and b end
/

例3-18 ブロック・ラベルによる、再宣言されたグローバル識別子の修飾

この例では、外側のブロックにouterという名前のラベルを付けています。そのため、サブブロックでは、グローバル変数birthdateを再宣言した後、ブロックのラベルを使用して変数名を修飾することで、このグローバル変数を参照できます。サブブロックでは、単純名によってローカル変数birthdateも参照できます。

<<outer>>  -- label
DECLARE
  birthdate DATE := TO_DATE('09-AUG-70', 'DD-MON-YY');
BEGIN
  DECLARE
    birthdate DATE := TO_DATE('29-SEP-70', 'DD-MON-YY');
  BEGIN
    IF birthdate = outer.birthdate THEN
      DBMS_OUTPUT.PUT_LINE ('Same Birthday');
    ELSE
      DBMS_OUTPUT.PUT_LINE ('Different Birthday');
    END IF;
  END;
END;
/
 

結果:

Different Birthday

例3-19 サブプログラム名による識別子の修飾

この例では、プロシージャcheck_creditで、変数ratingとファンクションcheck_ratingを宣言しています。ファンクションで変数を再宣言します。その後、ファンクションはグローバル変数をプロシージャ名で修飾して参照します。

CREATE OR REPLACE PROCEDURE check_credit (credit_limit NUMBER) AS
  rating NUMBER := 3;
  
  FUNCTION check_rating RETURN BOOLEAN IS
    rating  NUMBER := 1;
    over_limit  BOOLEAN;
  BEGIN
    IF check_credit.rating <= credit_limit THEN  -- reference global variable
      over_limit := FALSE;
    ELSE
      over_limit := TRUE;
      rating := credit_limit;                    -- reference local variable
    END IF;
    RETURN over_limit;
  END check_rating;
BEGIN
  IF check_rating THEN
    DBMS_OUTPUT.PUT_LINE
      ('Credit rating over limit (' || TO_CHAR(credit_limit) || ').  '
      || 'Rating: ' || TO_CHAR(rating));
  ELSE
    DBMS_OUTPUT.PUT_LINE
      ('Credit rating OK.  ' || 'Rating: ' || TO_CHAR(rating));
  END IF;
END;
/
 
BEGIN
  check_credit(1);
END;
/
 

結果:

Credit rating over limit (1).  Rating: 3

例3-20 同じ有効範囲での重複する識別子

同じPL/SQLユニット内で、同じ識別子を2回宣言することはできません。この例に示すとおり、これを行うと、重複する識別子を参照したときにエラーが発生します。

DECLARE
  id  BOOLEAN;
  id  VARCHAR2(5);  -- duplicate identifier
BEGIN
  id := FALSE;
END;
/
 

結果:

  id := FALSE;
  *
ERROR at line 5:
ORA-06550: line 5, column 3:
PLS-00371: at most one declaration for 'ID' is permitted
ORA-06550: line 5, column 3:
PL/SQL: Statement ignored

例3-21 異なるユニットでの同じ識別子の宣言

2つの異なるユニットであれば、同じ識別子を宣言できます。その識別子で表される2つのオブジェクトは区別されます。この例に示すとおり、1つを変更しても、もう1つに影響はありません。同じ有効範囲内では、混乱や予期しない結果を回避するためにラベルとサブプログラムに一意の名前を付けます。

DECLARE
  PROCEDURE p
  IS
    x VARCHAR2(1);
  BEGIN
    x := 'a';  -- Assign the value 'a' to x
    DBMS_OUTPUT.PUT_LINE('In procedure p, x = ' || x);
  END;
 
  PROCEDURE q
  IS
    x VARCHAR2(1);
  BEGIN
    x := 'b';  -- Assign the value 'b' to x
    DBMS_OUTPUT.PUT_LINE('In procedure q, x = ' || x);
  END;
 
BEGIN
  p;
  q;
END;
/

結果:

In procedure p, x = a
In procedure q, x = b

例3-22 同じ有効範囲内で同じ名前を持つラベルとサブプログラム

この例では、echoがブロックとサブプログラムの両方の名前になっています。ブロックおよびサブプログラムの両方で、xという変数を宣言しています。サブプログラム内では、echo.xは、グローバル変数xではなく、ローカル変数xを参照します。

<<echo>>
DECLARE
  x  NUMBER := 5;
  
  PROCEDURE echo AS
    x  NUMBER := 0;
  BEGIN
    DBMS_OUTPUT.PUT_LINE('x = ' || x);
    DBMS_OUTPUT.PUT_LINE('echo.x = ' || echo.x);
  END;
 
BEGIN
  echo;
END;
/
 

結果:

x = 0
echo.x = 0

例3-23 複数の重複したラベルを使用するブロック

この例では、外側のブロックに対して2つのラベルcompute_ratioanother_labelが存在します。2番目のラベルは、内側のブロックに再度現れます。内側のブロック内では、another_label.denominatorはローカル変数denominatorを参照し、グローバル変数denominatorを参照しないため、ZERO_DIVIDEエラーが発生します。

<<compute_ratio>>
<<another_label>>
DECLARE
  numerator   NUMBER := 22;
  denominator NUMBER := 7;
BEGIN
  <<another_label>>
  DECLARE
    denominator NUMBER := 0;
  BEGIN
    DBMS_OUTPUT.PUT_LINE('Ratio with compute_ratio.denominator = ');
    DBMS_OUTPUT.PUT_LINE(numerator/compute_ratio.denominator);
 
    DBMS_OUTPUT.PUT_LINE('Ratio with another_label.denominator = ');
    DBMS_OUTPUT.PUT_LINE(numerator/another_label.denominator);
 
  EXCEPTION
    WHEN ZERO_DIVIDE THEN
      DBMS_OUTPUT.PUT_LINE('Divide-by-zero error: can''t divide '
        || numerator || ' by ' || denominator);
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('Unexpected error.');
  END another_label;
END compute_ratio;
/
 

結果:

Ratio with compute_ratio.denominator =
3.14285714285714285714285714285714285714
Ratio with another_label.denominator =
Divide-by-zero error: cannot divide 22 by 0