| Oracle Database PL/SQLユーザーズ・ガイドおよびリファレンス 10g リリース2(10.2) B19257-01 |
|
この章では、文の集合を、再利用可能なサブプログラムとして作成する方法について説明します。サブプログラムは、メンテナンスしやすいモジュール構造のアプリケーションを構成するビルディング・ブロックと似ています。
この章の項目は、次のとおりです。
サブプログラムは、パラメータの組を指定してコールできる、名前の付けられたPL/SQLブロックです。PL/SQLにはプロシージャとファンクションの2種類のサブプログラムがあります。一般に、プロシージャはアクションを実行するために使用し、ファンクションは値を計算するために使用します。
無名ブロックと同様に、サブプログラムは次の部分から構成されます。
例8-1に、入力パラメータと出力パラメータの両方を受け取り、潜在的なエラーを処理する文字列操作プロシージャdoubleの例を示します。
DECLARE in_string VARCHAR2(100) := 'This is my test string.'; out_string VARCHAR2(200); PROCEDURE double ( original IN VARCHAR2, new_string OUT VARCHAR2 ) AS BEGIN new_string := original || ' + ' || original; EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('Output buffer not long enough.'); END; BEGIN double(in_string, out_string); DBMS_OUTPUT.PUT_LINE(in_string || ' - ' || out_string); END; /
例8-2に、一時的な結果を保持するローカル変数を宣言し、終了時に値を戻す数値ファンクションsquareを示します。
DECLARE FUNCTION square(original NUMBER) RETURN NUMBER AS original_squared NUMBER; BEGIN original_squared := original * original; RETURN original_squared; END; BEGIN DBMS_OUTPUT.PUT_LINE(square(100)); END; /
|
注意:
|
サブプログラムを使用すると、PL/SQL言語を拡張できます。プロシージャは新しい文のように機能します。ファンクションは新しい式および演算子のように機能します。
サブプログラムを使用すると、プログラムを管理の容易な、正しく定義されたモジュールに分けることができます。この特性を利用すると、トップダウン設計と段階的詳細化アプローチによって問題を解決できます。
サブプログラムによって、再利用性が向上します。テスト済のサブプログラムは、いくつものアプリケーションで再利用できます。PL/SQLサブプログラムは、多くの異なる環境からコールできます。そのため、新しい言語またはAPIを使用してデータベースにアクセスするたびにサブプログラムを最初から作成しなおす必要はありません。
サブプログラムによって、メンテナンス性が向上します。コール元のサブプログラムを変更することなく、サブプログラムの内部を変更できます。パッケージやオブジェクト型などの、メンテナンス性を向上する他の機能においても、サブプログラムは重要な役割を果たします。
ダミーのサブプログラム(スタブ)を使用すると、メイン・プログラムのテストが終了するまで、プロシージャまたはファンクションを定義しないで済ますことができます。処理の詳細にとらわれることなく、抽象的な思考方法によるトップダウン手法でアプリケーションを設計できます。
PL/SQLサブプログラムを使用してAPIを定義する場合は、サブプログラムをPL/SQLパッケージにグループ化することで、コードをさらに再利用しやすく、メンテナンス性の高いものにできます。パッケージの詳細は、第9章「PL/SQLパッケージの使用」を参照してください。
プロシージャとは、特定のアクションを実行するサブプログラムのことです。プロシージャの名前、パラメータ、ローカル変数およびBEGIN-ENDブロック(コードを含み、例外を処理する)を指定します。PROCEDURE宣言の構文は、「プロシージャ宣言」を参照してください。
各パラメータでは、次の内容を指定します。
IN、OUTまたはIN OUT)。パラメータ・モードを省略した場合、デフォルトはINです。オプションのNOCOPYキーワードを指定すると、サイズの大きいOUTパラメータまたはIN OUTパラメータの処理がスピードアップします。
プロシージャの実行時に、プロシージャを定義したユーザーのスキーマおよび権限を使用するか、プロシージャをコールするユーザーのスキーマおよび権限を使用するかを指定できます。詳細は、「実行者権限と定義者権限の使用(AUTHID句)」を参照してください。
プロシージャをカレント・トランザクションの一部として実行するか、コール元のトランザクションを終了することなくプロシージャ自身のトランザクションでCOMMITまたはROLLBACKを実行するかを指定できます。詳細は、「自律型トランザクションによる独立した作業単位の実行」を参照してください。
プロシージャには、仕様部と本体の2つの部分があります。プロシージャの仕様部は、キーワードPROCEDUREで始め、プロシージャ名またはパラメータ・リストで終わり、その後に予約語IS(またはAS)が続きます。パラメータ宣言はオプションです。パラメータを取らないプロシージャではカッコを書きません。
プロシージャの本体は、予約語IS(またはAS)で始め、キーワードENDで終わります。ENDの後には、オプションとしてプロシージャ名を続けることができます。プロシージャの本体には、宣言部、実行部、例外処理部(オプション)の3つの部分があります。
宣言部では、ローカル宣言を置きます。無名PL/SQLブロックで使用するキーワードDECLAREは、プロシージャでは使用しません。実行部では、キーワードBEGINとEXCEPTION(またはEND)の間に文を置きます。プロシージャの実行部には、少なくとも1つの文が存在している必要があります。NULL文を使用すると、プレースホルダ・プロシージャを定義したり、プロシージャでアクションを実行しないように指定できます。例外処理部では、キーワードEXCEPTIONとENDの間に例外ハンドラを置きます。
プロシージャはPL/SQL文としてコールされます。たとえば、プロシージャraise_salaryは次のようにしてコールできます。
raise_salary(emp_id, amount);
ファンクションとは、値を計算するサブプログラムのことです。ファンクションとプロシージャは同じような構造を持ちますが、ファンクションの方はRETURN句を持っています。ファンクションには多くのオプションのキーワードがあり、テーブル・ファンクションと呼ばれる特別なファンクションのクラスを宣言するために使用されます。通常、これらのキーワードは、データ・ウェアハウス・アプリケーションで大量のデータを変換する場合に使用します。FUNCTION宣言の構文は、「ファンクション宣言」を参照してください。
AUTHID句は、ストアド・ファンクションをその所有者(デフォルト)と現行ユーザーのどちらの権限で実行するかという点、およびスキーマ・オブジェクトへの未修飾の参照を所有者と現行ユーザーのどちらのスキーマで解決するかという点を決定します。CURRENT_USERを指定すると、デフォルトの動作を変更できます。
PARALLEL_ENABLEオプションは、ストアド・ファンクションがパラレルDML評価のスレーブ・セッションで安全に使用されることを宣言します。メイン(ログオン)・セッションの状態が、スレーブ・セッションと共有されることはありません。スレーブ・セッションごとに固有の状態があり、セッション開始時に初期化されます。ファンクションの結果がセッション(static)変数の状態に依存しないようにしてください。さもないと、セッションごとに結果が異なる可能性があります。
DETERMINISTICオプションは、オプティマイザが冗長なファンクション・コールを回避するのに役立ちます。ストアド・ファンクションが同じ引数で事前にコールされた場合は、オプティマイザは前の結果を使用できます。DETERMINISTICオプションの詳細および制限については、『Oracle Database SQLリファレンス』の「CREATE FUNCTION」を参照してください。
AUTONOMOUS_TRANSACTIONプラグマはファンクションを自律型(独立型)としてマークするようにPL/SQLコンパイラに指示します。自律型トランザクションを使用すると、メイン・トランザクションを停止し、SQL操作を実行してその操作をコミットまたはロールバックしてから、メイン・トランザクションを再開できます。
パラメータまたはファンクション戻り値のデータ型に、NOT NULLなどで制約を課すことはできません。ただし、回避策を使用して、パラメータ型に間接的にサイズ制約を課すことができます。「PL/SQLプロシージャ」を参照してください。
プロシージャと同様に、ファンクションも仕様部と本体の2つの部分を持ちます。ファンクションの仕様部はキーワードFUNCTIONで始め、戻り値のデータ型を指定するRETURN句で終わります。パラメータ宣言はオプションです。パラメータを取らないファンクションではカッコを書きません。
ファンクション本体は、キーワードIS(またはAS)で始め、キーワードENDで終わります。ENDの後には、オプションとしてファンクション名を続けることができます。ファンクション本体には、宣言部、実行部、例外処理部(オプション)の3つの部分があります。
宣言部では、キーワードISとBEGINの間にローカル宣言を置きます。キーワードDECLAREは使用しません。実行部では、キーワードBEGINとEXCEPTION(またはEND)の間に文を置きます。ファンクションの実行部には、1つまたは複数のRETURN文が存在している必要があります。例外処理部では、キーワードEXCEPTIONとENDの間に例外ハンドラを置きます。
ファンクションは、式の一部としてコールされます。次に例を示します。
IF sal_ok(new_sal, new_title) THEN ...
RETURN文は、サブプログラムの実行を即座に終了し、コール元に制御を戻します。サブプログラム・コールの直後の文から、実行が継続されます。(このRETURN文を、ファンクションの仕様部の中で戻り値のデータ型を指定するRETURN句と混同しないようにしてください。)
サブプログラムでは、複数のRETURN文を使用できます。サブプログラムをRETURN文で終える必要はありません。どのRETURN文を実行しても、サブプログラムは即座に終了します。
プロシージャでは、RETURN文は値を戻さないため、式を含めることはできません。RETURN文は、プロシージャが終了する前に、コール元に制御を戻します。
ファンクションでは、RETURN文に、RETURN文の実行時に評価される式が含まれている必要があります。結果として得られる値は、RETURN句で指定された型の変数と同様の機能を持つファンクション識別子に代入されます。RETURN文の使用については、例8-2を参照してください。
ファンクションの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つの実行パスが必要です。実行パスがない場合は、実行時に「ファンクションが値なしで戻されました」というエラーが発生します。
サブプログラムは、任意のPL/SQLブロック、サブプログラムまたはパッケージの中で宣言できます。サブプログラムは、宣言部の末尾で他のすべての項目の後に置く必要があります。
サブプログラムは、コールする前に宣言する必要があります。このため、相互にコールする複数のネストしたサブプログラムを宣言することは困難になります。
前方宣言を使用すると、相互に関連したネストしたサブプログラムを宣言できます。前方宣言は、セミコロンで終わる、本体のないサブプログラム仕様部です。
仮パラメータのリストは前方宣言の中にも指定しますが、サブプログラム本体にも必要です。サブプログラム本体の位置は、前方宣言の後であればどこでもかまいませんが、同じプログラム・ユニットの中に置く必要があります。
DECLARE PROCEDURE proc1(number1 NUMBER); -- forward declaration PROCEDURE proc2(number2 NUMBER) IS BEGIN proc1(number2); -- calls proc1 END; PROCEDURE proc1(number1 NUMBER) IS BEGIN proc2 (number1); -- calls proc2 END; BEGIN NULL; END; /
この項では、パラメータを使用してPL/SQLサブプログラムと情報をやり取りする方法について説明します。この項の項目は、次のとおりです。
サブプログラムはパラメータを使用して情報を渡します。
実パラメータと仮パラメータに別々の名前を付けることは、プログラミングの習慣として好ましいことです。
プロシージャをコールすると、実パラメータが評価され、対応する仮パラメータにその結果が代入されます。実パラメータの値を仮パラメータに代入する前に、必要に応じてPL/SQLは値のデータ型を変換します。たとえば、パラメータとして文字列を取るプロシージャに数値を渡すと、PL/SQLがパラメータを変換し、プロシージャは文字列を受け取ります。
実パラメータと、それに対応する仮パラメータは、互換性のあるデータ型を持っている必要があります。たとえば、PL/SQLはDATEデータ型とNUMBERデータ型を変換できません。また、ドル記号などの特別な文字を含む文字列を数値に変換できません。
例8-4内のプロシージャは、emp_idおよびamountの2つの仮パラメータを宣言し、プロシージャ・コールは実パラメータemp_numおよびbonusを指定します。
DECLARE emp_num NUMBER(6) := 120; bonus NUMBER(6) := 100; merit NUMBER(4) := 50; PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER) IS BEGIN UPDATE employees SET salary = salary + amount WHERE employee_id = emp_id; END raise_salary; BEGIN raise_salary(emp_num, bonus); -- procedure call specifies actual parameters raise_salary(emp_num, merit + bonus); -- expressions can be used as parameters END; /
サブプログラムをコールする場合、次のいずれかの方法を使用して実パラメータを指定します。
この表記法は簡潔ですが、パラメータ(特にリテラル)を間違った順序で指定すると、不具合の検出が困難になります。プロシージャのパラメータ・リストが変更された場合は、コードを変更する必要があります。
=>)です。パラメータの順序は意味を持ちません。この表記法はより冗長ですが、コードが読みやすくなり、メンテナンス性が向上します。たとえば、パラメータの順序が変更されたり、新しいオプション・パラメータが追加されるなど、プロシージャのパラメータ・リストが変更されたときに、コードを変更しなくてもよい場合があります。他のユーザーのAPIをコールするコード、または他のユーザーが使用できるようにAPIを定義するコードでは、名前表記法を使用することは習慣として好ましいことです。
この表記法を使用して、必須パラメータの後にオプション・パラメータが続くプロシージャをコールできます。
例8-5に、位置表記法、名前表記法または混合表記法を使用した同等なプロシージャ・コールを示します。
DECLARE emp_num NUMBER(6) := 120; bonus NUMBER(6) := 50; PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER) IS BEGIN UPDATE employees SET salary = salary + amount WHERE employee_id = emp_id; END raise_salary; BEGIN raise_salary(emp_num, bonus); -- positional procedure call for actual parameters raise_salary(amount => bonus, emp_id => emp_num); -- named parameters raise_salary(emp_num, amount => bonus); -- mixed parameters END; /
パラメータ・モードは、仮パラメータの動作を定義する場合に使用します。パラメータ・モードには、IN(デフォルト)、OUTおよびIN OUTの3つがあります。
任意のサブプログラムで任意のパラメータ・モードを使用できます。ファンクションでは、OUTモードとIN OUTモードを使用しないでください。ファンクションが複数の値を戻すようなプログラミングは、好ましくありません。また、サブプログラム専用ではない変数の値を変更するなどの副作用にも注意してください。
INパラメータは、コールされるサブプログラムに値を渡すために使用します。サブプログラムの中では、INパラメータは定数のように取り扱われます。値は代入できません。
定数、リテラル、初期化された変数または式をINパラメータとして渡せます。
INパラメータはデフォルト値で初期化できます。デフォルト値は、サブプログラム・コールでINパラメータが省略された場合に使用されます。詳細は、「サブプログラムのパラメータのデフォルト値の使用」を参照してください。
OUTパラメータは、サブプログラムのコール元に値を戻します。サブプログラムの中では、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 has been updated for: ' || emp_last_name); END; /
OUTパラメータには変数を渡す必要があります。定数または式は渡せません。NOCOPYキーワードを指定していない場合、または未処理例外が発生してサブプログラムが正常に終了した場合、以前の値は失われます。「サブプログラムのパラメータのデフォルト値の使用」を参照してください。
変数と同じように、OUT仮パラメータはNULLに初期設定されます。OUT仮パラメータのデータ型は、組込みサブタイプNATURALNやPOSITIVENなどのNOT NULLとして定義されたサブタイプにはできません。そうしないと、サブプログラムをコールしたときにPL/SQLはVALUE_ERRORを呼び出します。
サブプログラムを終了する前に、すべてのOUT仮パラメータに値を代入してください。そうしないと、対応する実パラメータの値はNULLになります。正常に終了した場合、PL/SQLは実パラメータに値を代入します。未処理例外が発生して終了すると、PL/SQLは実パラメータに値を代入しません。
IN OUTパラメータは、サブプログラムに初期値を渡し、更新された値をコール元に戻します。IN OUTパラメータに値を代入したり、その値を読み取ることができます。通常、IN OUTパラメータは文字列バッファまたは数値アキュムレータであり、サブプログラム内で読み取られた後に更新されます。
IN OUT仮パラメータに対応する実パラメータは、定数や式ではなく、変数である必要があります。
サブプログラムを正常に終了した場合、PL/SQLは実パラメータに値を代入します。未処理例外が発生して終了すると、PL/SQLは実パラメータに値を代入しません。
表8-1に、パラメータ・モードの概要をまとめます。
INパラメータをデフォルト値に初期化することで、サブプログラムに様々な数の実パラメータを渡し、省略したパラメータにはデフォルト値を指定できます。さらに、サブプログラムへのコールを個々に変更しなくても、仮パラメータを新しく追加できます。
パラメータが省略されると、対応する仮パラメータのデフォルト値が使用されます。実パラメータを省略して、仮パラメータを1つスキップすることはできません。最初のパラメータを省略して2番目のパラメータを指定するには、名前表記法を使用します。
実パラメータを省略して、初期化されていない仮パラメータにNULLを代入することはできません。明示的にNULLを渡すか、または宣言部でデフォルト値にNULLを指定します。
例8-7に、サブプログラム・パラメータのデフォルト値の使用法を示します。
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 raise_salary(120); -- same as raise_salary(120, 100, 50) raise_salary(emp_num, extra => 25); -- same as raise_salary(120, 100, 25) END; /
PL/SQLでは、サブプログラム名と型のメソッドをオーバーロードできます。仮パラメータの数、順序またはデータ型が異なっていれば、同じ名前を複数のサブプログラムで使用できます。パッケージ内のオーバーロードされたプロシージャの例については、例9-3を参照してください。
例8-8に、同じ名前を持つ2つのサブプログラムの定義方法を示します。プロシージャでは様々な型のコレクションを初期化します。この2つのプロシージャは同じ処理を実行しているため、同じ名前を与えるのが論理的です。
オーバーロードされるこの2つのinitializeプロシージャは、同じブロック、サブプログラム、パッケージまたはオブジェクト型の中に置くことができます。PL/SQLは仮パラメータをチェックして、どちらのプロシージャをコールするかを判断します。PL/SQLが使用するinitializeのバージョンは、プロシージャをDateTabTypパラメータまたはNumTabTypパラメータのどちらでコールするかによって異なります。
DECLARE TYPE DateTabTyp IS TABLE OF DATE INDEX BY PLS_INTEGER; TYPE NumTabTyp IS TABLE OF NUMBER INDEX BY PLS_INTEGER; hiredate_tab DateTabTyp; sal_tab NumTabTyp; PROCEDURE initialize (tab OUT DateTabTyp, n INTEGER) IS BEGIN FOR i IN 1..n LOOP tab(i) := SYSDATE; END LOOP; END initialize; PROCEDURE initialize (tab OUT NumTabTyp, n INTEGER) IS BEGIN FOR i IN 1..n LOOP tab(i) := 0.0; END LOOP; END initialize; BEGIN initialize(hiredate_tab, 50); -- calls first (DateTabTyp) version initialize(sal_tab, 100); -- calls second (NumTabTyp) version END; /
2つのサブプログラムの仮パラメータの違いが数値データ型のみの場合、それらのサブプログラムはオーバーロードできます。この手法は、ファンクションの複数のバージョンが同じ名前を使用し、それぞれが異なる数値型を受け取る数値演算Application Programming Interface(API)を記述する場合に有効です。たとえば、BINARY_FLOATを受け取るファンクションはより高速で、BINARY_DOUBLEを受け取るファンクションは精度がより高くなる場合があります。
オーバーロードされたサブプログラムにパラメータを渡す場合、次のことに注意して、問題または予期しない結果を回避します。
BINARY_FLOATおよびBINARY_DOUBLEを受け取る場合、「5.0」のようなVARCHAR2リテラルを渡すと、どちらのファンクションがコールされるかを確認します。
5.0f(BINARY_FLOAT)、5.0d(BINARY_DOUBLE)のようなリテラル、またはTO_BINARY_FLOAT()、TO_BINARY_DOUBLE()、TO_NUMBER()のような変換ファンクションを使用します。
PL/SQLが一致する数値パラメータを検索する順序は、最初にPLS_INTEGERまたはBINARY_INTEGER、次にNUMBER、次にBINARY_FLOAT、最後にBINARY_DOUBLEです。指定されたパラメータに最初に一致したオーバーロードされるサブプログラムが使用されます。VARCHAR2値は、NUMBER、BINARY_FLOATまたはBINARY_DOUBLEパラメータに一致します。
たとえば、1つのパラメータを受け取るSQRTファンクションについて考えます。NUMBER、BINARY_FLOATまたはBINARY_DOUBLEパラメータを受け取るオーバーロードされるファンクションがあるとします。PLS_INTEGERパラメータを渡した場合、(前述の順序を使用すると)最初に一致してオーバーロードされるファンクションは、最も低速である可能性が高いNUMBERパラメータのファンクションです。より高速なバージョンを使用するには、TO_BINARY_FLOATまたはTO_BINARY_DOUBLEファンクションを使用して、パラメータを適切なデータ型に変換します。
次に、同じ型の2つのパラメータを受け取る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に変換します。
オーバーロードできるのは、ローカル・サブプログラム、パッケージ・サブプログラムまたは型の順序のみです。スタンドアロン・サブプログラムはオーバーロードできません。
サブプログラムの仮パラメータの違いが名前またはパラメータ・モードのみの場合は、2つのサブプログラムをオーバーロードできません。たとえば、次の2つのプロシージャはオーバーロードできません。
DECLARE PROCEDURE balance (acct_no IN INTEGER) IS BEGIN NULL; END; PROCEDURE balance (acct_no OUT INTEGER) IS BEGIN NULL; END; BEGIN DBMS_OUTPUT.PUT_LINE('The following procedure call raises an error.'); -- balance(100); raises an error because the procedure declaration is not unique END; /
サブプログラムのパラメータの違いがサブタイプのみの場合は、サブプログラムをオーバーロードできません。たとえば、INTEGERパラメータを受け取るプロシージャとREALパラメータを受け取るプロシージャは、オーバーロードできません。INTEGERとREALは両方ともNUMBERのサブタイプであり、同じファミリに属すためです。
戻り値のデータ型のみが異なる2つのファンクションは、その型のファミリが異なっている場合でも、オーバーロードできません。たとえば、BOOLEANを戻すファンクションとINTEGERを戻すファンクションは、オーバーロードできません。
図8-1に、PL/SQLコンパイラがサブプログラム・コールを解決する方法を示します。コンパイラがプロシージャ・コールまたはファンクション・コールを発見すると、そのコールに合う宣言を探します。コンパイラはまず現在の有効範囲を検索し、必要ならば外側の有効範囲を順に検索します。コールされたサブプログラムの名前と同じ名前のサブプログラム宣言が1つ以上見つかると、コンパイラはさらに厳密に検索します。
同じ有効範囲のレベルに同じような名前のサブプログラムが存在する場合は、コールを解決するために、コンパイラは実パラメータと仮パラメータが正確に一致するものを発見する必要があります。パラメータは、(いくつかの仮パラメータにデフォルト値が代入されている場合を除き)数、順序およびデータ型が一致している必要があります。一致するものが見つからなかった場合、または一致するものが複数見つかった場合、コンパイラはセマンティック・エラーを生成します。
例8-10は、ファンクションbalanceから外側のプロシージャswapをコールしています。現在の有効範囲の中にあるswapの宣言が、いずれもプロシージャ・コールと一致しないために、コンパイラはエラーを生成します。
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 error'); -- swap(num1, num2); wrong number or types of arguments in call to 'SWAP' RETURN x; END balance; BEGIN NULL;END swap; BEGIN NULL; END; /
オーバーロード・アルゴリズムでは、スーパータイプである仮パラメータのかわりにサブタイプの値を使用できます。この機能は代替性と呼ばれます。オーバーロードされるプロシージャの複数のインスタンスがプロシージャ・コールと一致する場合は、どのプロシージャがコールされるかを判断するために次の規則が適用されます。
オーバーロードされる各プロシージャのシグネチャの違いが、一部のパラメータが同じスーパータイプ/サブタイプ階層のオブジェクト型であるということのみの場合は、最大一致のものが使用されます。最大一致とは、すべてのパラメータがオーバーロードされる他のインスタンスと少なくとも同程度に近く、少なくとも1つのパラメータが近い場合を指します。この近さは、サブタイプとスーパータイプ間の継承の深さによって決まります。
オーバーロードされる2つのインスタンスが一致し、オーバーロードされる一方のプロシージャ内の一部の引数の型が他のインスタンス内よりも実引数に近いと、セマンティック・エラーが発生します。
また、一部のパラメータの位置がオブジェクト型階層内で異なり、他のパラメータのデータ型が異なる場合も、暗黙的な変換が必要になるため、セマンティック・エラーが発生します。
たとえば、例8-11に示すとおり、3つのレベルを持つ型階層を作成してから、ファンクションのオーバーロードされたインスタンスを2つ宣言します。両者の引数の型の違いは、この型階層内での位置のみです。final_t型の変数を宣言してから、オーバーロードされるファンクションをコールします。ファンクションのインスタンスのうち実行されるのは、sub_tパラメータを受け入れるインスタンスです。これは、その型が階層内でsuper_tよりもfinal_tに近いためです。
CREATE OR REPLACE TYPE super_t AS OBJECT (n NUMBER) NOT final; / CREATE OR REPLACE TYPE sub_t UNDER super_t (n2 NUMBER) NOT final; / CREATE OR REPLACE TYPE final_t UNDER sub_t (n3 NUMBER); / CREATE OR REPLACE PACKAGE p IS FUNCTION func (arg super_t) RETURN NUMBER; FUNCTION func (arg sub_t) RETURN NUMBER; END; / CREATE OR REPLACE PACKAGE BODY p IS FUNCTION func (arg super_t) RETURN NUMBER IS BEGIN RETURN 1; END; FUNCTION func (arg sub_t) RETURN NUMBER IS BEGIN RETURN 2; END; END; / DECLARE v final_t := final_t(1,2,3); BEGIN DBMS_OUTPUT.PUT_LINE(p.func(v)); -- prints 2 END; /
例8-11では、コールするインスタンスはコンパイル時に選択されます。例8-12では、この選択が動的に行われます。vをsuper_tのインスタンスとして宣言しますが、そこにsub_tの値を代入するために、ファンクションの適切なインスタンスがコールされます。この機能は動的ディスパッチと呼ばれます。
CREATE TYPE super_t AS OBJECT (n NUMBER, MEMBER FUNCTION func RETURN NUMBER) NOT final; / CREATE TYPE BODY super_t AS MEMBER FUNCTION func RETURN NUMBER IS BEGIN RETURN 1; END; END; / CREATE OR REPLACE TYPE sub_t UNDER super_t (n2 NUMBER, OVERRIDING MEMBER FUNCTION func RETURN NUMBER) NOT final; / CREATE TYPE BODY sub_t AS OVERRIDING MEMBER FUNCTION func RETURN NUMBER IS BEGIN RETURN 2; END; END; / CREATE OR REPLACE TYPE final_t UNDER sub_t (n3 NUMBER); / DECLARE v super_t := final_t(1,2,3); BEGIN DBMS_OUTPUT.PUT_LINE(v.func); -- prints 2 END; /
デフォルトでは、ストアド・プロシージャおよびSQLメソッドは、現行ユーザーの権限ではなく所有者の権限で実行します。このような定義者権限サブプログラムは、サブプログラムが存在するスキーマにバインドされ、同じスキーマのオブジェクトを名前の修飾なしに参照できます。たとえば、スキーマHRとOEの両方に表departmentsがある場合、HRが所有するプロシージャは、HR.departmentsのかわりにdepartmentsを使用してその表を参照できます。ユーザーOEがHRのプロシージャをコールした場合、プロシージャはHRが所有するdepartments表にアクセスします。
同じプロシージャを両方のスキーマでコンパイルする場合、SQL*Plusでスキーマ名を変数として定義し、&schema..departmentsのように表を参照できます。このコードは移植性がありますが、コードを変更した場合はそれぞれのスキーマで再コンパイルする必要があります。
よりメンテナンス性の高い方法は、AUTHID句を使用することです。これによって、ストアド・プロシージャおよびSQLメソッドをコール元のユーザーの権限とスキーマ・コンテキストで実行できます。プロシージャのインスタンスを1つ作成し、多くのユーザーがそのプロシージャをコールしてユーザー自身のデータにアクセスできます。
このような実行者権限サブプログラムは、特定のスキーマにバインドされません。たとえば、次に示すプロシージャcreate_deptは、コール元のユーザーの権限で実行され、そのユーザーのdepartments表に行を挿入します。
CREATE OR REPLACE PROCEDURE create_dept ( v_deptno NUMBER, v_dname VARCHAR2, v_mgr NUMBER, v_loc NUMBER) AUTHID CURRENT_USER AS BEGIN INSERT INTO departments VALUES (v_deptno, v_dname, v_mgr, v_loc); END; / CALL create_dept(44, 'Information Technology', 200, 1700);
実行者権限サブプログラムを使用すると、コードを再利用し、アプリケーション・ロジックを集中化できます。これは、異なるスキーマの同じ名前の表にデータを格納するアプリケーションで特に便利です。1つのインスタンス内のすべてのスキーマは、集中管理された1つのスキーマが所有するプロシージャをコールできます。異なるインスタンスのスキーマから、データベース・リンクを使用して、集中管理されたプロシージャをコールすることもできます。
ストアド・プロシージャを使用して売上を分析する会社の場合を考えてみます。この会社に複数のスキーマがあり、それぞれによく似たSALES表がある場合、通常、各スキーマにストアド・プロシージャのコピーが必要になります。
この問題を解決するには、ストアド・プロシージャの実行者権限バージョンを集中管理されたスキーマにインストールします。これによって、他のすべてのスキーマから同じプロシージャをコールでき、プロシージャはそれぞれの場合に応じて適切なSALES表を問い合せます。
実行者権限サブプログラムから、機密データを含む表への問合せまたは更新を行う定義者権限サブプログラムをコールすることによって、機密データへのアクセスを制限できます。複数のユーザーが実行者権限サブプログラムをコールできますが、機密データに直接アクセスできません。
実行者権限を実装するには、AUTHID句を使用して、サブプログラムを所有者と現行ユーザーのどちらの権限で実行するかを指定します。またこの句は、外部参照(サブプログラムの外側のオブジェクトへの参照)が所有者と現行ユーザーのどちらのスキーマで解決されるかも指定します。
AUTHID句は、スタンドアロン・サブプログラム、パッケージ仕様部またはオブジェクト型仕様部のヘッダーでのみ使用できます。CREATE FUNCTION文、CREATE PROCEDURE文、CREATE PACKAGE文またはCREATE TYPE文では、宣言部の先頭のISまたはASの直前に、AUTHID CURRENT_USERまたはAUTHID DEFINERのいずれかを含めることができます。
DEFINERはデフォルトのオプションです。パッケージまたはオブジェクト型では、AUTHID句はすべてのサブプログラムに適用されます。
提供されているPL/SQLパッケージの大部分(DBMS_LOB、DBMS_PIPE、DBMS_ROWID、DBMS_SQLおよびUTL_REF)は、実行者権限パッケージです。
一連のコールで制御が実行者権限サブプログラム内にある場合は常に、現行ユーザーとはセッション・ユーザーを指します。定義者権限サブプログラムがコールされた場合は、そのサブプログラムの所有者が現行ユーザーになります。新しいサブプログラムがコールされたり、サブプログラムが終了すると、現行ユーザーは変更されます。
USER_USERSデータ・ディクショナリ・ビューを参照すると、いつでも現行ユーザーを確認できます。実行者権限サブプログラム内では、このビューの値はUSER組込みファンクションの値と異なる場合があります。このファンクションは、常にセッション・ユーザーの名前を戻します。
AUTHID CURRENT_USERを指定すると、実行時に現行ユーザーの権限がチェックされ、外部参照は現行ユーザーのスキーマで解決されます。ただし、これは次の文の外部参照にのみ適用されます。
SELECT、INSERT、UPDATEおよびDELETE DML文
LOCK TABLEトランザクション制御文
OPENおよびOPEN-FORカーソル制御文
EXECUTE IMMEDIATEおよびOPEN-FOR-USING動的SQL文
DBMS_SQL.PARSE()を使用して解析されたSQL文
それ以外の文の場合は、コンパイル時に所有者の権限がチェックされ、外部参照は所有者のスキーマで解決されます。たとえば、例8-14の代入文は、例1-13に示されたemp_actionsパッケージ内のパッケージ・ファンクションnum_above_salaryを参照します。この外部参照はプロシージャabove_salaryの所有者のスキーマで解決されます。
CREATE PROCEDURE above_salary (emp_id IN NUMBER) AUTHID CURRENT_USER AS emps NUMBER; BEGIN emps := emp_actions.num_above_salary(emp_id); DBMS_OUTPUT.PUT_LINE( 'Number of employees with higher salary: ' || TO_CHAR(emps)); END; / CALL above_salary(120);
PL/SQLコンパイラは、表および他のオブジェクトへのすべての参照をコンパイル時に解決する必要があります。実行者権限サブプログラムの所有者は、同じスキーマ内に正しい名前と列のオブジェクトを持つ必要があります。オブジェクトには、データが存在していなくてもかまいません。実行時に、コール元のスキーマ内の対応するオブジェクトは、一致する定義を持っている必要があります。そうでない場合、エラーまたは予期しない結果(コール元のスキーマには存在するがサブプログラムを含むスキーマには存在しない表の列が無視されるなど)が発生します。
コール元のスキーマではなく特定のスキーマを参照するために、未修飾の名前が必要な場合があります。実行者権限サブプログラムと同じスキーマ内で、CREATE SYNONYM文を使用して、表、プロシージャ、ファンクションまたはその他のオブジェクトのパブリック・シノニムを作成します。
CREATE PUBLIC SYNONYM emp FOR hr.employees;
実行者権限サブプログラムがこの名前を参照している場合、サブプログラム自身のスキーマ内のシノニムと一致し、指定されたスキーマ内のオブジェクトへ解決されます。コール元のスキーマに同じ名前のスキーマ・オブジェクトまたはプライベート・シノニムがすでに存在する場合、この手法は使用できません。その場合は、実行者権限サブプログラムで参照を完全修飾する必要があります。
サブプログラムを直接コールするには、ユーザーはそのサブプログラムに対してEXECUTE権限を持っている必要があります。権限を付与することで、ユーザーに次のことを許可します。
現行ユーザーのスキーマで解決される外部参照の場合(DML文内など)、現行ユーザーはサブプログラムが参照するスキーマ・オブジェクトへのアクセスに必要な権限を持っている必要があります。その他すべての外部参照(ファンクション・コールなど)の場合は、所有者の権限がコンパイル時にチェックされ、実行時にはチェックされません。
定義者権限サブプログラムは、実行者にかかわらず、その所有者のセキュリティ・ドメインで作動します。所有者はサブプログラムが参照するスキーマ・オブジェクトへのアクセスに必要な権限を持っている必要があります。
複数のサブプログラムからなるプログラムを作成できます。定義者権限を持つものと実行者権限を持つものとを混在させることもできます。この場合、EXECUTE権限を使用して、プログラムのエントリ・ポイントを制限できます。これによって、エントリ・ポイント・サブプログラムのユーザーは他のサブプログラムを直接ではなく間接的に実行できます。
ユーザーUTILが、サブプログラムFFTに対するEXECUTE権限を、ユーザーAPPに付与するとします。
GRANT EXECUTE ON util.fft TO app;
ユーザーAPPは、サブプログラムFFTをコールするファンクションおよびプロシージャをコンパイルできるようになります。実行時には、このコールについての権限チェックは行われません。図8-2に示すように、ユーザーUTILは、FFTを間接的にコールするユーザーに1人ずつEXECUTE権限を付与する必要はありません。
サブプログラムutil.fftは実行者権限サブプログラムapp.entryからのみ直接コールされるため、ユーザーutilは、EXECUTE権限をユーザーAPPにのみ付与する必要があります。UTIL.FFTを実行すると、その現行ユーザーはAPP、SCOTTまたはBLAKEになります。SCOTTおよびBLAKEがEXECUTE権限を付与されていない場合も同様です。
サブプログラムでのロールの使用は、定義者権限と実行者権限のどちらで実行するかによって異なります。定義者権限サブプログラムでは、すべてのロールが使用禁止になります。ロールは権限チェックには使用されません。また、ロールを設定することはできません。
実行者権限サブプログラムでは、ロールは使用可能になります(サブプログラムが定義者権限サブプログラムによって直接または間接的にコールされた場合を除きます)。ロールは権限チェックに使用されます。また、システム固有の動的SQLを使用してセッションにロールを設定できます。ただし、ロールはコンパイル時ではなく実行時に適用されるため、ロールを使用してテンプレート・オブジェクトに権限を付与することはできません。
ビュー式内で実行される実行者権限サブプログラムの場合は、ビューを問い合せているスキーマではなく、ビューを作成したスキーマが現行ユーザーとみなされます。この規則は、データベース・トリガーにも適用されます。
データベース・リンクを作成して、実行者権限を使用できます。
現行ユーザー・リンクでは、そのユーザー権限を持つ別のユーザーとしてリモート・データベースに接続できます。接続するために、Oracleでは現行ユーザーのユーザー名を使用します(実行者はグローバル・ユーザーである必要があります)。ユーザーOEが所有する実行者権限サブプログラムが、次のデータベース・リンクを参照するとします。グローバル・ユーザーHRがそのサブプログラムをコールしていれば、現行ユーザーであるユーザーHRでデータベースDallasに接続します。
CREATE DATABASE LINK dallas CONNECT TO CURRENT_USER USING ...
定義者権限サブプログラムの場合、現行ユーザーはOEであり、サブプログラムはグローバル・ユーザーOEでデータベースDallasに接続します。
任意のスキーマで使用するオブジェクト型を定義するために、AUTHID CURRENT_USER句を指定します。オブジェクト型の詳細は、『Oracle Databaseアプリケーション開発者ガイド-オブジェクト・リレーショナル機能』を参照してください。
ユーザーHRが次のようにオブジェクト型を作成するとします。
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をコールして、その表にデータを入れます。
CONNECT oe/oe; 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'); END; /
コールは成功しました。これはプロシージャがその所有者(HR)の権限ではなく現行ユーザー(OE)の権限で実行されたためです。
オブジェクト型階層内のサブタイプには、次の規則が適用されます。
AUTHID句が明示的に指定されていない場合は、スーパータイプのAUTHIDを継承します。
AUTHID句が指定されている場合、そのAUTHIDがスーパータイプのAUTHIDと一致する必要があります。また、AUTHIDがDEFINERの場合は、スーパータイプとサブタイプの両方が同じスキーマに作成されている必要があります。
実行者権限インスタンス・メソッドは、インスタンスの作成者ではなく、実行者の権限で実行します。person_typが例8-15で作成された実行者権限オブジェクト型で、ユーザーHRが、型person_typのオブジェクトであるp1を作成するとします。例8-16に示すとおり、ユーザーOEが、オブジェクトp1で操作を行うためのインスタンス・メソッドchange_jobをコールする場合、メソッドの現行ユーザーは、HRではなくOEです。
-- oe creates a procedure that calls 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; CONNECT hr/hr -- user 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; /
再帰はアルゴリズムの設計を単純化する強力な手法です。一般に、再帰とは自己参照を意味します。再帰的な数列の個々の項は、それ以前の項に計算式を適用することで得られます。フィボナッチ数列(0, 1, 1, 2, 3, 5, 8, 13, 21, ...)がその一例です。この数列では、2番以降の各項が、すぐ前の2つの項の合計になっています。
再帰定義では、それ自身をさらに単純なバージョンに定義します。nの階乗(n!、1〜nのすべての整数の積)の定義を考えてみます。
n!= n * (n - 1)!
再帰的サブプログラムとは、自分自身をコールするサブプログラムのことです。再帰的コールが行われるたびに、パラメータ、変数、カーソル、例外など、そのサブプログラムで宣言されているすべての項目の新しいインスタンスが作成されます。また、再帰を繰り返して進む過程の各レベルで、SQL文の新しいインスタンスが作成されます。
再帰的コールを入れる位置には注意してください。カーソルFORループの中や、OPEN文とCLOSE文の間に入れると、コールのたびに新しいカーソルがオープンされます。これによって、OracleのOPEN_CURSORS初期化パラメータによって設定された限界を超える場合があります。
再帰的サブプログラムには、再帰的コールへ導くパスとそうではないパスの、少なくとも2つのパスが必要です。終了条件へ導くパスが少なくとも1つは必要だということです。そうでない場合、メモリーが足りなくなり、PL/SQLによって事前定義の例外STORAGE_ERRORが呼び出されるまで、再帰が続きます。
PL/SQLは強力かつ柔軟な言語ですが、他の言語を使用した方が容易に実行できる作業もあります。Cのような低レベルの言語は非常に高速です。Javaのような広く使用されている言語には、共通の設計パターン用の再利用可能なライブラリが存在します。
PL/SQLのコール仕様を使用すると、別の言語で記述された外部サブプログラムを起動して、それらの機能およびライブラリをPL/SQLから使用できます。たとえば、Javaストアド・プロシージャは、任意のPL/SQLブロック、サブプログラムまたはパッケージからコールできます。Javaストアド・プロシージャの詳細は、『Oracle Database Java開発者ガイド』を参照してください。
次のJavaクラスがデータベースに格納されている場合は、例8-17に示すとおり、このクラスをコールできます。
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には、従業員の給与を指定のパーセンテージ分のみ増やすメソッドがあります。raiseSalaryはvoidメソッドであるため、例8-17に示されたコール仕様を使用してプロシージャとして発行してから、プロシージャraise_salaryを無名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); -- call external subprogram END; /
Javaコール仕様はネストされたプロシージャとしては宣言できませんが、オブジェクト型の仕様部、オブジェクト型の本体、PL/SQLパッケージの仕様部、PL/SQLパッケージの本体で指定したり、トップレベルPL/SQLプロシージャおよびファンクションとして指定することができます。
例8-18に、PL/SQLプロシージャからのJavaファンクションへのコールを示します。
-- the following nested Java call spec is not valid, throws PLS-00999 -- CREATE PROCEDURE sleep (milli_seconds in number) IS -- PROCEDURE java_sleep (milli_seconds IN NUMBER) AS ... -- first, create the Java call spec, then call from a 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アプリケーション開発者ガイド-基礎編』を参照してください。
ストアド・ファンクション(およびそのファンクションによってコールされるサブプログラム)は、次に示す副作用を制御するための純正規則に従っている場合にのみ、SQL文からコールできます。
SELECT文またはパラレル化INSERT文、UPDATE文またはDELETE文からコールされた場合、ファンクションはデータベース表を変更できません。
INSERT文、UPDATE文またはDELETE文からコールされた場合、ファンクションは、その文によって変更されたデータベース表の問合せや変更はできません。
SELECT文、INSERT文、UPDATE文またはDELETE文からコールされた場合、ファンクションはSQLトランザクション制御文(COMMITなど)、セッション制御文(SET ROLEなど)またはシステム制御文(ALTER SYSTEMなど)を実行できません。また、DDL文(CREATEなど)には自動コミットが続くため、これも実行できません。
ファンクション本体内のSQL文が規則に違反すると、実行時(文が解析されるとき)にエラーが発生します。
この規則に違反していないかどうかを確認するには、RESTRICT_REFERENCESプラグマ(コンパイラ・ディレクティブ)を使用します。プラグマは、ファンクションがデータベース表またはパッケージ変数に対する読込みまたは書込みを行っていないことを示しますたとえば次のプラグマは、パッケージ・ファンクションcredit_okがデータベースへの書込み禁止状態(WNDS)、およびパッケージに読込み禁止状態(RNPS)であることを示します。
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に違反します。
詳細は、「RESTRICT_REFERENCESプラグマ」を参照してください。純正規則の詳細は、『Oracle Databaseアプリケーション開発者ガイド-基礎編』を参照してください。
サブプログラムのコールを最適化するために、PL/SQLコンパイラでは、2つのパラメータ引渡し方式のいずれかを選択できます。値方式では、実パラメータの値がサブプログラムに渡されます。参照方式では、値へのポインタのみが渡されます。この場合、実パラメータと仮パラメータとは同じ項目を参照します。
NOCOPYコンパイラ・ヒントによって、エイリアシングの可能性が高くなります(つまり、異なる2つの名前が同じメモリー位置を参照するようになります)。これは、グローバル変数がサブプログラムのコールの中で実パラメータとして使用され、そのサブプログラム内で参照されると発生します。結果はコンパイラが選択するパラメータの引渡し方式に依存するため、予測不能になります。
例8-19では、プロシージャADD_ENTRYは、VARRAY LEXICONをパラメータとグローバル変数の両方として参照しています。ADD_ENTRYがコールされると、識別子WORD_LISTおよびLEXICONは同じVARRAYを指定します。
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-20では、n2がIN OUTのパラメータであるため、実パラメータの値は、プロシージャが終了するまで更新されません。そのため、最初のPUT_LINEは10(nの初期値)を出力し、3番目のPUT_LINEは20を出力します。ただし、n3はNOCOPYパラメータであるため、実パラメータの値はただちに更新されます。2番目のPUT_LINEが30を出力するのはこのためです。
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-21では、代入の後、emp_cv2はemp_cv1の別名になります。これは、両者が同じ問合せ作業領域を指すためです。最初の2行はすでにemp_cv1からフェッチされているため、emp_cv2からの1番目のフェッチは、1番目ではなく3番目の行をフェッチします。emp_cv1は閉じられているため、emp_cv2からの2番目のフェッチは失敗します。
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 called END; BEGIN get_emp_data(c1, c2); END; /
|
![]() Copyright © 2005 Oracle Corporation. All Rights Reserved. |
|