2024 年 9 月 26 日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本: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 SCROLLSCROLL 的实现假设重新读取查询的输出将给出一致的结果,而易变函数可能不会这样做。

41.7.2. 打开游标 #

在可以使用游标检索行之前,必须先 打开 它。(这相当于 SQL 命令 DECLARE CURSOR。)PL/pgSQL 有三种形式的 OPEN 语句,其中两种使用未绑定游标变量,而第三种使用绑定游标变量。

注意

绑定游标变量也可以在没有显式打开游标的情况下使用,方法是通过 第 41.7.4 节 中描述的 FOR 语句。一个 FOR 循环将打开游标,并在循环完成后将其关闭。

打开游标涉及创建一个称为 门户 的服务器内部数据结构,该结构保存游标查询的执行状态。门户有一个名称,在门户存在期间,它在会话中必须是唯一的。默认情况下,PL/pgSQL 将为其创建的每个门户分配一个唯一的名称。但是,如果将一个非空字符串值分配给一个游标变量,则该字符串将用作其门户名称。此功能可用于 第 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。与 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 命令中允许的任何变体,包括可以获取多行的变体;游标将定位到最后一行。(但是,如果 direction 子句只是一个 count 表达式,没有关键字,则在 PL/pgSQL 中已弃用。该语法与完全省略 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 期间一样(参见 第 41.7.2.3 节)。

变量 recordvar 会自动定义为 record 类型,并且只在循环内部存在(循环内部会忽略变量名称的任何现有定义)。游标返回的每一行都会依次分配给此记录变量,然后执行循环主体。

提交更正

如果您在文档中发现任何不正确的信息、与您对特定功能的体验不符或需要进一步说明,请使用 此表格 报告文档问题。