カーソルと同様に、カーソル変数も複数行のクエリ結果セット内の現在のデータ行を指すポインタです。
適用対象
この内容はOceanBaseデータベースEnterprise Editionにのみ適用されます。OceanBaseデータベースCommunity EditionはMySQLモードのみを提供します。
ただし、カーソルとは異なり、カーソル変数は動的であり、カーソルは静的です。カーソルは指定されたクエリにのみ接続可能であり、つまり特定のクエリのメモリ処理領域を固定して指しますが、カーソル変数は異なるクエリステートメントに接続可能であり、それぞれのクエリステートメントのメモリ処理領域を指すことができます(ただし、同時に複数のメモリ処理領域を指すことはできず、ある瞬間には1つのクエリステートメントにしか接続できません)。これらのクエリステートメントの戻り値型が互換性があればよいのです。
カーソル変数の宣言
カーソル変数はポインタであり、その型は REF CURSOR です。したがって、カーソル変数の型を宣言する前に、まずカーソル変数の型を定義する必要があります。PLでは、ブロック、サブプログラム、およびパッケージの宣言領域内でカーソル変数の参照型を定義できます。
構文は以下のとおりです:
TYPE ref_type_name IS REF CURSOR [ RETURN return_type];
ここで、ref_type_name は新しく定義されたカーソル変数の型名、return_type はカーソル変数の戻り値の型であり、レコード変数である必要があります。
カーソル変数の型を定義する際には、強い型定義と弱い型定義の2種類があります。強い型定義ではカーソル変数の戻り値の型を指定する必要がありますが、弱い型定義では戻り値の型を明記しません。
強い型のカーソル変数の例は以下のとおりです:
obclient> CREATE TABLE emp(
empno NUMBER(4,0),
empname VARCHAR(10),
job VARCHAR(10),
deptno NUMBER(2,0)
);
Query OK, 0 rows affected
obclient>INSERT INTO emp VALUES (200,'Jennifer','AD_ASST',1);
obclient>INSERT INTO emp VALUES (202,'Pat','MK_REP',2);
obclient>INSERT INTO emp VALUES (119,'Karen','PU_CLERK', 4);
obclient>INSERT INTO emp VALUES (118,'Guy','PU_CLERK', 4);
obclient>INSERT INTO emp VALUES (201,'Michael','MK_MAN', 3);
obclient> DECLARE
TYPE rec_emp_job IS RECORD(
employee_id emp.empno%TYPE,
employee_name emp.empname%TYPE,
job_id emp.job%TYPE
);
TYPE emp_job_refcur_type IS REF CURSOR RETURN rec_emp_job;
refcur_emp emp_job_refcur_type;
emp_job rec_emp_job;
BEGIN
OPEN refcur_emp FOR
SELECT empno, empname, job
FROM emp
ORDER BY deptno;
FETCH refcur_emp INTO emp_job;
WHILE refcur_emp%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_job.employee_id||': '||emp_job.employee_name||' is a '||emp_job.job_id);
FETCH refcur_emp INTO emp_job;
END LOOP;
END;
/
Query OK, 0 rows affected
200: Jennifer is a AD_ASST
202: Pat is a MK_REP
201: Michael is a MK_MAN
119: Karen is a PU_CLERK
118: Guy is a PU_CLERK
...
弱い型のカーソル変数の例は以下のとおりです:
obclient> DECLARE
TYPE rec_emp_job IS RECORD(
employee_id emp.empno%TYPE,
employee_name emp.empname%TYPE,
job_id emp.job%TYPE
);
-- ここでは emp_job_refcur_type の戻り値の型は定義されていません。
TYPE emp_job_refcur_type IS REF CURSOR;
refcur_emp emp_job_refcur_type;
emp_job rec_emp_job;
BEGIN
OPEN refcur_emp FOR
SELECT empno, empname, job
FROM emp
ORDER BY deptno;
FETCH refcur_emp INTO emp_job;
WHILE refcur_emp%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_job.employee_id||': '||emp_job.employee_name||' is a '||emp_job.job_id);
FETCH refcur_emp INTO emp_job;
END LOOP;
END;
/
Query OK, 0 rows affected
200: Jennifer is a AD_ASST
202: Pat is a MK_REP
201: Michael is a MK_MAN
119: Karen is a PU_CLERK
118: Guy is a PU_CLERK
...
弱い型のカーソルは戻り値がなく、汎用的な型です。システム組み込みの型 SYS_REFCURSOR を使用して直接定義できます。したがって、上記の例は次のように書き換えることができます:
obclient> DECLARE
TYPE rec_emp_job IS RECORD(
employee_id emp.empno%TYPE,
employee_name emp.empname%TYPE,
job_id emp.job%TYPE
);
-- refcur_emp の型を SYS_REFCURSOR と定義
refcur_emp SYS_REFCURSOR;
emp_job rec_emp_job;
BEGIN
OPEN refcur_emp FOR
SELECT empno, empname, job
FROM emp
ORDER BY deptno;
FETCH refcur_emp INTO emp_job;
WHILE refcur_emp%FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_job.employee_id||': '||emp_job.employee_name||' is a '||emp_job.job_id);
FETCH refcur_emp INTO emp_job;
END LOOP;
END;
/
Query OK, 0 rows affected
200: Jennifer is a AD_ASST
202: Pat is a MK_REP
201: Michael is a MK_MAN
119: Karen is a PU_CLERK
118: Guy is a PU_CLERK
...
注意
- 弱い型のカーソル変数については、PLエンジンはコンパイル時に変数の型を知らないため、実行時にバインディングを行う必要があります。これは、プログラムが実行時により多くのリソースと時間を消費することを意味します。
SYS_REFCURSOR型のカーソル変数は再利用が可能です。
動的SQLを使用したカーソル変数の定義
動的SQLとは、PLプログラムの実行時にSQL文を生成・実行する高度なプログラミング手法です。カーソル変数も同様に動的バインディングをサポートしており、OPEN... FOR 句を使用して文字列で定義します。以下の例では、カーソル cv を文字列変数 query_2 を用いて定義しています。
obclient> DECLARE
cv SYS_REFCURSOR; -- カーソル変数
query_2 VARCHAR2(200) :=
'SELECT * FROM emp
ORDER BY deptno';
v_employees emp%ROWTYPE; -- テーブル行のレコード変数
BEGIN
OPEN cv FOR query_2;
LOOP
FETCH cv INTO v_employees;
EXIT WHEN cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_employees.empno||'-'||v_employees.empname);
END LOOP;
CLOSE cv;
END;
/
Query OK, 0 rows affected
200-Jennifer
202-Pat
201-Michael
119-Karen
118-Guy
...
動的SQLの詳細については、動的SQLを参照してください。
カーソル変数を使用してレコードセットの各行をループで取得する
カーソル変数を使用してレコードセットの各行をループで取得する方法は以下のとおりです:
宣言部で、以下の操作を実行します:
- 動的カーソルタイプを定義し、ステートメントを実行します:
TYPE cursor_type IS REF CURSOR [ RETURN return_type ]; - 上記の動的カーソルタイプを使用して変数を宣言し、ステートメントを実行します:
cursor_variable cursor_type; - カーソルが返すレコードを格納するためのレコードを宣言し、ステートメントを実行します:
record_name return_type;
- 動的カーソルタイプを定義し、ステートメントを実行します:
実行部で、以下の操作を実行します:
- カーソル変数を特定のクエリにバインドし、ステートメントを実行します:
OPEN cursor_variable FOR query; - カーソルレコードセットから行データを取得します。毎回1行ずつ、結果の下記形式のLOOPステートメントを一緒に使用することで、複数行のデータを取得できます。
LOOP FETCH cursor_variable INTO record_name; EXIT WHEN cursor_variable%NOTFOUND; statement; [ statement; ]... END LOOP; - カーソルを閉じるには、ステートメントを実行します:
CLOSE cursor_name;またはカーソル変数を再開して別のクエリにバインドし(現在のクエリのカーソルは自動的に閉じられます)、上記の手順を繰り返します。
- カーソル変数を特定のクエリにバインドし、ステートメントを実行します:
例:カーソル変数を使用してレコードセットの各行をループで取得する。
obclient> CREATE OR REPLACE PACKAGE pkg_ware_mgmt AS
TYPE TYPE_REFCURSOR_WARE IS REF CURSOR RETURN ware%ROWTYPE;
PROCEDURE sp_record_print;
END;
/
Query OK, 0 rows affected
obclient> CREATE OR REPLACE PACKAGE BODY pkg_ware_mgmt
AS
PROCEDURE sp_record_print_by_record(p_cursor IN TYPE_REFCURSOR_WARE)
AS
rec_ware ware%ROWTYPE;
BEGIN
LOOP
dbms_output.put_line('Try to fetch a row from the ref cursor .');
FETCH p_cursor INTO rec_ware;
EXIT WHEN p_cursor%NOTFOUND ;
dbms_output.put_line('Print a record :');
dbms_output.put_line('W_ID : ' || rec_ware.w_id
|| ', W_YTD : ' || rec_ware.w_ytd
|| ', W_TAX : ' || rec_ware.w_tax
|| ', W_NAME : ' || rec_ware.w_name
|| ', W_STREET_1 : ' || rec_ware.w_street_1
|| ', W_STREET_2 : ' || rec_ware.w_street_2
|| ', W_CITY : ' || rec_ware.w_city
|| ', W_STATE : ' || rec_ware.w_state
|| ', W_ZIP : ' || rec_ware.w_zip )
;
dbms_output.put_line('');
END LOOP;
dbms_output.put_line('Processed ' || p_cursor%ROWCOUNT || ' rows. ');
END;
PROCEDURE sp_record_print
IS
cursor_ware TYPE_REFCURSOR_WARE ;
BEGIN
OPEN cursor_ware FOR SELECT * FROM ware ORDER BY w_id ;
dbms_output.put_line('Open a ref cursor using query at ware.');
sp_record_print_by_record(cursor_ware);
CLOSE cursor_ware;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Raise an unkown exception !');
END ;
END;
/
Query OK, 0 rows affected
obclient> set serveroutput on;
Query OK, 0 rows affected
obclient> CALL pkg_ware_mgmt.sp_record_print();
Query OK, 0 rows affected
Open a ref cursor using query at ware.
Try to fetch a row from the ref cursor .
Processed 0 rows.