Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

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;

(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 变量)。查询指定为 String 表达式,与 EXECUTE 命令中相同。与往常一样,这提供了灵活性,因此查询计划可以一次运行与下一次运行之间发生变化(参见 第 41.11.2 节),并且还意味着不向命令字符串完成变量替换。与 EXECUTE 一样,可以通过 format()USING 将参数值插入动态命令。 SCROLLNO SCROLL 选项与绑定游标的含义相同。

一个示例

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

在此示例中,通过 format() 将表名插入查询中。通过 USING 参数插入 col1 的比较值,因此不需要引号。

41.7.2.3. 打开绑定游标 #

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

此形式的 OPEN 用于打开在声明它时已查询绑定到它的游标变量。游标不能已打开。当且仅当声明游标会采用参数时,必须出现实际参数值表达式的列表。这些值将替换查询中。

绑定游标的查询计划始终被认为是可缓存的;在这种情况下,没有 EXECUTE 等价项。请注意,由于游标的滚动行为已确定,因此无法在 OPEN 中指定 SCROLLNO SCROLL

可以使用 位置已命名 符号法传递参数值。在位置符号法中,所有参数按顺序指定。在已命名符号法中,每个参数的名称使用 := 指定,以将其与参数表达式分开。类似于调用函数(在 第 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 命令)。如果游标未使用 SCROLL 选项声明或打开,则需要向后移动的 direction 值很可能失败。

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