ヘッダーをスキップ
Oracle Database PL/SQL言語リファレンス
11g リリース1(11.1)
E05670-03
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

8 PL/SQLのサブプログラムの使用

この章では、文の集合を、再利用可能なサブプログラムとして作成する方法について説明します。サブプログラムは、メンテナンスしやすいモジュール構造のアプリケーションのビルディング・ブロックです。

ここでのトピック:

PL/SQLサブプログラムの概要

PL/SQLサブプログラムは、一連のパラメータによって起動できる名前付きPL/SQLブロックです。 サブプログラムは、プロシージャまたはファンクションのいずれかです。一般に、プロシージャはアクションを実行するために使用し、ファンクションは値を計算して戻すために使用します。

サブプログラムは、スキーマ・レベル、パッケージ内またはPL/SQLブロック内(別のサブプログラムの可能性もあります)で作成できます。

スキーマ・レベルで作成されるサブプログラムは、スタンドアロン・ストアド・サブプログラムです。 このサブプログラムは、CREATE PROCEDURE文またはCREATE FUNCTION文を使用して作成できます。 このサブプログラムは、DROP PROCEDURE文またはDROP FUNCTION文を使用して削除するまでデータベースに格納されます。

パッケージ内で作成されるサブプログラムは、パッケージ・サブプログラムです。DROP PACKAGE文を使用してパッケージを削除するまで、データベースに格納されます。

PL/SQLブロック内で作成されるサブプログラムは、ネストしたサブプログラムです。 宣言と定義を同時に行うか、または最初に宣言(前方宣言)して、後で同じブロック内で定義できます。 ネストしたサブプログラムは、スタンドアロン・サブプログラムまたはパッケージ・サブプログラム内でネストした場合にのみ、データベースに格納されます。


参照:


サブプログラムのコール

サブプログラムのコールの形式は次のとおりです。

subprogram_name [ (parameter [, parameter]... ) ]

プロシージャ・コールはPL/SQL文です。次に例を示します。

raise_salary(employee_id, amount);

ファンクション・コールは式の一部です。次に例を示します。

IF salary_ok(new_salary, new_title) THEN ...

参照:

サブプログラム・コールの詳細は、「サブプログラムのパラメータの宣言および引渡し」を参照してください。

サブプログラムを使用する理由

サブプログラムの各部

サブプログラムには常に名前があり、パラメータ・リストがある場合もあります。

すべてのPL/SQLブロックと同様に、サブプログラムにはオプションの宣言部、必須の実行部およびオプションの例外処理部があり、PRAGMA AUTONOMOUS_TRANSACTIONを指定して自律型(独立型)にすることができます。

サブプログラム以外のブロックの宣言部とは異なり、サブプログラムの宣言部は、キーワードDECLAREでは始まりません。 宣言部には、型、カーソル、定数、変数、例外およびネストしたサブプログラムの宣言が含まれています。 これらの項目は、サブプログラムに対してローカルで、そのサブプログラムの実行が完了すると消滅します。

サブプログラムの実行部には、値の代入、実行の制御およびデータの操作を実行する文が含まれています。

サブプログラムの例外処理部には、ランタイム・エラーを処理するコードが含まれています。

例8-1では、無名ブロック内でプロシージャを(同時に)宣言および定義しています。 プロシージャには、必須の実行部とオプションの例外処理部がありますが、オプションの宣言部はありません。 プロシージャは、ブロックの実行部によって起動されます。

例8-1 単純なPL/SQLプロシージャの宣言、定義および起動

-- Declarative part of block begins
DECLARE
  in_string  VARCHAR2(100) := 'This is my test string.';
  out_string VARCHAR2(200);

  -- Procedure declaration and definition begins
  PROCEDURE double (original IN VARCHAR2,
                    new_string OUT VARCHAR2)
  IS
    -- Declarative part of procedure (optional) goes here
    -- Executable part of procedure begins
    BEGIN
      new_string := original || ' + ' || original;
    -- Executable part of procedure ends
    -- Exception-handling part of procedure begins
    EXCEPTION
      WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Output buffer not long enough.');
    END;
    -- Exception-handling part of procedure ends
    -- Procedure declaration and definition ends
-- Declarative part of block ends
-- Executable part of block begins
BEGIN
  double(in_string, out_string);  -- Procedure invocation
  DBMS_OUTPUT.PUT_LINE(in_string || ' - ' || out_string);
END;
-- Executable part of block ends
/

プロシージャとファンクションの構造は、次の点を除いて同じです。

オプション 説明
DETERMINISTICオプション オプティマイザが冗長なファンクション・コールを回避するために役立ちます。
PARALLEL_ENABLEDオプション ファンクションがパラレルDML評価のスレーブ・セッションで安全に使用できるようにします。
PIPELINEDオプション テーブル・ファンクションの結果を反復的に戻します。
RESULT_CACHEオプション ファンクションの結果をPL/SQLファンクション結果キャッシュに格納します。
RESULT_CACHE ファンクションの結果のデータ・ソースを指定します。


参照:


RETURN文

RETURN文(ファンクションの戻り値のデータ型を指定するRETURN句とは異なる)は、この文を含むサブプログラムの実行を即座に終了し、コール元に制御を戻します。サブプログラム・コールの直後の文から、実行が継続されます。

サブプログラムでは、複数のRETURN文を使用できます。 サブプログラムをRETURN文で終える必要はありません。どのRETURN文を実行しても、サブプログラムは即座に終了します。

プロシージャでは、RETURN文に式を含めることはできず、値も戻されません。

ファンクションでは、RETURN文に式が含まれている必要があります。 RETURN文を実行すると、式が評価され、その値がファンクション識別子に代入されます。 ファンクション識別子は、RETURN句で指定された型の変数と同様に機能します。

ファンクションのRETURN文では、任意で複雑な式も使用できます。次に例を示します。

CREATE OR REPLACE FUNCTION half_of_square(original NUMBER)
  RETURN NUMBER IS
BEGIN
  RETURN (original * original)/2 + (original * 4);
END half_of_square;
/

ファンクションには、RETURN文へ導く少なくとも1つの実行パスが必要です。


参照:

RETURN文の構文は、「RETURN文」を参照してください。

例8-2では、無名ブロック内でファンクションを(同時に)宣言および定義しています。 ファンクションには、オプションの宣言部と必須の実行部がありますが、オプションの例外処理部はありません。 ファンクションはブロックの実行部によって起動されます。

例8-2 単純なPL/SQLファンクションの宣言、定義および起動

-- Declarative part of block begins
DECLARE
  -- Function declaration and definition begins
  FUNCTION square (original NUMBER)
    RETURN NUMBER  -- RETURN clause
  AS
  -- Declarative part of function begins
    original_squared NUMBER;
  -- Declarative part of function ends
  -- Executable part of function begins
  BEGIN
    original_squared := original * original;
    RETURN original_squared;  -- RETURN statement
  -- Exception-handling part of function (optional) goes here
  END;
  -- Executable part of function ends
  -- Function declaration and definition ends
-- Declarative part of block ends
-- Executable part of block begins
BEGIN
  DBMS_OUTPUT.PUT_LINE(square(100));  -- Function invocation
END;
-- Executable part of block ends
/

相互に起動し合うネストしたサブプログラムの作成

ブロックでは、複数のネストしたサブプログラムを作成できます。 サブプログラムは起動の前に宣言する必要があるため、ネストしたサブプログラムが相互に起動し合う場合は、前方宣言が必要です。 前方宣言を使用してサブプログラムを宣言しますが、このプログラムは、起動元となる他のサブプログラムを定義するまで定義しないでください。 前方宣言および前方宣言に対応する定義は、同じブロックに指定する必要があります。

例8-3のブロックでは、相互に起動し合う2つのプロシージャを作成しています。

例8-3 相互に起動し合うネストしたサブプログラムの作成

DECLARE
  -- Declare proc1 (forward declaration):
  PROCEDURE proc1(number1 NUMBER);

  -- Declare and define proc 2:
  PROCEDURE proc2(number2 NUMBER) IS
  BEGIN
    proc1(number2);
  END;

  -- Define proc 1:
  PROCEDURE proc1(number1 NUMBER) IS
  BEGIN
    proc2 (number1);
  END;

BEGIN
  NULL;
END;
/

サブプログラムのパラメータの宣言および引渡し

サブプログラムのヘッダーで、仮パラメータを宣言できます。 仮パラメータの各宣言で、モードおよびデフォルト値を指定できます。 サブプログラムの起動時に、サブプログラムに実パラメータを渡すことができます。

ここでのトピック:

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

仮パラメータは、サブプログラムのヘッダーで宣言され、サブプログラムの実行部で参照される変数です。実パラメータは、サブプログラムの起動時にそのサブプログラムに渡す変数または式です。 対応する仮パラメータと実パラメータのデータ型には、互換性が必要です。

例8-4に示すように、プログラミングの習慣として、仮パラメータと実パラメータには別々の名前を付けることをお薦めします。

例8-4 仮パラメータと実パラメータ

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
        WHERE employee_id = emp_id;
  END raise_salary;

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

サブプログラムを起動すると、PL/SQLによって各実パラメータが評価され、対応する仮パラメータにそれぞれの値が代入されます。 必要に応じて、PL/SQLは、代入前に、実パラメータのデータ型を対応する仮パラメータのデータ型に暗黙的に変換します(このため、対応する仮パラメータと実パラメータのデータ型に互換性が必要となります)。 暗黙的な変換の詳細は、「暗黙的な変換」を参照してください。

明示的な変換(「明示的な変換」を参照)を使用するか、または対応する仮パラメータと同じデータ型の実パラメータとして使用する変数を宣言することによって暗黙的変換を回避することは、推奨されるプログラミング方法です。たとえば、pkgに次の仕様部があるとします。

PACKAGE pkg IS
  PROCEDURE s (n IN PLS_INTEGER);
END pkg;

pkg.sを次のように起動すると、暗黙的な変換は実行されません。

DECLARE
  y PLS_INTEGER :=1;
BEGIN
  pkg.s(y);
END;

pkg.sを次のように起動すると、暗黙的な変換が実行されます。

DECLARE
  y INTEGER :=1;
BEGIN
  pkg.s(y);
END;

注意:

Oracle提供の多くのパッケージと型の仕様部では、次の表記法を使用して仮パラメータを宣言します。
i1 IN VARCHAR2 CHARACTER SET ANY_CS
i2 IN VARCHAR2 CHARACTER SET i1%CHARSET

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


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

パラメータ・モードは、仮パラメータのアクションを定義します。パラメータ・モードには、IN(デフォルト)、OUTおよびIN OUTの3つがあります。

任意のサブプログラムで任意のパラメータ・モードを使用できます。ファンクションでは、OUTモードとIN OUTモードを使用しないでください。ファンクションが複数の値を戻すようなプログラミングは、好ましくありません。また、サブプログラム専用ではない変数の値を変更する副作用がファンクションで発生しないようにしてください。

ここでのトピック:

INモードの使用

INパラメータは、起動されるサブプログラムに値を渡すために使用します。サブプログラムの中では、INパラメータは定数のように取り扱われます。値は代入できません。

定数、リテラル、初期化された変数または式をINパラメータとして渡せます。

INパラメータはデフォルト値で初期化できます。デフォルト値は、サブプログラム・コールでINパラメータが省略された場合に使用されます。 詳細は、「サブプログラムのパラメータのデフォルト値の指定」を参照してください。

OUTモードの使用

OUTパラメータは、サブプログラムのコール元に値を戻します。サブプログラムの中では、OUTパラメータは変数のように取り扱われます。 例8-5に示すように、値を変更して、代入後に値を参照できます。

例8-5 OUTモードの使用

DECLARE
  emp_num       NUMBER(6) := 120;
  bonus         NUMBER(6) := 50;
  emp_last_name VARCHAR2(25);
  PROCEDURE raise_salary (emp_id IN NUMBER, amount IN NUMBER,
                          emp_name OUT VARCHAR2) IS
    BEGIN
      UPDATE employees SET salary =
        salary + amount WHERE employee_id = emp_id;
      SELECT last_name INTO emp_name
        FROM employees
       WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  raise_salary(emp_num, bonus, emp_last_name);
  DBMS_OUTPUT.PUT_LINE
    ('Salary was updated for: ' || emp_last_name);
END;
/

OUTパラメータには変数を渡す必要があります。定数または式は渡せません。NOCOPYキーワードを指定していない場合、または未処理例外が発生してサブプログラムが正常に終了した場合、以前の値は失われます。 詳細は、「サブプログラムのパラメータのデフォルト値の指定」を参照してください。

OUTパラメータの初期値はNULLです。このため、OUTパラメータのデータ型は、組込みサブタイプNATURALNPOSITIVENなどのNOT NULLとして定義されたサブタイプにはできません。これに従わなかった場合、サブプログラムの起動時にPL/SQLはVALUE_ERRORを呼び出します。

サブプログラムを終了する前に、すべてのOUT仮パラメータに値を代入してください。そうしないと、対応する実パラメータの値はNULLになります。正常に終了した場合、PL/SQLは実パラメータに値を代入します。未処理例外が発生して終了すると、PL/SQLは実パラメータに値を代入しません。

IN OUTモードの使用

IN OUTパラメータは、サブプログラムに初期値を渡し、更新された値をコール元に戻します。IN OUTパラメータに値を代入したり、その値を読み取ることができます。通常、IN OUTパラメータは文字列バッファまたは数値アキュムレータであり、サブプログラム内で読み取られた後に更新されます。

IN OUT仮パラメータに対応する実パラメータは、定数や式ではなく、変数である必要があります。

サブプログラムを正常に終了した場合、PL/SQLは実パラメータに値を代入します。未処理例外が発生して終了すると、PL/SQLは実パラメータに値を代入しません。

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

表8-1に、パラメータ・モードの特性の概要を示します。

表8-1 パラメータのモード

IN OUT IN OUT

デフォルト

指定する必要がある

指定する必要がある

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

コール元に値を戻す

サブプログラムに初期値を渡し、更新された値をコール元に戻す

仮パラメータは定数のように取り扱われる

仮パラメータは初期化されてない変数のように取り扱われる

仮パラメータは初期化された変数のように取り扱われる

仮パラメータに値を代入できない

仮パラメータには値を代入する必要がある

仮パラメータには値を代入する必要がある

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

実パラメータは変数である必要がある

実パラメータは変数である必要がある

実パラメータは参照方式によって渡される(コール元がサブプログラムに値を指すポインタを渡す)

NOCOPYが指定されていない場合、実パラメータは値方式によって渡される(サブプログラムがコール元に値のコピーを渡す)

NOCOPYが指定されていない場合、実パラメータは値方式によって渡される(コール元がサブプログラムに値のコピーを渡し、サブプログラムがコール元に値のコピーを渡す)


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

IN仮パラメータをデフォルト値に初期化することで、サブプログラムに様々な数の実パラメータを渡し、省略された実パラメータにデフォルト値を指定できます。さらに、サブプログラムへのコールを個々に変更しなくても、仮パラメータを新しく追加できます。

実パラメータが省略されると、対応する仮パラメータのデフォルト値が使用されます。

実パラメータを省略して、仮パラメータを1つスキップすることはできません。 最初のパラメータを省略して2番目のパラメータを指定するには、名前表記法を使用します(「位置表記法、名前表記法または混合表記法でのサブプログラムの実パラメータの引渡し」を参照)。

実パラメータを省略して、初期化されていない仮パラメータにNULLを代入することはできません。デフォルト値としてNULLを代入するか、または明示的にNULLを渡す必要があります。

例8-6に、サブプログラム・パラメータのデフォルト値の使用法を示します。

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

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6);
  merit   NUMBER(4);
  PROCEDURE raise_salary (emp_id IN NUMBER,
    amount IN NUMBER DEFAULT 100,
                          extra IN NUMBER DEFAULT 50) IS
    BEGIN
      UPDATE employees SET salary = salary + amount + extra
        WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  -- Same as raise_salary(120, 100, 50)
  raise_salary(120);
  -- Same as raise_salary(120, 100, 25)
  raise_salary(emp_num, extra => 25);
END;
/

例8-7に示すように、仮パラメータのデフォルト値が式である場合、サブプログラムの起動時に対応する実パラメータを指定しても、その式は評価されません。

例8-7 デフォルト値として式が指定されている仮パラメータ

DECLARE
  cnt pls_integer := 0;
  FUNCTION dflt RETURN pls_integer IS
  BEGIN
    cnt := cnt + 1;
    RETURN 42;
  END dflt;
  -- Default is expression
  PROCEDURE p(i IN pls_integer DEFAULT dflt()) IS
     BEGIN
       DBMS_Output.Put_Line(i);
  END p;
BEGIN
  FOR j IN 1..5 LOOP
    p(j);  -- Actual parameter is provided
  END loop;
  DBMS_Output.Put_Line('cnt: '||cnt);
  p();  -- Actual parameter is not provided
  DBMS_Output.Put_Line('cnt: '||cnt);
END;

例8-7の出力は次のようになります。

1
2
3
4
5
Cnt: 0
42
Cnt: 1

位置表記法、名前表記法または混合表記法でのサブプログラムの実パラメータの引渡し

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

表8-2 PL/SQLサブプログラムのパラメータの表記法

表記法 説明 使用上の注意

位置

プロシージャで宣言されている順序と同じ順序で同じパラメータを指定します。

簡潔で読みやすいですが、次のデメリットがあります。

  • パラメータ(特にリテラル)を間違った順序で指定すると、不具合の検出が困難になります。

  • プロシージャのパラメータ・リストが変更された場合は、コードを変更する必要があります。

名前

結合演算子=>を使用して、各パラメータの名前と値を指定します。パラメータの順序は重要ではありません。

位置表記法と比較すると、冗長ですが、読みやすく、メンテナンスも簡単です。(たとえば、パラメータの順序が変更されたり、新しいオプション・パラメータが追加されるなど、)プロシージャのパラメータ・リストが変更されたときに、コードを変更しなくてもよい場合があります。定義しなかったAPIを起動する場合、または他のユーザーが使用できるようにAPIを定義する場合、位置表記法より安全です。

混合

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

必須パラメータの後にオプションのパラメータが続くプロシージャを起動し、数個のみのオプションのパラメータを指定する必要がある場合に使用することをお薦めします。


例8-8に、位置表記法、名前表記法および混合表記法を使用した同等なサブプログラム・コールを示します。

例8-8 位置表記法、名前表記法または混合表記法でのサブプログラムのコール

SQL> DECLARE
  2    emp_num NUMBER(6) := 120;
  3    bonus   NUMBER(6) := 50;
  4    PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER) IS
  5      BEGIN
  6        UPDATE employees SET salary =
  7          salary + amount WHERE employee_id = emp_id;
  8    END raise_salary;
  9  BEGIN
 10    -- Positional notation:
 11    raise_salary(emp_num, bonus);
 12    -- Named notation (parameter order is insignificant):
 13    raise_salary(amount => bonus, emp_id => emp_num);
 14    raise_salary(emp_id => emp_num, amount => bonus);
 15    -- Mixed notation:
 16    raise_salary(emp_num, amount => bonus);
 17  END;
 18  /

PL/SQL procedure successfully completed.

SQL> REM Clean up
SQL> ROLLBACK;

Rollback complete.

SQL>
SQL> CREATE OR REPLACE FUNCTION compute_bonus (emp_id NUMBER, bonus NUMBER)
  2    RETURN NUMBER
  3  IS
  4    emp_sal NUMBER;
  5  BEGIN
  6    SELECT salary INTO emp_sal
  7      FROM employees
  8        WHERE employee_id = emp_id;
  9    RETURN emp_sal + bonus;
 10  END compute_bonus;
 11  /

Function created.

SQL> SELECT compute_bonus(120, 50) FROM DUAL;                   -- positional
  2  SELECT compute_bonus(bonus => 50, emp_id => 120) FROM DUAL; -- named
  3  SELECT compute_bonus(120, bonus => 50) FROM DUAL;           -- mixed
  4
SQL>

PL/SQLサブプログラム名のオーバーロード

PL/SQLでは、ローカル・サブプログラム、パッケージ・サブプログラムおよび型のメソッドをオーバーロードできます。 仮パラメータの数、順序またはデータ型が異なっていれば、同じ名前を複数のサブプログラムで使用できます。

例8-9では、同じ名前(initialize)を持つ2つのサブプログラムを定義しています。プロシージャでは様々な型のコレクションを初期化します。この2つのプロシージャは同じ処理を実行しているため、同じ名前を与えるのが論理的です。

この2つのinitializeプロシージャは、同じブロック、サブプログラム、パッケージまたはオブジェクト型の中に置くことができます。PL/SQLは仮パラメータをチェックして、どちらのプロシージャを起動するかを判断します。PL/SQLが使用するinitializeのバージョンは、プロシージャをdate_tab_typパラメータまたはnum_tab_typパラメータのどちらで起動するかによって異なります。

例8-9 サブプログラム名のオーバーロード

DECLARE
  TYPE date_tab_typ IS TABLE OF DATE   INDEX BY PLS_INTEGER;
  TYPE num_tab_typ  IS TABLE OF NUMBER INDEX BY PLS_INTEGER;

  hiredate_tab  date_tab_typ;
  sal_tab       num_tab_typ;

  PROCEDURE initialize (tab OUT date_tab_typ, n INTEGER) IS
  BEGIN
    FOR i IN 1..n LOOP
      tab(i) := SYSDATE;
    END LOOP;
  END initialize;

  PROCEDURE initialize (tab OUT num_tab_typ, n INTEGER) IS
  BEGIN
    FOR i IN 1..n LOOP
      tab(i) := 0.0;
    END LOOP;
  END initialize;

BEGIN
  initialize(hiredate_tab, 50);  -- Invokes first (date_tab_typ) version
  initialize(sal_tab, 100);      -- Invokes second (num_tab_typ) version
END;
/

パッケージ内のオーバーロードされたプロシージャの例は、例10-3を参照してください。

ここでのトピック:

数値型のオーバーロードのガイドライン

サブプログラムの仮パラメータの違いが数値データ型のみの場合、それらのサブプログラムはオーバーロードできます。ファンクションの複数のバージョンが同じ名前を使用でき、それぞれが異なる数値型を受け取ることができるため、この手法は数値演算Application Programming Interface(API)を記述する場合に有効です。たとえば、BINARY_FLOATを受け取るファンクションはより高速で、BINARY_DOUBLEを受け取るファンクションは精度がより高くなる場合があります。

オーバーロードされたサブプログラムにパラメータを渡す場合、次のことに注意して、問題または予期しない結果を回避します。

  • 予期されるパラメータ・セットごとに、目的のバージョンのサブプログラムが起動されることを確認します。たとえば、オーバーロードされるファンクションがBINARY_FLOATおよびBINARY_DOUBLEを受け取る場合、「5.0」のようなVARCHAR2リテラルを渡すと、どちらのファンクションが起動されるかを確認します。

  • 数値リテラルを修飾したり、変換ファンクションを使用して、目的のパラメータ型を明示します。たとえば、5.0fBINARY_FLOAT)、5.0dBINARY_DOUBLE)のようなリテラル、またはTO_BINARY_FLOATTO_BINARY_DOUBLETO_NUMBERのような変換ファンクションを使用します。

PL/SQLは、次の順序で、一致する数値パラメータを検索します。

  1. PLS_INTEGER(または同じデータ型であるBINARY_INTEGER

  2. NUMBER

  3. BINARY_FLOAT

  4. BINARY_DOUBLE

VARCHAR2値は、NUMBERBINARY_FLOATまたはBINARY_DOUBLEパラメータに一致します。

PL/SQLは、指定されたパラメータに最初に一致してオーバーロードされるサブプログラムを使用します。たとえば、SQRTファンクションは1つのパラメータを受け取ります。NUMBERBINARY_FLOATまたはBINARY_DOUBLEパラメータを受け取るオーバーロードされるファンクションがあるとします。PLS_INTEGERパラメータを渡した場合、最初に一致してオーバーロードされるファンクションは、NUMBERパラメータを持つファンクションです。

NUMBERパラメータを受け取るSQRTファンクションは、最も低速となる可能性が高くなります。 より高速なバージョンを使用するには、パラメータをSQRTファンクションに渡す前に、TO_BINARY_FLOATまたはTO_BINARY_DOUBLEファンクションを使用して、そのパラメータを別のデータ型に変換します。

PL/SQLは、パラメータを別のデータ型に変換する必要がある場合、まずそのパラメータをより上位のデータ型に変換しようとします。次に例を示します。

  • ATAN2ファンクションは、同じ型の2つのパラメータを受け取ります。異なる型のパラメータを渡した場合(たとえば、1つがPLS_INTEGERで、1つがBINARY_FLOATの場合)、PL/SQLは、両方のパラメータでより高度な型が使用されている場合に一致するものを検索します。この例では、2つのBINARY_FLOATパラメータを受け取るバージョンのATAN2が使用されます。PLS_INTEGERパラメータは、上位変換されます。

  • あるファンクションは、異なる型の2つのパラメータを受け取ります。オーバーロードされるバージョンの1つは、PLS_INTEGERパラメータおよびBINARY_FLOATパラメータを受け取ります。別のオーバーロードされるバージョンは、NUMBERパラメータおよびBINARY_DOUBLEパラメータを受け取ります。このファンクションを起動して、2つのNUMBERパラメータを渡した場合、PL/SQLは、まず2つ目のパラメータがBINARY_FLOATのオーバーロードされるバージョンを検出します。このパラメータは、他方のオーバーロードされるバージョンのBINARY_DOUBLEパラメータよりも一致度が高いため、次にPL/SQLは下位検索し、1つ目のNUMBERパラメータをPLS_INTEGERに変換します。

オーバーロードの制限

次のサブプログラムはオーバーロードできません。

  • スタンドアロン・サブプログラム

  • 仮パラメータの違いがモードのみのサブプログラム。次に例を示します。

    PACKAGE pkg IS
      PROCEDURE s (p IN  VARCHAR2);
      PROCEDURE s (p OUT VARCHAR2);
    END pkg;
    
  • 仮パラメータの違いがサブタイプのみのサブプログラム。次に例を示します。

    PACKAGE pkg IS
      PROCEDURE s (p INTEGER);
      PROCEDURE s (p REAL);
    END pkg;
    

    INTEGERおよびREALは、NUMBERのサブタイプであるため、同じデータ型のファミリに属しています。

  • 戻り値のデータ型のみが異なるファンクション(そのデータ型のファミリが異なっている場合でも)。次に例を示します。

    PACKAGE pkg IS
      FUNCTION f (p INTEGER) RETURN BOOLEAN;
      FUNCTION f (p INTEGER) RETURN INTEGER;
    END pkg;
    

コンパイラがオーバーロード・エラーを捕捉する場合

PL/SQLコンパイラは、起動されたサブプログラムを判別できなくなると判断するとすぐに、オーバーロード・エラーを捕捉します。複数のサブプログラムに同一のヘッダーがある場合、サブプログラム自体のコンパイル(サブプログラムがローカルである場合)、またはサブプログラムを宣言しているパッケージ仕様部のコンパイル(サブプログラムがパッケージ化されている場合)を試行すると、コンパイラはオーバーロード・エラーを捕捉します。複数のサブプログラムに同一のヘッダーがない場合は、サブプログラムの曖昧な起動のコンパイルを試行すると、コンパイラはエラーを捕捉します。

例8-10に示す、同一のヘッダーを持つ複数のサブプログラムを宣言しているパッケージ仕様部をコンパイルしようとすると、コンパイル時エラーPLS-00305が発生します。

例8-10 コンパイル時エラーの原因となるオーバーロード違反が含まれているパッケージ仕様部

PACKAGE pkg1 IS
  PROCEDURE s (p VARCHAR2);
  PROCEDURE s (p VARCHAR2);
END pkg1;

例8-11に示すパッケージ仕様部は、仮パラメータの違いがサブタイプのみのサブプログラムはオーバーロードできないという規則に違反していますが、エラーを生成せずにコンパイルできます。

例8-11 エラーを生成せずにコンパイルできる、オーバーロード違反が含まれているパッケージ仕様部

PACKAGE pkg2 IS
  SUBTYPE t1 IS VARCHAR2(10);
  SUBTYPE t2 IS VARCHAR2(10);
  PROCEDURE s (p t1);
  PROCEDURE s (p t2);
END pkg2;

ただし、例8-12に示すようにpkg2.sの起動をコンパイルしようとすると、コンパイル時エラーPLS-00307が発生します。

例8-12 不適切にオーバーロードされたサブプログラムの起動

PROCEDURE p IS
  a pkg.t1 := 'a';
BEGIN
  pkg.s(a)  -- Causes compile-time error PLS-00307;
END p;

次に示すように、オーバーロードされたサブプログラムの仮パラメータに別の名前を付けることによって、例8-11に示されているオーバーロード違反を修正するとします。

PACKAGE pkg2 IS
  SUBTYPE t1 IS VARCHAR2(10);
  SUBTYPE t2 IS VARCHAR2(10);
  PROCEDURE s (p1 t1);
  PROCEDURE s (p2 t2);
END pkg2;

これによって、名前表記法を使用して実パラメータを指定する場合に、エラーを生成せずにpkg2.sの起動をコンパイルできます。次に例を示します。

PROCEDURE p IS
  a pkg.t1 := 'a';
BEGIN
  pkg.s(p1=>a);  -- Compiles without error
END p;

例8-12に示されているように位置表記法を使用して実パラメータを指定すると、コンパイル時エラーPLS-00307が発生します。

例8-13に示すパッケージ仕様部は、オーバーロード規則に違反していないため、エラーを生成せずにコンパイルできます。 ただし、オーバーロードされたプロシージャを起動すると、例8-14の2つ目の起動に示すように、コンパイル時エラーPLS-00307が発生します。

例8-13 オーバーロード違反が含まれていないパッケージ仕様部

PACKAGE pkg3 IS
  PROCEDURE s (p1 VARCHAR2);
  PROCEDURE s (p1 VARCHAR2, p2 VARCHAR2 := 'p2');
END pkg3;

例8-14 適切にオーバーロードされたサブプログラムの不適切な起動

PROCEDURE p IS
  a1 VARCHAR2(10) := 'a1';
  a2 VARCHAR2(10) := 'a2';
BEGIN
  pkg.s(p1=>a1, p2=>a2);  -- Compiles without error
  pkg.s(p1=>a1);          -- Causes compile-time error PLS-00307
END p;

PL/SQLサブプログラムのコールの解決方法

図8-1に、PL/SQLコンパイラがサブプログラム・コールを解決する方法を示します。コンパイラは、サブプログラム・コールを検出すると、そのコールに合う宣言を検索します。コンパイラはまず現在の有効範囲を検索し、必要ならば外側の有効範囲を順に検索します。コールされたサブプログラムの名前と同じ名前のサブプログラム宣言が1つ以上見つかると、コンパイラはさらに厳密に検索します。

同じ有効範囲のレベルに同じような名前のサブプログラムが存在する場合は、コールを解決するために、コンパイラは実パラメータと仮パラメータが正確に一致するものを発見する必要があります。 パラメータは、(いくつかの仮パラメータにデフォルト値が代入されている場合を除き)数、順序およびデータ型が一致している必要があります。一致するものが見つからなかった場合、または一致するものが複数見つかった場合、コンパイラはセマンティック・エラーを生成します。

図8-1 PL/SQLコンパイラによるコールの解決方法

PL/SQLコンパイラによるコールの解決方法
「図8-1 PL/SQLコンパイラによるコールの解決方法」の説明

例8-15は、ファンクションbalanceから外側のプロシージャswapを起動しています。現在の有効範囲の中にあるswapの宣言が、いずれもプロシージャ・コールと一致しないために、コンパイラはエラーを生成します。

例8-15 PL/SQLプロシージャ名の解決

DECLARE
  PROCEDURE swap (n1 NUMBER, n2 NUMBER) IS
    num1 NUMBER;
    num2 NUMBER;
    FUNCTION balance (bal NUMBER) RETURN NUMBER IS
      x NUMBER := 10;
      PROCEDURE swap (d1 DATE, d2 DATE) IS BEGIN NULL; END;
      PROCEDURE swap (b1 BOOLEAN, b2 BOOLEAN) IS BEGIN NULL; END;
    BEGIN
      DBMS_OUTPUT.PUT_LINE('The following raises an exception');
--      swap(num1, num2);
--      wrong number or types of arguments in call to 'SWAP'
      RETURN x;
    END balance;
  BEGIN NULL;END swap;
BEGIN
  NULL;
END;
/

実行者権限または定義者権限の使用(AUTHID句)

ストアドPL/SQLユニットのAUTHIDプロパティは、実行時にユニットによって発行されるSQL文の名前解決および権限チェックに影響します。 AUTHIDプロパティはコンパイルには影響しません。また、コレクション型などのコードのないユニットに対しては意味を持ちません。

AUTHIDプロパティ値は、静的データ・ディクショナリ・ビュー*_PROCEDURESに公開されます。 AUTHIDが意味を持つユニットの場合はビューに値CURRENT_USERまたはDEFINERが表示され、他のユニットの場合はビューにNULLが表示されます。

次の文で作成または変更したストアドPL/SQLユニットでは、オプションのAUTHID句を使用して、CURRENT_USERまたはDEFINERのいずれかを指定できます。 デフォルトはDEFINERです。

AUTHID値がCURRENT_USERのユニットは、実行者権限ユニットまたはIRユニットと呼ばれます。 AUTHID値がDEFINERのユニットは、定義者権限ユニットまたはDRユニットと呼ばれます。 無名ブロックは、常にIRユニットのように動作します。 トリガーまたはビューは、常にDRユニットのように動作します。

ユニットのAUTHIDプロパティによって、そのユニットがIRであるかDRであるかが決まります。このプロパティは実行時の名前解決および権限チェックの両方に影響します。

セッションが開始されると、CURRENT_SCHEMAの値はSESSION_USERが所有するスキーマの値となり、CURRENT_USERの値はSESSION_USERの値と同じになります。 (CURRENT_SCHEMACURRENT_USERまたはSESSION_USERの現在の値を取得するには、『Oracle Database SQL言語リファレンス』で説明されているSYS_CONTEXTファンクションを使用します。)

CURRENT_SCHEMAは、SQL文ALTER SESSION SET CURRENT_SCHEMAを使用してセッション中に変更できます。 CURRENT_USERは、プログラムでは変更できませんが、PL/SQLユニットまたはビューがコール・スタックに格納されたり、コール・スタックから取り出されると、変更される場合があります。


注意:

ALTER SESSION SET CURRENT_SCHEMAは、ストアドPL/SQLユニット内から発行しないことをお薦めします。

サーバー・コール中にDRユニットがコール・スタックに格納されると、データベースによって、現在有効になっているロール、およびCURRENT_USERCURRENT_SCHEMAの現在の値が格納されます。 次に、CURRENT_USERCURRENT_SCHEMAの両方がDRユニットの所有者に変更され、ロールPUBLICのみが有効にされます。 (格納されたロールおよび値と新しいロールおよび値は、異なるとはかぎりません。) DRユニットがコール・スタックから取り出されると、データベースによって、格納されたロールおよび値がリストアされます。 これに対して、IRユニットがコール・スタックに格納されるか、またはコール・スタックから取り出されても、CURRENT_USERCURRENT_SCHEMAの値、および現在有効になっているロールは変更されません。

PL/SQLユニットによって発行される動的SQL文の場合、名前解決および権限チェックは実行時に1回のみ行われます。 静的SQL文の場合、名前解決および権限チェックは、PL/SQLユニットのコンパイル時、およびその後の実行時の2回行われます。 コンパイル時に、AUTHIDプロパティは影響しません。DRユニットとIRユニットの両方がDRユニットのように処理されます。 ただし、実行時には、AUTHIDプロパティによってユニットがIRであるかDRであるか決まり、その結果に従ってユニットは処理されます。

ここでのトピック:

AUTHID CURRENT_USERとAUTHID DEFINERとの使い分け

使用例: 表へのアクセスが制限されていないプロシージャを持つAPIを作成するとします。ただし、一般ユーザーが表のデータを直接選択し、INSERT文、UPDATE文およびDELETE文を使用して変更しないようにする必要があります。

解決方法: 特別なスキーマで、APIを構成する表およびプロシージャを作成します。 デフォルトでは各プロシージャはDRユニットであるため、作成時にAUTHID DEFINERを指定する必要はありません。 他のユーザーに対しては、EXECUTE権限を付与し、データにアクセスする権限は付与しないようにします。

使用例: 開発者に対してコンパイル・エラーを表示するPL/SQLプロシージャを作成するとします。 プロシージャは静的データ・ディクショナリ・ビューALL_SOURCEALL_ERRORSを結合し、プロシージャDBMS_OUTPUT.PUT_LINEを使用して、エラーのリストの後に、各エラーの発生箇所を示す番号の付いたソース行のウィンドウを表示します。 すべての開発者がプロシージャを実行できるようにする必要があります。また、プロシージャが各開発者をALL_SOURCEおよびALL_ERRORSに対してCURRENT_USERとして扱うようにする必要があります。

解決方法: プロシージャの作成時に、AUTHID CURRENT_USERを指定します。 PUBLICに対してEXECUTE権限を付与します。 このプロシージャはIRユニットであるため、ALL_SOURCEおよびALL_ERRORSは、プロシージャを起動するユーザーの権限で動作します。


注意:

別の解決方法として、プロシージャをDRユニットにして、DBA_SOURCEおよびDBA_ERRORSの両方に対するSELECT権限を所有者に付与する方法もあります。 ただし、この解決方法では、プログラムの作成が難しくなるのみでなく、EXECUTE権限を持たないユニットのソース・コードをユーザーが参照できないようにするという条件を監査することが非常に難しくなります。

AUTHIDおよびSQLコマンドSET ROLE

SQLコマンドSET ROLEは、コール・スタックにDRユニットがない場合にのみ正常に実行されます。 少なくとも1つのDRユニットがコール・スタックにある場合、SET ROLEコマンドを発行すると、ORA-06565が発生します。


注意:

PL/SQLからSET ROLEコマンドを実行するには、動的SQLを使用する必要があります。EXECUTE IMMEDIATE文が推奨です。 この文の詳細は、「EXECUTE IMMEDIATE文の使用」を参照してください。

IRサブプログラムでのテンプレート・オブジェクトの必要性

PL/SQLコンパイラは、表および他のオブジェクトへのすべての参照をコンパイル時に解決する必要があります。IRサブプログラムの所有者は、同じスキーマ内に正しい名前と列のオブジェクトを持つ必要があります。オブジェクトには、データが存在していなくてもかまいません。実行時に、起動元のスキーマ内の対応するオブジェクトは、一致する定義を持っている必要があります。そうでない場合、エラーまたは予期しない結果(起動元のスキーマには存在するがサブプログラムを含むスキーマには存在しない表の列が無視されるなど)が発生します。

IRサブプログラムでのデフォルトの名前解決のオーバーライド

IRユニットに対する実行時名前解決規則(起動するたびに同じ未修飾の名前が異なるオブジェクトに解決される)は適切でない場合があります。 むしろ、起動のたびに特定のオブジェクトを使用する必要があります。 ただし、別の理由からIRユニットが必要になります。 たとえば、CURRENT_USERに対する権限を評価することが重要である場合があります。 このような状況では、オブジェクトを所有するスキーマで名前を修飾します。

パブリック・シノニムであることを示す未修飾の名前は、CURRENT_USERのスキーマに競合する名前がある場合、取得されるリスクがあることに注意してください。 パブリック・シノニムは"PUBLIC"で修飾できます。 PUBLICを二重引用符で囲む必要があります。次に例を示します。

SELECT sysdate INTO today FROM "PUBLIC".DUAL;

注意:

SQL文ALTER SESSION SET CURRENT_SCHEMAは、ストアドPL/SQLユニット内から発行しないことをお薦めします。

IRサブプログラムでのビューおよびデータベース・トリガーの使用

ビュー式内で実行されるIRサブプログラムの場合は、ビューを問い合せているユーザーではなく、ビューを作成したユーザーが現行ユーザーとみなされます。この規則は、データベース・トリガーにも適用されます。


注意:

SYS_CONTEXTがビューのSQL文の定義で直接使用されると、CURRENT_USERに戻される値は、ビューの所有者ではなく問い合せているユーザーになります。

IRサブプログラムでのデータベース・リンクの使用

データベース・リンクを作成して、実行者権限を使用できます。

CREATE DATABASE LINK link_name CONNECT TO CURRENT_USER
  USING connect_string;

現行ユーザー・リンクでは、そのユーザー権限を持つ別のユーザーとしてリモート・データベースに接続できます。 接続するために、データベースでは現行ユーザーのユーザー名が使用されます(実行者はグローバル・ユーザーである必要があります)。ユーザーOEが所有するIRサブプログラムが、次のデータベース・リンクを参照するとします。グローバル・ユーザーHRがそのサブプログラムを起動していれば、現行ユーザーであるユーザーHRでデータベースDallasに接続します。

CREATE DATABASE LINK dallas CONNECT TO CURRENT_USER USING ...

定義者権限サブプログラムの場合、現行ユーザーはOEであり、サブプログラムはグローバル・ユーザーOEでデータベースDallasに接続します。

IRサブプログラムでのオブジェクト型の使用

任意のスキーマで使用するオブジェクト型を定義するために、AUTHID CURRENT_USER句を指定します。オブジェクト型の詳細は、『Oracle Databaseオブジェクト・リレーショナル開発者ガイド』を参照してください。

例8-16で、ユーザーHRがオブジェクト型を作成するとします。

例8-16 AUTHID CURRENT USERを使用したオブジェクト型の作成

CREATE TYPE person_typ AUTHID CURRENT_USER AS OBJECT (
  person_id   NUMBER,
  person_name VARCHAR2(30),
  person_job  VARCHAR2(10),
  STATIC PROCEDURE new_person_typ (
    person_id NUMBER, person_name VARCHAR2, person_job VARCHAR2,
    schema_name VARCHAR2, table_name VARCHAR2),
  MEMBER PROCEDURE change_job (SELF IN OUT NOCOPY person_typ,
                               new_job VARCHAR2)
  );
/
CREATE TYPE BODY person_typ AS
  STATIC PROCEDURE new_person_typ (
    person_id NUMBER, person_name VARCHAR2, person_job VARCHAR2,
    schema_name VARCHAR2, table_name VARCHAR2) IS
    sql_stmt VARCHAR2(200);
  BEGIN
    sql_stmt := 'INSERT INTO ' || schema_name || '.'
       || table_name || ' VALUES (HR.person_typ(:1, :2, :3))';
    EXECUTE IMMEDIATE sql_stmt
      USING person_id, person_name, person_job;
  END;
  MEMBER PROCEDURE change_job (SELF IN OUT NOCOPY person_typ,
                               new_job VARCHAR2) IS
  BEGIN
    person_job := new_job;
  END;
END;
/

次にユーザーHRは、オブジェクト型person_typに対するEXECUTE権限を、ユーザーOEに付与します。

GRANT EXECUTE ON person_typ TO OE;

最後に、ユーザーOEは、person_typ型のオブジェクトを格納するためにオブジェクト表を作成し、次にプロシージャnew_person_typを起動して、その表にデータを入れます。

CREATE TABLE person_tab OF hr.person_typ;

BEGIN
  hr.person_typ.new_person_typ(1001,
                               'Jane Smith',
                               'CLERK',
                               'oe',
                               'person_tab');
  hr.person_typ.new_person_typ(1002,
                               'Joe Perkins',
                               'SALES','oe',
                               'person_tab');
  hr.person_typ.new_person_typ(1003,
                               'Robert Lange',
                               'DEV',
                               'oe', 'person_tab');
                               'oe', 'person_tab');
END;
/

コールは成功しました。これはプロシージャがその所有者(HR)の権限ではなく現行ユーザー(OE)の権限で実行されたためです。

オブジェクト型階層内のサブタイプには、次の規則が適用されます。

  • サブタイプでAUTHID句が明示的に指定されていない場合は、スーパータイプのAUTHIDを継承します。

  • サブタイプでAUTHID句が指定されている場合、そのAUTHIDがスーパータイプのAUTHIDと一致する必要があります。また、AUTHIDDEFINERの場合は、スーパータイプとサブタイプの両方が同じスキーマに作成されている必要があります。

IRインスタンス・メソッドの起動

IRインスタンス・メソッドは、インスタンスの作成者ではなく、実行者の権限で実行します。 person_typ例8-16で作成されたIRオブジェクト型で、ユーザーHRが、型person_typのオブジェクトであるp1を作成するとします。 例8-17に示すとおり、ユーザーOEが、オブジェクトp1で操作を行うためのインスタンス・メソッドchange_jobを起動する場合、メソッドの現行ユーザーは、HRではなくOEです。

例8-17 IRインスタンス・メソッドの起動

-- OE creates a procedure that invokes change_job
CREATE PROCEDURE reassign
  (p IN OUT NOCOPY hr.person_typ, new_job VARCHAR2) AS
BEGIN
   p.change_job(new_job); -- executes with the privileges of oe
END;
/
-- OE grants EXECUTE to HR on procedure reassign
GRANT EXECUTE ON reassign to HR;

-- HR passes a person_typ object to the procedure reassign
DECLARE
   p1 person_typ;
BEGIN
   p1 := person_typ(1004,  'June Washburn', 'SALES');
   oe.reassign(p1, 'CLERK'); -- current user is oe, not hr
END;
/

再帰的PL/SQLサブプログラムの使用

再帰的サブプログラムとは、自分自身を起動するサブプログラムのことです。再帰的コールが行われるたびに、パラメータ、変数、カーソル、例外など、そのサブプログラムで宣言されているすべての項目の新しいインスタンスが作成されます。また、再帰を繰り返して進む過程の各レベルで、SQL文の新しいインスタンスが作成されます。

再帰的コールを入れる位置には注意してください。 カーソルFORループの中や、OPEN文とCLOSE文の間に入れると、コールのたびに新しいカーソルがオープンされます。これによって、データベースのOPEN_CURSORS初期化パラメータによって設定された限界を超える場合があります。

再帰的サブプログラムには、再帰的コールへ導くパスとそうではないパスの、少なくとも2つのパスが必要です。終了条件へ導くパスが少なくとも1つは必要だということです。そうでない場合、メモリーが足りなくなり、PL/SQLによって事前定義の例外STORAGE_ERRORが呼び出されるまで、再帰が続きます。

再帰はアルゴリズムの設計を単純化する強力な手法です。一般に、再帰とは自己参照を意味します。再帰的な数列の個々の項は、それ以前の項に計算式を適用することで得られます。フィボナッチ数列(0, 1, 1, 2, 3, 5, 8, 13, 21, ...)がその一例です。この数列では、2番以降の各項が、すぐ前の2つの項の合計になっています。

再帰定義では、それ自身をさらに単純なバージョンに定義します。nの階乗(n!、1からnのすべての整数の積)の定義を考えてみます。

n! = n * (n - 1)!

外部サブプログラムの起動

PL/SQLは強力かつ柔軟な言語ですが、他の言語を使用した方が容易に実行できる作業もあります。Cのような低レベルの言語は非常に高速です。 Javaのような広く使用されている言語には、共通の設計パターン用の再利用可能なライブラリが存在します。

PL/SQLのコール仕様を使用すると、別の言語で記述された外部サブプログラムを起動して、それらの機能およびライブラリをPL/SQLから使用できます。たとえば、Javaストアド・プロシージャは、任意のPL/SQLブロック、サブプログラムまたはパッケージから起動できます。Javaストアド・プロシージャの詳細は、『Oracle Database Java開発者ガイド』を参照してください。

次のJavaクラスがデータベースに格納されている場合は、例8-18に示すとおり、このクラスを起動できます。

import java.sql.*;
import oracle.jdbc.driver.*;
public class Adjuster {
  public static void raiseSalary (int empNo, float percent)
  throws SQLException {
    Connection conn = new OracleDriver().defaultConnection();
    String sql = "UPDATE employees SET salary = salary * ?
                    WHERE employee_id = ?";
    try {
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setFloat(1, (1 + percent / 100));
      pstmt.setInt(2, empNo);
      pstmt.executeUpdate();
      pstmt.close();
    } catch (SQLException e)
          {System.err.println(e.getMessage());}
    }
}

クラスAdjusterには、従業員の給与を指定のパーセンテージ分のみ増やすメソッドがあります。 raiseSalaryvoidメソッドであるため、例8-18に示すコール仕様を使用してプロシージャとしてパブリッシュしてから、プロシージャraise_salaryを無名PL/SQLブロックから起動できます。

例8-18 PL/SQLからの外部プロシージャの起動

CREATE OR REPLACE PROCEDURE raise_salary (empid NUMBER, pct NUMBER)
AS LANGUAGE JAVA
NAME 'Adjuster.raiseSalary(int, float)';
/

DECLARE
   emp_id  NUMBER := 120;
   percent NUMBER := 10;
BEGIN
   -- get values for emp_id and percent
   raise_salary(emp_id, percent);  -- invoke external subprogram
END;
/

Javaコール仕様はネストされたプロシージャとしては宣言できませんが、オブジェクト型の仕様部、オブジェクト型の本体、PL/SQLパッケージの仕様部、PL/SQLパッケージの本体で指定したり、トップレベルPL/SQLプロシージャおよびファンクションとして指定することができます。

例8-19に、PL/SQLプロシージャからのJavaファンクションへのコールを示します。

例8-19 PL/SQLからのJavaファンクションの起動

-- the following invalid nested Java call spec throws PLS-00999
--   CREATE PROCEDURE sleep (milli_seconds in number) IS
--     PROCEDURE java_sleep (milli_seconds IN NUMBER) AS ...

-- Create Java call spec, then call from PL/SQL procedure
CREATE PROCEDURE java_sleep (milli_seconds IN NUMBER)
  AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
/
CREATE PROCEDURE sleep (milli_seconds in number) IS
-- the following nested PROCEDURE spec is not legal
--  PROCEDURE java_sleep (milli_seconds IN NUMBER)
--    AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
BEGIN
  DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.get_time());
  java_sleep (milli_seconds);
  DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.get_time());
END;
/

外部Cサブプログラムは、組込みシステムとのインタフェース、技術的な分野の問題解決、データの分析、リアルタイムのデバイスや処理の制御に使用します。外部Cサブプログラムを使用すると、データベース・サーバーの機能性を拡張し、計算専用プログラムをクライアントからサーバーに移動できます。サーバーの方が高速に処理できます。外部Cサブプログラムの詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

PL/SQLサブプログラムの副作用の制御

副作用が少なければ、特にPARALLEL_ENABLEヒントまたはDETERMINISTICヒントを使用している場合に、問合せ内でファンクションをより効率化できます。

ストアド・ファンクション(およびそのファンクションによって起動されるサブプログラム)は、次に示す副作用を制御するための純正規則に従っている場合にのみ、SQL文からコールできます。

ファンクション本体内のSQL文が規則に違反すると、実行時(文が解析されるとき)にエラーが発生します。

コンパイル時に純正規則に違反していないかどうかを確認するには、RESTRICT_REFERENCESプラグマを使用して、ファンクションがデータベース表またはパッケージ変数に対する読取りまたは書込みを行っていないことを示します(構文は、「RESTRICT_REFERENCESプラグマ」を参照)。

例8-20では、RESTRICT_REFERENCESプラグマは、パッケージ・ファンクションcredit_okがデータベースに対する書込み禁止状態(WNDS)、およびパッケージに対する読取り禁止状態(RNPS)であることを示します。

例8-20 RESTRICT_REFERENCESプラグマ

CREATE PACKAGE loans AS
   FUNCTION credit_ok RETURN BOOLEAN;
   PRAGMA RESTRICT_REFERENCES (credit_ok, WNDS, RNPS);
END loans;
/

静的INSERT文、UPDATE文またはDELETE文は、常にWNDSに違反します。また、列を読み取る場合は、RNDS(データベースに対する読取り禁止状態)にも違反します。動的INSERT文、UPDATE文またはDELETE文は、常にWNDSおよびRNDSの両方に違反します。

PL/SQLサブプログラムのパラメータのエイリアシングの理解

サブプログラムのコールを最適化するために、PL/SQLコンパイラでは、2つのパラメータ引渡し方式のいずれかを選択できます。BY VALUE方式では、実パラメータの値がサブプログラムに渡されます。BY REFERENCE方式では、値へのポインタのみが渡されます。この場合、実パラメータと仮パラメータとは同じ項目を参照します。

NOCOPYコンパイラ・ヒントによって、エイリアシングの可能性が高くなります(つまり、異なる2つの名前が同じメモリー位置を参照するようになります)。これは、グローバル変数がサブプログラムのコールの中で実パラメータとして使用され、そのサブプログラム内で参照されると発生します。結果はコンパイラが選択するパラメータの引渡し方式に依存するため、予測不能になります。

例8-21では、プロシージャADD_ENTRYは、VARRAY LEXICONをパラメータとグローバル変数の両方として参照しています。ADD_ENTRYが起動されると、識別子WORD_LISTおよびLEXICONは同じVARRAYを指定します。

例8-21 NOCOPYヒントを指定したグローバル変数の引渡しによるエイリアシング

DECLARE
  TYPE Definition IS RECORD (
    word    VARCHAR2(20),
    meaning VARCHAR2(200));
  TYPE Dictionary IS VARRAY(2000) OF Definition;
  lexicon Dictionary := Dictionary();
  PROCEDURE add_entry (word_list IN OUT NOCOPY Dictionary) IS
    BEGIN
      word_list(1).word := 'aardvark';
      lexicon(1).word := 'aardwolf';
    END;
BEGIN
  lexicon.EXTEND;
  add_entry(lexicon);
  DBMS_OUTPUT.PUT_LINE(lexicon(1).word);
END;
/

コンパイラがNOCOPYヒントに従う場合、プログラムはaardwolfを出力します。WORD_LISTへの代入はポインタを通じてただちに実行され、LEXICONへの代入によって上書きされます。

NOCOPYヒントが省略された場合またはコンパイラがNOCOPYヒントに従わない場合、プログラムはaardvarkを出力します。WORD_LISTへの代入にはVARRAYの内部コピーが使用されます。これは、プロシージャの終了時に実パラメータにコピーされます(LEXICONの内容を上書きします)。

エイリアシングは、1回のサブプログラム・コールに、同じ実パラメータが2回以上現れる場合にも発生します。 例8-22では、n2IN OUTのパラメータであるため、実パラメータの値は、プロシージャが終了するまで更新されません。そのため、最初のPUT_LINEは10(nの初期値)を出力し、3番目のPUT_LINEは20を出力します。ただし、n3NOCOPYパラメータであるため、実パラメータの値はただちに更新されます。2番目のPUT_LINEが30を出力するのはこのためです。

例8-22 同じパラメータの複数回の引渡しによるエイリアシング

DECLARE
   n NUMBER := 10;
   PROCEDURE do_something (
      n1 IN NUMBER,
      n2 IN OUT NUMBER,
      n3 IN OUT NOCOPY NUMBER) IS
   BEGIN
      n2 := 20;
      DBMS_OUTPUT.put_line(n1);  -- prints 10
      n3 := 30;
      DBMS_OUTPUT.put_line(n1);  -- prints 30
   END;
BEGIN
   do_something(n, n, n);
   DBMS_OUTPUT.put_line(n);  -- prints 20
END;
/

カーソル変数はポインタであるため、カーソル変数にもエイリアシングの可能性があります。 例8-23では、代入の後、emp_cv2emp_cv1の別名になります。これは、両者が同じ問合せ作業領域を指すためです。最初の2行はすでにemp_cv1からフェッチされているため、emp_cv2からの1番目のフェッチは、1番目ではなく3番目の行をフェッチします。emp_cv1は閉じられているため、emp_cv2からの2番目のフェッチは失敗します。

例8-23 同じ作業領域へのカーソル変数の代入によるエイリアシング

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;
    FETCH emp_cv1 INTO emp_rec;  -- fetches first row
    FETCH emp_cv1 INTO emp_rec;  -- fetches second row
    FETCH emp_cv2 INTO emp_rec;  -- fetches third row
    CLOSE emp_cv1;
    DBMS_OUTPUT.put_line('The following raises an invalid cursor');
--  FETCH emp_cv2 INTO emp_rec;
--  raises invalid cursor when get_emp_data is invoked
  END;
BEGIN
  get_emp_data(c1, c2);
END;
/

PL/SQLファンクション結果キャッシュの使用

PL/SQLファンクション結果キャッシュのメカニズムは、アプリケーションを実行するすべてのセッションで使用可能な共有グローバル領域(SGA)にPL/SQLファンクションの結果をキャッシュするための、言語サポートおよびシステム管理された手段を提供します。 このキャッシュ・メカニズムは、使いやすく効率的です。このメカニズムを使用すると、独自のキャッシュおよびキャッシュ管理ポリシーを設計および開発する負担がなくなります。

ファンクションの結果キャッシュを使用可能にするには、RESULT_CACHE句を使用します。結果がキャッシュされるファンクションが起動されると、システムによってキャッシュがチェックされます。 以前同じパラメータ値を指定してファンクションをコールした際の結果がキャッシュに含まれている場合は、キャッシュされた結果が起動元に戻され、ファンクション本体は再実行されません。キャッシュに結果が含まれていない場合は、ファンクション本体が実行され、制御が起動元に戻される前に、(これらのパラメータ値の)結果が追加されます。


注意:

ファンクションを実行した結果、未処理例外が発生した場合、この例外結果はキャッシュに格納されません。

キャッシュには、非常に多くの結果を蓄積できます。つまり、結果がキャッシュされる各ファンクションの起動時に指定されたパラメータ値の一意の組合せごとに1つの結果を蓄積できます。システムでは、必要なメモリーが不足すると、キャッシュされた結果が1つ以上エージ・アウト(削除)されます。

キャッシュされる結果の計算に使用されるデータベース・オブジェクトは指定できます。このため、指定したデータベース・オブジェクトのいずれかが更新されると、キャッシュされた結果は無効となり、結果を再計算する必要があります。結果キャッシュの対象として最良のファンクションは、頻繁に起動され、ほとんどまたはまったく変更されない情報に依存するファンクションです。

ここでのトピック:

ファンクションの結果キャッシュの有効化

ファンクションの結果がキャッシュされるようにするには、次の操作を実行します。

  • ファンクション宣言に、オプションRESULT_CACHEを含めます。

  • ファンクション定義で、次の操作を実行します。

    • RESULT_CACHE句を含めます。

    • オプションのRELIES_ON句で、ファンクションの結果が依存する表またはビューを指定します。

RESULT_CACHE句およびRELIES_ON句の構文は、「ファンクション宣言と定義」を参照してください。

例8-24では、パッケージdepartment_pksが、結果がキャッシュされるファンクションget_dept_infoを宣言し、定義しています。このファンクションによって、指定した部門の平均給与および従業員数が戻されます。get_dept_infoは、データベース表EMPLOYEESに依存します。

例8-24 結果がキャッシュされるファンクションの宣言および定義

-- Package specification
CREATE OR REPLACE PACKAGE department_pks IS
  TYPE dept_info_record IS RECORD (average_salary      NUMBER,
                                   number_of_employees NUMBER);
  -- Function declaration
  FUNCTION get_dept_info (dept_id NUMBER) RETURN dept_info_record
  RESULT_CACHE;
END department_pks;
/
CREATE OR REPLACE PACKAGE BODY department_pks AS
  -- Function definition
  FUNCTION get_dept_info (dept_id NUMBER) RETURN dept_info_record
    RESULT_CACHE RELIES_ON (EMPLOYEES)
  IS
    rec dept_info_record;
  BEGIN
    SELECT AVG(SALARY), COUNT(*) INTO rec
      FROM EMPLOYEES
        WHERE DEPARTMENT_ID = dept_id;
    RETURN rec;
  END get_dept_info;
END department_pks;
/
DECLARE
  dept_id   NUMBER := 50;
  avg_sal   NUMBER;
  no_of_emp NUMBER;
BEGIN
  avg_sal   := department_pks.get_dept_info(50).average_salary;
  no_of_emp := department_pks.get_dept_info(50).number_of_employees;
  DBMS_OUTPUT.PUT_LINE('dept_id = ' ||dept_id);
  DBMS_OUTPUT.PUT_LINE('average_salary = '|| avg_sal);
  DBMS_OUTPUT.PUT_LINE('number_of_employees = ' ||no_of_emp);
END;
/

ファンクションget_dept_infoは、他のファンクションを起動する場合と同様に起動します。たとえば、次のコールでは、部門番号10の従業員の平均給与と従業員数が戻されます。

department_pks.get_dept_info(10);

次のコールでは、部門番号10の平均給与のみが戻されます。

department_pks.get_dept_info(10).average_salary;

get_dept_info(10)の結果がすでに結果キャッシュに含まれている場合、結果はこのキャッシュから戻されます。そうではない場合、結果は計算されてキャッシュに追加されます。RELIES_ON句にEMPLOYEESが指定されているため、EMPLOYEESが更新されると、department_pks.get_dept_infoのキャッシュされた結果はすべて無効になります。このため、EMPLOYEESが変更される可能性のあるすべての場所でキャッシュ無効化ロジックをプログラミングする必要がなくなります。

結果がキャッシュされるファンクションを使用するアプリケーションの開発

結果がキャッシュされるファンクションを使用するアプリケーションを開発する場合、指定したパラメータ値のセットに対してそのファンクションの本体が実行される回数については何も想定しないでください。

結果がキャッシュされるファンクションの本体が実行される状況をいくつか次に示します。

  • このデータベース・インスタンスでのセッションが、これらのパラメータ値を使用してファンクションを初めて起動したとき

  • これらのパラメータ値のキャッシュされた結果が無効である場合

    ファンクション定義のRELIES_ON句に指定されたデータベース・オブジェクトのいずれかが変更されると、キャッシュされた結果は無効になります。

  • これらのパラメータ値のキャッシュされた結果がエージ・アウトされた場合

    システムでは、必要なメモリーが不足すると、キャッシュされた値で最も古いものが破棄されます。

  • ファンクションがキャッシュをバイパスする場合(「結果キャッシュのバイパス」を参照)

結果がキャッシュされるファンクションの制限

結果がキャッシュされるようにするには、ファンクションで次のすべての条件が満たされている必要があります。

  • 実行者権限を持つモジュール内または無名ブロック内で定義されていない

  • パイプライン・テーブル・ファンクションでない

  • OUTパラメータまたはIN OUTパラメータを含んでいない

  • 次のいずれの型のINパラメータも含んでいない

    • BLOB

    • CLOB

    • NCLOB

    • REF CURSOR

    • コレクション

    • オブジェクト

    • レコード

  • 戻り型が次のいずれでもない

    • BLOB

    • CLOB

    • NCLOB

    • REF CURSOR

    • オブジェクト

    • 前述のサポートされていない戻り型のいずれかが含まれているレコードまたはPL/SQLコレクション

結果がキャッシュされるファンクションでは、次の条件も満たされていることが推奨されています。

結果がキャッシュされるファンクションの例

結果キャッシュの対象として最良のファンクションは、(最初の例がこれに該当している可能性がありますが)頻繁に起動され、ほとんど変更されない情報に依存するファンクションです。結果キャッシュを行うことによって、再帰ファンクションでの冗長計算が回避されます。

結果がキャッシュされるアプリケーション構成パラメータ

グローバル・レベル、アプリケーション・レベルまたはロール・レベルのいずれのレベルで設定できる構成パラメータを持つアプリケーションについて考えてみます。このアプリケーションは、構成情報を次の表に格納します。

-- Global Configuration Settings
CREATE TABLE global_config_params
  (name  VARCHAR2(20), -- parameter NAME
   value VARCHAR2(20), -- parameter VALUE
   PRIMARY KEY (name)
  );

-- Application-Level Configuration Settings
CREATE TABLE app_level_config_params
  (app_id  VARCHAR2(20), -- application ID
   name    VARCHAR2(20), -- parameter NAME
   value   VARCHAR2(20), -- parameter VALUE
   PRIMARY KEY (app_id, name)
  );

-- Role-Level Configuration Settings
CREATE TABLE role_level_config_params
  (role_id  VARCHAR2(20), -- application (role) ID
   name     VARCHAR2(20),  -- parameter NAME
   value    VARCHAR2(20),  -- parameter VALUE
   PRIMARY KEY (role_id, name)
  );

各構成パラメータで、ロール・レベルの設定はアプリケーション・レベルの設定をオーバーライドし、アプリケーション・レベルの設定はグローバル設定をオーバーライドします。パラメータに適用される設定を決定するために、このアプリケーションはPL/SQLファンクションget_valueを定義します。パラメータ名、アプリケーションIDおよびロールIDを指定すると、get_valueはそのパラメータに適用される設定を戻します。

ファンクションget_valueが頻繁に起動され、構成情報はほとんど変更されない場合、このファンクションは結果キャッシュの対象として最良のファンクションとなります。global_config_paramsapp_level_config_paramsまたはrole_level_config_paramsに対する変更がコミットされるとget_valueのキャッシュされた結果が無効になるようにするには、RELIES_ON句にそれらの名前を含めます。

例8-25に、get_valueに指定可能な定義を示します。

例8-25 構成パラメータの設定を戻す、結果がキャッシュされるファンクション

CREATE OR REPLACE FUNCTION get_value
  (p_param VARCHAR2,
   p_app_id  NUMBER,
   p_role_id NUMBER
  )
  RETURN VARCHAR2
  RESULT_CACHE RELIES_ON
    (role_level_config_params,
     app_level_config_params,
     global_config_params
    )
IS
  answer VARCHAR2(20);
BEGIN
  -- Is parameter set at role level?
  BEGIN
    SELECT value INTO answer
      FROM role_level_config_params
        WHERE role_id = p_role_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at application level?
  BEGIN
    SELECT value INTO answer
      FROM app_level_config_params
        WHERE app_id = p_app_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at global level?
    SELECT value INTO answer
     FROM global_config_params
      WHERE name = p_param;
    RETURN answer;
END;

結果がキャッシュされる再帰ファンクション

フィボナッチ数列の数学的定義を模倣した、フィボナッチ数列のn番目の項を検索するための再帰ファンクションは、多くの冗長計算を実行する可能性があります。 たとえば、fibonacci(7)を評価するために、このファンクションはfibonacci(6)およびfibonacci(5)を計算する必要があります。 fibonacci(6)を計算するために、このファンクションはfibonacci(5)およびfibonacci(4)を計算する必要があります。 このため、fibonacci(5)などのいくつかの項は、重複して計算されます。結果キャッシュを行うことによって、これらの冗長計算が回避されます。RELIES_ON句は不要です。

CREATE OR REPLACE FUNCTION fibonacci (n NUMBER)
   RETURN NUMBER RESULT_CACHE IS
BEGIN
  IF (n =0) OR (n =1) THEN
    RETURN 1;
  ELSE
    RETURN fibonacci(n - 1) + fibonacci(n - 2);
  END IF;
END;
/

結果がキャッシュされるファンクションの高度なトピック

ここでのトピック:

キャッシュ・ヒットの規則

結果がキャッシュされるファンクションが異なるパラメータ値で起動されるたびに、それらのパラメータおよびそれぞれの結果がキャッシュに格納されます。それ以降、同じファンクションが同じパラメータ値で起動されると(つまり、キャッシュ・ヒットがある場合)、結果は再計算されるのではなく、キャッシュから取り出されます。

キャッシュ・ヒット用のパラメータ比較の規則は、次に示すように、PL/SQLの「等号」(=)演算子の規則とは異なります。

キャッシュ・ヒットの規則 「等号」演算子の規則
NULLNULLと同じです。 NULL = NULLの評価結果はNULLです。
NULLでないスカラーは、それぞれの値が同一である場合にのみ同じとなります。つまり、指定されたプラットフォームでそれぞれの値が同一のビット・パターンを持っている場合にのみ同じとなります。たとえば、CHAR'AA''AA 'は同じではありません(この規則の方が、「等号」演算子の規則より厳密です)。 NULLでないスカラーは、指定されたプラットフォームでそれぞれの値が同一のビット・パターンを持っていない場合でも、等しくなる可能性があります。たとえば、CHAR'AA''AA 'は等しくなります。

結果キャッシュのバイパス

場合によって、キャッシュはバイパスされます。キャッシュがバイパスされる場合を次に示します。

  • ファンクションが結果をキャッシュから取り出すのではなく、計算する場合。

  • ファンクションが計算する結果がキャッシュに追加されない場合。

キャッシュがバイパスされる場合の例を次にいくつか示します。

  • すべてのセッションでキャッシュを使用できない場合。

    たとえば、データベース管理者がアプリケーションへのパッチの適用中に、結果キャッシュを使用できない状態にした場合などです(「結果がキャッシュされるファンクションが依存するPL/SQLユニットへのホット・パッチの適用」を参照)。

  • 結果がキャッシュされるファンクションのRELIES_ON句に指定された表またはビューに対して、セッションがDML文を実行している場合。このセッションは、そのDML文が完了するまで(コミットまたはロールバックされるまで)そのファンクションの結果キャッシュをバイパスし、その後、そのファンクションのキャッシュの使用を再開します。

    キャッシュをバイパスすると、次のことが保証されます。

    • 各セッションのユーザーは、コミットされていないユーザー独自の変更を参照できます。

    • PL/SQLファンクションの結果キャッシュには、すべてのセッションで参照可能なコミットされた変更のみが含まれます。このため、あるセッションでコミットされていない変更は、他のセッションでは参照できません。

結果がキャッシュされるファンクションによるセッション固有の設定の処理

セッションによって異なる可能性がある設定(NLS_DATE_FORMATTIME ZONEなど)にファンクションが依存している場合は、様々な設定を処理できるようにそのファンクションを変更できる場合にのみ、そのファンクションの結果がキャッシュされるようにします。

次のファンクションについて考えてみます。

例8-26

CREATE OR REPLACE FUNCTION get_hire_date (emp_id NUMBER) RETURN VARCHAR
 RESULT_CACHE RELIES_ON (HR.EMPLOYEES)
IS
  date_hired DATE;
BEGIN
  SELECT hire_date INTO date_hired
    FROM HR.EMPLOYEES
      WHERE EMPLOYEE_ID = emp_id;
  RETURN TO_CHAR(date_hired);
END;
/

前述のファンクションget_hire_dateは、TO_CHARファンクションを使用してDATE項目をVARCHAR項目に変換しています。ファンクションget_hire_dateに書式マスクが指定されていないため、書式マスクは、デフォルトでNLS_DATE_FORMATに指定されている書式マスクになります。get_hire_dateをコールするセッションのNLS_DATE_FORMAT設定が異なっている場合、キャッシュされた結果の書式も異なる可能性があります。あるセッションで計算されてキャッシュされた結果がエージ・アウトされ、別のセッションで再計算された場合、同じパラメータ値に対する場合でも、書式が異なる可能性があります。キャッシュされた結果がセッションで取得され、結果の書式がセッションの書式とは異なる場合、その結果は不適切である可能性があります。

この問題の解決方法をいくつか次に示します。

  • get_hire_dateの戻り型をDATEに変更し、各セッションがTO_CHARファンクションを起動するようにします。

  • ある共通の書式がすべてのセッションで受入れ可能である場合は、書式マスクを指定して、NLS_DATE_FORMATへの依存性を削除します。次に例を示します。

    TO_CHAR(date_hired, 'mm/dd/yy');
    
  • 書式マスクのパラメータをget_hire_dateに追加します。次に例を示します。

    CREATE OR REPLACE FUNCTION get_hire_date
      (emp_id NUMBER, fmt VARCHAR) RETURN VARCHAR
      RESULT_CACHE RELIES_ON (HR.EMPLOYEES)
    IS
      date_hired DATE;
    BEGIN
      SELECT hire_date INTO date_hired
        FROM HR.EMPLOYEES
          WHERE EMPLOYEE_ID = emp_id;
      RETURN TO_CHAR(date_hired, fmt);
    END;
    /
    

結果がキャッシュされるファンクションによるセッション固有のアプリケーション・コンテキストの処理

アプリケーション・コンテキストとは、属性とそれらの値の集合のことです。アプリケーション・コンテキストは、グローバルまたはセッション固有のいずれかになります。PL/SQLファンクションは、次の1つ以上の項目を実行する場合、セッション固有のアプリケーション・コンテキストに依存します。

  • 指定したコンテキストで指定した属性の値を戻す組込みファンクションSYS_CONTEXTの直接起動

  • ファイングレイン・セキュリティのための仮想プライベート・データベース(VPD)・メカニズムを使用したSYS_CONTEXTの間接起動

    (VPDの詳細は、『Oracle Databaseセキュリティ・ガイド』を参照してください)

PL/SQLファンクションの結果キャッシュ機能は、セッション固有のアプリケーション・コンテキストへの依存性を自動的には処理しません。セッション固有のアプリケーション・コンテキストに依存しているファンクションの結果をキャッシュする必要がある場合は、アプリケーション・コンテキストをパラメータとしてファンクションに渡す必要があります。このパラメータにはデフォルト値を指定できるため、すべてのユーザーがこのパラメータを指定する必要があるわけではありません。

例8-27では、表config_tabに、次に示す問合せを変換するVPDポリシーがあると想定しています。

SELECT value FROM config_tab
   WHERE name = param_name;

前述の問合せが、次の問合せに変換されます。

SELECT value FROM config_tab
   WHERE name = param_name
     AND app_id = SYS_CONTEXT('Config', 'App_ID');

例8-27 セッション固有のアプリケーション・コンテキストに依存する、結果がキャッシュされるファンクション

CREATE OR REPLACE FUNCTION get_param_value
  (param_name VARCHAR,
   appctx     VARCHAR  DEFAULT SYS_CONTEXT('Config', 'App_ID')
  )
  RETURN VARCHAR
  RESULT_CACHE RELIES_ON (config_tab)
IS
  rec VARCHAR(2000);
BEGIN
  SELECT value INTO rec
    FROM config_tab
      WHERE Name = param_name;
END;
/

結果キャッシュの粒度の選択

PL/SQLにはファンクション結果キャッシュが用意されていますが、キャッシュの粒度はユーザーが選択します。粒度の概念を理解するために、Order Entry(OE)サンプル・スキーマ内のProduct_Descriptions表について考えてみます。

NAME                     NULL?      TYPE
----------------------   --------   ---------------
PRODUCT_ID               NOT NULL   NUMBER(6)
LANGUAGE_ID              NOT NULL   VARCHAR2(3)
TRANSLATED_NAME          NOT NULL   NVARCHAR2(50)
TRANSLATED_DESCRIPTION   NOT NULL   NVARCHAR2(2000)

この表には、各製品の名前と説明が複数の言語で記載されています。各行の一意のキーは、PRODUCT_IDおよびLANGUAGE_IDです。

PRODUCT_IDおよびLANGUAGE_IDを受け取って、関連付けられたTRANSLATED_NAMEを戻すファンクションを定義するとします。また、変換された名前をキャッシュする必要もあるとします。これらの名前をキャッシュする場合の粒度の選択肢の一部を次に示します。

  • 一度に1つの名前(粒度が細かい)

  • 一度に1つの言語(粒度が粗い)

表8-3 粒度が細かいキャッシュと粗いキャッシュの比較

粒度が細かい 粒度が粗い

各ファンクション結果は、1つの論理結果に対応しています。

各ファンクション結果には、多数の論理的部分結果が含まれています。

1回以上必要とされるデータのみを格納します。

使用されることのないデータを格納する場合もあります。

各データ項目は、個別にエージ・アウトされます。

1つのデータ項目がエージ・アウトされると、全体がエージ・アウトされます。

バルク・ロードは最適化できません。

バルク・ロードを最適化できます。


次の4つの各例では、ファンクションproductNamePRODUCT_IDおよびLANGUAGE_IDを受け取って、関連付けられたTRANSLATED_NAMEを戻しています。productNameの各バージョンは、変換された名前をキャッシュしますが、キャッシュする際の粒度はそれぞれ異なっています。

例8-28では、get_product_name_1は結果がキャッシュされるファンクションです。get_product_name_1は、別のPRODUCT_IDおよびLANGUAGE_IDで起動されると、常に関連付けられたTRANSLATED_NAMEをキャッシュします。get_product_name_1がコールされるたびに、最大1つのTRANSLATED_NAMEがキャッシュに追加されます。

例8-28 一度に1つの名前のキャッシュ(粒度が細かい)

CREATE OR REPLACE FUNCTION get_product_name_1 (prod_id NUMBER, lang_id VARCHAR2)
  RETURN NVARCHAR2
  RESULT_CACHE RELIES_ON (Product_Descriptions)
IS
  result VARCHAR2(50);
BEGIN
  SELECT translated_name INTO result
    FROM Product_Descriptions
      WHERE PRODUCT_ID = prod_id
        AND LANGUAGE_ID = lang_id;
  RETURN result;
END;

例8-29では、get_product_name_2は、結果がキャッシュされるファンクションall_product_namesを定義します。get_product_name_2が別のLANGUAGE_IDall_product_namesを起動すると、常にall_product_namesはそのLANGUAGE_IDに関連付けられたすべてのTRANSLATED_NAMEをキャッシュします。all_product_namesがコールされるたびに、最大1つのLANGUAGE_IDのすべてのTRANSLATED_NAMEがキャッシュに追加されます。

例8-29 一度に1つの言語の変換された名前のキャッシュ(粒度が粗い)

CREATE OR REPLACE FUNCTION get_product_name_2 (prod_id NUMBER, lang_id VARCHAR2)
  RETURN NVARCHAR2
IS
  TYPE product_names IS TABLE OF NVARCHAR2(50) INDEX BY PLS_INTEGER;

  FUNCTION all_product_names (lang_id NUMBER) RETURN product_names
    RESULT_CACHE RELIES_ON (Product_Descriptions)
  IS
    all_names product_names;
  BEGIN
    FOR c IN (SELECT * FROM Product_Descriptions
      WHERE LANGUAGE_ID = lang_id) LOOP
        all_names(c.PRODUCT_ID) := c.TRANSLATED_NAME;
    END LOOP;
    RETURN all_names;
  END;
BEGIN
  RETURN all_product_names(lang_id)(prod_id);
END;

Oracle RAC環境での結果キャッシュ

キャッシュされた結果はシステム・グローバル領域(SGA)に格納されます。Oracle RAC環境では、各データベース・インスタンスに、そのインスタンス上のセッションでのみ使用できるプライベートなファンクション結果キャッシュがあります。

インスタンスのアクセス・パターンおよびワークロードによって、そのインスタンスのプライベート・キャッシュに格納される結果セットが決定されます。このため、インスタンスが異なると、そのプライベート・キャッシュに格納される結果セットも異なります。

必要な結果がローカル・インスタンスのプライベート・キャッシュから欠落している場合、ファンクションの本体が実行されて、結果が計算されます。その後、この結果がローカル・キャッシュに追加されます。この結果が別のインスタンスのプライベート・キャッシュから取り出されることはありません。

各データベース・インスタンスには、そのインスタンス独自のキャッシュされた結果セットが含まれている可能性がありますが、無効な結果を処理するメカニズムはOracle RAC環境全体にわたります。ローカル・インスタンスの結果キャッシュでのみ結果が無効にされた場合、他のインスタンスで無効な結果が使用される可能性があります。たとえば、データベース表内のデータから計算される品目の価格の結果キャッシュについて考えてみます。ある品目の価格に影響を与える方法でこれらのデータベース表のいずれかが更新された場合、キャッシュされたその品目の価格をOracle RAC環境内のすべてのデータベース・インスタンスで無効にする必要があります。

結果キャッシュの管理

PL/SQLファンクション結果キャッシュは、その管理および管理性インフラストラクチャを結果キャッシュと共有します。結果キャッシュの詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。

データベース管理者は、次のものを使用して、結果キャッシュを管理できます。

  • RESULT_CACHE_MAX_SIZEおよびRESULT_CACHE_MAX_RESULT初期化パラメータ

    RESULT_CACHE_MAX_SIZEは、結果キャッシュが使用できるSGAメモリーの最大量を(バイト単位で)指定し、RESULT_CACHE_MAX_RESULTは、1つの結果が使用できる結果キャッシュの最大パーセンテージを指定します。これらのパラメータの詳細は、『Oracle Databaseリファレンス』および『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。


    参照:

    • RESULT_CACHE_MAX_SIZEの詳細は、『Oracle Databaseリファレンス』を参照してください。

    • RESULT_CACHE_MAX_RESULTの詳細は、『Oracle Databaseリファレンス』を参照してください。

    • 結果キャッシュの概念の詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。


  • DBMS_RESULT_CACHEパッケージ

    DBMS_RESULT_CACHEパッケージでは、DBAが、SQL結果キャッシュおよびPL/SQLファンクション結果キャッシュによって使用される共有プールのその部分を管理できるインタフェースが提供されます。このパッケージの詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。

  • 動的パフォーマンス・ビュー:

    • [G]V$RESULT_CACHE_STATISTICS

    • [G]V$RESULT_CACHE_MEMORY

    • [G]V$RESULT_CACHE_OBJECTS

    • [G]V$RESULT_CACHE_DEPENDENCY

    [G]V$RESULT_CACHE_STATISTICS[G]V$RESULT_CACHE_MEMORY[G]V$RESULT_CACHE_OBJECTSおよび[G]V$RESULT_CACHE_DEPENDENCYの詳細は、『Oracle Databaseリファレンス』を参照してください。

結果がキャッシュされるファンクションが依存するPL/SQLユニットへのホット・パッチの適用

結果がキャッシュされるファンクションが依存するPL/SQLユニットに(直接または間接的に)ホット・パッチを適用する際、結果がキャッシュされるファンクションに関連付けられているキャッシュされた結果がすべての場合に自動的にフラッシュされるとはかぎりません。

たとえば、結果がキャッシュされるファンクションP1.foo()がパッケージ・サブプログラムP2.bar()に依存しているとします。パッケージP2の本体の新しいバージョンがロードされた場合、P1.foo()に関連付けられているキャッシュされた結果は、自動的にはフラッシュされません。

このため、PL/SQLユニットへのホット・パッチの適用には、次の手順を実行することをお薦めします。

  1. 結果キャッシュをバイパス・モードに設定し、既存の結果をフラッシュします。

    BEGIN
      DBMS_RESULT_CACHE.Bypass(TRUE);
      DBMS_RESULT_CACHE.Flush;
    END;
    /
    

    Oracle RAC環境では、各データベース・インスタンスに対してこの手順を実行します。

  2. PL/SQLコードにパッチを適用します。

  3. 結果キャッシュの使用を再開します。

    BEGIN
      DBMS_RESULT_CACHE.Bypass(FALSE);
    END;
    /
    

    Oracle RAC環境では、各データベース・インスタンスに対してこの手順を実行します。