7 埋込みPL/SQL
PL/SQLトランザクション処理ブロックをプログラム内に埋め込むことにより、パフォーマンスを改善する方法を説明します。この章のトピックは、次のとおりです:
関連項目
7.1 PL/SQLの利点
この項では、PL/SQLによって提供される次のような機能および利点を説明します。
7.1.3 カーソルFORループ
PL/SQLを使用すれば、カーソルを定義して操作するために、DECLARE、OPEN、FETCHおよびCLOSE文を使用する必要はありません。かわりに、カーソルFORループを使用でき、ループ索引をレコードとして暗黙的に宣言し、指定された問合せに関連付けられているカーソルをオープンして、データを繰り返しカーソルからフェッチしてレコードに入れてから、カーソルをクローズします。次に例を示します。
DECLARE ... BEGIN FOR emprec IN (SELECT empno, sal, comm FROM emp) LOOP IF emprec.comm / emprec.sal > 0.25 THEN ... ... END LOOP; END;
ドット表記法を使用すると、レコード内のコンポーネントを参照できます。
7.1.4 プロシージャおよびファンクション
PL/SQLにはプロシージャとファンクションと呼ばれる2種類のサブプログラムがあり、これらを使用すると、各動作を分離できるため、アプリケーションの開発が容易になります。一般に、プロシージャはアクションを実行するために使用し、ファンクションは値を計算するために使用します。
プロシージャおよびファンクションには拡張性があります。つまり、プロシージャとファンクションを使用することにより、PL/SQL言語を必要に応じて調整できます。たとえば、新しい部門を作成するプロシージャが必要な場合、次のように記述します。
PROCEDURE create_dept (new_dname IN CHAR(14), new_loc IN CHAR(13), new_deptno OUT NUMBER(2)) IS BEGIN SELECT deptno_seq.NEXTVAL INTO new_deptno FROM dual; INSERT INTO dept VALUES (new_deptno, new_dname, new_loc); END create_dept;
このプロシージャをコールすると、プロシージャでは新しい部門名と場所を受け取り、部門番号データベース順序の次の値を選択し、その新しい番号、名前および場所をdept表に挿入してから、新しい番号をコール元に戻します。
仮パラメータの動作を定義するには、パラメータ・モードを使用します。パラメータ・モードにはIN (デフォルト)、OUTおよびIN OUTの3つがあります。INパラメータを使用すると、コールされるサブプログラムに値を渡せます。OUTパラメータを使用すると、サブプログラムのコール元に値を戻せます。IN OUTパラメータを使用すると、コールされるサブプログラムに初期値を渡し、更新された値をコール元に戻すことができます。
それぞれの実パラメータのデータ型は、対応する仮パラメータのデータ型に変換可能であることが必要です。表7-1は、データ型間の正当な変換を示しています。
7.1.5 パッケージ
PL/SQLでは、論理的に関連する型、プログラム・オブジェクトおよびサブプログラムを1つのパッケージにまとめることができます。プロシージャ・データベース拡張機能がある場合、パッケージをコンパイルして、Oracleデータベースに格納でき、そのデータベースの内容は多くのアプリケーションで共有できます。
パッケージには通常、仕様部および本体の2つの部分があります。仕様部とは、アプリケーションへのインタフェースで、使用可能な型、定数、変数、例外、カーソルおよびサブプログラムが宣言されます。本体は、カーソルとサブプログラムを定義し、仕様を実行します。次の例では、2つの雇用プロシージャをパッケージ化しています。
PACKAGE emp_actions IS -- package specification
PROCEDURE hire_employee (empno NUMBER, ename CHAR, ...);
PROCEDURE fire_employee (emp_id NUMBER);
END emp_actions;
PACKAGE BODY emp_actions IS -- package body
PROCEDURE hire_employee (empno NUMBER, ename CHAR, ...) IS
BEGIN
INSERT INTO emp VALUES (empno, ename, ...);
END hire_employee;
PROCEDURE fire_employee (emp_id NUMBER) IS
BEGIN
DELETE FROM emp WHERE empno = emp_id;
END fire_employee;
END emp_actions;
パッケージ仕様部内の宣言のみ参照可能で、アプリケーションからアクセスできます。パッケージ本体中の詳細な実装内容は非表示のためアクセスできません。
7.1.6 PL/SQL表
PL/SQLにはTABLEの名前の複合データ型が用意されています。TABLE型のオブジェクトは、PL/SQL表と呼ばれ、データベース表をモデルとしています(まったく同じではありません)。PL/SQL表は1列からなり、主キーを使用して、配列と同じ方法で行にアクセスします。列は、任意のスカラー型(CHAR、DATEまたはNUMBERなど)にできますが、主キーはBINARY_INTEGER型、PLS_INTEGER型またはVARCHAR2型にする必要があります。
ブロック、プロシージャ、ファンクションまたはパッケージのいずれかの宣言部でPL/SQL表型を宣言できます。次の例では、NumTabTypと呼ばれるTABLE型を宣言しています。
...
DECLARE
TYPE NumTabTyp IS TABLE OF NUMBER
INDEX BY BINARY_INTEGER;
...
BEGIN
...
END;
...
次の例に示すように、一度NumTabTyp型を定義すると、その型のPL/SQL表を宣言できます。
num_tab NumTabTyp;
識別子num_tabは、PL/SQL表全体を表しています。
配列に似た構文を使用してPL/SQL表の中の行を参照し、主キーの値を指定します。たとえば、num_tabの名前のPL/SQL表の中の9番目の行を参照するには次のように指定します。
num_tab(9) ...
7.1.7 ユーザー定義のレコード
%ROWTYPE属性を使用して、表の中の行を表すレコード、またはカーソルによってフェッチされる行を表すレコードを宣言できます。しかし、レコード内のコンポーネントのデータ型は指定できません。また、ユーザー独自のコンポーネントも定義できません。複合データ型RECORDを使用すると、これらの制限事項を取り除くことができます。
RECORD型のオブジェクトはレコードと呼ばれます。PL/SQL表とは異なり、レコードには一意の名前を持つコンポーネントがあります。コンポーネントのデータ型はそれぞれ異なっていてもかまいません。たとえば、ある従業員について異なる種類のデータ(名前、給与、雇用日など)があるとします。このデータは、型は異なりますが、論理的に関連しています。従業員の名前、給与および雇用日などのコンポーネントを持つレコードによって、1つの論理単位としてデータを処理できます。
ブロック、プロシージャ、ファンクションまたはパッケージのいずれかの宣言部で、レコード型およびレコード・オブジェクトを宣言できます。次の例では、DeptRecTypと呼ばれるRECORD型を宣言しています。
DECLARE
TYPE DeptRecTyp IS RECORD
(deptno NUMBER(4) NOT NULL, -- default is NULL allowed
dname CHAR(9),
loc CHAR(14));
コンポーネント宣言は変数宣言に似ているため注意してください。各コンポーネントには一意の名前および固有のデータ型を指定します。コンポーネント宣言にNOT NULLオプションを追加すると、そのコンポーネントにはNULLを割り当てられません。
次の例に示すように、一度DeptRecTypを定義すると、その型のレコードを宣言できます。
dept_rec DeptRecTyp;
識別子dept_recは、レコード全体を表しています。
ドット表記法を使用すると、レコード内の個別コンポーネントを参照できます。たとえば、次のようにdept_recレコード内でdnameコンポーネントを参照します。
dept_rec.dname ...
7.2 埋込みPL/SQLブロック
Pro*C/C++プリコンパイラでは、PL/SQLブロックが1つの埋込みSQL文と同様に扱われます。したがって、PL/SQLブロックは、プログラム内のSQL文を記述できる位置であればどこにでも記述できます。
PL/SQLブロックをPro*C/C++プログラム内に埋め込むには、次のように、EXEC SQL EXECUTEおよびEND-EXECキーワードでPL/SQLブロックを囲むのみです。
EXEC SQL EXECUTE DECLARE ... BEGIN ... END; END-EXEC;
END-EXECキーワードの後には、セミコロンを付ける必要があります。
プログラムを作成した後に、通常の方法でソース・ファイルをプリコンパイルします。
プログラムに埋込みPL/SQLが含まれている場合、PL/SQLはOracleサーバーによって解析される必要があるため、必ずSQLCHECK=SEMANTICSコマンドライン・オプションを指定してください。サーバーに接続する場合には、SQLCHECK=SEMANTICSのみでなくUSERIDオプションも指定してください。
関連項目
7.3 ホスト変数
ホスト変数は、ホスト言語とPL/SQLブロック間の通信を仲介します。ホスト変数はPL/SQLと共有できるので、PL/SQLではホスト変数の設定および参照ができます。
たとえば、ユーザーに情報の提供を求め、この情報をPL/SQLブロックに渡すためのホスト変数を使用するようにユーザーに指示できます。これにより、PL/SQLを使用してデータベースにアクセスし、ホスト変数を介してその結果をホスト・プログラムに戻せるようになります。
PL/SQLブロック内ではホスト変数はブロック全体のグローバル変数として扱われ、PL/SQL変数を使用できる位置であればどこにでも使用できます。SQL文内におけるホスト変数と同様、PL/SQLブロック内のホスト変数も先頭にコロンを付ける必要があります。コロンは、ホスト変数とPL/SQL変数およびデータベース・オブジェクトとを区切ります。
注意:
PL/SQLブロックでVARCHAR、CHARZまたはSTRING型を出力ホスト変数として使用するには、ブロックを入力する前に長さを初期化する必要があります。次の例のように、長さはVARCHAR、CHARZまたはSTRINGの宣言された(最大の)長さに設定してください。
関連項目
7.3.1 例: PL/SQLでのホスト変数の使用
次の例では、PL/SQLにおけるホスト変数の使用方法を示します。プログラムでは、ユーザーに従業員番号の入力を求めるプロンプトが表示され、その番号に応じて、従業員の役職名、雇用日、給与が表示されます。
char username[100], password[20];
char job_title[20], hire_date[9], temp[32];
int emp_number;
float salary;
#include <sqlca.h>
printf("Username? \n");
gets(username);
printf("Password? \n");
gets(password);
EXEC SQL WHENEVER SQLERROR GOTO sql_error;
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("Connected to Oracle\n");
for (;;)
{
printf("Employee Number (0 to end)? ");
gets(temp);
emp_number = atoi(temp);
if (emp_number == 0)
{
EXEC SQL COMMIT WORK RELEASE;
printf("Exiting program\n");
break;
}
/*-------------- begin PL/SQL block -----------------*/
EXEC SQL EXECUTE
BEGIN
SELECT job, hiredate, sal
INTO :job_title, :hire_date, :salary
FROM emp
WHERE empno = :emp_number;
END;
END-EXEC;
/*-------------- end PL/SQL block -----------------*/
printf("Number Job Title Hire Date Salary\n");
printf("------------------------------------\n");
printf("%6d %8.8s %9.9s %6.2f\n",
emp_number, job_title, hire_date, salary);
}
...
exit(0);
sql_error:
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK RELEASE;
printf("Processing error\n");
exit(1);
ホスト変数emp_numberがPL/SQLブロックが入力される前に設定され、ホスト変数job_title、hire_dateおよびsalaryがブロック内で設定されていることに注意してください。
7.3.2 複雑な例
次の例では、ユーザーに銀行口座番号、取引の種類、取引金額の入力を求めるプロンプトが表示されてから、口座に記帳されます。口座が存在しない場合、例外が発生します。取引が完了すると、そのステータスが表示されます。
#include <stdio.h>
#include <sqlca.h>
char username[20];
char password[20];
char status[80];
char temp[32];
int acct_num;
double trans_amt;
void sql_error();
main()
{
char trans_type;
strcpy(password, "TIGER");
strcpy(username, "SCOTT");
EXEC SQL WHENEVER SQLERROR DO sql_error();
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("Connected to Oracle\n");
for (;;)
{
printf("Account Number (0 to end)? ");
gets(temp);
acct_num = atoi(temp);
if(acct_num == 0)
{
EXEC SQL COMMIT WORK RELEASE;
printf("Exiting program\n");
break;
}
printf("Transaction Type - D)ebit or C)redit? ");
gets(temp);
trans_type = temp[0];
printf("Transaction Amount? ");
gets(temp);
trans_amt = atof(temp);
/*----------------- begin PL/SQL block -------------------*/
EXEC SQL EXECUTE
DECLARE
old_bal NUMBER(9,2);
err_msg CHAR(70);
nonexistent EXCEPTION;
BEGIN
:trans_type := UPPER(:trans_type);
IF :trans_type = 'C' THEN -- credit the account
UPDATE accts SET bal = bal + :trans_amt
WHERE acctid = :acct_num;
IF SQL%ROWCOUNT = 0 THEN -- no rows affected
RAISE nonexistent;
ELSE
:status := 'Credit applied';
END IF;
ELSIF :trans_type = 'D' THEN -- debit the account
SELECT bal INTO old_bal FROM accts
WHERE acctid = :acct_num;
IF old_bal >= :trans_amt THEN -- enough funds
UPDATE accts SET bal = bal - :trans_amt
WHERE acctid = :acct_num;
:status := 'Debit applied';
ELSE
:status := 'Insufficient funds';
END IF;
ELSE
:status := 'Invalid type: ' || :trans_type;
END IF;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND OR nonexistent THEN
:status := 'Nonexistent account';
WHEN OTHERS THEN
err_msg := SUBSTR(SQLERRM, 1, 70);
:status := 'Error: ' || err_msg;
END;
END-EXEC;
/*----------------- end PL/SQL block ----------------------- */
printf("\nStatus: %s\n", status);
}
exit(0);
}
void
sql_error()
{
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK RELEASE;
printf("Processing error\n");
exit(1);
}7.3.3 VARCHAR疑似型
VARCHARデータ型は、可変長文字列の宣言に使用できます。VARCHARが入力ホスト変数の場合は、どのくらいの長さを前提にすればよいかをOracleに通知する必要があります。したがって、文字列コンポーネントに格納される値の実際の長さに、長さコンポーネントを設定してください。
VARCHARが出力ホスト変数の場合は、Oracleで自動的に長さコンポーネントが設定されます。ただし、PL/SQLブロックでVARCHAR(CHARZおよびSTRING)出力ホスト変数を使用するには、ブロックを入力する前に長さコンポーネントを初期化する必要があります。したがって、次の例に示すとおり、長さコンポーネントはVARCHARの宣言された(最大の)長さに設定してください。
int emp_number;
varchar emp_name[10];
float salary;
...
emp_name.len = 10; /* initialize length component */
EXEC SQL EXECUTE
BEGIN
SELECT ename, sal INTO :emp_name, :salary
FROM emp
WHERE empno = :emp_number;
...
END;
END-EXEC;
...
7.3.4 制限事項
PL/SQLブロックでは、Cポインタまたは配列構文を使用しないでください。PL/SQLコンパイラはCホスト変数の式を認識できないため、解析できません。たとえば、次の例は無効です。
EXEC SQL EXECUTE
BEGIN
:x[5].name := 'SCOTT';
...
END;
END-EXEC;
構文エラーを回避するには、プレースホルダ(一時変数)を使用して、構造体の移入を行う構造体フィールドのアドレスを保持します。次の例は有効です。
name = x[5].name ;
EXEC SQL EXECUTE
BEGIN
:name := ...;
...
END;
END-EXEC;7.4 標識変数
PL/SQLでは、NULLを操作できるため、標識変数は必要ありません。たとえば、PL/SQL内では、次のようにIS NULL演算子を使用してNULLをテストできます。
IF variable IS NULL THEN ...
その後、次のように代入演算子(:=)を使用して、NULL値を指定できます。
variable := NULL;
ただし、C言語のようなホスト言語はNULL値を操作できないため、標識変数を必要とします。埋込みPL/SQLでは、次の目的でインジケータ変数を使用できるため、この要件を満たします。
-
ホスト・プログラムからのNULL入力値の受入れ
-
NULLまたは切り捨てられた値のホスト・プログラムへの出力
PL/SQLブロックでインジケータ変数を使用するときは、次の規則に従ってください。
-
インジケータ変数は単独では参照できません。関連付けられたホスト変数に追加する必要があります。
-
インジケータ変数を指定してホスト変数を参照する場合、同じブロックでは常に同じ方法で参照する必要があります。
次の例では、標識変数ind_commがSELECT文でそのホスト変数commissionとともに表示されているため、IF文でも同じように表示する必要があります。
...
EXEC SQL EXECUTE
BEGIN
SELECT ename, comm
INTO :emp_name, :commission :ind_comm
FROM emp
WHERE empno = :emp_number;
IF :commission :ind_comm IS NULL THEN ...
...
END;
END-EXEC;
PL/SQLでは:commission :ind_commはその他の単純な変数と同じように扱われるため注意してください。PL/SQLブロック内の標識変数は直接参照できませんが、PL/SQLでは、ブロックに入るときに標識変数の値がチェックされ、ブロックから出るときにその値が正しく設定されます。
7.4.1 NULLの処理
ブロックに入るとき、標識変数の値が-1であれば、PL/SQLによってNULLがホスト変数に自動的に割り当てられます。ブロックから出るとき、ホスト変数がNULLであれば、PL/SQLによって値-1が標識変数に自動的に割り当てられます。次の例では、PL/SQLブロックに入力される前にind_salの値が-1であった場合に、salary_missing例外が発生します。例外とは、名前が指定されたエラー状態のことです。
...
EXEC SQL EXECUTE
BEGIN
IF :salary :ind_sal IS NULL THEN
RAISE salary_missing;
END IF;
...
END;
END-EXEC;
...7.5 ホスト配列
入力ホスト配列およびインジケータ配列は、PL/SQLブロックに渡せます。これらは、BINARY_INTEGER型またはPLS_INTEGER型のPL/SQL変数を使用して索引付けができます。VARCHAR2型のキーは使用できません。通常は、ホスト配列全体がPL/SQLに渡されますが、ARRAYLEN文(後述)を使用すれば、より小さい配列サイズを指定できます。
さらに、ホスト配列のすべての値をPL/SQL表の複数の行に割り当てるために、プロシージャ・コールを使用できます。配列のサブスクリプトの範囲がmからnの場合、対応するPL/SQL表の索引範囲は常に1からn - m + 1になります。たとえば、配列サブスクリプト範囲が5から10の場合、対応するPL/SQL表の索引範囲は、1から(10 - 5 + 1)または1から6です。
次の例では、salaryという名前の配列をPL/SQLブロックに渡し、そのブロックのファンクション・コール内でその配列を使用しています。このファンクションは、一連の数値の中央値を検出するため、medianという名前が付いています。その仮パラメータには、num_tabというPL/SQL表が含まれています。このファンクション・コールにより、実パラメータsalary内のすべての値を仮パラメータnum_tab内の行に割り当てます。
...
float salary[100];
/* populate the host array */
EXEC SQL EXECUTE
DECLARE
TYPE NumTabTyp IS TABLE OF REAL
INDEX BY BINARY_INTEGER;
median_salary REAL;
n BINARY_INTEGER;
...
FUNCTION median (num_tab NumTabTyp, n INTEGER)
RETURN REAL IS
BEGIN
-- compute median
END;
BEGIN
n := 100;
median_salary := median(:salary, n);
...
END;
END-EXEC;
...
注意:
動的SQL方法4では、TABLE型のパラメータを使用して、ホスト配列をPL/SQLプロシージャにバインドすることはできません。
PL/SQL表のすべての行の値をホスト配列の対応する要素に割り当てる場合にも、プロシージャ・コールを使用できます。
表7-1に、PL/SQL表の行の値とホスト配列のエレメント間での有効な変換を示します。たとえば、LONG型のホスト配列は、VARCHAR2、LONG、RAWまたはLONG RAW型のPL/SQL表と互換性があります。ただし、CHAR型のPL/SQL表とは互換性がないため注意してください。
表7-1 正当なデータ型変換
| PL/SQL表→ホスト配列 | CHAR | DATE | LONG | LONG RAW | NUMBER | RAW | ROWID | VARCHAR2 |
|---|---|---|---|---|---|---|---|---|
|
CHARF |
X |
- |
- |
- |
- |
- |
- |
- |
|
CHARZ |
X |
- |
- |
- |
- |
- |
- |
- |
|
DATE |
- |
X |
- |
- |
- |
- |
- |
- |
|
DECIMAL |
- |
- |
- |
- |
X |
- |
- |
- |
|
DISPLAY |
- |
- |
- |
- |
X |
- |
- |
- |
|
FLOAT |
- |
- |
- |
- |
X |
- |
- |
- |
|
INTEGER |
- |
- |
- |
- |
X |
- |
- |
- |
|
LONG |
X |
- |
X |
- |
- |
- |
- |
- |
|
LONG VARCHAR |
- |
- |
X |
X |
- |
X |
- |
X |
|
LONG VARRAW |
- |
- |
- |
X |
- |
X |
- |
- |
|
NUMBER |
- |
- |
- |
- |
X |
- |
- |
- |
|
RAW |
- |
- |
- |
X |
- |
X |
- |
- |
|
ROWID |
- |
- |
- |
- |
- |
- |
X |
- |
|
STRING |
- |
- |
X |
X |
- |
X |
- |
X |
|
UNSIGNED |
- |
- |
- |
- |
X |
- |
- |
- |
|
VARCHAR |
- |
- |
X |
X |
- |
X |
- |
X |
|
VARCHAR2 |
- |
- |
X |
X |
- |
X |
- |
X |
|
VARNUM |
- |
- |
- |
- |
X |
- |
- |
- |
|
VARRAW |
- |
- |
- |
X |
- |
X |
- |
- |
注意:
Pro*C/C++プリコンパイラでは、ホスト配列の使用方法はチェックされません。たとえば、索引範囲チェックは実行されません。
7.5.1 ARRAYLEN文
入力ホスト配列をPL/SQLブロックに渡して処理するとします。デフォルトでは、入力ホスト配列をバインドすると、Pro*C/C++プリコンパイラは、その宣言されたサイズを使用します。ただし、配列全体を処理する必要がない場合があります。この場合には、ARRAYLEN文を使用して、より小さい配列サイズを指定できます。ARRAYLEN文では、ホスト配列をホスト変数と対応付け、そのホスト変数がより小さいサイズを格納します。文の構文は次のとおりです。
EXEC SQL ARRAYLEN host_array (dimension) [EXECUTE];
dimensionは4バイトの整数型ホスト変数です。リテラルや式ではありません。
EXECUTEはオプションのキーワードです。
ARRAYLEN文はhost_arrayおよびdimensionの宣言とともに(ただし、それらの宣言よりも後に)表示する必要があります。ホスト配列にオフセットは指定できません。しかし、この目的にC言語の機能が使用できる場合もあります。次の例では、ARRAYLENを使用して、bonusという名前のCホスト配列のデフォルトのサイズを上書きしています。
float bonus[100];
int dimension;
EXEC SQL ARRAYLEN bonus (dimension);
/* populate the host array */
...
dimension = 25; /* set smaller array dimension */
EXEC SQL EXECUTE
DECLARE
TYPE NumTabTyp IS TABLE OF REAL
INDEX BY BINARY_INTEGER;
median_bonus REAL;
FUNCTION median (num_tab NumTabTyp, n INTEGER)
RETURN REAL IS
BEGIN
-- compute median
END;
BEGIN
median_bonus := median(:bonus, :dimension);
...
END;
END-EXEC;
ARRAYLENでホスト配列のサイズが100要素から25要素に減少するため、25の配列要素のみがPL/SQLブロックに渡されます。その結果、PL/SQLブロックが実行のためOracleに送信されるとき、一緒に送られるホスト配列はずっと小さくなります。これにより、時間を節約し、ネットワーク化された環境でネットワークの通信量を削減できます。
7.5.2 オプション・キーワードEXECUTE
動的SQL方法2のEXEC SQL EXECUTE文で使用されるホスト配列には、オプション・キーワードEXECUTEの有無によって、2つの異なる解釈があります。
デフォルト(ARRAYLEN文にEXECUTEキーワードがないとき):
-
ホスト配列はPL/SQLブロックが実行される回数が決定されるときに考慮されます。(最小値の配列サイズが使用されます。)
-
ホスト配列はPL/SQL索引表に結合されません。
EXECUTEキーワードが存在しているとき:
-
ホスト配列は索引表に結合されます。
-
PL/SQLブロックは1回のみ実行されます。
-
EXEC SQL EXECUTE文で指定されているすべてのホスト変数は、次のいずれかです。
-
ARRAYLEN ... EXECUTE文で指定
-
スカラー
-
たとえば、次のPL/SQLプロシージャを仮定します。
CREATE OR REPLACE PACKAGE pkg AS
TYPE tab IS TABLE OF NUMBER(5) INDEX BY BINARY_INTEGER;
PROCEDURE proc1 (parm1 tab, parm2 NUMBER, parm3 tab);
END;
次のPro*C/C++ファンクションは、特定のPL/SQLブロックを実行する回数を決定するためにホスト配列を使用する方法を示しています。この場合、PL/SQLブロックはemp表に新しい行が3行あるために3回実行されます。
func1()
{
int empno_arr[5] = {1111, 2222, 3333, 4444, 5555};
char *ename_arr[3] = {"MICKEY", "MINNIE", "GOOFY"};
char *stmt1 = "BEGIN INSERT INTO emp(empno, ename) VALUES :b1, :b2; END;";
EXEC SQL PREPARE s1 FROM :stmt1;
EXEC SQL EXECUTE s1 USING :empno_arr, :ename_arr;
}
次のPro*C/C++ファンクションは、動的方法2でホスト配列をPL/SQL索引表にバインドする方法を示しています。EXEC SQL EXECUTE文に指定されたすべてのホスト配列についてARRAYLEN...EXECUTE文があることに注意してください。
func2()
{
int ii = 2;
int int_tab[3] = {1,2,3};
int dim = 3;
EXEC SQL ARRAYLEN int_tab (dim) EXECUTE;
char *stmt2 = "begin pkg.proc1(:v1, :v2, :v3); end; ";
EXEC SQL PREPARE s2 FROM :stmt2;
EXEC SQL EXECUTE s2 USING :int_tab, :ii, :int_tab;
}
次のPro*C/C++ファンクションは、int_arrのARRAYLEN...EXECUTE文がないために、プリコンパイル時警告を生じます。
func3()
{
int int_arr[3];
int int_tab[3] = {1,2,3};
int dim = 3;
EXEC SQL ARRAYLEN int_tab (dim) EXECUTE;
char *stmt3 = "begin pkg.proc1(:v1, :v2, :v3); end; ";
EXEC SQL PREPARE s3 FROM :stmt3;
EXEC SQL EXECUTE s3 USING :int_tab, :int_arr, :int_tab;
}
7.6 埋込みPL/SQLでのカーソルの使用
プログラムで同時に使用できるカーソルの最大数は、データベース初期化パラメータOPEN_CURSORSによって設定されます。埋込みPL/SQLブロックの実行中に、1つのカーソル、親カーソルがブロック全体に対応付けられ、1つのカーソル、子カーソルが埋込みPL/SQLブロックの各SQL文に対応付けられます。親カーソルと子カーソルは、両方とも、OPEN_CURSORS制限にカウントされます。
次の計算は、使用するカーソルの最大数を決定する方法を示します。カーソル数の合計がOPEN_CURSORSを超えないようにします。
SQL statement cursors PL/SQL parent cursors PL/SQL child cursors + 6 cursors for overhead -------------------------- Sum of cursors in use
プログラムで、OPEN_CURSORSによって設定された制限を超える数のカーソルが使用された場合、エラーが発生します。
関連項目
7.7 ストアドPL/SQLおよびJavaサブプログラム
無名ブロックとは異なり、PL/SQLサブプログラム(プロシージャおよびファンクション)およびJavaメソッドは別個にコンパイルされ、Oracleデータベースに格納されて起動されます。
SQL*PlusなどのOracleツールを使用して明示的に作成したサブプログラムを、ストアド・サブプログラムと呼びます。ストアド・サブプログラムは、一度コンパイルしてデータ・ディクショナリに格納しておくと、再コンパイルしなくても再実行できるデータベース・オブジェクトになります。
PL/SQLブロック内のサブプログラムまたはストアド・プロシージャがアプリケーションによってOracleに送られると、それはインライン・サブプログラムと呼ばれます。Oracleは、インライン・サブプログラムをコンパイルし、システム・グローバル領域(SGA)にキャッシュしますが、データ・ディクショナリへのソース・コードまたはオブジェクト・コードの格納はしません。
パッケージ内で定義されているサブプログラムは、そのパッケージの一部とみなされ、パッケージ・サブプログラムと呼ばれます。パッケージで定義されていないストアド・サブプログラムはスタンドアロン・サブプログラムと呼ばれます。
7.7.1 ストアド・サブプログラムの作成について
次の例に示すように、SQL文CREATE FUNCTION、CREATE PROCEDUREおよびCREATE PACKAGEをホスト・プログラムに埋め込むことができます。
EXEC SQL CREATE
FUNCTION sal_ok (salary REAL, title CHAR)
RETURN BOOLEAN AS
min_sal REAL;
max_sal REAL;
BEGIN
SELECT losal, hisal INTO min_sal, max_sal
FROM sals
WHERE job = title;
RETURN (salary >= min_sal) AND
(salary <= max_sal);
END sal_ok;
END-EXEC;
埋込みCREATE {FUNCTION | PROCEDURE | PACKAGE}文は混成であることに注意してください。他のすべてのCREATE埋込み文と同様、キーワードEXEC SQL (EXEC SQL EXECUTEではありません)で始まります。ただし、PL/SQL終了記号のEND-EXECで終了する点は、他のCREATE埋込み文とは異なります。
次の例では、EMP表からひとまとまりの行をフェッチするget_employeesというプロシージャを含むパッケージを作成します。バッチ・サイズは、プロシージャのコール元(別のストアド・サブプログラムの場合もあれば、クライアント・アプリケーションの場合もある)によって決められます。
プロシージャでは、3つのPL/SQL表をOUT仮パラメータとして宣言し、その後雇用データのバッチをPL/SQL表にフェッチします。一致する実パラメータはホスト配列です。プロシージャの終了時には、PL/SQL表のすべての行の値が、ホスト配列の対応する要素に自動的に割り当てられます。
EXEC SQL CREATE OR REPLACE PACKAGE emp_actions AS
TYPE CharArrayTyp IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
TYPE NumArrayTyp IS TABLE OF FLOAT
INDEX BY BINARY_INTEGER;
PROCEDURE get_employees(
dept_number IN INTEGER,
batch_size IN INTEGER,
found IN OUT INTEGER,
done_fetch OUT INTEGER,
emp_name OUT CharArrayTyp,
job_title OUT CharArrayTyp,
salary OUT NumArrayTyp);
END emp_actions;
END-EXEC;
EXEC SQL CREATE OR REPLACE PACKAGE BODY emp_actions AS
CURSOR get_emp (dept_number IN INTEGER) IS
SELECT ename, job, sal FROM emp
WHERE deptno = dept_number;
PROCEDURE get_employees(
dept_number IN INTEGER,
batch_size IN INTEGER,
found IN OUT INTEGER,
done_fetch OUT INTEGER,
emp_name OUT CharArrayTyp,
job_title OUT CharArrayTyp,
salary OUT NumArrayTyp) IS
BEGIN
IF NOT get_emp%ISOPEN THEN
OPEN get_emp(dept_number);
END IF;
done_fetch := 0;
found := 0;
FOR i IN 1..batch_size LOOP
FETCH get_emp INTO emp_name(i),
job_title(i), salary(i);
IF get_emp%NOTFOUND THEN
CLOSE get_emp;
done_fetch := 1;
EXIT;
ELSE
found := found + 1;
END IF;
END LOOP;
END get_employees;
END emp_actions;
END-EXEC;
CREATE文でREPLACE句を指定すると、パッケージの削除、再作成および権限再付与を実行しなくても既存のパッケージを再定義できます。CREATE文の構文の詳細は、SQL文: COMMIT to CREATE JAVAを参照してください。
埋込みCREATE {FUNCTION | PROCEDURE | PACKAGE}文が失敗した場合、Oracleはエラーではなく警告を発行します。
7.7.2 ストアドPL/SQLまたはJavaサブプログラムのコールについて
ホスト・プログラムからストアド・サブプログラムをコールするには、無名PL/SQLブロックまたはCALL埋込みSQL文のいずれかを使用できます。
7.7.2.1 無名PL/SQLブロック
次の例では、raise_salaryという名前のスタンドアロン・プロシージャをコールします。
EXEC SQL EXECUTE
BEGIN
raise_salary(:emp_id, :increase);
END;
END-EXEC;
ストアド・サブプログラムにパラメータを組み込めることに注意してください。この例では、実パラメータemp_idおよびincreaseはC言語のホスト変数です。
次の例では、プロシージャraise_salaryがemp_actionsの名前のパッケージに格納されます。したがって、プロシージャ・コールを完全に修飾するにはドット表記法を使用する必要があります。
EXEC SQL EXECUTE
BEGIN
emp_actions.raise_salary(:emp_id, :increase);
END;
END-EXEC;
IN実パラメータには、リテラル、スカラー・ホスト変数、ホスト配列、PL/SQL定数、PL/SQL変数、PL/SQL表、PL/SQLユーザー定義レコード、プロシージャ・コールまたは式を使用できます。しかし、OUT実パラメータは、リテラル、プロシージャ・コールまたは式にしないでください。
埋込みPL/SQLブロックとともにプリコンパイラ・オプションSQLCHECK=SEMANTICSを使用する必要があります。
次の例では、仮パラメータのうち3つがPL/SQL表であり、対応する実パラメータはホスト配列です。プログラムでは、ストアド・プロシージャget_employeesを繰り返しコールし、データがなくなるまで、従業員データの各バッチを表示します。このプログラムは、demoディレクトリのsample9.pcファイルに入っており、オンラインで利用できます。CALLDEMOストアド・パッケージ作成用のSQLスクリプトは、calldemo.sqlファイルに入っています。
/*************************************************************
Sample Program 9: Calling a stored procedure
This program connects to ORACLE using the SCOTT/TIGER
account. The program declares several host arrays, then
calls a PL/SQL stored procedure (GET_EMPLOYEES in the
CALLDEMO package) that fills the table OUT parameters. The
PL/SQL procedure returns up to ASIZE values.
Sample9 keeps calling GET_EMPLOYEES, getting ASIZE arrays
each time, and printing the values, until all rows have been
retrieved. GET_EMPLOYEES sets the done_flag to indicate "no
more data."
*************************************************************/
#include <stdio.h>
#include <string.h>
EXEC SQL INCLUDE sqlca.h;
typedef char asciz[20];
typedef char vc2_arr[11];
EXEC SQL BEGIN DECLARE SECTION;
/* User-defined type for null-terminated strings */
EXEC SQL TYPE asciz IS STRING(20) REFERENCE;
/* User-defined type for a VARCHAR array element. */
EXEC SQL TYPE vc2_arr IS VARCHAR2(11) REFERENCE;
asciz username;
asciz password;
int dept_no; /* which department to query? */
vc2_arr emp_name[10]; /* array of returned names */
vc2_arr job[10];
float salary[10];
int done_flag;
int array_size;
int num_ret; /* number of rows returned */
EXEC SQL END DECLARE SECTION;
long SQLCODE;
void print_rows(); /* produces program output */
void sql_error(); /* handles unrecoverable errors */
main()
{
int i;
char temp_buf[32];
/* Connect to ORACLE. */
EXEC SQL WHENEVER SQLERROR DO sql_error();
strcpy(username, "scott");
strcpy(password, "tiger");
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("\nConnected to ORACLE as user: %s\n\n", username);
printf("Enter department number: ");
gets(temp_buf);
dept_no = atoi(temp_buf);/* Print column headers. */
printf("\n\n");
printf("%-10.10s%-10.10s%s\n", "Employee", "Job", "Salary");
printf("%-10.10s%-10.10s%s\n", "--------", "---", "------");
/* Set the array size. */
array_size = 10;
done_flag = 0;
num_ret = 0;
/* Array fetch loop.
* The loop continues until the OUT parameter done_flag is set.
* Pass in the department number, and the array size--
* get names, jobs, and salaries back.
*/
for (;;)
{
EXEC SQL EXECUTE
BEGIN calldemo.get_employees
(:dept_no, :array_size, :num_ret, :done_flag,
:emp_name, :job, :salary);
END;
END-EXEC;
print_rows(num_ret);
if (done_flag)
break;
}
/* Disconnect from the database. */
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
print_rows(n)
int n;
{
int i;
if (n == 0)
{
printf("No rows retrieved.\n");
return;
}
for (i = 0; i < n; i++)
printf("%10.10s%10.10s%6.2f\n",
emp_name[i], job[i], salary[i]);
}
/* Handle errors. Exit on any error. */
void
sql_error()
{
char msg[512];
int buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
buf_len = sizeof(msg);
sqlglm(msg, &buf_len, &msg_len);
printf("\nORACLE error detected:");
printf("\n%.*s \n", msg_len, msg);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
}
それぞれの実パラメータのデータ型は、対応する仮パラメータのデータ型に変換可能であることが必要です。また、ストアド・プロシージャの終了前には、すべてのOUT仮パラメータは割り当てられた値である必要があります。そうしないと、対応する実パラメータの値が未確定になります。
無名PL/SQLブロックを使用する場合、SQLCHECK=SEMANTICSは必須です。
関連項目
7.7.2.2 リモート・アクセス
PL/SQLを使用すると、データベース・リンクを使用してリモート・データベースにアクセスできます。一般的に、データベース・リンクは、データベース管理者(DBA)によって設定され、Oracleデータ・ディクショナリに格納されます。データベース・リンクは、リモート・データベースの位置、リモート・データベースへのパス、使用するOracleユーザー名およびパスワードをOracleに伝えます。次の例では、データベース・リンクdallasを使用して、raise_salaryプロシージャをコールします。
EXEC SQL EXECUTE
BEGIN
raise_salary@dallas(:emp_id, :increase);
END;
END-EXEC;
次の例に示すように、シノニムを作成して、リモート・サブプログラムに位置の透過性を与えることができます。
CREATE PUBLIC SYNONYM raise_salary FOR raise_salary@dallas;
7.7.2.3 CALL文
前述の埋込みPL/SQLブロックの概念はCALL文にも当てはまります。CALL埋込みSQL文の書式は次のようになります。
EXEC SQL CALL [schema.] [package.]stored_proc[@db_link](arg1, ...) [INTO :ret_var [[INDICATOR]:ret_ind]] ;
パラメータは次のとおりです。
schema
プロシージャを含むスキーマ
package
プロシージャを含むパッケージ
stored_proc
コールするJavaまたはPL/SQLストアド・プロシージャ
db_link
オプションのリモート・データベース・リンク
arg1...
引き渡す一連の引数(変数、リテラル、式)
ret_var
結果を受け取るオプションのホスト変数
ind_var
ret_varのオプションの標識変数。
CALL文には、SQLCHECK=SYNTAXまたはSEMANTICSのいずれかを使用できます。
7.7.2.4 CALLの例
次に示すように、入力された整数を受け取り、その階乗を整数で戻すPL/SQLファンクションfact(パッケージmathpkgに格納されています)を作成済とします。
EXEC SQL CREATE OR REPLACE PACKAGE BODY mathpkg as
function fact(n IN INTEGER) RETURN INTEGER AS
BEGIN
IF (n <= 0) then return 1;
ELSE return n * fact(n - 1);
END IF;
END fact;
END mathpkge;
END-EXEC.
次に、そのCALL文を使用しているPro*C/C++アプリケーションで、factを使用します。
... int num, fact; ... EXEC SQL CALL mathpkge.fact(:num) INTO :fact ; ...
関連項目:
-
CALL文の詳細は、CALL(実行可能埋込みSQL)を参照してください。
-
引数の渡し方およびその他の問題の詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。
7.7.3 ストアド・サブプログラムに関する情報を得る方法について
注意:
ログイン・データ領域(LDA)は、Oracleではサポートされなくなりました。Oracleの次のバージョンでは、Pro*C/C++プログラムにOCIリリース7コールを埋め込む機能は廃止になります。
データ型とホスト変数では、ホスト・プログラムにOCIコールを埋め込む方法を説明しています。ライブラリ・ルーチンSQLLDAをコールしてLDAを設定すると、OCIコールodesspを使用して、ストアド・サブプログラムに関する有益な情報を取得できます。odesspをコールするときには、有効なLDAとサブプログラムの名前を渡す必要があります。パッケージ・サブプログラムの場合は、パッケージ名も渡す必要があります。odesspからは、各サブプログラム・パラメータに関する情報(データ型、サイズ、位置など)が戻されます。
DBMS_DESCRIBEパッケージでは、DESCRIBE_PROCEDUREストアド・プロシージャを使用できます。
7.8 外部プロシージャ
PL/SQLでは、外部プロシージャのC言語関数をコールできます。外部プロシージャは、Dynamic Link Library (DLL)またはSolarisの.soライブラリなどに格納されています。
外部プロシージャをサーバーで実行する場合、同一トランザクション中でSQLおよびPL/SQLが実行されるようにサーバーにコールバックできます。サーバーで外部プロシージャを実行すると、クライアントで実行した場合よりも処理速度が速く、外部システムおよびデータ・ソースと、データベース・サーバーとのインタフェースとして使用できます。
サーバー側の外部C言語関数を実行する場合は、関数内でREGISTER CONNECT埋込みSQL文を使用する必要があります。文の構文は次のとおりです。
EXEC SQL REGISTER CONNECT USING :epctx [RETURNING :host_context] ;
epctxは、OCIExtProcContextへのタイプ・ポインタの外部プロシージャ・コンテキストです。epctxはPL/SQLによってプロシージャに渡されます。
host_contextは、外部プロシージャによって戻されるランタイム・コンテキストです。現在の設定は、デフォルト(グローバル)・コンテキストです。
REGISTER CONNECT文により、カレントのOracle接続およびトランザクションに対応付けられたOCIハンドル・セット(OCIEnv、OCISvcCtxおよびOCIError)が戻されます。これらのハンドルは、グローバルSQLLIBランタイム・コンテキストのPro*C/C++デフォルトである名前なし接続の定義に使用されます。このため、REGISTER CONNECTが、CONNECT文のかわりに使用されます。
後続の埋込みSQL文では、このOCIハンドル・セットを使用します。後続の埋込みSQL文は、グローバルSQLLIBランタイム・コンテキスト、名前なし接続に対して実行されます。これは、別々にプリコンパイルされたプログラム・ユニットにある場合でも同様です。コミットされていない変更は無効です。今後のバージョンでは、(非デフォルトの)ランタイム・コンテキストはオプションのRETURNING句で返されるようになります。
グローバル・ランタイム・コンテキストのアクティブなデフォルト接続はまだありません。すでに接続が確立しているときにREGISTER CONNECTを使用すると、ランタイム・エラーが戻されます。
実際の業務では、外部プロシージャは複数の異なるアプリケーションから再利用できるようにすることをお薦めします。
7.8.1 外部プロシージャの制限
外部プロシージャには次の規則があります。
-
外部プロシージャを使用できるのはCのみです。C++外部プロシージャはサポートされていません。
-
外部プロシージャ・コンテキストに接続した場合、接続を追加できません。ランタイム・エラーが発生します。
-
マルチスレッドの外部プロシージャはサポートされていません。EXEC SQL ENABLE THREADS文は使用できません。ランタイム・エラーが発生します。Pro*C/C++では、ここで説明している外部プロシージャを使用しない場合は、アプリケーションでのマルチスレッドがサポートされます。
-
DDL文を使用できません。ランタイム・エラーが発生します。
-
EXEC SQL COMMITおよびEXEC SQL ROLLBACKなどのトランザクション制御文を使用できません。
-
EXEC SQL OBJECTなどのオブジェクト・ナビゲーション文を使用できません。
-
EXEC SQL LOB文のポーリングを行うことはできません。
-
EXEC TOOLS文を使用できません。ランタイム・エラーが発生します。
7.8.2 外部プロシージャの作成について
外部プロシージャextp1を作成する場合の簡単な例を示します。
外部Cプロシージャを格納するには、コードのコンパイルおよびリンクを行い、DLLなどのライブラリに格納します。
NTの参照でユーザー・コメント9561が削除されました
次のSQLコマンドを1回実行して、外部プロシージャextp1を登録します。
CREATE OR REPLACE PROCEDURE extp1 AS EXTERNAL NAME "extp1" LIBRARY mylib WITH CONTEXT PARAMETERS(CONTEXT) ;
mylibは、プロシージャextp1が格納されるライブラリです。WITH CONTEXTが指定されているため、このプロシージャは、引数型OCIExtProcContext*で暗黙的にコールされます。このコールでは、コンテキストが省略されていますが、プロシージャには渡されます。ただし、CREATE文のCONTEXTキーワードは、プレース・マーカーとして指定されています。
このコンテキスト・パラメータは、extp1の内側のEXEC SQL REGISTER CONNECT文で参照されます。
外部プロシージャは、SQL*Plusから次のようにコールされます。
SQL> BEGIN INSERT INTO emp VALUES(9999,'JOHNSON','SALESMAN',7782, sysdate, 1200,150,10); extp1; END;
void extp1 (epctx)
OCIExtProcContext *epctx;
{
char name[15];
EXEC SQL REGISTER CONNECT USING :epctx;
EXEC SQL WHENEVER SQLERROR goto err;
EXEC SQL SELECT ename INTO :name FROM emp WHERE empno = 9999;
return;
err: SQLExtProcError(SQL_SINGLE_RCTX,sqlca.sqlerrm.sqlerrmc,sqlca.sqlerrm.sqlerrml);
return;
}関連項目
7.8.3 SQLExtProcError()
SQLLIB関数SQLExtProcError()を使用すると、外部Cプロシージャでエラーが発生した場合にPL/SQLに制御を戻すことができます。関数とその引数は次のとおりです。
SQLExtProcError (ctx, msg, msglen)
各パラメータの意味は次のとおりです。
ctx (IN) sql_context *
この関数は、REGISTER CONNECT文のターゲットSQLLIBランタイム・コンテキストです。REGISTER CONNECT文は、この関数が起動される前に実行する必要があります。現在、グローバル・ランタイム・コンテキストのみがサポートされています。
msg (OUT) char *
エラー・メッセージのテキスト
msglen (OUT) size_t
メッセージのバイト単位の長さ
この関数が実行されると、SQLLIBによりOCIサービス関数OCIExtProcRaiseExcpWithMsgがコールされます。
メッセージは、SQLCAの構造体sqlerrmから出力されます。
SQLExtProcError()の使用方法の例です。
void extp1 (epctx)
OCIExtProcContext *epctx;
{
char name[15];
EXEC SQL REGISTER CONNECT USING :epctx;
EXEC SQL WHENEVER SQLERROR goto err;
EXEC SQL SELECT ename INTO :name FROM emp WHERE smpno = 9999;
return;
err:
SQLExtProcError (SQL_SINGLE_RCTX, sqlca.sqlerrm.sqlerrmc,
sqlca.sqlerrm.sqlerrml);
printf("\n%*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
return;
}関連項目
7.9 動的SQLの使用方法について
プリコンパイラでは、PL/SQLブロック全体が1つのSQL文として処理されます。つまり、PL/SQLブロックをホスト変数の文字列に格納できることを意味します。この場合、ブロックにホスト変数が含まれない場合は、動的SQL方法1を使用して、PL/SQL文字列をEXECUTEできます。ブロックにホスト変数が含まれる場合、変数の数がわかっているときは、動的SQL方法2を使用してPL/SQL文字列をPREPAREおよびEXECUTEできます。ブロックに含まれるホスト変数の数がわからない場合は、動的SQL方法4を使用する必要があります。
注意:
動的SQL方法4では、TABLE型のパラメータを使用して、ホスト配列をPL/SQLプロシージャにバインドすることはできません。