ヘッダーをスキップ

Oracle Database アプリケーション開発者ガイド-基礎編
10gリリース2(10.2)

B19248-02
目次
目次
索引
索引

戻る 次へ

7 PL/SQLプロシージャおよびパッケージのコード化

この章では、Oracle Databaseがアプリケーション開発用に提供するプロシージャ機能のいくつかを説明します。この章の内容は次のとおりです。

PL/SQLプログラム・ユニットの概要

PL/SQLは、ブロック構造化プログラミング言語です。この言語が持ついくつかの機能を使用すると、高性能のデータベース・アプリケーションを容易に作成できます。たとえば、PL/SQLは、ループ文や条件文など標準SQLにはないプロシージャ構造を提供します。

PL/SQLブロック内部でSQLデータ操作言語(DML)の文を直接入力できます。また、Oracleが提供するプロシージャを使用して、データ定義言語(DDL)の文を実行できます。

PL/SQLコードはサーバー上で実行されるため、PL/SQLを使用するとデータベース・アプリケーションのかなりの部分を集中化でき、メンテナンス性およびセキュリティが強化されます。また、クライアント/サーバー・アプリケーションでは、ネットワークのオーバーヘッドも大幅に削減できます。


注意

Oracle Formsなど、一部のOracleのツール製品にはPL/SQLエンジンが組み込まれ、ローカルでPL/SQLを実行できます。 


また、一部のデータベース・アプリケーションでは、埋込みSQLまたはOracle Call Interface(OCI)を使用する3GLプログラムのかわりにPL/SQLを使用できます。

PL/SQLプログラム・ユニットには、次のものが含まれます。

無名ブロック

無名ブロックとは、名前のないPL/SQLプログラム・ユニットのことです。無名ブロックは、オプションの宣言部分、実行可能部分および1つまたは複数のオプションの例外ハンドラで構成されます。

宣言部にはPL/SQLの変数、例外およびカーソルを宣言します。実行可能部分にはPL/SQLコードおよびSQL文を含むネストされたブロックを含めることができます。例外ハンドラには、例外状況が発生したときに、事前定義のPL/SQL例外(NO_DATA_FOUNDまたはZERO_DIVIDE)として、またはユーザー定義の例外としてコールされるコードが含まれています。

次の無名PL/SQLブロックの例では、DBMS_OUTPUTパッケージを使用して、hr.employees表の部門20に所属するすべての従業員の名前を表示します。

DECLARE
   Last_name    VARCHAR2(10);
   Cursor       c1 IS SELECT last_name 
                       FROM employees
                       WHERE department_id = 20;
BEGIN
   OPEN c1;
   LOOP
      FETCH c1 INTO Last_name;
      EXIT WHEN c1%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE(Last_name);
   END LOOP;
END;
/


注意

SQL*Plusを使用してこのブロックをテストする場合は、DBMS_OUTPUTプロシージャを使用する出力(たとえば、PUT_LINE)がアクティブになるように、SET SERVEROUTPUT ONを入力します。また、出力をアクティブにするには、スラッシュ(/)を付けて例を終了します。 


例外を使用すると、PL/SQLプログラム・ロジック内のOracle Databaseエラー条件を処理できます。これによって、使用中のアプリケーションで、クライアント・アプリケーションを異常終了させるようなエラーをサーバーが発行しないようにできます。次の無名ブロックは、事前定義されたOracle Database例外NO_DATA_FOUNDを処理します(この例外が処理されない場合は、ORA-01403エラーが発生します)。

DECLARE
   Emp_number   INTEGER := 9999;
   Emp_name     VARCHAR2(10);
BEGIN
   SELECT Ename INTO Emp_name FROM Emp_tab
      WHERE Empno = Emp_number;   -- no such number
   DBMS_OUTPUT.PUT_LINE('Employee name is ' || Emp_name);
EXCEPTION
   WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('No such employee: ' || Emp_number);
END;

また、独自の例外を定義してブロックの宣言部に宣言し、それをブロックの例外部分に指定できます。次に例を示します。

DECLARE
   Emp_name           VARCHAR2(10);
   Emp_number         INTEGER;
   Empno_out_of_range EXCEPTION;
BEGIN
   Emp_number := 10001;
   IF Emp_number > 9999 OR Emp_number < 1000 THEN
      RAISE Empno_out_of_range;
   ELSE
      SELECT Ename INTO Emp_name FROM Emp_tab
         WHERE Empno = Emp_number;
      DBMS_OUTPUT.PUT_LINE('Employee name is ' || Emp_name);
END IF;
EXCEPTION
   WHEN Empno_out_of_range THEN
      DBMS_OUTPUT.PUT_LINE('Employee number ' || Emp_number ||
       ' is out of range.');
END;

無名ブロックは、通常、SQL*Plusなどのツール製品から対話形式で使用するか、プリコンパイラ、OCIまたはSQL*Moduleアプリケーションで使用します。通常、ストアド・プロシージャをコールするか、カーソル変数をオープンするために使用します。

関連項目

 

ストアド・プログラム・ユニット(プロシージャ、ファンクションおよびパッケージ)

ストアド・プロシージャ、ファンクションおよびパッケージは、次の特長を持つPL/SQLプログラム・ユニットです。

プロシージャおよびファンクションのネーミング

プロシージャまたはファンクションはデータベース内に格納されるため、名前を付ける必要があります。名前を付けることによって、他のストアド・プロシージャと区別され、アプリケーションでコールすることができます。パブリックで参照できるスキーマ内の個々のプロシージャまたはファンクションは、一意の名前を持つ必要があります。その名前は、有効なPL/SQL識別子である必要があります。


注意

SQL*Moduleによって生成されたスタブを使用してストアド・プロシージャをコールする場合、ストアド・プロシージャ名は、コール側ホストの3GL言語(AdaやCなど)の有効な識別子である必要もあります。 


プロシージャおよびファンクションのパラメータ

ストアド・プロシージャおよびファンクションには、パラメータを指定できます。次に、「無名ブロック」で説明されている無名ブロックに類似したストアド・プロシージャの例を示します。


注意

次の文を実行するには、CREATE OR REPLACE PROCEDURE...を使用してください。 


PROCEDURE Get_emp_names (Dept_num IN NUMBER) IS
   Emp_name       VARCHAR2(10);
   CURSOR         c1 (Depno NUMBER) IS
                     SELECT Ename FROM Emp_tab
                        WHERE deptno = Depno;
BEGIN
   OPEN c1(Dept_num);
   LOOP
      FETCH c1 INTO Emp_name;
      EXIT WHEN C1%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE(Emp_name);
   END LOOP;
   CLOSE c1;
END;

このストアド・プロシージャの例では、部門番号が入力パラメータになっています。入力パラメータは、パラメータ化されたカーソルc1のオープン時に使用されます。

プロシージャの仮パラメータには、表7-1に示す3つの主要な属性があります。

表 7-1    プロシージャ・パラメータの属性 
パラメータ属性  説明 

名前 

名前は、有効なPL/SQL識別子である必要があります。 

モード 

入力のみのパラメータ(IN)、出力のみのパラメータ(OUT)、入力と出力の両方のパラメータ(IN OUT)のどれであるかを示します。モードを指定しないと、INが想定されます。 

データ型 

パラメータのデータ型は、標準PL/SQLデータ型です。  

パラメータ・モード

パラメータ・モードは、仮パラメータの動作を定義します。3つのパラメータ・モード、IN(デフォルト)、OUTおよびIN OUTは、どのようなサブプログラムを使用する場合にも使用できます。ただし、OUTモードおよびIN OUTモードはファンクションには使用しないでください。ファンクションの目的は、引数をとらず、1つの値を戻すことです。ファンクションが複数の値を戻すようなプログラミングは、効率的ではありません。また、サブプログラムでローカルではない変数の値を変更するような副作用をファンクションが与えないようにしてください。

表7-2に、パラメータ・モードの概要を示します。

表 7-2    パラメータ・モード 
IN  OUT  IN OUT 

デフォルト 

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

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

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

値をコール側に戻します。 

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

仮パラメータが定数として動作します。 

仮パラメータが未初期化変数として動作します。 

仮パラメータが初期化変数として動作します。 

仮パラメータに値を割り当てることはできません。 

仮パラメータを式の中で使用できません。値を割り当てる必要があります。 

仮パラメータに値を割り当てる必要があります。 

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

実パラメータは変数である必要があります。 

実パラメータは変数である必要があります。 

関連項目

パラメータ・モードの詳細は、『PL/SQLユーザーズ・ガイドおよびリファレンス』を参照してください。 

パラメータのデータ型

仮パラメータのデータ型は、次のいずれかで構成されています。

%TYPE属性および%ROWTYPE属性

型属性%TYPEおよび%ROWTYPEは、パラメータを制約するために使用します。たとえば、「プロシージャおよびファンクションのパラメータ」にあるGet_emp_namesプロシージャの仕様部は、次のように作成できます。

PROCEDURE Get_emp_names(Dept_num IN Emp_tab.Deptno%TYPE)

これによって、Dept_numパラメータがEmp_tab表のDeptno列と同じデータ型を取ります。%TYPE(または%ROWTYPE)を使用した宣言を作成する場合は、列および表が使用可能である必要があります。

表の列の型が変更されてもアプリケーション・コードを変更する必要がないため、%TYPEの使用をお薦めします。

Get_emp_namesプロシージャがパッケージの一部である場合は、前に宣言したパブリック(パッケージ)変数を使用して、パラメータのデータ型を制約できます。次に例を示します。

Dept_number    number(2);
...
PROCEDURE Get_emp_names(Dept_num IN Dept_number%TYPE);

%ROWTYPE属性は、指定された表のすべての列を含むレコードを作成するために使用します。次の例では、Get_emp_recプロシージャを定義して、指定されたempnoに関する PL/SQLレコード内のEmp_tab表のすべての列を戻します。


注意

次の文を実行するには、CREATE OR REPLACE PROCEDURE...を使用してください。 


PROCEDURE Get_emp_rec (Emp_number  IN  Emp_tab.Empno%TYPE,
                       Emp_ret     OUT Emp_tab%ROWTYPE) IS
BEGIN
   SELECT Empno, Ename, Job, Mgr, Hiredate, Sal, Comm, Deptno
      INTO Emp_ret
      FROM Emp_tab
      WHERE Empno = Emp_number;
END;

次のようにして、PL/SQLブロックからこのプロシージャをコールできます。

DECLARE
   Emp_row      Emp_tab%ROWTYPE;     -- declare a record matching a
                                     -- row in the Emp_tab table
BEGIN
   Get_emp_rec(7499, Emp_row);   -- call for Emp_tab# 7499
   DBMS_OUTPUT.PUT(Emp_row.Ename || ' '                || Emp_row.Empno);
   DBMS_OUTPUT.PUT(' '           || Emp_row.Job || ' ' || Emp_row.Mgr);
   DBMS_OUTPUT.PUT(' '           || Emp_row.Hiredate   || ' ' || Emp_row.Sal);
   DBMS_OUTPUT.PUT(' '           || Emp_row.Comm || ' '|| Emp_row.Deptno);
   DBMS_OUTPUT.NEW_LINE;
END;

ストアド・ファンクションは、%ROWTYPEを使用して宣言される値を戻すこともできます。次に例を示します。

FUNCTION Get_emp_rec (Dept_num IN Emp_tab.Deptno%TYPE)
   RETURN Emp_tab%ROWTYPE IS ...
表およびレコード

PL/SQL表を、パラメータとしてストアド・プロシージャおよびファンクションに渡せます。レコードの表も、パラメータとして渡せます。


注意

リモート・プロシージャにPL/SQL表やレコードなどのユーザー定義型を渡す場合、タイプ・チェッカがソースを検証できるようにPL/SQLで同じ定義を使用するには、冗長なループバックDBLINKを作成してください。PL/SQLのコンパイル時に、両方のソースが同じ位置から引き出されます。  


デフォルトのパラメータ値

パラメータには、デフォルト値を設定できます。パラメータにデフォルト値を設定するには、DEFAULTキーワードまたは代入演算子を使用します。たとえば、Get_emp_namesプロシージャの仕様部は次のように作成できます。

PROCEDURE Get_emp_names (Dept_num IN NUMBER DEFAULT 20) IS ...

または

PROCEDURE Get_emp_names (Dept_num IN NUMBER := 20) IS ...

パラメータにデフォルト値を使用する場合は、プロシージャのコール時に実パラメータ・リストからそのパラメータを省略できます。コール時にパラメータ値を指定すると、デフォルト値がオーバーライドされます。


注意

無名PL/SQLブロック内とは異なり、ストアド・プロシージャ内では、変数、カーソルおよび例外の宣言の前にキーワードDECLAREを使用しないでください。使用するとエラーが発生します。 


ストアド・プロシージャおよびファンクションの作成

プロシージャまたはファンクションを作成するには、テキスト・エディタを使用します。プロシージャの先頭に、次の文を記述します。

CREATE PROCEDURE Procedure_name AS   ...

たとえば、「%TYPE属性および%ROWTYPE属性」にある例を使用する場合は、次のコードを含むget_emp.sqlというテキスト(ソース)・ファイルを作成します。

CREATE PROCEDURE Get_emp_rec (Emp_number  IN  Emp_tab.Empno%TYPE,
                              Emp_ret     OUT Emp_tab%ROWTYPE) AS
BEGIN
   SELECT Empno, Ename, Job, Mgr, Hiredate, Sal, Comm, Deptno
      INTO Emp_ret
      FROM Emp_tab
      WHERE Empno = Emp_number;
END;
/

その後、SQL*Plusなどの対話形式のツール製品を使用して次の文を入力し、プロシージャを含むテキスト・ファイルをロードします。

SQL> @get_emp

プロシージャが、get_emp.sqlファイル(sqlは、デフォルトのファイル拡張子)から現行のスキーマにロードされます。コードの終わりにはスラッシュ(/)を付けてください。これはコードの一部ではありません。プロシージャのロードをアクティブにするためのものです。

ファンクションを格納するには、CREATE [OR REPLACE] FUNCTION...文を使用します。


注意

新しいプロシージャを作成する場合、通常はCREATE OR REPLACE... PROCEDURE文を使用する方が便利です。このコマンドは、同一スキーマ内の前のバージョンのプロシージャを新しいバージョンに置き換えます。ただし、これは警告なしで実行されます。 


プロシージャ・パラメータ・リストの後にキーワードISまたはASを使用できます。

関連項目

CREATE PROCEDURE文およびCREATE FUNCTION文の構文の詳細は、『Oracle Databaseリファレンス』を参照してください。 

プロシージャおよびファンクションの作成に必要な権限

スタンドアロン・プロシージャまたはファンクション、あるいはパッケージ仕様部または本体を作成するには、次の権限が必要です。

プロシージャまたはパッケージの所有者の権限が変更された場合、実行前にそのプロシージャを再認証する必要があります。参照オブジェクトに必要な権限が、そのプロシージャまたはパッケージの所有者から取り消されている場合、そのプロシージャは実行できません。

プロシージャのEXECUTE権限があれば、他のユーザーが所有するプロシージャを実行できます。権限が付与されたユーザーは、そのプロシージャの所有者のセキュリティ・ドメインでプロシージャを実行します。このため、ユーザーは、プロシージャが参照するオブジェクトの権限を得る必要はありません。これによって、データベース・アプリケーションおよびそのユーザーによるさらに統制のとれた効率的なセキュリティ計画が可能になります。また、すべてのプロシージャおよびパッケージが(SYSTEM表領域内の)データ・ディクショナリに格納されます。プロシージャおよびパッケージを作成するユーザーが使用できる領域の容量は、割当て制限によっては制御されません。


注意

パッケージの作成にはソートが必要です。このため、パッケージを作成するユーザーは、対応付けられている一時表領域にソート・セグメントを作成できる必要があります。  


関連項目

「プロシージャの実行に必要な権限」 

ストアド・プロシージャおよびファンクションの変更

ストアド・プロシージャまたはファンクションを変更するには、DROP PROCEDURE文またはDROP FUNCTION文を使用して削除(DROP)した後、CREATE PROCEDURE文またはCREATE FUNCTION文を使用して再作成する必要があります。または、CREATE OR REPLACE PROCEDURE文またはCREATE OR REPLACE FUNCTION文を使用します。この文は、プロシージャまたはファンクションが存在する場合、まずそれを削除してから、指定どおりに再作成します。


注意

プロシージャまたはファンクションは警告なく削除されます。 


プロシージャおよびファンクションの削除

SQL文のDROP PROCEDUREDROP FUNCTIONDROP PACKAGE BODYおよびDROP PACKAGEを使用して、スタンドアロン・プロシージャ、スタンドアロン・ファンクション、パッケージ本体またはパッケージ全体を、それぞれ削除できます。 DROP PACKAGE文は、パッケージの仕様部と本体の両方を削除します。

次の文は、スキーマ内にあるOld_sal_raiseプロシージャを削除します。

DROP PROCEDURE Old_sal_raise;
プロシージャおよびファンクションの削除に必要な権限

プロシージャ、ファンクションまたはパッケージを削除するには、それらが自スキーマ内にあるか、またはDROP ANY PROCEDURE権限が必要です。パッケージ内の個々のプロシージャは削除できません。これらを削除せずに、パッケージ仕様部および本体を再作成する必要があります。

外部プロシージャ

Oracle Databaseのインスタンス上で実行するPL/SQLプロシージャは、3GLで作成された外部プロシージャをコールできます。3GLプロシージャは、データベースのアドレス空間とは別のアドレス空間で実行されます。

関連項目

外部プロシージャの詳細は、第14章「外部プロシージャのコール」を参照してください。 

PL/SQLパッケージ

パッケージとは、データベース内に格納されている関連プログラム・オブジェクト(プロシージャ、ファンクション、変数、定数、カーソル、例外など)がカプセル化されたコレクションです。

パッケージは、プロシージャおよびファンクションをスタンドアロンのスキーマ・オブジェクトとして作成するかわりに使用します。パッケージは、スタンドアロンのプロシージャおよびファンクションに比べて、多数のメリットがあります。たとえば、次のことができます。

パッケージ仕様部は、パッケージの有効範囲外で参照できるパブリック型、変数、定数およびサブプログラムを宣言します。パッケージ本体は、パッケージ外のアプリケーションが参照できないプライベート・オブジェクトのみでなく、仕様部で宣言されているオブジェクトも定義します。

PL/SQLパッケージ仕様部と本体の例

次に、Employee_managementというパッケージのパッケージ本体を示します。パッケージには、1つのストアド・ファンクションおよび2つのストアド・プロシージャが含まれています。このパッケージ本体は、ファンクションおよびプロシージャを定義します。

CREATE PACKAGE BODY Employee_management AS
   FUNCTION Hire_emp (Name VARCHAR2, Job VARCHAR2,
      Mgr NUMBER, Hiredate DATE, Sal NUMBER, Comm NUMBER,
      Deptno NUMBER) RETURN NUMBER IS
       New_empno    NUMBER(10);

-- This function accepts all arguments for the fields in
-- the employee table except for the employee number.
-- A value for this field is supplied by a sequence.
-- The function returns the sequence number generated
-- by the call to this function.

   BEGIN
      SELECT Emp_sequence.NEXTVAL INTO New_empno FROM dual;
      INSERT INTO Emp_tab VALUES (New_empno, Name, Job, Mgr,
         Hiredate, Sal, Comm, Deptno);
      RETURN (New_empno);
   END Hire_emp;

   PROCEDURE fire_emp(emp_id IN NUMBER) AS

-- This procedure deletes the employee with an employee
-- number that corresponds to the argument Emp_id. If
-- no employee is found, then an exception is raised.

   BEGIN
      DELETE FROM Emp_tab WHERE Empno = Emp_id;
      IF SQL%NOTFOUND THEN
      Raise_application_error(-20011, 'Invalid Employee
         Number: ' || TO_CHAR(Emp_id));
   END IF;
END fire_emp;

PROCEDURE Sal_raise (Emp_id IN NUMBER, Sal_incr IN NUMBER) AS

-- This procedure accepts two arguments. Emp_id is a
-- number that corresponds to an employee number.
-- SAL_INCR is the amount by which to increase the
-- employee's salary. If employee exists, then update 
-- salary with increase.

   BEGIN
      UPDATE Emp_tab
         SET Sal = Sal + Sal_incr
         WHERE Empno = Emp_id;
      IF SQL%NOTFOUND THEN
         Raise_application_error(-20011, 'Invalid Employee
            Number: ' || TO_CHAR(Emp_id));
      END IF;
   END Sal_raise;
END Employee_management;


注意

この例を実行する場合、まず順序番号Emp_sequenceを作成します。次のSQL文を使用して作成します。

SQL> CREATE SEQUENCE Emp_sequence
> START WITH 8000 INCREMENT BY 10;
 

PL/SQLオブジェクト・サイズの制限

プロシージャ、ファンクション、トリガー、パッケージなどのPL/SQLストアド・データベース・オブジェクトのサイズは、共有プール内のDIANA(Descriptive Intermediate Attributed Notation for Ada)コードのサイズ(バイト単位)に制限されています。 フラット化されたDIANA/pcodeのサイズは、UNIXでは64KBに制限されていますが、デスクトップ・プラットフォームでは32KBに制限されている場合があります。

ユーザーがアクセスできるもので最も密接に関連する数値は、データ・ディクショナリ・ビューUSER_OBJECT_SIZEPARSED_SIZEです。これには、SYS.IDL_xxx$表に格納されたDIANAのサイズがバイト単位で示されています。これは共有プールでのサイズではありません。(コンパイル中に使用される)PL/SQLコードのDIANA部分のサイズは、システム表内より共有プール内で非常に大きくなります。

パッケージの作成

パッケージの各部は、異なる文を使用して作成します。パッケージ仕様部は、CREATE PACKAGE文を使用して作成します。CREATE PACKAGE文でパブリック・パッケージ・オブジェクトを宣言します。

パッケージ本体を作成するには、CREATE PACKAGE BODY文を使用します。CREATE PACKAGE BODY文は、パッケージ仕様部で宣言されているパブリック・プロシージャおよびファンクションの手続き型コードを定義します。

パッケージ本体にはプライベート(またはローカル)・パッケージ・プロシージャ、ファンクションおよび変数を定義することもできます。これらのオブジェクトは、同一パッケージの本体内の他のプロシージャおよびファンクションでのみアクセスできます。外部ユーザーはどの権限を持っていても参照できません。

初めてアプリケーションを開発する場合、CREATE PACKAGE文またはCREATE PACKAGE BODY文にOR REPLACE句を追加すると便利な場合があります。このオプションの効果は、警告なしでパッケージまたはパッケージ本体が削除されることです。CREATE文は、次のようになります。

CREATE OR REPLACE PACKAGE Package_name AS ...

および

CREATE OR REPLACE PACKAGE BODY Package_name AS ...
パッケージ・オブジェクトの作成

パッケージ本体には、次のものを含めることができます。

パッケージ仕様部に宣言されているプロシージャ、ファンクション、カーソルおよび変数はグローバルです。これらをコールまたは使用できるのは、パッケージに対するEXECUTE権限を持つ外部ユーザーまたはEXECUTE ANY PROCEDURE権限を持つ外部ユーザーです。

パッケージ本体を作成する場合は、本体に定義する個々のプロシージャが、パッケージ仕様部の宣言と同じパラメータ(名前、データ型およびモード)を持つようにする必要があります。パッケージ本体内のファンクションは、パラメータと戻り型が、名前およびデータ型について仕様部と一致する必要があります。

パッケージの作成または削除に必要な権限

パッケージ仕様部またはパッケージ本体を作成または削除するために必要な権限は、スタンドアロン・プロシージャまたはファンクションの作成または削除に必要な権限と同じです。

関連項目

 

パッケージおよびパッケージ・オブジェクトのネーミング

パッケージおよびパッケージ内のすべてのパブリック・オブジェクトの名前は、所定のスキーマ内で一意である必要があります。パッケージ仕様部およびその本体は、同じ名前である必要があります。また、パッケージのメンバーの名前は、プロシージャ名の重複が必要な場合を除き、そのパッケージの有効範囲内で一意である必要があります。

パッケージの無効化とセッションの状態

パッケージ・オブジェクトを参照する各セッションは、対応するパッケージの独自のインスタンスを持っています。この中には、パブリック変数、プライベート変数、カーソルおよび定数に対する持続状態が含まれます。セッションのインスタンス化されたパッケージ(仕様部または本体)のいずれかが無効化されると、そのセッションのすべてのパッケージ・インスタンスが無効になり、再コンパイルされます。これにより、そのセッションのすべてのパッケージ・インスタンスに関するセッション状態は失われます。

セッションのパッケージが無効である場合、セッションが無効なパッケージ・インスタンスのオブジェクトを最初に使用しようとしたときに、次のようなエラーが戻されます。

ORA-04068: パッケージstring string stringの既存状態は廃棄されました。

2度目にセッションがこのようなパッケージ・コールを行うと、エラーは発生せずに、パッケージはセッションに対して再インスタンス化されます。


注意

パフォーマンスを最適な状態に保つため、Oracle Databaseがこのエラー・メッセージを戻すのは、パッケージ状態が破棄されるごとに1回のみです。

アプリケーションでこのエラーを処理する場合は、適切な処理方法を採用するように注意してください。たとえば、あるパッケージ内のプロシージャが別のパッケージ内のプロシージャをコールするものである場合には、両方のパッケージに対するセッション状態が失われていることを考慮するように、アプリケーションを作成する必要があります。  


本番環境の多くでは、パッケージが無効になるDDL操作は、通常、業務時間外に行われます。したがって、エンド・ユーザー・アプリケーションでは、このような状況は問題にならない可能性もあります。しかし、パッケージが業務時間中に無効になることがよくある場合は、パッケージ・コールが行われたときにこのエラーを処理するように、アプリケーションを作成することが必要になります。

Oracle Databaseが提供するパッケージ

データベースの機能性を拡張できるように、またはPL/SQLでSQL機能を使用できるように、Oracle Databaseには多数のパッケージが組み込まれています。これらのパッケージをアプリケーションからコールできます。

関連項目

これらのOracle Databaseパッケージの概要については、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。 

バルク・バインドの概要

Oracle Databaseは2つのエンジンを使用して、PL/SQLブロックおよびサブプログラムを実行します。PL/SQLエンジンは手続き型の文を実行し、SQLエンジンはSQL文を実行します。実行中は、すべてのSQL文がこの2つのエンジン間でコンテキストをスイッチングするため、パフォーマンスが低下します。

特定のブロックまたはサブプログラムの実行に必要なコンテキストのスイッチング回数を最小化すると、パフォーマンスを大幅に改善できます。バインド変数としてコレクション要素を使用するループ内でSQL文が実行される場合、ブロックが必要とする多数のコンテキストのスイッチングによってパフォーマンスが低下することがあります。コレクションには次のものが含まれます。

バインドとは、SQL文内のPL/SQL変数に対して値を代入することです。バルク・バインドとは、コレクション全体を一度にバインドすることです。バルク・バインドは1つの操作でコレクション全体を2つのエンジン間で受け渡すことができます。

通常、バルク・バインドの使用によって、4つ以上のデータベース行に影響するSQL文のパフォーマンスが改善されます。SQL文によって影響される行数が多いほど、バルク・バインドによるパフォーマンスの向上率は高くなります。


注意

この項では、PL/SQLアプリケーション内でバルク・バインドを使用するかどうかを判断するために役立つ、バルク・バインドの概要を説明します。バルク・バインドを操作する場合の例外の処理方法を含むバルク・バインドの使用方法については、『PL/SQLユーザーズ・ガイドおよびリファレンス』を参照してください。 


バルク・バインドを使用する場合

アプリケーションで次のような使用例がある場合、パフォーマンスを向上させるために、バルク・バインドの使用を検討します。

コレクションを参照するDML文

FORALLキーワードによって、コレクション文を参照するINSERT文、UPDATE文またはDELETE文のパフォーマンスを改善できます。

たとえば、次のPL/SQLブロックは、管理者のID番号が7902、7698または7839の従業員の給料を増額するものですが、バルク・バインドを使用する部分としない部分の両方があります。

DECLARE
   TYPE Numlist IS VARRAY (100) OF NUMBER;
   Id NUMLIST := NUMLIST(7902, 7698, 7839);
BEGIN

-- Efficient method, using a bulk bind
   FORALL i IN Id.FIRST..Id.LAST   -- bulk-bind the VARRAY
      UPDATE Emp_tab SET Sal = 1.1 * Sal
      WHERE Mgr = Id(i);

-- Slower method, running the UPDATE statements within a regular loop
   FOR i IN Id.FIRST..Id.LAST LOOP
      UPDATE Emp_tab SET Sal = 1.1 * Sal
      WHERE Mgr = Id(i);
   END LOOP;
END;

PL/SQLがバルク・バインドを使用しないで、各従業員を更新するためにSQLエンジンにSQL文を送信すると、コンテキストのスイッチングによってパフォーマンスが低下します。

PL/SQL表に用意された一連の行がある場合、次のようなループを使用して、データをバルク挿入またはバルク更新できます。

FORALL i in Emp_Data.FIRST..Emp_Data.LAST
    INSERT INTO Emp_tab VALUES(Emp_Data(i));
コレクションを参照するSELECT文

BULK COLLECT INTO句によって、コレクションを参照する問合せのパフォーマンスを改善できます。

たとえば、次のPL/SQLブロックは、バルク・バインドを使用して複数の値をPL/SQL表に問い合せます。

-- Find all employees whose manager's ID number is 7698.
DECLARE
   TYPE Var_tab IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
   Empno VAR_TAB;
   Ename VAR_TAB;
   Counter NUMBER;
   CURSOR C IS
      SELECT Empno, Ename FROM Emp_tab WHERE Mgr = 7698;
BEGIN

-- Efficient method, using a bulk bind
    SELECT Empno, Ename BULK COLLECT INTO Empno, Ename
        FROM Emp_Tab WHERE Mgr = 7698;

-- Slower method, assigning each collection element within a loop.

   counter := 1;  
   FOR rec IN C LOOP  
      Empno(Counter) := rec.Empno;
      Ename(Counter) := rec.Ename;
      Counter := Counter + 1;
   END LOOP;
END; 

スカラー値表または%TYPE値表にBULK COLLECT INTOを使用できます。

PL/SQLがバルク・バインドを使用しないで、選択された各従業員に対してSQLエンジンにSQL文を送信すると、コンテキストのスイッチングによってパフォーマンスが低下します。

コレクションおよびRETURNING INTO句を参照するFORループ

BULK COLLECT INTOキーワードとともにFORALLキーワードを使用すると、コレクションを参照しDMLを戻すFORループのパフォーマンスを改善できます。

たとえば、次のPL/SQLブロックは、従業員コレクションの賞与を計算してEmp_tab表を更新し、次にBonlistという列に賞与を戻します。この操作をバルク・バインドを使用して行います。

DECLARE
   TYPE Emplist IS VARRAY(100) OF NUMBER;
   Empids EMPLIST := EMPLIST(7369, 7499, 7521, 7566, 7654, 7698);
   TYPE Bonlist IS TABLE OF Emp_tab.sal%TYPE;
   Bonlist_inst BONLIST;
BEGIN
   Bonlist_inst := BONLIST(1,2,3,4,5);

   FORALL i IN Empids.FIRST..empIDs.LAST
      UPDATE Emp_tab SET Bonus = 0.1 * Sal
      WHERE Empno = Empids(i)
      RETURNING Sal BULK COLLECT INTO Bonlist;

   FOR i IN Empids.FIRST..Empids.LAST LOOP
      UPDATE Emp_tab Set Bonus = 0.1 * sal        
         WHERE Empno = Empids(i)
       RETURNING Sal INTO BONLIST(i);
   END LOOP;
END;

PL/SQLがバルク・バインドを使用しないで、各従業員を更新するためにSQLエンジンにSQL文を送信すると、コンテキストのスイッチングによってパフォーマンスが低下します。

トリガー

トリガーは、特殊な種類の無名PL/SQLブロックです。文レベルで、または影響を受ける各行に対して、SQL文の前後で起動するようにトリガーを定義できます。INSTEAD OFトリガーまたはシステム・トリガー(DATABASEまたはSCHEMAに対するトリガー)も定義できます。

関連項目

第9章「トリガーのコーディング」 

ネイティブ実行のためのPL/SQLプロシージャのコンパイル

共有ライブラリにあるネイティブ・コードにPL/SQLプロシージャをコンパイルすると、PL/SQLプロシージャの処理を高速化できます。プロシージャはCコードに変換され、通常のCコンパイラでコンパイルされた後、Oracle Databaseプロセスにリンクされます。

Oracle DatabaseのPL/SQLパッケージおよびユーザーが作成したプロシージャの両方に、この方法を使用できます。ALTER SYSTEMまたはALTER SESSIONコマンドを使用するか、あるいは初期化ファイルを更新して、PLSQL_CODE_TYPEパラメータをNATIVEに設定できます(デフォルト設定はINTERPRETEDです)。

この方法では、これらのプロシージャからコールされたSQL文の処理は高速化されないため、SQL実行にあまり時間を必要としない計算集中型プロシージャに最も有効です。

Javaの場合は、ncompツールを使用して独自のパッケージおよびクラスをコンパイルできます。

関連項目

  • PL/SQLのネイティブ・コンパイルの詳細は、『PL/SQLユーザーズ・ガイドおよびリファレンス』 を参照してください。

  • Javaのネイティブ・コンパイルの詳細は、『Oracle Database Java開発者ガイド』を参照してください。

 

リモート依存性

PL/SQLプログラム・ユニット間の依存性は、次の2つの方法で処理できます。

タイムスタンプ

PL/SQLプログラム・ユニット間の依存性を処理するためにタイムスタンプを使用する場合は、プログラム・ユニットまたは関連するスキーマ・オブジェクトを変更するたびに、すべての依存ユニットに無効のマークが付けられるため、再コンパイルしないかぎり実行できません。

各プログラム・ユニットは、そのユニットが作成または再コンパイルされるときに、サーバーによってタイムスタンプが設定されます。図7-1に、この依存性について示します。プロシージャP1およびP2は、ストアド・プロシージャP3をコールします。ストアド・プロシージャP3T1表を参照します。この例では、各プロシージャは、いずれもT1表に依存しています。P3T1に直接依存しますが、P1P2は間接的に依存します。

図 7-1    依存性の関係


画像の説明

P1およびP2P3と同じサーバー上にある場合に、P3が変更されると、P1およびP2に無効のマークがすぐに付けられます。P1およびP2がコンパイルされた状態では、P3のタイムスタンプのレコードが含まれています。そのため、プロシージャP3が変更され再コンパイルされた場合、P3のタイムスタンプは、P1およびP2のコンパイル中にP3に対して記録された値と一致しなくなります。

P1およびP2がクライアント・システム上にある場合、または分散環境内の別のOracle Databaseインスタンス上にある場合、実行時に、タイムスタンプ情報を使用して、この2つのプロシージャに無効のマークが付けられます。

タイムスタンプ・モデルのデメリット

この依存性モデルのデメリットは、必要以上に制限が多いということです。ネットワークを介した依存オブジェクトは、必ずしも必要でないときにも再コンパイルされることが多いため、パフォーマンスが低下します。

さらに、クライアント側のアプリケーションがPL/SQLバージョン2を使用して作成されている場合には、クライアント側では、タイムスタンプ・モデルによってアプリケーションが実行しない状態になる可能性があります。クライアント側でPL/SQLバージョン1を使用していたOracle Formsなどの初期のリリースのツールでは、この依存性モデルを使用していませんでした。PL/SQLバージョン1がストアド・プロシージャをサポートしていなかったためです。

クライアント側のPL/SQLバージョン2と統合されたOracle Formsのリリースの場合、タイムスタンプ・モデルで問題が発生する可能性があります。たとえば、そのアプリケーションが使用するクライアント側のPL/SQLプロシージャがクライアント側で再コンパイルされないかぎり、アプリケーションはインストール中に無効にされます。また、クライアント側のプロシージャがサーバー側のプロシージャに依存しており、そのサーバー側のプロシージャが変更または自動的に再コンパイルされた場合には、クライアント側のPL/SQLプロシージャも再コンパイルする必要があります。ただし、多くのアプリケーション環境(たとえば、Formsランタイム・アプリケーション)では、クライアントで使用できるPL/SQLコンパイラはありません。このため、アプリケーションは実行できません。このような場合、クライアント・アプリケーションの開発者は、すべての顧客に対して、そのアプリケーションの新しいバージョンを再度配布する必要があります。

シグネチャ

タイムスタンプのみの依存性モデルに関する問題のいくつかを軽減するために、Oracle Databaseではシグネチャを使用したリモート依存性という追加機能を提供します。シグネチャ機能は、リモート依存性のみに影響します。ローカル(同一サーバー)依存性には影響しません。この環境では、再コンパイルが常に可能なためです。

シグネチャは、コンパイル済の各ストアド・プログラム・ユニットと対応付けられます。シグネチャは、次の基準でユニットを識別します。

ユーザーは、シグネチャまたはタイムスタンプがリモート依存性を管理するかどうかを制御できます。

関連項目

「リモート依存性の制御」 

シグネチャ依存性モデルが使用されるとき、その依存ユニットに親ユニット内のサブプログラムへのコールが含まれており、このサブプログラムのシグネチャの変更により非互換が発生した場合は、リモート・プログラム・ユニットへの依存性によって、その依存ユニットは無効になります。

たとえば、ボストンにあるサーバー(BOSTON_SERVER)に格納されているプロシージャGet_emp_nameについて検討してみます。このプロシージャは、次のように定義されています。


注意

次のようなデータ構造を設定しないと機能しない例もあります。

CONNECT system/manager
CREATE PUBLIC DATABASE LINK boston_server USING 'inst1_alias';
CONNECT scott/tiger
 

CREATE OR REPLACE PROCEDURE get_emp_name (
   emp_number   IN  NUMBER,
   hire_date    OUT VARCHAR2,
   emp_name     OUT VARCHAR2) AS
BEGIN
   SELECT ename, to_char(hiredate, 'DD-MON-YY')
      INTO emp_name, hire_date
      FROM emp
      WHERE empno = emp_number;
END;

Get_emp_nameBOSTON_SERVERでコンパイルされると、そのシグネチャは、そのタイムスタンプとともに記録されます。

カリフォルニアにある別のサーバーで、PL/SQLコードがBOSTON_SERVERというDBリンクを使用してget_emp_nameを識別し、次のようにget_emp_nameをコールするとします。

CREATE OR REPLACE PROCEDURE print_ename (emp_number IN NUMBER) AS
   hire_date    VARCHAR2(12);
   ename        VARCHAR2(10);
BEGIN
   get_emp_name@BOSTON_SERVER(emp_number, hire_date, ename);
   dbms_output.put_line(ename);
   dbms_output.put_line(hire_date);
END;

このカリフォルニアのサーバー・コードがコンパイルされるときに、次の処理が行われます。

実行時には、変更の有無にかかわらず、カリフォルニアのサーバーからボストンのサーバーへのリモート・プロシージャのコール中に、print_enameのコンパイルされた状態で保存されていたget_emp_nameの記録済のシグネチャが、ボストンのサーバーまで送信されます。

タイムスタンプ依存性モードが有効である場合、タイムスタンプの不一致によって、コール側プロシージャにエラー状況が戻されます。

ただし、シグネチャ・モードが有効である場合は、タイムスタンプに不一致があっても無視され、カリフォルニアのサーバーのPrint_enameのコンパイル済状態にあるget_emp_nameの記録済のシグネチャが、ボストンのサーバーにあるget_emp_nameの現行のシグネチャと比較されます。2つのシグネチャが一致した場合は、コールは正常に進行します。2つのシグネチャが一致しない場合は、エラー・ステータスがprint_nameプロシージャに戻されます。

ボストンのサーバーのget_emp_nameプロシージャは、変更されている可能性もあることに注意してください。または、そのタイムスタンプは、サーバーが新しいリリースでインストールされたなどの理由で、カリフォルニアのサーバーのprint_nameプロシージャに記録されたタイムスタンプと異なっている場合があります。シグネチャ・リモート依存性モードがカリフォルニアのサーバーで有効であるかぎり、get_emp_nameがコールされたときにタイムスタンプの不一致が原因でエラーが発生することはありません。


注意

DETERMINISTICPARALLEL_ENABLEおよび純粋度情報は、シグネチャ・モードでは使用されません。リモート・システム上のファンクションが別の設定で再定義された場合、これらの設定に基づく最適化は、自動的には再考慮されません。したがって、SQL文でこのリモート・ファンクションへのコールが(間接的にでも)発生した場合、またはファンクション索引でリモート・ファンクションが(間接的にでも)使用された場合に、問合せ結果は正しくないことがあります。 


シグネチャが変更される時点

次に、シグネチャが変更される時点について示します。

データ型クラスの切替え

シグネチャは、あるクラスのデータ型を別のクラスに切り替えた場合に変更されます。各データ型クラスには、複数の型が存在している可能性があります。1つのクラス内でパラメータのデータ型を別の型に変更しても、シグネチャが変更されることはありません。次の一覧に示されていないNCHARTIMESTAMPなどのデータ型は、どのクラスの一部でもありません。その型を変更すると、必ずシグネチャの不一致が発生します。

VARCHAR型: VARCHAR2、VARCHAR、STRING、LONG、ROWID

文字列型: CHARACTER、CHAR

RAW型: RAW、LONG RAW

整数型: BINARY_INTEGER、PLS_INTEGER、BOOLEAN、NATURAL、POSITIVE、POSITIVEN、NATURALN

数値型: NUMBER、INTEGER、INT、SMALLINT、DECIMAL、DEC、REAL、FLOAT、NUMERIC、DOUBLE PRECISION、DOUBLE PRECISION、NUMERIC

日付型: DATE、TIMESTAMP、TIMESTAMP WITH TIME ZONE、TIMESTAMP WITH LOCAL TIME ZONE、INTERVAL YEAR TO MONTH、INTERVAL DAY TO SECOND

モード

デフォルトのパラメータ・モードINの明示的指定に変更があっても、サブプログラムのシグネチャは変更されません。たとえば、次の2つを入れ換えます。

PROCEDURE P1 (Param1 NUMBER);
PROCEDURE P1 (Param1 IN NUMBER);

入れ換えてもシグネチャは変更されません。これ以外のパラメータ・モードの変更が行われた場合、シグネチャは変更されます。

デフォルトのパラメータ値

デフォルトのパラメータ値の指定を変更しても、シグネチャは変更されません。たとえば、プロシージャP1は、次の2つの例では同じシグネチャを持っています。

PROCEDURE P1 (Param1 IN NUMBER := 100);
PROCEDURE P1 (Param1 IN NUMBER := 200);

コール側で新しいデフォルト値を取得できるようにする必要がある場合、アプリケーション開発者は、コールされたプロシージャを再コンパイルする必要がありますが、デフォルトのパラメータ値の割当てが変更されても、シグネチャに基づいて無効にされることはありません。

プロシージャ・シグネチャの変更例

「プロシージャおよびファンクションのパラメータ」に定義されているGet_emp_namesプロシージャを使用します。このプロシージャ本体が次のように変更されるとします。

DECLARE
   Emp_number  NUMBER;
   Hire_date   DATE;
BEGIN
-- date format model changes
  
   SELECT Ename, To_char(Hiredate, 'DD/MON/YYYY')
      INTO Emp_name, Hire_date
      FROM Emp_tab
      WHERE Empno = Emp_number;
END;

プロシージャ仕様部は変更されていないため、シグネチャも変更されていません。

ただし、プロシージャ仕様部が次のように変更されたとします。

CREATE OR REPLACE PROCEDURE Get_emp_name (
   Emp_number  IN  NUMBER,
   Hire_date   OUT DATE,
   Emp_name    OUT VARCHAR2) AS

それに応じて本体が変更された場合、シグネチャは変更されます。これは、パラメータHire_dateが別のデータ型になっているためです。

ただし、そのパラメータの名前がWhen_hiredに変更され、データ型はVARCHAR2、モードはOUTのままの場合、シグネチャは変更されません。仮パラメータの名前が変更されても、そのユニットのシグネチャは変更されません。

次の例について検討してみます。

CREATE OR REPLACE PACKAGE Emp_package AS
    TYPE Emp_data_type IS RECORD (
        Emp_number NUMBER,
        Hire_date  VARCHAR2(12),
        Emp_name   VARCHAR2(10));
    PROCEDURE Get_emp_data
        (Emp_data IN OUT Emp_data_type);
END;

CREATE OR REPLACE PACKAGE BODY Emp_package AS
    PROCEDURE Get_emp_data
        (Emp_data IN OUT Emp_data_type) IS
   BEGIN
       SELECT Empno, Ename, TO_CHAR(Hiredate, 'DD/MON/YY')
           INTO Emp_data
           FROM Emp_tab
           WHERE Empno = Emp_data.Emp_number;
   END;
END;

レコードのフィールド名が変更されるようにパッケージ仕様部が変更された場合、型がそのままである場合はシグネチャには影響しません。たとえば、次のパッケージ仕様部は、前のパッケージ仕様部の例と同じシグネチャを持っています。

CREATE OR REPLACE PACKAGE Emp_package AS
    TYPE Emp_data_type IS RECORD (
        Emp_num    NUMBER,         -- was Emp_number
        Hire_dat   VARCHAR2(12),   -- was Hire_date
        Empname    VARCHAR2(10));  -- was Emp_name
    PROCEDURE Get_emp_data
        (Emp_data IN OUT Emp_data_type);
END;

型が以前のままである場合には、パラメータの型の名前を変更してもシグネチャは変更されません。たとえば、次のようなEmp_packageのパッケージ仕様部は、最初のものと同じです。

CREATE OR REPLACE PACKAGE Emp_package AS
    TYPE Emp_data_record_type IS RECORD (
        Emp_number NUMBER,
        Hire_date  VARCHAR2(12),
        Emp_name   VARCHAR2(10));
    PROCEDURE Get_emp_data
        (Emp_data IN OUT Emp_data_record_type);
END;

リモート依存性の制御

タイムスタンプまたはシグネチャの依存性モデルが有効であるかどうかは、REMOTE_DEPENDENCIES_MODE動的初期化パラメータによって制御されます。

この場合は、タイムスタンプのみを使用して依存性が解決されます(動的に明示的にオーバーライドされない場合)。

この場合は、シグネチャを使用して依存性が解決されます(動的に明示的にオーバーライドされない場合)。

REMOTE_DEPENDENCIES_MODEパラメータが、init.oraパラメータ・ファイル内で指定されていないか、DDL文ALTER SESSIONまたはALTER SYSTEMを使用して指定されていない場合には、タイムスタンプがデフォルト値です。したがって、明示的にREMOTE_DEPENDENCIES_MODEパラメータまたは適切なDDL文を使用しないかぎり、使用中のサーバーは、タイムスタンプ依存性モデルを使用して動作します。

REMOTE_DEPENDENCIES_MODE=SIGNATUREを使用する場合は、次の点に注意してください。

依存性の解決

REMOTE_DEPENDENCIES_MODE = TIMESTAMP(デフォルト値)の場合、プログラム・ユニット間の依存性は、実行時にタイムスタンプを比較して処理されます。コールされたリモート・プロシージャのタイムスタンプが、コールされたプロシージャのタイムスタンプと一致しない場合、コール側の(依存)ユニットは無効になり、再コンパイルする必要があります。この場合、ローカルPL/SQLコンパイラがない場合は、コール側のアプリケーションを処理できません。

タイムスタンプ依存性モードでは、シグネチャは比較されません。ローカルPL/SQLコンパイラがある場合には、コール側プロシージャが実行されると自動的に再コンパイルされます。

REMOTE_DEPENDENCIES_MODE = SIGNATUREの場合、コール側のユニット内に記録されたタイムスタンプは、まず最初に、コールされたリモート・ユニット内の現在のタイムスタンプと比較されます。2つのタイムスタンプが一致した場合は、コールが進行します。タイムスタンプが一致しなかった場合、コールされたリモート・サブプログラムのシグネチャは、コール側サブプログラムに記録されたままの状態で、コールされたサブプログラムの現行のシグネチャと比較されます。この2つのシグネチャが一致しない場合は、「シグネチャが変更される時点」の項で説明されている基準を使用した結果、コール側セッションにエラーが戻されます。

依存性を管理するための提案

次のガイドラインに従ってREMOTE_DEPENDENCIES_MODEパラメータを設定してください。

カーソル変数

カーソルは静的オブジェクトであり、カーソル変数はカーソルへのポインタです。そのため、プロシージャおよびファンクションとの間でパラメータとして受け渡すことができます。カーソル変数は、その存続期間内に別のカーソルを参照することもできます。

カーソル変数には、前述以外に次のような利点もあります。

カーソル変数の宣言およびオープン

メモリーは、通常、適切なALLOCATE文を使用してクライアント・アプリケーションのカーソル変数に割り当てられます。Pro*Cでは、EXEC SQL ALLOCATE <cursor_name>文を使用します。OCIでは、カーソル・データ域を使用します。

また、1つのサーバー・セッションのみで実行するアプリケーションでも、カーソル変数を使用できます。PL/SQLサブプログラムでカーソル変数を宣言してオープンし、他のPL/SQLサブプログラムのパラメータとして使用できます。

カーソル変数の例

この項には、PL/SQLでのカーソル変数の使用例がいくつか示されています。プログラム・インタフェースを使用するカーソル変数の例がさらに必要な場合は、次のマニュアルを参照してください。

データのフェッチ

次のパッケージは、PL/SQLカーソル変数型Emp_val_cv_typeおよび2つのプロシージャを定義しています。最初のプロシージャ(Open_emp_cv)は、WHERE句にバインド変数を使用してカーソル変数をオープンします。2番目のプロシージャ(Fetch_emp_data)は、カーソル変数を使用してEmp_tab表から行をフェッチします。

CREATE OR REPLACE PACKAGE Emp_data AS
  TYPE Emp_val_cv_type IS REF CURSOR RETURN Emp_tab%ROWTYPE;
  PROCEDURE Open_emp_cv (Emp_cv          IN OUT Emp_val_cv_type,
                         Dept_number     IN     INTEGER); 
  PROCEDURE Fetch_emp_data (emp_cv       IN     Emp_val_cv_type,
                            emp_row      OUT    Emp_tab%ROWTYPE);
END Emp_data;

CREATE OR REPLACE PACKAGE BODY Emp_data AS
  PROCEDURE Open_emp_cv (Emp_cv      IN OUT Emp_val_cv_type,
                         Dept_number IN     INTEGER) IS
  BEGIN
    OPEN emp_cv FOR SELECT * FROM Emp_tab WHERE deptno = dept_number;
  END open_emp_cv;
  PROCEDURE Fetch_emp_data (Emp_cv      IN  Emp_val_cv_type,
                            Emp_row     OUT Emp_tab%ROWTYPE) IS
  BEGIN
    FETCH Emp_cv INTO Emp_row;
  END Fetch_emp_data;
END Emp_data;

次に、PL/SQLブロックからEmp_dataパッケージ・プロシージャをコールする方法を示します。

DECLARE
-- declare a cursor variable
   Emp_curs Emp_data.Emp_val_cv_type;
   Dept_number Dept_tab.Deptno%TYPE;
   Emp_row Emp_tab%ROWTYPE;

BEGIN
   Dept_number := 20;
-- open the cursor using a variable
   Emp_data.Open_emp_cv(Emp_curs, Dept_number);
-- fetch the data and display it
   LOOP
     Emp_data.Fetch_emp_data(Emp_curs, Emp_row);
     EXIT WHEN Emp_curs%NOTFOUND;
     DBMS_OUTPUT.PUT(Emp_row.Ename || '  ');
     DBMS_OUTPUT.PUT_LINE(Emp_row.Sal);
   END LOOP;
END;

可変レコードの実装

カーソル変数は、異なるカーソルを指す機能にその本質があります。次のパッケージ例では、判別子を使用して、2つの異なるカーソルのうちの1つを指すようにカーソル変数をオープンします。

CREATE OR REPLACE PACKAGE Emp_dept_data AS
  TYPE Cv_type IS REF CURSOR;
  PROCEDURE Open_cv (Cv          IN OUT cv_type,
                     Discrim     IN     POSITIVE); 
END Emp_dept_data;

CREATE OR REPLACE PACKAGE BODY Emp_dept_data AS
  PROCEDURE Open_cv (Cv      IN OUT cv_type,
                     Discrim IN     POSITIVE) IS
  BEGIN
    IF Discrim = 1 THEN
      OPEN Cv FOR SELECT * FROM Emp_tab WHERE Sal > 2000;
    ELSIF Discrim = 2 THEN
      OPEN Cv FOR SELECT * FROM Dept_tab;
    END IF;
  END Open_cv;
END Emp_dept_data;

Open_cvプロシージャをコールしてカーソル変数をオープンし、Emp_tab表またはDept_tab表に関する問合せを指すことができます。次のPL/SQLブロックは、カーソル変数を使用してフェッチする方法、およびROWTYPE_MISMATCH事前定義例外を使用して各フェッチ・レコードを処理する方法を示しています。

DECLARE
  Emp_rec  Emp_tab%ROWTYPE;
  Dept_rec Dept_tab%ROWTYPE;
  Cv       Emp_dept_data.CV_TYPE;

BEGIN
  Emp_dept_data.open_cv(Cv, 1); -- Open Cv For Emp_tab Fetch
  Fetch cv INTO Dept_rec;       -- but fetch into Dept_tab record
                                -- which raises ROWTYPE_MISMATCH
  DBMS_OUTPUT.PUT(Dept_rec.Deptno);
  DBMS_OUTPUT.PUT_LINE('  ' || Dept_rec.Loc);

EXCEPTION
  WHEN ROWTYPE_MISMATCH THEN
    BEGIN
      DBMS_OUTPUT.PUT_LINE
           ('Row type mismatch, fetching Emp_tab data...');
      FETCH Cv INTO Emp_rec;
      DBMS_OUTPUT.PUT(Emp_rec.Deptno);
      DBMS_OUTPUT.PUT_LINE('  ' || Emp_rec.Ename);
    END;

PL/SQLコンパイル時のエラー処理

SQL*Plusを使用してPL/SQLコードを送り、そのコードにエラーがあると、コンパイル・エラーが発生したことが通知されますが、エラーの種類はすぐには識別されません。たとえば、ファイルproc1.sqlのスタンドアロン(またはストアド)・プロシージャPROC1を次のように送るとします。

SQL> @proc1

コードに1つ以上のエラーが存在すると、次のようなエラー・メッセージが戻されます。

MGR-00072: 警告: プロシージャproc1が作成されましたが、コンパイル・エラーがあります。

この場合、SQL*PlusでSHOW ERRORS文を使用し、検出されたエラーのリストを取得します。引数を持たないSHOW ERRORSは、最後のコンパイルで発生したエラーをリストします。SHOW ERRORSは、プロシージャ、ファンクション、パッケージまたはパッケージ本体の名前を使用して修飾できます。

SQL> SHOW ERRORS PROC1
SQL> SHOW ERRORS PROCEDURE PROC1

関連項目

SHOW ERRORS文の詳細は、『SQL*Plusユーザーズ・ガイドおよびリファレンス』を参照してください。 


注意

長い行を出力するには、SHOW ERRORS文を発行する前にSET LINESIZE文を使用してください。通常、次のように値を132に指定することをお薦めします。次に例を示します。

SET LINESIZE 132
 

SQL*Plusを使用して従業員表のレコードを削除する簡単なプロシージャを作成します。

CREATE OR REPLACE PROCEDURE Fire_emp(Emp_id NUMBER) AS
   BEGIN
      DELETE FROM Emp_tab WHER Empno = Emp_id;
   END
/

CREATE PROCEDURE文にエラーが2つあることに注意してください。まず、DELETE文にエラーがあります(WHEREに「E」がありません)。また、ENDの後にセミコロン(;)がありません。

CREATE PROCEDURE文が入力されエラーが戻されると、SHOW ERRORS文は次の行を戻します。

SHOW ERRORS;

ERRORS FOR PROCEDURE Fire_emp:
LINE/COL       ERROR
-------------- --------------------------------------------
3/27           PL/SQL-00103: Encountered the symbol "EMPNO" wh. . .
5/0            PL/SQL-00103: Encountered the symbol "END" when . . .
2 rows selected.

SHOW ERRORS文によって、エラーが発生した行および列の番号がそれぞれ表示されます。

他のツールまたはアプリケーションを使用している場合は、次のデータ・ディクショナリ・ビューを使用してエラーを表示できます。

プロシージャのコンパイルに関するエラー・メッセージは、プロシージャを置き換えると更新され、プロシージャを削除すると削除されます。

ALL_SOURCEUSER_SOURCEDBA_SOURCEの各ビューを使用すると、データ・ディクショナリから元のソース・コードを取得できます。

関連項目

これらのデータ・ディクショナリ・ビューの詳細は、『Oracle Databaseリファレンス』を参照してください。 

PL/SQLのランタイム・エラー処理

Oracle Databaseでは、ユーザー定義エラーの番号およびメッセージがクライアント・アプリケーションに戻されるようにPL/SQLコード内のユーザー定義エラーを処理できます。クライアント・アプリケーションでは、Oracle Databaseが戻したユーザー定義エラーの番号およびメッセージに基づいて、エラーを処理します。

ユーザー定義エラーのメッセージは、RAISE_APPLICATION_ERRORプロシージャを使用して戻されます。次に例を示します。

RAISE_APPLICATION_ERROR(Error_number, 'text', Keep_error_stack)

このプロシージャはプロシージャの実行を停止し、プロシージャによるすべての影響をロールバックして、ユーザー定義エラー番号およびメッセージを戻します(例外ハンドラによってエラーが検出されない場合)。ERROR_NUMBERは、-20000〜-20999の範囲内にある必要があります。

エラー番号-20000は、ユーザーに情報を伝えることが重要で、一意のエラー番号は必要とされないメッセージの一般的な番号として使用します。テキストは、2KB以下の文字式である必要があります(それより長いメッセージは無視されます)。スタック上の既存のエラーにエラーを追加する場合はKeep_error_stackTRUEに、既存のエラーと置き換える場合はFALSEにします。デフォルトでは、このオプションはFALSEです。


注意

DBMS_OUTPUTDBMS_DESCRIBEDBMS_ALERTなど、Oracle Databaseのパッケージの中には、-20000〜-20005の範囲のアプリケーション・エラー番号を使用するものがあります。詳細は、これらのパッケージの説明を参照してください。  


RAISE_APPLICATION_ERRORプロシージャは、例外ハンドラまたは論理PL/SQLコードによく使用されます。たとえば、次の例外ハンドラは、ユーザー定義エラー・メッセージに関係する文字列を選択した後、RAISE_APPLICATION_ERRORプロシージャをコールします。

...
WHEN NO_DATA_FOUND THEN
   SELECT Error_string INTO Message
   FROM Error_table,
   V$NLS_PARAMETERS V
   WHERE Error_number = -20101 AND Lang = v.value AND
      v.parameter = "NLS_LANGUAGE";
   Raise_application_error(-20101, Message);
...

関連項目

リモート・プロシージャをコールする場合の例外処理の詳細は、「リモート・プロシージャでのエラー処理」を参照してください。 

次の項では、ユーザー定義エラーの番号をトリガーからプロシージャに渡す例を示します。

例外および例外処理ルーチンの宣言

ユーザー定義例外は、そのアプリケーションに固有のエラーの処理を制御するためにPL/SQLブロック内で明示的に定義され、通知されます。例外が発生する(通知される)と、通常のPL/SQLブロックの実行は停止し、例外ハンドラと呼ばれるルーチンがコールされます。この例外ハンドラによって内部例外またはユーザー定義例外が処理されます。

アプリケーション・コードを使用すると、IF文により特に注意が必要な条件をチェックできます。エラー条件がある場合、次の2つのオプションのどちらかを選択できます。

例外ハンドラは、ユーザー定義エラー・メッセージを処理するために定義することもできます。たとえば、図7-2では、次のことが示されています。

ユーザー定義例外は、プロシージャ本体またはパッケージ本体で宣言するか(プライベート例外)、パッケージ仕様部で宣言します(パブリック例外)。例外ハンドラは、プロシージャ本体(スタンドアロンまたはパッケージ)に定義します。

図 7-2    例外およびユーザー定義エラー


画像の説明

未処理例外

データベースのPL/SQLプログラム・ユニットでは、適切な例外ハンドラによって検出されない未処理のユーザー・エラー条件または内部エラー条件が原因で、プログラム・ユニットの暗黙的なロールバックが発生します。プログラム・ユニットで未処理例外がある場所の前にCOMMIT文が含まれている場合、そのプログラム・ユニットの暗黙的なロールバックは直前のCOMMITまで実行されます。

さらに、データベースに格納されたPL/SQLのプログラム・ユニットの未処理例外は、プログラム・ユニットをコールするクライアント側のアプリケーションに渡されます。このアプリケーションでは、その例外はデータベースにSQL文として送られるため、アプリケーション・プログラム・ユニット・コールのみがロールバックされます(アプリケーション・プログラム・ユニット全体ではありません)。

データベースのPL/SQLプログラム・ユニット内の未処理例外がデータベース・アプリケーションに戻される場合は、例外を処理するためにデータベースのPL/SQLコードを変更する必要があります。アプリケーションで、データベース・プログラム・ユニットをコールしたときに未処理例外を検出し、それらのエラーを処理できます。

分散問合せでのエラー処理

分散問合せは、トリガーまたはストアド・プロシージャを使用して作成できます。この分散問合せは、ローカルのOracle Databaseインスタンスによって、対応する数のリモート問合せに分解されてリモート・ノードに送られます。リモート・ノードはその問合せを実行し、ローカル・ノードにその結果を送ります。その後、ローカル・ノードは必要な後処理を行い、ユーザーまたはアプリケーションに結果を戻します。

たとえば、整合性制約違反のために分散問合せ文の一部でエラーが発生すると、Oracle Databaseはエラー番号ORA-02055を戻します。後続する文またはプロシージャ・コールは、ロールバック、またはセーブポイントまでのロールバックが入力されるまで、エラー番号ORA-02067を戻します。

分散更新の一部でエラーが発生したことを示すエラー・メッセージがチェックされるように、アプリケーションを設計してください。エラーを検出した場合、アプリケーションが処理を継続する前に、トランザクション全体をロールバック(またはセーブポイントまでロールバック)してください。

リモート・プロシージャでのエラー処理

プロシージャがローカルまたはリモートで実行される場合、次の4種類の例外が発生する可能性があります。

ローカル・プロシージャを使用する場合、これらのすべてのメッセージは例外ハンドラを作成することによって検出できます。次に例外ハンドラの例を示します。

EXCEPTION
    WHEN ZERO_DIVIDE THEN
    /* ...handle the exception */

なお、WHEN句の例外名は必須です。RAISE_APPLICATION_ERRORで生成される 例外のように、発生した例外に名前がない場合は、プラグマPRAGMA_EXCEPTION_INITを使用して名前を割り当てることができます。次に例を示します。

DECLARE
    ...
    Null_salary EXCEPTION;
    PRAGMA EXCEPTION_INIT(Null_salary, -20101);
BEGIN
    ...
    RAISE_APPLICATION_ERROR(-20101, 'salary is missing');
    ...
EXCEPTION
    WHEN Null_salary THEN
        ...

また、リモート・プロシージャをコールするときには、ローカル例外ハンドラを作成することによって例外を処理します。リモート・プロシージャは、ローカルのコール側プロシージャにエラー番号を戻す必要があります。その後、ローカル・プロシージャは、先の例に示したように、例外を処理します。PL/SQLのユーザー定義例外は、常にローカル・プロシージャにORA-06510を戻すため、これらの例外は処理できません。その他すべてのリモート例外は、ローカル例外と同じ方法で処理できます。

ストアド・プロシージャのデバッグ

ストアド・プロシージャのコンパイルにはコードの構文エラーの修正が含まれます。プロシージャが正常に実行され、エラーが修正されていることを確認するには、追加のデバックを行う必要があります。次のようなデバッグが考えられます。

Oracle JDeveloper

Oracle JDeveloperの最新のリリースには、PL/SQL、Javaおよびマルチ言語プログラムをデバッグする広範な機能が含まれています。各種Oracle製品の一部としてOracle JDeveloperを取得できます。通常、最新のリリースはhttp://otn.oracle.co.jp/からダウンロードできます。

Oracle Procedure BuilderおよびTEXT_IOパッケージ

Oracle Procedure Builderは、データベース・アプリケーションを透過的にデバッグする高度なクライアント/サーバー・デバッガです。Oracle Procedure Builderを使用すると、制御されたデバッグ環境でPL/SQLプロシージャおよびトリガーを実行し、ブレークポイントの設定、変数の値のリスト、その他のデバッグ作業を実行できます。Oracle Procedure Builderは、Oracle Developerセットの一部です。デバッグ情報を出力する場合に有効なTEXT_IOパッケージも提供されています。

DBMS_OUTPUTパッケージ

DBMS_OUTPUTパッケージを使用すると、ストアド・プロシージャおよびトリガーもデバッグできます。コードにはPUT文およびPUT_LINE文を入れて、変数および式の値を端末に出力します。

PL/SQLおよびJavaのストアド・プロシージャのデバッグ権限

Oracle Database 10gから、データベース内で実行されるPL/SQLおよびJavaコードのデバッグに新しい権限モデルが適用されるようになりました。このモデルは、Oracle JDeveloper、Oracle Developer、または各種サード・パーティのPL/SQLやJavaの開発環境のいずれを使用していても適用され、DBMS_DEBUG APIおよびDBMS_DEBUG_JDWP APIの両方に影響します。

デバッガに接続するセッションでは、DEBUG CONNECT SESSIONシステム権限を所有するユーザーが接続操作を実行する必要があります。接続コールに関係する定義者権限ルーチンの所有者であるユーザーも、この操作を実行できます。

デバッガがセッションに接続されると、セッション・ログイン・ユーザーおよび現在有効なセッションレベルのロールが、このデバッグ用の接続の権限環境として決定されます。デバッグに必要なすべてのDEBUG権限またはEXECUTE権限を、このユーザーとロールの組合せに付与する必要があります。

このような権限要件に加えて、コードの各行で停止する機能とデバッガからの変数へのアクセスを実行できるのは、生成されたデバッグ情報とともにコンパイルされているコード内のみという制限があります。ALTER PACKAGEなどのコマンドでPLSQL_DEBUGパラメータおよびDEBUGキーワードを使用し、PL/SQLコンパイラで結果にデバッグ情報を含めるかどうかを制御できます。デバッグ情報を含めない場合、変数にはアクセスできず、コードの各行でステップ実行やブレークポイントを指定することもできません。PL/SQLのwrapユーティリティを使用してあいまいにしているコードでは、PL/SQLコンパイラによってデバッグ情報は生成されません。

関連項目

『PL/SQLユーザーズ・ガイドおよびリファレンス』のPL/SQLソース・コードのあいまい化に関する項を参照してください。 

DEBUG ANY PROCEDUREシステム権限の効力は、データベース内のすべてのオブジェクトにDEBUG権限を付与したときの効力と同じです。O7_DICTIONARY_ACCESSIBILITYパラメータの値がTRUEである場合は、SYSが所有しているオブジェクトにも付与します。

デバッグ・ロール・メカニズムを使用すると、セッションでは通常使用できない、デバッグ用の権限を保持できます。デバッグ・ロールおよび必要な関連パスワードの指定方法の詳細は、マニュアルでDBMS_DEBUGパッケージおよびDBMS_DEBUG_JDWPパッケージについての説明を参照してください。

JAVADEBUGPRIVロールは、DEBUG CONNECT SESSION権限およびDEBUG ANY PROCEDURE権限を保持します。必要な場合にのみ、これらの権限をこのロールに付与してください。


注意

SYSが所有するオブジェクトにDEBUG ANY PROCEDURE権限またはDEBUG権限を付与することは、すべての権限をデータベースに付与することを意味します。  


低レベルのデバッグ・コードの書込み

実際にデバッガの一部のコードを書き込む場合は、DBMS_DEBUG_JDWPDBMS_DEBUGなどのパッケージを使用する必要があります。

DBMS_DEBUG_JDWPパッケージ

Oracle9iリリース2から提供されているDBMS_DEBUG_JDWPパッケージは、将来DBMS_DEBUGパッケージと置き換わるマルチ言語デバッグ用のフレームワークを提供します。PL/SQLとJavaの組合せのプログラムには特に有効です。

DBMS_DEBUGパッケージ

Oracle8iから提供されているDBMS_DEBUGパッケージでは、サーバー側のデバッガが実装されていて、サーバー側のPL/SQLプログラム・ユニットをデバッグする方法を提供します。Oracle Procedure Builderやその他の様々なサード・パーティ・ベンダーが提供するソリューションなど、デバッガのいくつかでは、このAPIが使用されています。

関連項目

  • 『Oracle Procedure Builder Developer's Guide』を参照してください。

  • DBMS_DEBUGパッケージとDBMS_OUTPUTパッケージ、および関連する権限の詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。

  • DBMS_DEBUG_JDWPパッケージの使用方法の詳細は、Oracle JDeveloperのドキュメントを参照してください。

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

  • 低レベルのデバッグ・コードの書込みの詳細は、http://otn.oracle.co.jp/のPL/SQLページを参照してください。

 

ストアド・プロシージャのコール


注意

次のようなデータ構造を設定しないと機能しない例もあります。

CREATE TABLE Emp_tab (
Empno NUMBER(4) NOT NULL,
Ename VARCHAR2(10),
Job VARCHAR2(9),
Mgr NUMBER(4),
Hiredate DATE,
Sal NUMBER(7,2),
Comm NUMBER(7,2),
Deptno NUMBER(2));

CREATE OR REPLACE PROCEDURE fire_emp1(Emp_id NUMBER) AS
BEGIN
DELETE FROM Emp_tab WHERE Empno = Emp_id;
END;
VARIABLE Empnum NUMBER;
 

プロシージャは、次のように様々な環境から起動できます。次に例を示します。

この項では、これらの環境からのプロシージャの起動に関して一般的な例をいくつか紹介します。

関連項目

「SQL式からのストアド・ファンクションのコール」 

別のプロシージャをコールするプロシージャまたはトリガー

プロシージャまたはトリガーによって、別のストアド・プロシージャをコールできます。たとえば、プロシージャ本体に次の行を組み込むことができます。

. . .
Sal_raise(Emp_id, 200);
. . .

この行がSal_raiseプロシージャをコールします。Emp_idは、このプロシージャのコンテキスト内の変数です。PL/SQL内では再帰プロシージャ・コールが可能なため、プロシージャがプロシージャ自身をコールできます。

Oracle Databaseのツール製品からのプロシージャの対話形式コール

プロシージャは、SQL*Plusなど、Oracle Databaseのツール製品から対話形式でコールできます。たとえば、自分が所有しているSAL_RAISEというプロシージャをコールするには、次のように無名PL/SQLブロックを使用できます。

BEGIN
    Sal_raise(7369, 200);
END;


注意

SQL*Plusなどの対話形式のツール製品では、PL/SQLブロックを実行するには、これらの行の終わりにスラッシュ(/)を付けてください。 


SQL*PlusのEXECUTE文を使用すると、ブロックをより簡単に実行できます。これによって、コードに入力するBEGIN文およびEND文がラップされます。次に例を示します。

EXECUTE Sal_raise(7369, 200);

対話形式のツール製品を使用すると、セッション変数を作成できます。SQL*Plusを使用している場合、次の文によってセッション変数が作成されます。

VARIABLE Assigned_empno NUMBER

一度定義したセッション変数は、そのセッション中にのみ有効です。たとえば、あるファンクションを実行して、その戻り値をセッション変数に格納できます。

EXECUTE :Assigned_empno := Hire_emp('JSMITH', 'President', 
   1032, SYSDATE, 5000, NULL, 10);
PRINT Assigned_empno;
ASSIGNED_EMPNO
--------------
          2893

関連項目

  • 『SQL*Plusユーザーズ・ガイドおよびリファレンス』

  • 開発ツールを使用して同様の操作を実行する詳細は、ご使用のツール製品のドキュメントを参照してください。

 

3GLアプリケーション内でのプロシージャのコール

プリコンパイラやOCIアプリケーションなどの3GLデータベース・アプリケーションでは、プロシージャへのコールをそのアプリケーションのコード内に記述できます。

アプリケーションのPL/SQLブロック内のプロシージャを実行するには、単にそのプロシージャをコールします。次のPL/SQLブロックでは、Fire_empプロシージャをコールします。

Fire_emp1(:Empnun);

この場合、:Empnoは、アプリケーションのコンテキスト内のホスト(バインド)変数です。

プリコンパイラ・アプリケーションからプロシージャを実行するには、EXECコール・インタフェースを使用する必要があります。たとえば、次の文は、プリコンパイラ・アプリケーションのコード内でFire_empプロシージャをコールします。

EXEC SQL EXECUTE
   BEGIN
      Fire_emp1(:Empnum);
   END;
END-EXEC;

関連項目

3GLアプリケーション内部からのPL/SQLプロシージャのコールの詳細は、次のマニュアルを参照してください。

  • 『Oracle Call Interfaceプログラマーズ・ガイド』

  • 『Pro*C/C++プログラマーズ・ガイド』

  • 『Oracle SQL*Module for Ada Programmer's Guide』

 

プロシージャ・コール時の名前の変換

プロシージャおよびパッケージに対する参照は、『Oracle Database管理者ガイド』の第13章「スキーマ・オブジェクトの一般的な管理」、「オブジェクトの名前解決の管理」の項で説明されているアルゴリズムに従って変換されます。 

プロシージャの実行に必要な権限

スタンドアロン・プロシージャまたはパッケージを所有している場合は、前の項で説明したように、スタンドアロン・プロシージャまたはパッケージ・プロシージャ、あるいはパブリック・プロシージャまたはパッケージ・プロシージャをいつでも実行できます。他のユーザーが所有するスタンドアロン・プロシージャまたはパッケージ・プロシージャを実行するには、次の条件を満たす必要があります。

プロシージャ引数の値の指定

プロシージャをコールするときには、そのプロシージャの引数に対してそれぞれ値またはパラメータを指定します。引数の値は次のどちらか、または両方を組み合せて指定します。

たとえば、次の文はどちらもSal_raiseプロシージャをコールして、従業員番号が7369の社員の給与を500増額します。

Sal_raise(7369, 500);

Sal_raise(Sal_incr=>500, Emp_id=>7369);

Sal_raise(7369, Sal_incr=>500);

最初の文ではプロシージャ仕様部で宣言されている順序で、引数の値を指定します。

2番目の文では引数の値を名前で指定し、プロシージャ仕様部で宣言されている順序とは異なる順序で指定します。引数の名前を指定する場合、引数は任意の順序で指定できます。

3番目の文では、前述の2つの方法を組み合せて引数の値を指定します。引数の名前と順序を組み合せて指定する場合は、順序で指定する値が名前で指定する値よりも前にある必要があります。

DEFAULTオプションを使用して、サブプログラムに対するINパラメータのデフォルト値を定義した場合(『PL/SQLユーザーズ・ガイドおよびリファレンス』を参照)は、異なる数のパラメータをサブプログラムに渡し、デフォルト値をそのまま使用するか、またはオーバーライドします。実際の値が渡されない場合は、対応するデフォルト値が使用されます。指定を省略する引数(対応するデフォルト値を使用する引数)の後の引数に値を割り当てる場合は、その値のみでなく引数の名前も明示的に指定する必要があります。

リモート・プロシージャのコール

リモート・プロシージャは、適切なデータベース・リンクおよびプロシージャの名前を使用してコールします。次のSQL*Plus文は、BOSTON_SERVERというローカル・データベース・リンクが示すデータベース内にあるプロシージャFire_empを実行します。

EXECUTE fire_emp1@boston_server(1043);

関連項目

リモート・プロシージャをコールする場合の例外処理の詳細は、「リモート・プロシージャでのエラー処理」を参照してください。 

リモート・プロシージャのコールおよびパラメータ値

デフォルト値がある場合でも、すべてのリモート・プロシージャのパラメータに対して値を明示的に渡す必要があります。リモート・パッケージ変数および定数にはアクセスできません。

リモート・オブジェクトの参照

リモート・オブジェクトは、ローカルで定義されているプロシージャ本体で参照できます。次のプロシージャでは、リモートの従業員表の行を削除します。

CREATE OR REPLACE PROCEDURE fire_emp(emp_id NUMBER) IS
BEGIN
    DELETE FROM emp@boston_server WHERE empno = emp_id;
END;

リモート・プロシージャをコールする方法を、コール側の環境別に示します。

リモート・プロシージャのコールは、更新処理を前提とします。このためこのような参照でも、常に2フェーズ・コミットのトランザクションが必要です(リモート・プロシージャが読取り専用の場合を含む)。また、リモート・プロシージャを含むトランザクションをロールバックする場合、リモート・プロシージャのコールによって実行された処理も同時にロールバックされます。

リモート・プロシージャでは、ローカル・プロシーシャと同じCOMMITROLLBACKSAVEPOINT文を実行できます。ただし、次のように、動作に少し違いがあります。

分散更新ではデータは複数のデータベースで更新されます。異なるデータベースのデータにアクセスする複数のリモート更新を含むプロシージャを使用できます。構文内の文はリモート・データベースに送信され、構文の実行はユニット単位で正常終了または異常終了します。分散更新の一部がエラーとなり、一部が正常終了した場合、処理を続けるには(トランザクション全体またはセーブポイントまでの)ロールバックが必要です。分散更新を実行するプロシージャを作成する場合は、この点を考慮する必要があります。

リモート・プロシージャのコールにローカル・プロシージャを使用する場合は、特に注意が必要です。ローカル・プロシージャの実行中にタイムスタンプの不一致が見つかると、リモート・プロシージャは実行されず、そのローカル・プロシージャは無効となります。

プロシージャおよびパッケージのシノニム

スタンドアロン・プロシージャおよびパッケージのシノニムは、次の目的で作成します。

権限が付与されているユーザーがプロシージャをコールする場合、対応するシノニムを使用できます。パッケージ内に定義されているプロシージャは個々のオブジェクトではないため(パッケージはオブジェクトです)、パッケージ内の個々のプロシージャのシノニムは作成できません。

SQL式からのストアド・ファンクションのコール

ユーザー作成のPL/SQLファンクションをSQL式に組み込むことができます(ただし、PL/SQLリリース2.1以上が必要です)。SQL文でPL/SQLファンクションを使用すると、次のことができます。

PL/SQLファンクションの使用

PL/SQLファンクションは、SQL文内で使用される前に、トップレベルのファンクションとして作成するか、またはパッケージ仕様部内部で宣言する必要があります。ストアドPL/SQLファンクションは、組込みOracleファンクション(SUBSTRABSなど)と同じ方法で使用できます。

PL/SQLファンクションは、SQL文内でOracleファンクションを入れることができる場所、またはSQL内で式を入れることができる場所であればどこにでも入れることができます。たとえば、PL/SQLファンクションは、次の場所からコールできます。

CREATE文またはALTER TABLE文のCHECK制約句からPL/SQLストアド・ファンクションをコールしたり、PL/SQLストアド・ファンクションを使用して列のデフォルト値を指定することはできません。このような状況では、不変の定義が必要になるためです。


注意

式の一部としてコールされるファンクションとは異なり、プロシージャは文としてコールされます。したがって、PL/SQLプロシージャは、SQL文からは直接コールできません。ただし、PL/SQL文からコールされるファンクションまたはSQL式で参照されるファンクションは、PL/SQLプロシージャをコールできます。 


PL/SQLファンクションをコールするSQL構文

SQLからPL/SQLファンクションを参照するには、次の構文を使用します。

[[schema.]package.]function_name[@dblink][(param_1...param_n)]

たとえば、Scottスキーマ内のMy_funcs_pkgパッケージに作成した、2つの数値パラメータをとるMy_funcという名前のファンクションを参照するには、次のようにコールします。

SELECT Scott.My_funcs_pkg.My_func(10,20) FROM dual;

ネーミング規則

オプションのスキーマ名またはパッケージ名のどちらか1つが指定されている場合、最初の識別子は、スキーマ名またはパッケージ名のいずれかです。たとえば、リファレンスPayroll.Tax_ratePayrollがスキーマ名かパッケージ名かを判断するために、Oracle Databaseは次の処理を行います。

トップレベルのストアド・ファンクションに対して定義したシノニムを使用して、そのファンクションを参照することもできます。

名前の優先順位

SQL文では、データベースの列の名前は、パラメータなしのファンクションの名前より優先されます。たとえば、スキーマScottは、次の2つのオブジェクトを作成します。

CREATE TABLE Emp_tab(New_sal NUMBER ...);
CREATE FUNCTION New_sal RETURN NUMBER IS ...;

その後、次の2つの文では、New_salへの参照は、列Emp_tab.New_salを参照します。

SELECT New_sal FROM Emp_tab;
SELECT Emp_tab.New_sal FROM Emp_tab;

new_salファンクションにアクセスするには、次のように入力します。

SELECT Scott.New_sal FROM Emp_tab;
SQLからのPL/SQLファンクションのコール例

スキーマScottからPL/SQLファンクションTax_rateをコールし、このファンクションをTax_table内のSs_no列およびsal列に対して実行し、その結果を変数Income_taxに入れるには、次のように指定します。


注意

次のようなデータ構造を設定しないと機能しない例もあります。

CREATE TABLE Tax_table (
Ss_no NUMBER,
Sal NUMBER);

CREATE OR REPLACE FUNCTION tax_rate (ssn IN NUMBER, salary IN NUMBER) RETURN NUMBER IS
sal_out NUMBER;
BEGIN
sal_out := salary * 1.1;
END;
 

DECLARE
   Tax_id     NUMBER;
   Income_tax NUMBER;
BEGIN
   SELECT scott.tax_rate (Ss_no, Sal)
      INTO Income_tax
      FROM Tax_table
      WHERE Ss_no = Tax_id;
END;

これらのPL/SQLファンクションのコール例は、SQL式で使用できます。

Circle_area(Radius)
Payroll.Tax_rate(Empno)
scott.Payroll.Tax_rate@boston_server(Dependents, Empno)

引数

任意の数の引数を1つのファンクションに渡すには、引数をカッコに入れます。この場合、位置表記法を使用する必要があります。名前表記法は、サポートされていません。引数の不要なファンクションの場合は、カッコを使用します。

デフォルト値の使用

ストアド・ファンクションGross_payでは、次のようにDEFAULT句を使用してその仮パラメータの2つをデフォルト値に初期化します。次に例を示します。

CREATE OR REPLACE FUNCTION Gross_pay 
    (Emp_id  IN NUMBER, 
     St_hrs  IN NUMBER DEFAULT 40, 
     Ot_hrs  IN NUMBER DEFAULT 0) RETURN NUMBER AS 
  ... 

手続き型の文からGross_payをコールする場合は、常にSt_hrsのデフォルト値を受け入れることができます。これは、パラメータをスキップする名前表記法を使用できるためです。次に例を示します。

IF Gross_pay(Eenum, Ot_hrs => Otime) > Pay_limit 
THEN ... 

ただし、SQL式からGross_payをコールする場合、Ot_hrsのデフォルト値を受け入れないかぎり、St_hrsのデフォルト値を受け入れることはできません。これは、名前表記法を使用できないためです。

権限

SQLからPL/SQLファンクションをコールするには、その所有者であるか、またはそのファンクションに対するEXECUTE権限が必要です。PL/SQLファンクションを使用して定義されているビューから選択するには、そのビューに対するSELECT権限が必要です。そのビューからの選択には、別のEXECUTE権限は必要ありません。

SQL式からのPL/SQLファンクションのコール要件

SQL式からコールできるようにするには、ユーザー定義のPL/SQLファンクションが次の基本的な要件を満たす必要があります。

たとえば、次のストアド・ファンクションは、これらの基本的な要件を満たしています。


注意

次のようなデータ構造を設定しないと機能しない例もあります。

CREATE TABLE Payroll(
Srate NUMBER,
Orate NUMBER,
Acctno NUMBER);
 

CREATE FUNCTION Gross_pay 
      (Emp_id IN NUMBER, 
       St_hrs IN NUMBER DEFAULT 40, 
       Ot_hrs IN NUMBER DEFAULT 0) RETURN NUMBER AS 
   St_rate  NUMBER; 
   Ot_rate  NUMBER; 

BEGIN 
   SELECT Srate, Orate INTO St_rate, Ot_rate FROM Payroll 
      WHERE Acctno = Emp_id; 
   RETURN St_hrs * St_rate + Ot_hrs * Ot_rate; 
END Gross_pay; 

副作用の制御

ストアド・サブプログラム(ファンクションまたはプロシージャ)の純粋度とは、データベース表またはパッケージ変数に対してそのサブプログラムが及ぼす副作用のことをいいます。副作用によって、問合せのパラレル処理が妨害されたり、処理順序に依存する(したがって、不確定な)結果が発生したり、ユーザー・セッションにまたがったパッケージ状態のメンテナンスが必要になります。サブプログラムがSQL問合せまたはDML文からコールされる場合は、様々な副作用は受け入れられません。

Oracle8iより前のリリースでは、Oracle DatabaseはPL/SQLコンパイラを使用して、ストアド・サブプログラムまたはSQL文のコンパイル中に制限を施行していました。Oracle8iからは、コンパイル時の制限は緩和され、実行中の制限も少なくなっています。

この変更によって、PL/SQL、JavaおよびCで作成されたストアド・サブプログラムが統一してサポートされ、プログラマには最大限の柔軟性が提供されています。

制限事項

SQL文が実行されるとき、すでに実行中のSQL文の中にこのSQL文が論理的に埋め込まれているかどうかが確認されます。文がトリガーまたはすでに実行中のSQL文からコールされたサブプログラムから実行されると、この確認が行われます。このような場合は、新しいSQL文が特定のコンテキスト内で安全かどうかを判断するために、さらに確認が行われます。

サブプログラムには次の制限が適用されます。

前述のすべての制限は、サブプログラムまたはトリガー内でSQL文が実行される方法にかかわらず適用されます。次に例を示します。

新しいSQL文の実行が、すでに実行中の文のコンテキストに論理的に埋め込まれていない場合は、前述の制約を回避できます。PL/SQLの自律型トランザクションが1つの回避方法を提供します(「自律型トランザクション」を参照)。外部C関数からOracle Call Interface(OCI)を使用するという別の回避方法もあります。この場合は、OCIExtProcContext引数から使用できるハンドルを使用せず、新しい接続を作成します。

ファンクションの宣言

キーワードDETERMINISTICおよびPARALLEL_ENABLEを、ファンクションを宣言する構文内で使用できます。この2つのキーワードは最適化ヒントで、問合せオプティマイザおよび他のソフトウェア・コンポーネントに対して、次のファンクションについての情報を提供します。

ファンクション索引および特定のスナップショットやマテリアライズド・ビューで使用できるのは、DETERMINISTICを指定したファンクションのみです。

確定的な(DETERMINISTIC)ファンクションは、引数として渡される値のみに依存し、パッケージ変数またはデータベースの内容を参照したり変更することがなく、また、他の副作用を持ちません。このようなファンクションは、渡される引数値の組合せが同じであるかぎり、同じ戻り値を生成します。

the DETERMINISTICキーワードは、ファンクションの宣言の中で戻り値の型の後に入れます。次に例を示します。

CREATE FUNCTION F1 (P1 NUMBER) RETURN NUMBER DETERMINISTIC IS 
BEGIN 
  RETURN P1 * 2; 
END; 

このキーワードは、次の場所に挿入できます。

CREATE PACKAGE BODYまたはCREATE TYPE BODY文のファンクションまたはメソッドには、キーワードは置けません。

DETERMINISTICとマークされているファンクションに対するコールでは、他にアクションを実行しなくても、パフォーマンスがある程度最適化されます。次の機能では、その機能で使用されるすべてのファンクションをDETERMINISTICとして宣言する必要があります。

これらの機能では、ファンクションをコールするのではなく、事前に計算されている結果をできるだけ使用しようとします。

次のカテゴリのファンクションは、通常DETERMINISTICです。

Oracle Databaseでは、既存のアプリケーションを壊すことなく前述のカテゴリのファンクションを明示的にDETERMINISTICと宣言するように要求することはできませんが、このキーワードを使用することはアプリケーションのスタイルとして賢明な選択といえます。

DETERMINISTICを作成する場合は、次の点に注意してください。

パラレル問合せおよびパラレルDML

Oracle Databaseのパラレル実行機能により、SQL文の実行作業が複数のプロセスにわたって分割されます。パラレルで実行されるSQL文からコールされるファンクションは、各プロセス内で実行される個別のコピーを持つことができ、それぞれのコピーは、そのプロセスによって処理される行のサブセットのためにのみコールされます。

各プロセスには、そのプロセス専用のパッケージ変数のコピーがあります。パラレル実行が開始されると、コピーされたパッケージ変数は、新しいユーザーがシステムにログインするときのように、パッケージ仕様部および本体の情報に基づいて初期化されます。パッケージ変数内の値は、元のログイン・セッションからはコピーされません。パッケージ変数に対する変更は、多数のセッション間で伝播したり、元のセッションに伝播することはありません。JavaのSTATICクラス属性は、同様に、各プロセス内で独立して初期化され変更されます。ファンクションでは、検出される様々な行の値をパッケージ(またはJavaのSTATIC)変数を使用して蓄積できるため、Oracle Databaseでは、すべてのユーザー定義ファンクションの実行をパラレル化しても安全であるとはいえません。

検索(SELECT)文の場合、8.1.5より前のリリースのOracle Databaseでは、ファンクションがPRAGMA RESTRICT_REFERENCES宣言内でRNPSおよびWNPSとして記されているかどうかは、パラレル問合せ最適化機能により調べられていました。RNPSかつWNPSとしてマークされているファンクションは、パラレルで実行できました。CREATE FUNCTION文を使用して定義されているファンクションでは、ファンクションが十分に純粋であるかどうかを判断するために、暗黙的にコードが調べられていました。パラレル実行は、これらのファンクションに対してプラグマを指定できない場合でも、発生する可能性があります。

関連項目

「下位互換性のためのPRAGMA RESTRICT_REFERENCES」 

DML文の場合、8.1.5より前のリリースのOracle Databaseでは、ファンクションがPRAGMA RESTRICT_REFERENCES宣言内でRNDSWNDSRNPSおよびWNPSの4つすべてが記されているかどうかは、パラレル化最適化機能により調べられていました。データベースまたはパッケージ変数のいずれかに対して読取りでもなく書込みでもないとマークされたファンクションは、パラレルで実行できました。ここでも、CREATE FUNCTION文を使用して定義されているファンクションでは、ファンクションが実際に十分に純粋であるかどうかを判断するために、明示的にコードが調べられていました。パラレル実行は、これらのファンクションに対してプラグマを指定できない場合でも発生する可能性があります。

Oracle Databaseリリース8.1.5以降でも、以前のバージョンでパラレル化可能として認識されていたファンクションは、引き続きパラレル化されます。コードをパラレル実行用として安全であるとマークする方法として、PARALLEL_ENABLEキーワードをお薦めします。 このキーワードは、「ファンクションの宣言」で説明しているDETERMINISTICと構文的に類似しています。このキーワードは、次に示すように、ファンクション宣言の戻り値型の後に入れます。

CREATE FUNCTION F1 (P1 NUMBER) RETURN NUMBER PARALLEL_ENABLE IS 
BEGIN 
  RETURN P1 * 2; 
END; 

CREATE FUNCTIONを使用して定義されるPL/SQLファンクションは、そのファンクションがパッケージ変数の読取りも書込みも行わず、パッケージ変数の読取りまたは書込みを行う可能性のあるファンクションもコールしないことをシステムで判断できる場合は、パラレルで実行しても安全であると明示的に宣言しなくても、パラレルで実行できます。JavaメソッドまたはC関数は、プログラマがコール仕様にPARALLEL_ENABLEと明示的に指定するか、またはPRAGMA RESTRICT_REFERENCESを指定してファンクションが十分に純粋であることを示さないかぎり、システムはパラレルでの実行が安全であるとはみなしません。

パラレルDML文の一部としてパラレル実行されるファンクションに対しては、追加のランタイム制約が設けられています。このようなファンクションは、DML文の実行を許可されません。検索(SELECT)文の中で実行されるファンクションに対して適用される制約と同じ制約を受けます。

関連項目

「制限事項」 

下位互換性のためのPRAGMA RESTRICT_REFERENCES

8.1.5(Oracle8i)より前のOracle Databaseバージョンでは、プログラマはサブプログラムの純粋度レベルの宣言にプラグマRESTRICT_REFERENCESを使用していました。それ以降のバージョンでは、かわりにヒントparallel-enableおよびdeterministicを使用して、サブプログラムの純粋度についてOracle Databaseと通信します。

コードからRESTRICT_REFERENCESを削除できます。ただし、このプラグマは次のような下位互換性が必要となる場合に備えて残されています。

既存のPL/SQLアプリケーションでは、既存コードとの統合を容易にするために、新しいファンクションに対してもプラグマを引き続き使用することができます。まったく新しいアプリケーションではプラグマは使用しないでください。

プラグマRESTRICT_REFERENCESを使用する場合は、パッケージ本体ではなく、パッケージ仕様部に含めます。このプラグマは、サブプログラム(ファンクションまたはプロシージャ)の宣言の後に置く必要がありますが、直後に置く必要はありません。所定のサブプログラム宣言を参照できるプラグマは、1つのみです。


注意

プラグマRESTRICT_REFERENCESは、ファンクションとプロシージャの両方に適用されます。純粋度レベルはファンクションのみでなく、ファンクションによってコールされるプロシージャでも重要です。 


プラグマRESTRICT_REFERENCESをコーディングするには、次の構文を使用します。

PRAGMA RESTRICT_REFERENCES ( 
    Function_name, WNDS [, WNPS] [, RNDS] [, RNPS] [, TRUST] ); 

パラメータは次のとおりです。

キーワード  説明 

WNDS 

サブプログラムはデータベース状態を書き込みません(データベース表を変更しないということです)。 

RNDS 

サブプログラムはデータベース状態を読み込みません(データベース表を問い合せないということです)。 

WNPS 

サブプログラムはパッケージ状態を書き込みません(パッケージ変数の値を変更しないということです)。 

RNPS 

サブプログラムはパッケージ状態を読み込みません(パッケージ変数の値を参照しないということです)。 

TRUST 

プラグマにリストされている制限は適用されません。真であると判断できます。これにより、RESTRICT_REFERENCES宣言を持つファンクションからこの宣言を持たないファンクションへのコールが簡単になります。  

引数はどんな順序でも渡せます。サブプログラム本体にあるSQL文のいずれかが規則に違反する場合は、その文が解析されるときにエラーが発生します。

次の例では、ファンクションcompoundにより、データベースまたはパッケージ状態の読取りおよび書込みは行われないため、最大の純粋度レベルを宣言できます。サブプログラムで可能な最高の純粋度レベルを常に保つようにしてください。これによって、PL/SQLコンパイラがサブプログラムを必要以上に拒否することはなくなります。


注意

次のようなデータ構造を設定しないと機能しない例もあります。

CREATE TABLE Accts (
Yrs NUMBER,
Amt NUMBER,
Acctno NUMBER,
Rte NUMBER);
 

CREATE PACKAGE Finance AS  -- package specification 
   FUNCTION Compound 
         (Years  IN NUMBER, 
          Amount IN NUMBER, 
          Rate   IN NUMBER) RETURN NUMBER; 
   PRAGMA RESTRICT_REFERENCES (Compound, WNDS, WNPS, RNDS, RNPS); 
END Finance; 
 
CREATE PACKAGE BODY Finance AS  --package body 
   FUNCTION Compound 
         (Years  IN NUMBER, 
          Amount IN NUMBER, 
          Rate   IN NUMBER) RETURN NUMBER IS 
   BEGIN 
      RETURN Amount * POWER((Rate / 100) + 1, Years); 
   END Compound; 
                   -- no pragma in package body 
END Finance; 

後で、次のようにPL/SQLブロックからcompoundをコールできます。

DECLARE
   Interest NUMBER;
   Acct_id NUMBER;
BEGIN 
   SELECT Finance.Compound(Yrs, Amt, Rte)  -- function call       
   INTO   Interest       
   FROM   Accounts       
   WHERE  Acctno = Acct_id; 
キーワードTRUSTの使用

キーワードTRUSTRESTRICT_REFERENCES構文で使用すると、RESTRICT_REFERENCES宣言を持つファンクションからこの宣言を持たないファンクションへのコールが容易になります。TRUSTが指定されていると、プラグマにリストされている制限は実際には適用されませんが、真であると判断できます。

プラグマを使用するコード・セクションからプラグマを使用しないコード・セクションをコールするときは、2種類の使用スタイルがあります。1つは、コールされるルーチン上にプラグマを入れるスタイルです。たとえば、Javaメソッド用のコール仕様上に入れます。これで、PL/SQLからこのメソッドをコールした場合、メソッドの制約がコール側のサブプログラムの制約より少ない場合、コールでエラーが発生します。次に例を示します。

CREATE OR REPLACE PACKAGE P1 IS 
   FUNCTION F1 (P1 NUMBER) RETURN NUMBER IS 
      LANGUAGE JAVA NAME 'CLASS1.METHODNAME(int) return int'; 
      PRAGMA RESTRICT_REFERENCES(F1,WNDS,TRUST); 
   FUNCTION F2 (P1 NUMBER) RETURN NUMBER; 

   PRAGMA RESTRICT_REFERENCES(F2,WNDS); 
END; 
 
CREATE OR REPLACE PACKAGE BODY P1 IS 
   FUNCTION F2 (P1 NUMBER) RETURN NUMBER IS 
   BEGIN 
      RETURN F1(P1); 
   END; 
END;   

ここで、F1WNDSとして宣言されているため、F2F1をコールできます。

もう1つのスタイルは、コール側のみをマークする方法です。マークされたコール側は、エラーなしで任意のサブプログラムをコールできます。次に例を示します。

CREATE OR REPLACE PACKAGE P1a IS 
   FUNCTION F1 (P1 NUMBER) RETURN NUMBER IS 
      LANGUAGE JAVA NAME 'CLASS1.METHODNAME(int) return int'; 
   FUNCTION F2 (P1 NUMBER) RETURN NUMBER; 
   PRAGMA RESTRICT_REFERENCES(F2,WNDS,TRUST); 
END; 
 
CREATE OR REPLACE PACKAGE BODY P1a IS 
   FUNCTION F2 (P1 NUMBER) RETURN NUMBER IS 
   BEGIN 
      RETURN F1(P1); 
   END; 
END; 

ここでは、F2F1をコールできます。これは、F2WNDSと指定されています(TRUSTが指定されているため)が、F2の本体がWNDS制約を本当に満たすかどうかが実際に調べられていないためです。F2が調べられないため、F1にはPRAGMA RESTRICT_REFERENCESが指定されていないにもかかわらず、F2からF1にコールできます。

静的SQL文と動的SQL文の違い

静的なINSERTUPDATEおよびDELETE文は、表の列などのデータベース状態を明示的に読み込まない場合は、RNDSには違反しません。ただし、動的なINSERTUPDATEおよびDELETE文の場合は、データベース状態を明示的に読み込むかどうかにかかわらず、常にRNDSに違反します。

次のINSERTは、動的に実行される場合はRNDSに違反しますが、静的に実行される場合はRNDSに違反しません。

INSERT INTO my_table values(3, 'SCOTT'); 

次のUPDATEは、my_tableの列名を明示的に読み込むため、静的に実行された場合も動的に実行された場合もRNDSに違反します。

UPDATE my_table SET id=777 WHERE name='SCOTT';
パッケージPL/SQLファンクションのオーバーロード

PL/SQLでは、パッケージ・ファンクション(スタンドアロン以外)のオーバーロードが可能です。仮パラメータの数、順序、データ型ファミリなどが異なっていれば、別のファンクションに対して同じ名前を使用できます。

ただし、RESTRICT_REFERENCESプラグマは、1つのファンクション宣言にのみ適用できます。したがって、オーバーロードされたファンクションの名前を参照するプラグマは、常に前にある、最も近いファンクション宣言に適用されます。

次の例では、プラグマは、2番目のvalidの宣言に適用されます。

CREATE PACKAGE Tests AS 
    FUNCTION Valid (x NUMBER) RETURN CHAR; 
    FUNCTION Valid (x DATE) RETURN CHAR; 
    PRAGMA RESTRICT_REFERENCES (valid, WNDS); 
 END;

逐次再利用可能PL/SQLパッケージ

通常、PL/SQLパッケージでは、パッケージ内のパッケージ変数およびカーソルの数に応じて、ユーザー・グローバル領域(UGA)のメモリーが消費されます。このメモリーはユーザー数に比例して増加するため、スケーラビリティが制限されます。これを解決するには、プラグマ構文を使用して一部のパッケージにSERIALLY_REUSABLEのマークを付けます。

逐次再利用可能パッケージの場合、パッケージのグローバル・メモリーは各ユーザーのUGAではなく、小さなプールに保持され、複数の異なるユーザーのために再利用されます。これは、このようなパッケージのグローバル・メモリーは、作業単位内でのみ使用されることを意味します。そのため、作業単位の終了時にメモリーはそのプールに解放され、(すべてのグローバル変数の初期化コードの実行後)別のユーザーによって再利用されます。

逐次再利用可能パッケージの作業単位とは、サーバーへのOCIコール、PL/SQLのクライアント・サーバー間RPCコール、PL/SQLのサーバー間RPCコールなどのサーバーへのコールです。

パッケージ状態

再利用不可能パッケージ(SERIALLY_REUSABLEのマークが付いていない)の状態は、セッションの存続期間を通じて持続します。パッケージ状態には、グローバル変数やカーソルなどが含まれます。

逐次再利用可能パッケージの状態は、サーバーへのコールの存続期間中のみ持続します。サーバーへの後続のコールでは、逐次再利用パッケージが参照される場合、Oracle Databaseにより逐次再利用可能パッケージの新しいインスタンス化が作成され、すべてのグローバル変数をNULLに、または指定したデフォルト値に初期化します。サーバーへの以前のコール内の逐次再利用可能パッケージ状態に対して行われた変更は参照できません。


注意

サーバーへのコール時に逐次再利用可能パッケージの新しいインスタンス化が作成されても、Oracle Databaseによってメモリーが割り当てられたり、インスタンス化オブジェクトが構成されるとはかぎりません。Oracle Databaseは、SGAの前回使用されてから最も時間の経過している(LRU)プールにある、このパッケージで使用可能な(割当ておよび構成済の)インスタンス化作業領域を探します。

サーバーへのコールの終了時に、この作業域はLRUプールに戻されます。SGA内にプールがあるのは、同じパッケージに対する要求を持つユーザー間で、作業域を再利用できるようにするためです。 


逐次再利用可能パッケージを使用する理由

再利用不可能パッケージの状態はセッションの存続期間を通じて持続するので、セッション全体のUGAメモリーがロックされます。Oracle Officeなどのアプリケーションでは、ログイン・セッションは一般に何日も持続します。アプリケーションでは、特定のパッケージをセッション中の特定のローカル期間のみで使用することが必要な場合がよくあります。また、パッケージの使用後は、セッションの途中でパッケージ状態を非インスタンス化するのが理想的です。

逐次再利用可能パッケージ(SERIALLY_REUSABLE)を使用すると、アプリケーション開発者は、メモリーをより適切に管理してスケーラビリティを向上させるアプリケーションをモデル化できます。サーバーへのコール中にのみ管理されるパッケージ状態は、SERIALLY_REUSABLEパッケージ内に獲得する必要があります。

逐次再利用可能パッケージの構文

パッケージは、PRAGMA SERIALLY_REUSABLEにより逐次再利用可能のマークが付けられます。プラグマの構文は、次のとおりです。

PRAGMA SERIALLY_REUSABLE;

パッケージ仕様部は、対応するパッケージ本体の有無にかかわらず、逐次再利用可能のマークを付けられます。パッケージに本体がある場合、対応する仕様部に逐次再利用可能プラグマがあると、本体にもそのプラグマが必要です。逐次再利用可能プラグマは、仕様部にそのプラグマがないかぎり、本体に含めることはできません。

逐次再利用可能パッケージのセマンティック

SERIALLY_REUSABLEのマークが付いたパッケージには、次のプロパティがあります。

逐次再利用可能パッケージの例

この項では、逐次再利用可能パッケージの例を示します。

例1: コール境界を超えるパッケージ変数の機能

次に、逐次再利用可能パッケージ仕様部の例を示します(本体はありません)。

CONNECT Scott/Tiger 

CREATE OR REPLACE PACKAGE Sr_pkg IS 
  PRAGMA SERIALLY_REUSABLE; 
  N NUMBER := 5;                -- default initialization 
END Sr_pkg; 

Enterprise Manager(またはSQL*Plus)アプリケーションから次の文が発行されるとします。

CONNECT Scott/Tiger 

# first CALL to server 
BEGIN 
   Sr_pkg.N := 10; 
END; 

# second CALL to server 
BEGIN 
   DBMS_OUTPUT.PUT_LINE(Sr_pkg.N); 
END; 

このプログラムにより、次のような結果が出力されます。

5 


注意

パッケージにプラグマSERIALLY_REUSABLEがない場合は、10が出力されます。 


例2: コール境界を超えるパッケージ変数の機能

次に、逐次再利用可能なパッケージ仕様部およびパッケージ本体の例を示します。

CONNECT Scott/Tiger 

DROP PACKAGE Sr_pkg;
CREATE OR REPLACE PACKAGE Sr_pkg IS
   PRAGMA SERIALLY_REUSABLE;
   TYPE Str_table_type IS TABLE OF VARCHAR2(200) INDEX BY BINARY_INTEGER;
   Num     NUMBER        := 10;
   Str     VARCHAR2(200) := 'default-init-str';
   Str_tab STR_TABLE_TYPE;
   
    PROCEDURE Print_pkg;
    PROCEDURE Init_and_print_pkg(N NUMBER, V VARCHAR2);
END Sr_pkg;
CREATE OR REPLACE PACKAGE BODY Sr_pkg IS
   -- the body is required to have the pragma because the
  -- specification of this package has the pragma
  PRAGMA SERIALLY_REUSABLE;
   PROCEDURE Print_pkg IS
   BEGIN
      DBMS_OUTPUT.PUT_LINE('num: ' || Sr_pkg.Num);
      DBMS_OUTPUT.PUT_LINE('str: ' || Sr_pkg.Str);
      DBMS_OUTPUT.PUT_LINE('number of table elems: ' || Sr_pkg.Str_tab.Count);
      FOR i IN 1..Sr_pkg.Str_tab.Count LOOP
         DBMS_OUTPUT.PUT_LINE(Sr_pkg.Str_tab(i));
      END LOOP;
   END;
   PROCEDURE Init_and_print_pkg(N NUMBER, V VARCHAR2) IS
   BEGIN
   -- init the package globals
      Sr_pkg.Num := N;
      Sr_pkg.Str := V;
      FOR i IN 1..n LOOP
         Sr_pkg.Str_tab(i) := V || ' ' || i;
   END LOOP;
   -- print the package
   Print_pkg;
   END;
 END Sr_pkg;

SET SERVEROUTPUT ON;

Rem SR package access in a CALL:

BEGIN
   -- initialize and print the package
   DBMS_OUTPUT.PUT_LINE('Initing and printing pkg state..');
   Sr_pkg.Init_and_print_pkg(4, 'abracadabra');
   -- print it in the same call to the server.
   -- we should see the initialized values.
   DBMS_OUTPUT.PUT_LINE('Printing package state in the same CALL...');
   Sr_pkg.Print_pkg;
END;

Initing and printing pkg state..
num: 4
str: abracadabra
number of table elems: 4
abracadabra 1
abracadabra 2
abracadabra 3
abracadabra 4
Printing package state in the same CALL...
num: 4
str: abracadabra
number of table elems: 4
abracadabra 1
abracadabra 2
abracadabra 3
abracadabra 4

REM SR package access in subsequent CALL:
BEGIN
   -- print the package in the next call to the server. 
   -- We should that the package state is reset to the initial (default) values.
   DBMS_OUTPUT.PUT_LINE('Printing package state in the next CALL...');
   Sr_pkg.Print_pkg;
END;
Statement processed.
Printing package state in the next CALL...
num: 10
str: default-init-str
number of table elems: 0
例3: コール境界での逐次再利用可能パッケージのオープン・カーソル

この例は、逐次再利用可能パッケージのすべてのオープン・カーソルが作業境界(コール)の終了時に自動的にクローズされることを示します。また、新しいコールでは、これらのカーソルを再度オープンする必要があります。

REM  For serially reusable pkg: At the end work boundaries
REM  (which is currently the OCI call boundary) all open
REM  cursors will be closed.
REM
REM  Because the cursor is closed - every time we fetch we 
REM  will start at the first row again. 

CONNECT Scott/Tiger
DROP PACKAGE  Sr_pkg;
DROP TABLE People;
CREATE TABLE People (Name VARCHAR2(20));
INSERT INTO  People  VALUES ('ET');
INSERT INTO  People  VALUES ('RAMBO');
CREATE OR REPLACE PACKAGE Sr_pkg IS
   PRAGMA SERIALLY_REUSABLE;
   CURSOR C IS SELECT Name FROM People;
END Sr_pkg;
SQL> SET SERVEROUTPUT ON;
SQL> 
CREATE OR REPLACE PROCEDURE Fetch_from_cursor IS
Name VARCHAR2(200);
BEGIN
   IF (Sr_pkg.C%ISOPEN) THEN
      DBMS_OUTPUT.PUT_LINE('cursor is already open.');
   ELSE
      DBMS_OUTPUT.PUT_LINE('cursor is closed; opening now.');
      OPEN Sr_pkg.C;
   END IF;
   -- fetching from cursor.
   FETCH sr_pkg.C INTO name;
   DBMS_OUTPUT.PUT_LINE('fetched: ' || Name);
   FETCH Sr_pkg.C INTO name;
   DBMS_OUTPUT.PUT_LINE('fetched: ' || Name);
   -- Oops forgot to close the cursor (Sr_pkg.C).
   -- But, because it is a Serially Reusable pkg's cursor, 
   -- it will be closed at the end of this CALL to the server.
END;
EXECUTE fetch_from_cursor;
cursor is closed; opening now.
fetched: ET
fetched: RAMBO

ファンクションからの大量のデータの戻し

データ・ウェアハウス環境では、大量のデータを変換するためにPL/SQLファンクションを使用します。データは、異なるファンクションによる一連の変換を経由して渡されます。PL/SQL表ファンクションを使用すると、このような変換を、かなりのメモリー・オーバーヘッドを必要としたり、各変換の間でデータを表に格納する必要なく実行できます。これらのファンクションは、複数の行を受け入れて戻すことが可能で、行は、一度ではなく準備できた順に戻すことができます。さらにパラレル化も可能です。

この方法は、次のように行います。

次に例を示します。

CREATE FUNCTION StockPivot(p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED IS
  out_rec TickerType := TickerType(NULL,NULL,NULL);
  in_rec p%ROWTYPE;
BEGIN
  LOOP
-- Function accepts multiple rows through a REF CURSOR argument.
    FETCH p INTO in_rec;
    EXIT WHEN p%NOTFOUND;
-- Return value is a record type that matches the table definition.
    out_rec.ticker := in_rec.Ticker;
    out_rec.PriceType := 'O';
    out_rec.price := in_rec.OpenPrice;
-- Once a result row is ready, we send it back to the calling program,
-- and continue processing.
    PIPE ROW(out_rec);
-- This function outputs twice as many rows as it receives as input.
    out_rec.PriceType := 'C';
    out_rec.Price := in_rec.ClosePrice;
    PIPE ROW(out_rec);
  END LOOP;
  CLOSE p;
-- The function ends with a RETURN statement that does not specify any value.
  RETURN;
END;
/

-- Here we use the result of this function in a SQL query.
SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable)));

-- Here we use the result of this function in a PL/SQL block.
DECLARE
  total NUMBER := 0;
  price_type VARCHAR2(1);
BEGIN
  FOR item IN (SELECT * FROM TABLE(StockPivot(CURSOR(SELECT * FROM StockTable))))
  LOOP
-- Access the values of each output row.
-- We know the column names based on the declaration of the output type.
-- This computation is just for illustration.
    total := total + item.price;
    price_type := item.price_type;
  END LOOP;
END;
/

独自の集計関数のコード化

一連の行を分析して結果値を計算するために、次のようにSUMなどの組込み集計と同様に機能する集計関数をコード化することができます。


1 次のようなデータ構造を設定しないと機能しない例もあります。 CONNECT SYS/password AS SYSDBA;CREATE USER Jward IDENTIFIED BY Jward;GRANT CREATE ANY PACKAGE TO Jward;GRANT CREATE SESSION TO Jward;GRANT EXECUTE ANY PROCEDURE TO Jward;CONNECT Scott/Tiger


戻る 次へ
Oracle
Copyright © 2006 Oracle Corporation.

All Rights Reserved.
目次
目次
索引
索引