支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

41.7. 游標 #

可以設定一個游標來封裝查詢,然後一次讀取少量資料列的查詢結果,而不是一次執行整個查詢。這樣做的一個原因是避免結果包含大量資料列時記憶體溢出。(但是,PL/pgSQL 使用者通常不需要擔心這個問題,因為 FOR 迴圈會在內部自動使用游標來避免記憶體問題。)一個更有趣的用法是傳回函數建立的游標的參考,允許呼叫者讀取這些資料列。這提供了一種從函數傳回大型資料列集合的有效方式。

41.7.1. 宣告游標變數 #

PL/pgSQL 中,對游標的所有存取都透過游標變數進行,游標變數的資料類型始終為特殊的 refcursor。建立游標變數的一種方法是將其宣告為 refcursor 類型的變數。另一種方法是使用游標宣告語法,通常是

name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;

(FOR 可以替換為 IS 以實現與 Oracle 的相容性。)如果指定了 SCROLL,則游標將能夠向後捲動;如果指定了 NO SCROLL,則會拒絕向後提取;如果未出現任何規格,則是否允許向後提取取決於查詢。 arguments(如果已指定)是以逗號分隔的 name datatype 配對清單,用於定義名稱以替換給定查詢中的參數值。稍後開啟游標時,將指定替換這些名稱的實際值。

一些範例

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

所有這三個變數都具有 refcursor 資料類型,但第一個可以與任何查詢一起使用,而第二個已經繫結了一個完全指定的查詢,最後一個繫結了一個參數化查詢。(開啟游標時,key 將被替換為整數參數值。)變數 curs1 被稱為未繫結,因為它未繫結到任何特定查詢。

當游標的查詢使用 FOR UPDATE/SHARE 時,無法使用 SCROLL 選項。此外,最好將 NO SCROLL 用於涉及 volatile 函數的查詢。SCROLL 的實作假設重新讀取查詢的輸出會產生一致的結果,而 volatile 函數可能不會這樣做。

41.7.2. 開啟游標 #

在使用游標檢索資料列之前,必須先開啟它。(這相當於 SQL 指令 DECLARE CURSOR。)PL/pgSQL 有三種形式的 OPEN 陳述式,其中兩種使用未繫結的游標變數,而第三種使用繫結的游標變數。

注意

也可以透過 第 41.7.4 節 中描述的 FOR 陳述式,在不顯式開啟游標的情況下使用繫結的游標變數。FOR 迴圈將開啟游標,然後在迴圈完成時再次關閉它。

開啟游標涉及建立一個伺服器內部的資料結構,稱為入口,其中保存游標查詢的執行狀態。入口有一個名稱,在入口存在的期間內,該名稱在會話中必須是唯一的。依預設,PL/pgSQL 會為其建立的每個入口指派一個唯一的名稱。但是,如果您將非 null 字串值指派給游標變數,則該字串將用作其入口名稱。此功能可以使用 第 41.7.3.5 節 中所述的方式使用。

41.7.2.1. OPEN FOR query #

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

游標變數已開啟,並給定要執行的指定查詢。游標不能已開啟,並且必須宣告為未繫結的游標變數(也就是說,作為簡單的 refcursor 變數)。查詢必須是 SELECT,或傳回資料列的其他內容(例如 EXPLAIN)。查詢的處理方式與 PL/pgSQL 中的其他 SQL 指令相同:PL/pgSQL 變數名稱會被替換,並且查詢計畫會被快取,以便日後重複使用。將 PL/pgSQL 變數替換到游標查詢中時,替換的值是 OPEN 時的值;後續對變數的變更不會影響游標的行為。SCROLLNO SCROLL 選項的含義與繫結游標相同。

一個範例

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

41.7.2.2. OPEN FOR EXECUTE #

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR EXECUTE query_string
                                     [ USING expression [, ... ] ];

游標變數會被開啟,並給予指定的查詢來執行。游標不能已經是開啟的狀態,而且它必須被宣告為未繫結的游標變數(也就是,一個簡單的 refcursor 變數)。查詢以字串表達式的方式指定,和 EXECUTE 指令中相同。如同往常,這提供了彈性,所以查詢計畫可以每次執行都不同(參見第 41.11.2 節),同時這也表示變數替換不會在指令字串上進行。和 EXECUTE 一樣,參數值可以透過 format()USING 插入到動態指令中。SCROLLNO SCROLL 選項的意義和繫結游標相同。

一個範例

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

在這個例子中,表格名稱透過 format() 插入到查詢中。col1 的比較值透過 USING 參數插入,所以不需要加上引號。

41.7.2.3. 開啟一個繫結游標 #

OPEN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ];

這個形式的 OPEN 用來開啟一個游標變數,其查詢在宣告時就已經繫結到它。游標不能已經是開啟的狀態。如果且僅當游標被宣告為接受參數時,必須出現一個實際引數值表達式的清單。這些值將會被替換到查詢中。

繫結游標的查詢計畫總是會被認為是可以快取的;在這種情況下,沒有相當於 EXECUTE 的東西。請注意,SCROLLNO SCROLL 不能在 OPEN 中指定,因為游標的捲動行為已經被決定。

引數值可以使用位置式具名式的表示法傳遞。在位置式表示法中,所有引數都依照順序指定。在具名式表示法中,每個引數的名稱使用 := 與引數表達式隔開。類似於呼叫函數,在第 4.3 節中描述的,也允許混合位置式和具名式表示法。

範例(這些使用上述的游標宣告範例)

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);

因為變數替換是在繫結游標的查詢上進行,所以實際上將值傳遞到游標中有兩種方式:要麼明確地將引數傳遞給 OPEN,要麼透過在查詢中參考 PL/pgSQL 變數來隱含地傳遞。然而,只有在宣告繫結游標之前宣告的變數才會被替換到其中。無論哪種情況,要傳遞的值都是在 OPEN 的時候決定的。例如,另一種達到與上述 curs3 範例相同效果的方法是

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;

41.7.3. 使用游標 #

一旦游標被開啟,它就可以用這裡描述的敘述來操作。

這些操作不需要發生在開啟游標的同一個函數中。您可以將 refcursor 值從函數中傳回,並讓呼叫者對游標進行操作。(在內部,refcursor 值只是一個字串名稱,指向包含游標的有效查詢的入口。這個名稱可以傳遞、指派給其他的 refcursor 變數等等,而不會干擾該入口。)

所有的入口都會在交易結束時隱含地關閉。因此,refcursor 值只能在交易結束之前用來參考一個開啟的游標。

41.7.3.1. FETCH #

FETCH [ direction { FROM | IN } ] cursor INTO target;

FETCH 從游標中(依指示的方向)擷取下一列到目標中,目標可以是一個列變數、一個紀錄變數,或是一個逗號分隔的簡單變數清單,就像 SELECT INTO 一樣。如果沒有適合的列,則目標會被設定為 NULL(s)。和 SELECT INTO 一樣,可以檢查特殊變數 FOUND 來查看是否獲得了一列。如果沒有獲得列,則游標會根據移動方向定位在最後一列之後或第一列之前。

direction 子句可以是 SQL FETCH 指令中允許的任何變體,除了那些可以擷取超過一列的變體之外;也就是說,它可以是 NEXTPRIORFIRSTLASTABSOLUTE countRELATIVE countFORWARDBACKWARD。省略 direction 等同於指定 NEXT。在使用 count 的形式中,count 可以是任何整數值的表達式(不像 SQL FETCH 指令,它只允許整數常數)。需要向後移動的 direction 值很可能會失敗,除非游標已宣告或開啟了 SCROLL 選項。

cursor 必須是一個 refcursor 變數的名稱,該變數參考一個開啟的游標入口。

範例

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;

41.7.3.2. MOVE #

MOVE [ direction { FROM | IN } ] cursor;

MOVE 重新定位游標而不擷取任何資料。MOVE 的作用類似於 FETCH 指令,只是它只重新定位游標而不傳回移動到的列。direction 子句可以是 SQL FETCH 指令中允許的任何變體,包括那些可以擷取超過一列的變體;游標會定位到最後一列。(然而,在 PL/pgSQL 中,direction 子句只是一個沒有關鍵字的 count 表達式的情況已被棄用。該語法與完全省略 direction 子句的情況含糊不清,因此如果 count 不是常數,則可能會失敗。)和 SELECT INTO 一樣,可以檢查特殊變數 FOUND 來查看是否有要移動到的列。如果沒有這樣的列,則游標會根據移動方向定位在最後一列之後或第一列之前。

範例

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;

41.7.3.3. UPDATE/DELETE WHERE CURRENT OF #

UPDATE table SET ... WHERE CURRENT OF cursor;
DELETE FROM table WHERE CURRENT OF cursor;

當游標定位在一個表格列上時,可以使用游標來識別該列,從而更新或刪除該列。對游標的查詢有許多限制(特別是,沒有分組),最好在游標中使用 FOR UPDATE。有關更多資訊,請參閱 DECLARE 參考頁面。

一個範例

UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;

41.7.3.4. CLOSE #

CLOSE cursor;

CLOSE 關閉開啟游標底層的入口。這可以用於在交易結束之前釋放資源,或釋放游標變數以再次開啟。

一個範例

CLOSE curs1;

41.7.3.5. 傳回游標 #

PL/pgSQL 函數可以將游標傳回給呼叫者。這對於傳回多列或多欄非常有用,尤其是在處理非常大的結果集時。為此,函數會開啟游標並將游標名稱傳回給呼叫者(或者只是使用呼叫者指定或以其他方式知道的入口名稱開啟游標)。然後,呼叫者可以從游標中擷取列。游標可以由呼叫者關閉,或者會在交易關閉時自動關閉。

游標所使用的入口名稱可以由程式設計師指定,或者自動產生。 若要指定入口名稱,只需在開啟游標之前,將字串賦予給 refcursor 變數。 refcursor 變數的字串值將會被 OPEN 用作底層入口的名稱。 然而,如果 refcursor 變數的值為空 (預設情況下會是空值),則 OPEN 會自動產生一個與任何現有入口不衝突的名稱,並將其賦予給 refcursor 變數。

注意

PostgreSQL 16 之前的版本中,繫結游標變數會被初始化為包含它們自己的名稱,而不是保留為空值,因此預設情況下,底層入口名稱會與游標變數的名稱相同。 這樣的設計被更改的原因是,它在不同的函式中,造成了太多同名游標之間衝突的風險。

以下範例展示了呼叫者提供游標名稱的一種方式

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;

-- need to be in a transaction to use cursors.
BEGIN;
SELECT reffunc2();

      reffunc2
--------------------
 <unnamed cursor 1>
(1 row)

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

以下範例展示了從單一函式傳回多個游標的一種方式

CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT * FROM table_1;
    RETURN NEXT $1;
    OPEN $2 FOR SELECT * FROM table_2;
    RETURN NEXT $2;
END;
$$ LANGUAGE plpgsql;

-- need to be in a transaction to use cursors.
BEGIN;

SELECT * FROM myfunc('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;

41.7.4. 迴圈處理游標的結果 #

有一個 FOR 陳述式的變體,允許遍歷游標傳回的列。 語法如下

[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ] LOOP
    statements
END LOOP [ label ];

游標變數在宣告時必須已繫結到某個查詢,並且它不能已開啟。 FOR 陳述式會自動開啟游標,並在迴圈結束時再次關閉游標。 如果游標被宣告為需要引數,則必須出現實際引數值運算式清單。 這些值將被替換到查詢中,就像在 OPEN 期間一樣 (請參閱 Section 41.7.2.3)。

變數 recordvar 會自動定義為 record 類型,並且僅存在於迴圈內部 (迴圈內會忽略該變數名稱的任何現有定義)。 游標傳回的每一列都會依序賦予給這個紀錄變數,並且執行迴圈主體。

提交更正

如果您在文件中發現任何不正確的地方,或者與您使用特定功能的經驗不符,或者需要進一步澄清,請使用此表單來回報文件問題。