支持的版本:当前 (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 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。请注意,不能在 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。与 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 命令中允许的任何变体,包括可以获取多行的变体;游标定位到最后一行。(但是,在 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 变量的值为 null(默认情况下是 null),则 OPEN 会自动生成一个与任何现有入口都不冲突的名称,并将其赋值给 refcursor 变量。

注意

PostgreSQL 16 之前,绑定的游标变量被初始化为包含它们自己的名称,而不是保留为 null,以便默认情况下底层入口名称与游标变量的名称相同。更改此设置是因为它在不同函数中创建了太多名称相似的游标冲突风险。

以下示例显示了调用者提供游标名称的一种方式

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

提交更正

如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的地方,请使用此表单报告文档问题。