2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 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;

(為了與Oracle 相容,FOR 可以被替換為 IS。) 如果指定了 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 被稱為未繫結,因為它沒有繫結到任何特定的查詢。

SCROLL 選項不能與使用 FOR UPDATE/SHARE 的遊標查詢一起使用。另外,最好將 NO SCROLL 用於涉及易變函式的查詢。 SCROLL 的實現假定重新讀取查詢的輸出會得到一致的結果,而易變函式可能不會如此。

41.7.2. 開啟遊標 #

在使用遊標檢索行之前,必須先開啟它。(這是 SQL 命令 DECLARE CURSOR 的等效操作。)PL/pgSQL 有三種形式的 OPEN 語句,其中兩種使用未繫結的遊標變數,第三種使用繫結的遊標變數。

注意

繫結遊標變數也可以透過 FOR 語句在不顯式開啟遊標的情況下使用,該語句在 第 41.7.4 節 中進行了描述。 FOR 迴圈會開啟遊標,並在迴圈完成後將其關閉。

開啟遊標涉及建立一個伺服器內部資料結構,稱為portal,它儲存遊標查詢的執行狀態。Portal 有一個名稱,在 portal 存在期間必須在會話中唯一。預設情況下,PL/pgSQL 會為其建立的每個 portal 分配一個唯一名稱。但是,如果將非空字串值賦給遊標變數,該字串將用作其 portal 名稱。此功能可以按 第 41.7.3.5 節 中描述的方式使用。

41.7.2.1. OPEN FOR query #

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

遊標變數被開啟並給定指定的查詢以執行。遊標不能已開啟,並且必須宣告為未繫結的遊標變數(即,作為一個簡單的 refcursor 變數)。查詢必須是 SELECT,或任何其他返回行的內容(如 EXPLAIN)。查詢的處理方式與其他 SQL 命令在 PL/pgSQL 中的處理方式相同: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 用於開啟其查詢在宣告時已繫結到它的遊標變數。遊標不能已開啟。如果遊標宣告為接受引數,則必須提供實際引數值表示式列表。這些值將被替換到查詢中,就像在 OPEN 時一樣(參見 第 41.7.2.3 節)。

繫結遊標的查詢計劃始終被認為是可快取的;在這種情況下沒有 EXECUTE 的等價物。請注意,SCROLLNO SCROLL 不能在 OPEN 中指定,因為遊標的滾動行為已經確定。

可以使用位置命名表示法傳遞引數值。在位置表示法中,所有引數按順序指定。在命名錶示法中,使用 :==> 將每個引數的名稱與其引數表示式分隔開。類似於呼叫函式,如 第 4.3 節 中所述,也允許混合使用位置和命名錶示法。

示例(這些示例使用了上面的遊標宣告示例)

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 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 值只是包含遊標活動查詢的 portal 的字串名稱。此名稱可以傳遞,賦給其他 refcursor 變數,依此類推,而不會干擾 portal。)

所有 portal 在事務結束時都會被隱式關閉。因此,refcursor 值只能在事務結束前用於引用已開啟的遊標。

41.7.3.1. FETCH #

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

FETCH 將遊標中的下一行(按指定方向)檢索到目標中,目標可以是行變數、記錄變數或逗號分隔的簡單變數列表,就像 SELECT INTO 一樣。如果沒有合適的行,目標將被設定為 NULL。與 SELECT INTO 一樣,可以檢查特殊變數 FOUND 來檢視是否獲取了行。如果沒有獲取到行,則遊標將定位在最後一行之後或第一行之前,具體取決於移動方向。

direction 子句可以是 SQL FETCH 命令中允許的任何變體,但那些只能獲取一行的變體除外;即,它可以是 NEXTPRIORFIRSTLASTABSOLUTE countRELATIVE countFORWARDBACKWARD。省略 direction 等同於指定 NEXT。在帶有 count 的形式中,count 可以是任何整數值表示式(與僅允許整數常量的 SQL FETCH 命令不同)。需要向後移動的 direction 值很可能失敗,除非遊標在宣告或開啟時帶有 SCROLL 選項。

cursor 必須是一個 refcursor 變數的名稱,該變數引用了一個已開啟的遊標 portal。

示例

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 關閉已開啟遊標的底層 portal。這可以用於在事務結束前釋放資源,或使遊標變數可以重新開啟。

一個例子

CLOSE curs1;

41.7.3.5. 返回遊標 #

PL/pgSQL 函式可以向呼叫者返回遊標。這對於返回多行或多列非常有用,尤其是對於非常大的結果集。為此,函式會開啟遊標並將其遊標名稱返回給呼叫者(或者只是使用呼叫者指定或呼叫者已知的 portal 名稱開啟遊標)。呼叫者然後可以從遊標中獲取行。遊標可以由呼叫者關閉,或者在事務結束時自動關閉。

遊標使用的 portal 名稱可以由程式設計師指定,也可以自動生成。要指定 portal 名稱,只需在開啟遊標之前將字串賦給 refcursor 變數。 OPENrefcursor 變數的字串值用作底層 portal 的名稱。但是,如果 refcursor 變數的值為 null(預設情況下就是這樣),則 OPEN 會自動生成一個與任何現有 portal 都不衝突的名稱,並將其賦給 refcursor 變數。

注意

PostgreSQL 16 之前,繫結遊標變數被初始化為包含它們自己的名稱,而不是留空,因此底層 portal 名稱預設與遊標變數的名稱相同。此更改是因為它增加了不同函式中相似命名的遊標之間發生衝突的風險。

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

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 時一樣(參見 第 41.7.2.3 節)被替換到查詢中。

變數 recordvar 會自動定義為 record 型別,並且只在迴圈內部存在(在迴圈內部,任何已有的變數名定義都會被忽略)。遊標返回的每一行都會依次賦給這個記錄變數,然後執行迴圈體。

提交更正

如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。