37.8. カーソル

問い合わせ全体を一度に実行するのではなく、カーソルを設定して、問い合わせをカプセル化し、問い合わせの結果を一度に数行づつ読みとることができます。 これを行う理由の1つは、結果内に多数の行がある場合のメモリの枯渇を防ぐことです。 (しかし、PL/pgSQLユーザは通常これを心配する必要はありません。 FORループは自動的にカーソルを内部的に使用してメモリの問題を防ぐからです。) より興味深い使用方法として、呼び出し元が行を読み取ることのできるように、作成されたカーソルへの参照を返す方法があります。 これにより、関数から大量の行集合を返す際の効率が向上します。

37.8.1. カーソル変数の宣言

PL/pgSQLにおけるカーソルへのアクセスはすべて、カーソル変数を経由します。 カーソル変数は、常に特殊なrefcursorデータ型です。 カーソル変数を作成する1つの方法は、単にrefcursor型の変数として宣言することです。 他の方法は、カーソル宣言構文を使用することです。 以下にその一般形を示します。

name CURSOR [ ( arguments ) ] FOR query ;

(Oracleとの互換性のため、FORISに置き換えることができます。) argumentsはもしあれば、name datatypeをコンマで区切ったリストで、与えられた問い合わせ内のパラメータ値として置換される名前を定義します。 その名前に実際に置換される値は、カーソルを開いた時点より後に指定されます。

以下に例を示します。

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (key integer) IS SELECT * FROM tenk1 WHERE unique1 = key;

これら3つの変数はすべてrefcursorデータ型を持ちますが、最初のものは全ての問い合わせに使用でき、2番目には完全な問い合わせが既にバウンドされています(結びつけられています)。 また、最後のものには、パラメータ付きの問い合わせがバウンドされています。 (key はカーソルが開いた時に整数パラメータ値に置き換えられます。) curs1変数は、特定の問い合わせに結びつけられていませんので、アンバウンドであると呼ばれます。

37.8.2. カーソルを開く

カーソルを使用して行を取り出す前に、開かれる必要があります。 (これはDECLARE CURSOR SQLコマンドの動作と同じです。) PL/pgSQLには3種類のOPEN文があり、その内の2つはアンバウンドカーソル変数を使用し、残りの1つはバウンドカーソル変数を使用します。

37.8.2.1. OPEN FOR SELECT

OPEN unbound-cursor FOR SELECT ...;

カーソル変数は開かれ、実行するよう指定した問い合わせが付与されます。 既に開いたカーソルを開くことはできず、また、アンバウンドカーソルとして(つまり、単なるrefcursor変数として)宣言されていなければなりません。 このSELECT問い合わせは、他のPL/pgSQLSELECT文と同様の方法で扱われます。 PL/pgSQLの変数名は置き換えられ、問い合わせ計画は再利用できるようにキャッシュされます。

以下に例を示します。

OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;

37.8.2.2. OPEN FOR EXECUTE

OPEN unbound-cursor FOR EXECUTE query-string;

カーソル変数は開かれ、実行するよう指定した問い合わせが付与されます。 既に開いたカーソルを開くことはできず、また、アンバウンドカーソルとして(つまり、単なるrefcursor変数として)宣言されていなければなりません。 問い合わせは、EXECUTEコマンドと同じ方法による文字列式として指定されます。 通常と同様に、これにより、次回に実行する際に違った問い合わせをすることができる柔軟性が得られます。

以下に例を示します。

OPEN curs1 FOR EXECUTE ''SELECT * FROM '' || quote_ident($1);

37.8.2.3. バウンドカーソルを開く

OPEN bound-cursor [ ( argument_values ) ];

宣言時に問い合わせがバウンドされたカーソル変数を開くために使用されるOPENの形式です。 既に開いたカーソルを開くことはできません。 実引数の評価式のリストはカーソルが引数をとるものと宣言された場合にのみ現れます。 これらの値は、問い合わせの中で置き換えられます。 バウンドカーソルの問い合わせ計画は常にキャッシュ可能とみなされます。 この場合、EXECUTEと等価なものはありません。

OPEN curs2;
OPEN curs3(42);

37.8.3. カーソルの使用

カーソルを開いてから、ここで説明する文を使用してカーソルを扱うことができます。

これらの操作は、カーソルを開始するために開いた関数内で行う必要はありません。 関数からrefcursor値を返し、呼び出し元でそのカーソルの操作をさせることもできます。 (内部的にはrefcursor値は、カーソルへの有効な問い合わせを持つポータルの名前を示す単なる文字列です。 この名前は、ポータルを壊すことなく、他のrefcursor型の変数に代入することで、他に渡すことができます。)

全てのポータルは、暗黙的にトランザクションの終りで閉ざされます。 従って、refcursor値はそのトランザクションの終りまでの間のみ開いたカーソルへの参照として有効です。

37.8.3.1. FETCH

FETCH cursor INTO target;

FETCHSELECT INTOと同様に、カーソルから次の行を抽出し、対象に格納します。 対象とは、行変数、レコード変数、または単純な変数をコンマで区切ったリストです。 SELECT INTOの場合と同様、特殊なFOUND変数を検査することで、行が入手できたかどうかを確認することができます。

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;

37.8.3.2. CLOSE

CLOSE cursor;

CLOSEはポータルの背後のカーソルを閉じます。 これを使用してトランザクションの終りよりも前にリソースを解放することができ、また、カーソル変数を解放し、再度開くことができます。

CLOSE curs1;

37.8.3.3. カーソルを返す

PL/pgSQL関数では、呼び出し元にカーソルを返すことができます。 この方法は、関数から複数行または複数列を返す場合、特にその結果集合が非常に大きい場合に使用します。 これを行なうには、関数は、カーソルを開き、呼び出し元にカーソル名を返します(もしくは、もし呼出し元でポータル名が判っていれば、単純に指定されたポータル名を使用してカーソルを開きます)。 これにより、呼び出し元は、カーソルから行を取り出すことができるようになります。 カーソルは、呼び出し元で閉じることができます。または、トランザクションが終了した際に自動的に閉じられます。

カーソル用のポータル名は、プログラマが指定するか、または自動で生成されます。 ポータル名を指定するには、開く前に、単にrefcursor変数に文字列を代入します。 refcursor変数の文字列値はOPENによって、背後のポータル名として使用されます。 しかし、refcursor変数がNULLの場合、OPENは自動的に既存のポータルと競合しない名前を生成し、それをrefcursor変数に代入します。

注意: バウンドカーソル変数は、その名前を表現する文字列値で初期化されます。 そのため、プログラマがカーソルを開く前に代入により上書きしない限り、ポータル名はカーソル変数と同じになります。 しかし、アンバウンドカーソル変数の初期値はデフォルトでNULLです。 そのため、上書きされていない場合に自動的に生成される一意な名前を受け取ります。

以下の例は、呼び出し元でカーソル名を指定する方法を示しています。

CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS '
BEGIN
    OPEN $1 FOR SELECT col FROM test;
    RETURN $1;
END;
' LANGUAGE plpgsql;

BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;

以下の例では、自動カーソル名生成を使用しています。

CREATE FUNCTION reffunc2() RETURNS refcursor AS '
DECLARE
    ref refcursor;
BEGIN
    OPEN ref FOR SELECT col FROM test;
    RETURN ref;
END;
' LANGUAGE plpgsql;

BEGIN;
SELECT reffunc2();
  
      reffunc2      
--------------------
 <unnamed cursor 1>
(1 row)

FETCH ALL IN "<unnamed cursor 1>";
COMMIT;