サブプログラムのパラメータ

サブプログラムにパラメータが含まれている場合は、起動のたびにパラメータ値を変えることができます。

ここでのトピック

サブプログラムの仮パラメータと実パラメータ

サブプログラムにパラメータを保持する場合、サブプログラムのヘッダーで仮パラメータを宣言します。仮パラメータの宣言ごとに、パラメータの名前とデータ型を指定し、オプションでそのモードとデフォルト値を指定します。サブプログラムの実行部で、仮パラメータをその名前によって参照します。

サブプログラムを起動する際に、仮パラメータに代入する値が含まれる実パラメータを指定します。対応する実パラメータと仮パラメータのデータ型には、互換性が必要です。

ノート:

次のように、制約付きサブタイプの仮パラメータを宣言できます。

DECLARE
  SUBTYPE n1 IS NUMBER(1);
  SUBTYPE v1 IS VARCHAR2(1);
 
  PROCEDURE p (n n1, v v1) IS ...

ただし、次のように、仮パラメータの宣言内に制約を含めることはできません。

DECLARE
  PROCEDURE p (n NUMBER(1), v VARCHAR2(1)) IS ...

ヒント:

混同を避けるため、仮パラメータと実パラメータには異なる名前を使用してください。

ノート:

  • 実際のパラメータ(仮パラメータのデフォルト値を含む)はどのような順序でも評価できます。プログラムにより評価の順序が決定される場合は、プログラムで決定が行われる時点で動作は定義されません。

  • サーバー間リモート・プロシージャ・コール(RPC)ではLOBパラメータは使用できません。

例9-9では、プロシージャに仮パラメータのemp_idおよびamountが含まれます。プロシージャの最初の起動では、対応する実パラメータはemp_num(値120)およびbonus(値100)です。プロシージャの2回目の起動では、実パラメータはemp_num(値120)およびmerit + bonus(値150)です。

トピック:

関連項目:

例9-9 仮パラメータと実パラメータ

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6) := 100;
  merit   NUMBER(4) := 50;

  PROCEDURE raise_salary (
    emp_id NUMBER,  -- formal parameter
    amount NUMBER   -- formal parameter
  ) IS
  BEGIN
    UPDATE employees
    SET salary = salary + amount  -- reference to formal parameter
    WHERE employee_id = emp_id;   -- reference to formal parameter
  END raise_salary;

BEGIN
  raise_salary(emp_num, bonus);          -- actual parameters

  /* raise_salary runs this statement:
       UPDATE employees
       SET salary = salary + 100
       WHERE employee_id = 120;       */

  raise_salary(emp_num, merit + bonus);  -- actual parameters

  /* raise_salary runs this statement:
       UPDATE employees
       SET salary = salary + 150
       WHERE employee_id = 120;       */
END;
/

制約付きサブタイプの仮パラメータ

仮パラメータのデータ型が制約付きサブタイプの場合、次のことに注意してください。

  • サブタイプにNOT NULL制約が含まれる場合、実パラメータはその制約を継承します。

  • サブタイプにベース型VARCHAR2が含まれる場合、実パラメータはサブタイプのサイズを継承しません。

  • サブタイプに数値ベース型が含まれる場合、実パラメータはサブタイプの範囲を継承しますが、精度または位取りは継承しません。

ノート:

ファンクションでは、RETURN datatype句で非表示仮パラメータを宣言し、RETURN value文で対応する実パラメータを指定します。したがって、datatypeが制約付きデータ型の場合は、前述の規則がvalueに適用されます(例9-11を参照)。

例9-10で、サブプログラムの実パラメータは、NOT NULL制約を継承していますが、VARCHAR2サブタイプのサイズは継承していません。

「PL/SQLの事前定義のデータ型」に示されているように、PL/SQLには、他のデータ型の制約付きサブタイプである多くの事前定義のデータ型が用意されています。たとえば、INTEGERNUMBERの制約付きサブタイプです。

SUBTYPE INTEGER IS NUMBER(38,0);

例9-11では、ファンクションに仮パラメータINTEGERと戻り値INTEGERの両方が含まれています。無名ブロックは、整数でない実パラメータを使用してファンクションを起動します。実パラメータはINTEGERの範囲を継承しますが、精度と位取りは継承せず、また実パラメータはINTEGERの範囲内にあるため、起動が成功します。同じ理由で、RETURN文は正常に非整数値を戻します。

例9-12では、ファンクションは仮パラメータを戻す前に、その仮パラメータを制約付きサブタイプINTEGERに暗黙的に変換しています。

関連項目:

制約付きサブタイプの一般情報は、「制約付きサブタイプ」を参照してください

例9-10 サブタイプからNOT NULLのみを継承する実パラメータ

DECLARE
  SUBTYPE License IS VARCHAR2(7) NOT NULL;
  n  License := 'DLLLDDD';
 
  PROCEDURE p (x License) IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(x);
  END;
 
BEGIN
  p('1ABC123456789');  -- Succeeds; size is not inherited
  p(NULL);             -- Raises error; NOT NULL is inherited
END;
/

結果:

  p(NULL);             -- Raises error; NOT NULL is inherited
    *
ERROR at line 12:
ORA-06550: line 12, column 5:
PLS-00567: cannot pass NULL to a NOT NULL constrained formal parameter
ORA-06550: line 12, column 3:
PL/SQL: Statement ignored

例9-11 サブタイプから範囲のみを継承する実パラメータと戻り値

DECLARE
  FUNCTION test (p INTEGER) RETURN INTEGER IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('p = ' || p);
    RETURN p;
  END test;
 
BEGIN
  DBMS_OUTPUT.PUT_LINE('test(p) = ' || test(0.66));
END;
/

結果:

p = .66
test(p) = .66
 
PL/SQL procedure successfully completed.

例9-12 ファンクションによる仮パラメータから制約付きサブタイプへの暗黙的な変換

DECLARE
  FUNCTION test (p NUMBER) RETURN NUMBER IS
    q INTEGER := p;  -- Implicitly converts p to INTEGER
  BEGIN
    DBMS_OUTPUT.PUT_LINE('p = ' || q);  -- Display q, not p
    RETURN q;                           -- Return q, not p
  END test;
 
BEGIN
  DBMS_OUTPUT.PUT_LINE('test(p) = ' || test(0.66));
END;
/

結果:

p = 1
test(p) = 1
 
PL/SQL procedure successfully completed.

サブプログラムのパラメータの引渡し方法

PL/SQLコンパイラには、実パラメータをサブプログラムに引き渡す方法として次の2つがあります。

  • 参照渡し

    コンパイラは、実パラメータを指すポインタをサブプログラムに渡します。実パラメータと仮パラメータは、同じメモリー位置を参照します。

  • 値渡し

    コンパイラは、実パラメータの値を対応する仮パラメータに代入します。実パラメータと仮パラメータは、異なるメモリー位置を参照します。

    コンパイラは、必要に応じて実パラメータのデータ型を仮パラメータのデータ型に暗黙的に変換します。暗黙的なデータ変換の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

    ヒント:

    (『Oracle Database SQL言語リファレンス』で説明されている理由のために)暗黙的なデータ変換を回避するには、次のいずれかの方法を使用します。

    • 実パラメータとして使用する予定の変数を、それに対応する仮パラメータと同じデータ型で宣言します(例9-13の変数xの宣言を参照)。

    • 『Oracle Database SQL言語リファレンス』で説明されているSQL変換ファンクションを使用して、実パラメータをそれに対応する仮パラメータのデータ型に明示的に変換します(例9-13の3回目のプロシージャの起動を参照)。

例9-13では、プロシージャpに、値によって渡される1つのパラメータnが含まれています。無名ブロックは、pを3回起動していますが、そのうち2回は暗黙的な変換を回避しています。

コンパイラで特定の実パラメータが渡される方法は、そのモードによって異なります(「サブプログラムのパラメータ・モード」を参照)。

例9-13 実パラメータの暗黙的な変換の回避

CREATE OR REPLACE PROCEDURE p (
  n NUMBER
) AUTHID DEFINER IS
BEGIN
  NULL;
END;
/
DECLARE
  x NUMBER      :=  1;
  y VARCHAR2(1) := '1';
BEGIN
  p(x);             -- No conversion needed
  p(y);             -- z implicitly converted from VARCHAR2 to NUMBER
  p(TO_NUMBER(y));  -- z explicitly converted from VARCHAR2 to NUMBER
END;
/

サブプログラムのパラメータ・モード

仮パラメータのモードによって、その動作が決定されます。

表9-1に、サブプログラムのパラメータ・モードの特性の概要および比較を示します。

表9-1 PL/SQLサブプログラムのパラメータ・モード

パラメータ・モード デフォルト ロール

IN

デフォルト・モード

サブプログラムに値を渡します。

OUT

指定する必要があります。

起動元に値を戻します。

IN OUT

指定する必要があります。

サブプログラムに初期値を渡し、更新された値を起動元に戻します。

表9-2 PL/SQLサブプログラムのパラメータ・モードの特性

パラメータ・モード 仮パラメータ 実パラメータ 参照渡し

IN

仮パラメータは定数のように動作します。サブプログラムが起動すると、その値は実パラメータまたはデフォルト値のいずれかの値となり、サブプログラムではその値を変更できません。

実パラメータには定数、リテラル、初期化された変数または式が使用できます。

実パラメータは参照によって渡されます。

OUT

仮パラメータはその型のデフォルト値に初期化されます。NULL以外のデフォルト値を持つレコード型を除いて、型のデフォルト値はNULLです(例9-16を参照)。

サブプログラムが起動すると、実パラメータの値にかかわらず、仮パラメータの値は初期値になります。サブプログラムで仮パラメータに値を割り当てることをお薦めします。

仮パラメータの型のデフォルト値がNULLである場合、実パラメータは、データ型がNOT NULLとして定義されていない変数である必要があります。

デフォルトでは、実パラメータは値によって渡されます。NOCOPYを指定すると、参照によって渡される場合があります。

IN OUT

仮パラメータは初期化された変数のように動作します(サブプログラムが起動すると、その値は実パラメータの値になります)。サブプログラムで値を更新することをお薦めします。

実パラメータは、変数である必要があります(通常、文字列バッファまたは数値アキュムレータです)。

デフォルトでは、実パラメータは値によって渡されます(どちらの向きに対しても)。NOCOPYを指定すると、参照によって渡される場合があります。

ヒント:

ファンクションのパラメータには、OUTおよびIN OUTを使用しないでください。ファンクションは、0(ゼロ)個以上のパラメータを取り、単一の値を戻すことが理想です。IN OUTパラメータを持つファンクションは、複数の値を戻すため、副作用が発生します。

ノート:

Oracle Database提供の多くのパッケージと型の仕様部では、次の表記法を使用して仮パラメータを宣言します。

i1 IN VARCHAR2 CHARACTER SET ANY_CS
i2 IN VARCHAR2 CHARACTER SET i1%CHARSET

独自の仮パラメータまたは実パラメータを宣言する場合は、この表記法を使用しないでください。これは、提供されているパッケージ・タイプのOracle実装のために予約されています。

OUTまたはIN OUTパラメータが渡される方法にかかわらず、次のようになります。

  • サブプログラムが正常に終了した場合、実パラメータの値が、仮パラメータに代入される最終的な値になります。(仮パラメータには、少なくとも1つの値(初期値)が代入されます。)

  • サブプログラムが例外によって終了した場合、実パラメータの値は定義されません。

  • OUTおよびIN OUT仮パラメータは任意の順序で返されます。次の例では、xおよびyの最終的な値は定義されていません。

    CREATE OR REPLACE PROCEDURE p (x OUT INTEGER, y OUT INTEGER) AS
    BEGIN
      x := 17; y := 93;
    END;
    /
    

OUTまたはIN OUTパラメータが参照によって渡される場合、実パラメータと仮パラメータは同じメモリー位置を参照します。そのため、サブプログラムによって仮パラメータの値が変更されると、その変更は実パラメータに即座に反映されます(「参照渡しパラメータによるサブプログラムのパラメータのエイリアシング」を参照)。

例9-14では、プロシージャpに2つのINパラメータ、1つのOUTパラメータおよび1つのIN OUTパラメータが含まれます。OUTパラメータとIN OUTパラメータは、値によって渡されます(デフォルト)。無名ブロックは、異なる実パラメータを使用してpを2回起動しています。各起動の前に、無名ブロックは実パラメータの値を出力します。プロシージャpは、仮パラメータの初期値を出力します。各起動の後にも、無名ブロックは実パラメータの値を出力します。

例9-15では、無名ブロックがプロシージャp(例9-14を参照)を起動していますが、その実パラメータによってpで事前定義の例外ZERO_DIVIDEが呼び出されます(pはこの例外を処理しません)。例外は無名ブロックに伝播され、無名ブロックは、ZERO_DIVIDEを処理し、pINおよびIN OUTパラメータの実パラメータが起動前に保持していた値を現在も維持していることを示します。(例外の伝播の詳細は、「例外の伝播」を参照してください。)

例9-16では、プロシージャpに3つのOUT仮パラメータがあります(NULL以外のデフォルト値を持つレコード型のxNULL以外のデフォルト値を持たないレコード型のy、およびレコードではないz)。

xyおよびzの対応する実パラメータはそれぞれ、r1r2およびsです。sは初期値を使用して宣言されます。ただし、pが起動されると、sの値はNULLに初期化されます。r1およびr2の値は、それらのレコード型のデフォルト値(それぞれabcdeNULL)に初期化されます。

例9-14 プロシージャの起動前、起動中および起動後のパラメータ値

CREATE OR REPLACE PROCEDURE p (
  a        PLS_INTEGER,  -- IN by default
  b     IN PLS_INTEGER,
  c    OUT PLS_INTEGER,
  d IN OUT BINARY_FLOAT
) AUTHID DEFINER IS
BEGIN
  -- Print values of parameters:
 
  DBMS_OUTPUT.PUT_LINE('Inside procedure p:');
 
  DBMS_OUTPUT.PUT('IN a = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(a), 'NULL'));
 
  DBMS_OUTPUT.PUT('IN b = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(b), 'NULL'));
 
  DBMS_OUTPUT.PUT('OUT c = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(c), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('IN OUT d = ' || TO_CHAR(d));
 
  -- Can reference IN parameters a and b,
  -- but cannot assign values to them.
 
  c := a+10;  -- Assign value to OUT parameter
  d := 10/b;  -- Assign value to IN OUT parameter
END;
/
DECLARE
  aa  CONSTANT PLS_INTEGER := 1;
  bb  PLS_INTEGER  := 2;
  cc  PLS_INTEGER  := 3;
  dd  BINARY_FLOAT := 4;
  ee  PLS_INTEGER;
  ff  BINARY_FLOAT := 5;
BEGIN
  DBMS_OUTPUT.PUT_LINE('Before invoking procedure p:');
 
  DBMS_OUTPUT.PUT('aa = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(aa), 'NULL'));
 
  DBMS_OUTPUT.PUT('bb = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(bb), 'NULL'));
 
  DBMS_OUTPUT.PUT('cc = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(cc), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('dd = ' || TO_CHAR(dd));
 
  p (aa, -- constant
     bb, -- initialized variable
     cc, -- initialized variable 
     dd  -- initialized variable
  );
 
  DBMS_OUTPUT.PUT_LINE('After invoking procedure p:');
 
  DBMS_OUTPUT.PUT('aa = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(aa), 'NULL'));
 
  DBMS_OUTPUT.PUT('bb = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(bb), 'NULL'));
 
  DBMS_OUTPUT.PUT('cc = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(cc), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('dd = ' || TO_CHAR(dd));
 
  DBMS_OUTPUT.PUT_LINE('Before invoking procedure p:');
 
  DBMS_OUTPUT.PUT('ee = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(ee), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('ff = ' || TO_CHAR(ff));
 
  p (1,        -- literal 
     (bb+3)*4, -- expression 
     ee,       -- uninitialized variable 
     ff        -- initialized variable
   );
 
  DBMS_OUTPUT.PUT_LINE('After invoking procedure p:');
 
  DBMS_OUTPUT.PUT('ee = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(ee), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('ff = ' || TO_CHAR(ff));
END;
/

結果:

Before invoking procedure p:
aa = 1
bb = 2
cc = 3
dd = 4.0E+000
Inside procedure p:
IN a = 1
IN b = 2
OUT c = NULL
IN OUT d = 4.0E+000
After invoking procedure p:
aa = 1
bb = 2
cc = 11
dd = 5.0E+000
Before invoking procedure p:
ee = NULL
ff = 5.0E+000
Inside procedure p:
IN a = 1
IN b = 20
OUT c = NULL
IN OUT d = 5.0E+000
After invoking procedure p:
ee = 11
ff = 5.0E-001
 
PL/SQL procedure successfully completed.

例9-15 例外処理後のOUTおよびIN OUTパラメータの値

DECLARE
  j  PLS_INTEGER  := 10;
  k  BINARY_FLOAT := 15;
BEGIN
  DBMS_OUTPUT.PUT_LINE('Before invoking procedure p:');
 
  DBMS_OUTPUT.PUT('j = ');
  DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(j), 'NULL'));
 
  DBMS_OUTPUT.PUT_LINE('k = ' || TO_CHAR(k));
 
  p(4, 0, j, k);  -- causes p to exit with exception ZERO_DIVIDE
 
EXCEPTION
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('After invoking procedure p:');
 
    DBMS_OUTPUT.PUT('j = ');
    DBMS_OUTPUT.PUT_LINE(NVL(TO_CHAR(j), 'NULL'));
 
    DBMS_OUTPUT.PUT_LINE('k = ' || TO_CHAR(k));
END;
/

結果:

Before invoking procedure p:
j = 10
k = 1.5E+001
Inside procedure p:
IN a = 4
IN b = 0
OUT c = NULL
IN OUT d = 1.5E+001
After invoking procedure p:
j = 10
k = 1.5E+001
 
PL/SQL procedure successfully completed.

例9-16 NULL以外のデフォルト値を持つレコード型のOUT仮パラメータ

CREATE OR REPLACE PACKAGE r_types AUTHID DEFINER IS
  TYPE r_type_1 IS RECORD (f VARCHAR2(5) := 'abcde');
  TYPE r_type_2 IS RECORD (f VARCHAR2(5));
END;
/
 
CREATE OR REPLACE PROCEDURE p (
  x OUT r_types.r_type_1,
  y OUT r_types.r_type_2,
  z OUT VARCHAR2) 
AUTHID CURRENT_USER IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('x.f is ' || NVL(x.f,'NULL'));
  DBMS_OUTPUT.PUT_LINE('y.f is ' || NVL(y.f,'NULL'));
  DBMS_OUTPUT.PUT_LINE('z is ' || NVL(z,'NULL'));
END;
/
DECLARE
  r1 r_types.r_type_1;
  r2 r_types.r_type_2;
  s  VARCHAR2(5) := 'fghij';
BEGIN
  p (r1, r2, s);
END;
/

結果:

x.f is abcde
y.f is NULL
z is NULL
 
PL/SQL procedure successfully completed.

サブプログラムのパラメータのエイリアシング

エイリアシングとは、同じメモリー位置に対して2つの異なる名前が存在することです。格納されている項目が複数のパスで表示可能な場合は、1つのパスでその項目を変更してからすべてのパスで変更を確認できます。

サブプログラムのパラメータのエイリアシングは、コンパイラが参照によって実パラメータを渡す場合に常に発生し、サブプログラムにカーソル変数パラメータが含まれる場合にも発生する可能性があります。

ここでのトピック

参照渡しパラメータによるサブプログラムのパラメータのエイリアシング

コンパイラが参照によって実パラメータを渡す場合、実パラメータと仮パラメータは同じメモリー位置を参照します。そのため、サブプログラムによって仮パラメータの値が変更されると、その変更は実パラメータに即座に反映されます。

コンパイラは常に参照によってINパラメータを渡しますが、サブプログラムはINパラメータに値を代入できないため、この場合のエイリアシングでは問題は発生しません。

コンパイラは、OUTまたはIN OUTパラメータにNOCOPYが指定されると、そのパラメータを参照によって渡す可能性があります。NOCOPYは単なるヒントで、コンパイラは、サブプログラムが起動されるたびに、NOCOPYに従うか無視するかを暗黙的に決定します。したがって、エイリアシングの発生の有無は起動ごとに異なる可能性があるため、サブプログラムの結果は予測不能です。たとえば:

  • 実パラメータがグローバル変数である場合、仮パラメータに対する代入は、グローバル・パラメータに反映される可能性があります(例9-17を参照)。

  • 同じ変数が2つの仮パラメータの実パラメータである場合、いずれかの仮パラメータに対する代入は、両方の仮パラメータに即座に反映される可能性があります(例9-18を参照)。

  • 実パラメータがパッケージ変数である場合、仮パラメータまたはパッケージ変数に対する代入は、仮パラメータとパッケージ変数の両方に即座に反映される可能性があります

  • サブプログラムが未処理例外を戻して終了した場合、仮パラメータに対する代入は、実パラメータに反映される可能性があります

関連項目:

コンパイラが常にNOCOPYを無視する事例は、「NOCOPY」を参照してください

例9-17では、プロシージャにIN OUT NOCOPY仮パラメータが含まれており、プロシージャはそのパラメータに値'aardvark'を代入します。無名ブロックは、グローバル変数に値'aardwolf'を代入し、そのグローバル変数をプロシージャに渡します。コンパイラがNOCOPYヒントに従う場合、グローバル変数の最終的な値は、'aardvark'です。コンパイラがNOCOPYヒントを無視する場合、グローバル変数の最終的な値は、'aardwolf'です。

例9-18では、プロシージャにINパラメータ、IN OUTパラメータおよびIN OUT NOCOPYパラメータが含まれます。無名ブロックは、3つのすべての仮パラメータに同じ実パラメータ(グローバル変数)を使用してプロシージャを起動します。プロシージャは、IN OUTパラメータの値を変更してからIN OUT NOCOPYパラメータの値を変更します。ただし、コンパイラがNOCOPYヒントに従う場合、後者の変更は、実パラメータに即座に反映されます。前者の変更は、プロシージャが正常に終了して制御が無名ブロックに戻された後に実パラメータに反映されます。

例9-17 実パラメータとしてのグローバル変数によるエイリアシング

DECLARE
  TYPE Definition IS RECORD (
    word     VARCHAR2(20),
    meaning  VARCHAR2(200)
  );

  TYPE Dictionary IS VARRAY(2000) OF Definition;

  lexicon  Dictionary := Dictionary();  -- global variable

  PROCEDURE add_entry (
    word_list IN OUT NOCOPY Dictionary  -- formal NOCOPY parameter
  ) IS
  BEGIN
    word_list(1).word := 'aardvark';
  END;

BEGIN
  lexicon.EXTEND;
  lexicon(1).word := 'aardwolf';
  add_entry(lexicon);  -- global variable is actual parameter
  DBMS_OUTPUT.PUT_LINE(lexicon(1).word);
END;
/

結果:

aardvark

例9-18 複数の仮パラメータに対する同じ実パラメータによるエイリアシング

DECLARE
  n NUMBER := 10;

  PROCEDURE p (
    n1 IN NUMBER,
    n2 IN OUT NUMBER,
    n3 IN OUT NOCOPY NUMBER
  ) IS
  BEGIN
    n2 := 20;  -- actual parameter is 20 only after procedure succeeds
    DBMS_OUTPUT.put_line(n1);  -- actual parameter value is still 10
    n3 := 30;  -- might change actual parameter immediately
    DBMS_OUTPUT.put_line(n1);  -- actual parameter value is either 10 or 30
  END;

BEGIN
  p(n, n, n);
  DBMS_OUTPUT.put_line(n);
END;
/

コンパイラがNOCOPYヒントに従う場合の結果:

10
30
20

コンパイラがNOCOPYヒントを無視する場合の結果:

10
10
30

カーソル変数パラメータによるサブプログラムのパラメータのエイリアシング

カーソル変数パラメータは、ポインタです。したがって、サブプログラムがあるカーソル変数パラメータを別のカーソル変数パラメータに代入すると、それらのパラメータは同じメモリー位置を参照します。このエイリアシングでは、意図しない結果が発生することがあります。

例9-19では、プロシージャに2つのカーソル変数パラメータ(emp_cv1およびemp_cv2)が含まれます。プロシージャは、emp_cv1をオープンし、その値(ポインタ)をemp_cv2に代入します。この場合、emp_cv1emp_cv2は、同じメモリー位置を参照します。プロシージャがemp_cv1をクローズすると、emp_cv2もクローズされます。そのため、プロシージャがemp_cv2からフェッチしようとすると、PL/SQLによって例外が呼び出されます。

例9-19 カーソル変数サブプログラム・パラメータによるエイリアシング

DECLARE
  TYPE EmpCurTyp IS REF CURSOR;
  c1 EmpCurTyp;
  c2 EmpCurTyp;

  PROCEDURE get_emp_data (
    emp_cv1 IN OUT EmpCurTyp,
    emp_cv2 IN OUT EmpCurTyp
  )
  IS
    emp_rec employees%ROWTYPE;
  BEGIN
    OPEN emp_cv1 FOR SELECT * FROM employees;
    emp_cv2 := emp_cv1;  -- now both variables refer to same location
    FETCH emp_cv1 INTO emp_rec;  -- fetches first row of employees
    FETCH emp_cv1 INTO emp_rec;  -- fetches second row of employees
    FETCH emp_cv2 INTO emp_rec;  -- fetches third row of employees
    CLOSE emp_cv1;  -- closes both variables
    FETCH emp_cv2 INTO emp_rec; -- causes error when get_emp_data is invoked
  END;
BEGIN
  get_emp_data(c1, c2);
END;
/

結果:

DECLARE
*
ERROR at line 1:
ORA-01001: cursor number is invalid or does not exist
ORA-06512: at line 19
ORA-06512: at line 22

サブプログラムのINパラメータのデフォルト値

IN仮パラメータを宣言する場合、そのパラメータにデフォルト値を指定できます。デフォルト値を持つ仮パラメータは、その対応する実パラメータがサブプログラムの起動においてオプションであるため、オプション・パラメータと呼ばれます。実パラメータが省略されると、起動によって仮パラメータにデフォルト値が代入されます。デフォルト値を持たない仮パラメータは、その対応する実パラメータがサブプログラムの起動において必須であるため、必須パラメータと呼ばれます。

実パラメータを省略しても、対応する仮パラメータの値はNULLになりません。仮パラメータの値をNULLにするには、デフォルト値または実パラメータとしてNULLを指定します。

例9-20では、プロシージャに1つの必須パラメータと2つのオプション・パラメータが含まれます。

例9-20では、プロシージャの起動によって、実パラメータが、それと対応する仮パラメータの宣言と同じ順序で指定されます(起動では位置表記法が使用されます)。位置表記法では、raise_salaryの2番目のパラメータを省略せずに、3番目のパラメータを指定します(途中のパラメータを省略するには、名前表記法または混合表記法を使用する必要があります)。詳細は、実パラメータの位置表記法、名前表記法および混合表記法を参照してください。

仮パラメータのデフォルト値には、パラメータに値を代入できる任意の式を指定できます(値およびパラメータのデータ型には互換性が必要です)。サブプログラムの起動で仮パラメータに実パラメータが指定されると、その起動ではデフォルト値が評価されません。

例9-21では、プロシージャpに、ファンクションfを起動するデフォルト値を持つパラメータが含まれます。ファンクションfは、グローバル変数の値を増分します。pが実パラメータなしで起動されると、pfを起動し、fはグローバル変数を増分します。pが実パラメータ付きで起動されると、pfを起動せず、グローバル変数の値は変化しません。

例9-22では、2つの必須パラメータが含まれるプロシージャを作成してそれを起動し、その後、3番目のオプション・パラメータを追加しています。3番目のパラメータはオプションであるため、元の起動も引き続き有効です。

例9-20 デフォルトのパラメータ値を使用したプロシージャ

DECLARE
  PROCEDURE raise_salary (
    emp_id IN employees.employee_id%TYPE,
    amount IN employees.salary%TYPE := 100,
    extra  IN employees.salary%TYPE := 50
  ) IS
  BEGIN
    UPDATE employees
    SET salary = salary + amount + extra
    WHERE employee_id = emp_id;
  END raise_salary;
 
BEGIN
  raise_salary(120);       -- same as raise_salary(120, 100, 50)
  raise_salary(121, 200);  -- same as raise_salary(121, 200, 50)
END;
/

例9-21 ファンクションによるデフォルトのパラメータ値の提供

DECLARE
  global PLS_INTEGER := 0;
 
  FUNCTION f RETURN PLS_INTEGER IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('Inside f.');
    global := global + 1;
    RETURN global * 2;
  END f;
 
  PROCEDURE p (
    x IN PLS_INTEGER := f()
  ) IS
  BEGIN  
    DBMS_OUTPUT.PUT_LINE (
      'Inside p. ' || 
      '  global = ' || global ||
      ', x = ' || x || '.'
    );
    DBMS_OUTPUT.PUT_LINE('--------------------------------');
  END p;
 
  PROCEDURE pre_p IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE (
     'Before invoking p,  global = ' || global || '.'
    );
    DBMS_OUTPUT.PUT_LINE('Invoking p.');
  END pre_p;
 
BEGIN
  pre_p;
  p();     -- default expression is evaluated
 
  pre_p;
  p(100);  -- default expression is not evaluated
 
  pre_p;
  p();     -- default expression is evaluated
END;
/

結果:

Before invoking p,  global = 0.
Invoking p.
Inside f.
Inside p.   global = 1, x = 2.
--------------------------------
Before invoking p,  global = 1.
Invoking p.
Inside p.   global = 1, x = 100.
--------------------------------
Before invoking p,  global = 1.
Invoking p.
Inside f.
Inside p.   global = 2, x = 4.
--------------------------------

例9-22 既存の起動を変更しないサブプログラム・パラメータの追加

プロシージャの作成:

CREATE OR REPLACE PROCEDURE print_name (
  first VARCHAR2,
  last VARCHAR2
) AUTHID DEFINER IS
BEGIN
  DBMS_OUTPUT.PUT_LINE(first || ' ' || last);
END print_name;
/

プロシージャの起動:

BEGIN
  print_name('John', 'Doe');
END;
/

結果:

John Doe

デフォルト値を持つ3番目のパラメータの追加:

CREATE OR REPLACE PROCEDURE print_name (
  first VARCHAR2,
  last VARCHAR2,
  mi   VARCHAR2 := NULL
) AUTHID DEFINER IS
BEGIN
  IF mi IS NULL THEN
    DBMS_OUTPUT.PUT_LINE(first || ' ' || last);
  ELSE
    DBMS_OUTPUT.PUT_LINE(first || ' ' || mi || '. ' || last);
  END IF;
END print_name;
/

プロシージャの起動:

BEGIN
  print_name('John', 'Doe');          -- original invocation
  print_name('John', 'Public', 'Q');  -- new invocation
END;
/

結果:

John Doe
John Q. Public

実パラメータの位置表記法、名前表記法および混合表記法

サブプログラムの起動時に、位置表記法、名前表記法または混合表記法のいずれかを使用して実パラメータを指定できます。表9-3に、これらの表記法の概要および比較を示します。

表9-3 PL/SQLの実パラメータの表記法

表記法 構文 オプションのパラメータ メリット デメリット

位置

仮パラメータが宣言されている順序と同じ順序で実パラメータを指定します。

後続のオプション・パラメータを省略できます。

 

実パラメータを間違った順序で指定すると(特に実パラメータがリテラルの場合)、検出が困難な問題が発生する可能性があります。

仮パラメータのリストが変更される場合、そのリストで新しい後続のオプション・パラメータのみが取得されるのでなければ、サブプログラムの起動を変更する必要があります(例9-22を参照)。

コードの明瞭性とメンテナンス性が下がります。サブプログラムに多数のパラメータがある場合はお薦めしません。

名前

次の構文を使用して、任意の順序で実パラメータを指定します。

formal => actual

formalは仮パラメータの名前で、actualは実パラメータです。

任意のオプション・パラメータを省略できます。

実パラメータを指定する場合の間違った順序はありません。

仮パラメータのリストで新しい必須パラメータが取得される場合にのみ、サブプログラムの起動を変更する必要があります。

自分以外のユーザーによって定義または管理されているサブプログラムを起動する場合に推奨されます。

 

混合

まず位置表記法を使用し、その後、残りのパラメータに名前表記法を使用します。

位置表記法では、後続のオプション・パラメータを省略できます。名前表記法では、任意のオプション・パラメータを省略できます。

必須パラメータの後にオプション・パラメータが続くサブプログラムを起動し、オプション・パラメータを数個のみ指定する必要がある場合に便利です。

位置表記法では、間違った順序で指定すると(特に実パラメータがリテラルの場合)、検出が困難な問題が発生する可能性があります。

仮パラメータのリストが変更される場合、位置表記法の変更が必要になる可能性があります。

例9-23では、プロシージャの起動で異なる表記法を使用していますが、同じ意味です。

例9-24では、異なる表記法による同等の起動を使用して、SQLのSELECT文でPL/SQLファンクションcompute_bonusを起動しています。

例9-23 無名ブロックでの異なる表記法を使用した同等な起動

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6) := 50;

  PROCEDURE raise_salary (
    emp_id NUMBER,
    amount NUMBER
  ) IS
  BEGIN
    UPDATE employees
    SET salary = salary + amount
    WHERE employee_id = emp_id;
  END raise_salary;

BEGIN
  -- Equivalent invocations:

  raise_salary(emp_num, bonus);                      -- positional notation
  raise_salary(amount => bonus, emp_id => emp_num);  -- named notation
  raise_salary(emp_id => emp_num, amount => bonus);  -- named notation
  raise_salary(emp_num, amount => bonus);            -- mixed notation
END;
/

例9-24 SELECT文での異なる表記法を使用した同等な起動

CREATE OR REPLACE FUNCTION compute_bonus (
  emp_id NUMBER,
  bonus NUMBER
) RETURN NUMBER
  AUTHID DEFINER
IS
  emp_sal NUMBER;
BEGIN
  SELECT salary INTO emp_sal
  FROM employees
  WHERE employee_id = emp_id;

  RETURN emp_sal + bonus;
END compute_bonus;
/
SELECT compute_bonus(120, 50) FROM DUAL;                   -- positional
SELECT compute_bonus(bonus => 50, emp_id => 120) FROM DUAL; -- named
SELECT compute_bonus(120, bonus => 50) FROM DUAL;           -- mixed