PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

41.7. 游标 #

41.7.1. 声明游标变量
41.7.2. 打开游标
41.7.3. 使用游标
41.7.4. 遍历游标结果集

可以设置一个封装查询的游标, 然后每次读取少量查询结果行,而不是一次执行整个查询。 这样做的一个原因是,当结果包含大量行时避免内存耗尽。 (不过,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 被称为未绑定的, 因为它没有绑定到任何特定查询。

当游标的查询使用 FOR UPDATE/SHARE 时, 不能使用 SCROLL 选项。此外, 对于涉及易变(volatile)函数的查询,最好使用 NO SCROLL。SCROLL 的实现假定重新读取查询的输出会给出一致的结果, 而易变函数可能做不到这一点。

41.7.2. 打开游标 #

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

Note

绑定游标变量也可以不显式打开游标就使用, 具体做法是使用 Section 41.7.4 中描述的 FOR 语句。 FOR 循环会打开游标,并在循环结束时再次关闭它。

打开游标涉及创建一个称为门户(portal) 的服务器内部数据结构,它保存游标查询的执行状态。 门户有一个名字,该名字在门户存在的整个期间内必须在会话内唯一。 默认情况下,PL/pgSQL 会为它创建的每个门户赋予一个唯一的名字。 不过,如果你给游标变量赋了一个非空的字符串值, 该字符串将被用作它的门户名。此特性可以按照 Section 41.7.3.5 中描述的方式使用。

41.7.2.1. OPEN FOR 静态查询打开 #

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

游标变量被打开并被给定指定的查询去执行。 该游标不能已经处于打开状态,而且它必须被声明为 未绑定游标变量(即,一个简单的 refcursor 变量)。 查询必须是一条 SELECT, 或者其他返回行的东西(例如 EXPLAIN)。 该查询的处理方式与 PL/pgSQL 中的其他 SQL 命令相同:会替换 PL/pgSQL 变量名, 查询计划也会被缓存以备重用。当 PL/pgSQL 变量被替换进游标查询时, 被替换的值是它在 OPEN 时刻所拥有的值; 此后对变量的更改不会影响游标的行为。 SCROLL 和 NO 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 命令中一样。 照例,这带来了灵活性,查询计划可以在不同次运行之间变化 (见 Section 41.11.2), 这也意味着不会对命令字符串做变量替换。 与 EXECUTE 一样, 参数值可以通过 format() 和 USING 插入动态命令中。 SCROLL 和 NO 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 等价的机制。 注意 SCROLL 和 NO SCROLL 不能在 OPEN 中指定, 因为游标的滚动行为已经被确定了。

参数值既可以使用位置记法传递, 也可以使用命名记法传递。 在位置记法中,所有参数按顺序指定。在命名记法中, 每个参数的名字使用 := 或 => 与参数表达式分隔。 与调用函数类似(见 Section 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 值只是包含该游标活动查询的 门户的字符串名字。这个名字可以被传递、赋给其他 refcursor 变量等等,而不会扰动该门户。)

所有门户都会在事务结束时被隐式关闭。因此, refcursor 值只能用来引用一个打开的游标 直到事务结束。

41.7.3.1. FETCH 抓取行 #

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

FETCH 把游标的下一行(按指定方向) 检索到一个目标中,目标可以是行变量、记录变量, 或以逗号分隔的简单变量列表,就像 SELECT INTO 一样。如果没有合适的行,目标将被设置为 NULL。 与 SELECT INTO 一样, 可以检查特殊变量 FOUND 来确定是否取到了行。 如果没有取到行,游标将被定位在最后一行之后或第一行之前, 取决于移动的方向。

direction 子句可以是 SQL FETCH 命令允许的任何变体, 但不包括一次能抓取多行的那些;也就是说,它可以是 NEXT、 PRIOR、 FIRST、 LAST、 ABSOLUTE count、 RELATIVE count、 FORWARD 或 BACKWARD。 省略 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 变量。

Note

在 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 类型,并且只存在于循环内部 (该变量名的任何现有定义在循环内都会被忽略)。 游标返回的每一行都会被依次赋给这个记录变量, 循环体对每一行执行一次。