Skip to content

43.7. 游标#

43.7.1. 声明游标变量
43.7.2. 打开游标
43.7.3. 使用游标
43.7.4. 循环游标结果

与其一次执行整个查询,可以设置一个封装查询的游标,然后一次读取几行查询结果。这样做的一个原因是避免在结果包含大量行时出现内存溢出。(但是,PL/pgSQL用户通常不必担心这一点,因为FOR循环在内部自动使用游标来避免内存问题。)一个更有趣的使用方法是返回函数创建的游标的引用,允许调用者读取行。这提供了一种从函数返回大型行集的有效方法。

43.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的实现假设重新读取查询的输出将给出一致的结果,而这是不稳定函数可能做不到的。

43.7.2. 打开游标#

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

注意

绑定游标变量也可以通过第 43.7.4 节中描述的FOR语句在不显式打开游标的情况下使用。FOR循环将打开游标,然后在循环完成后再次关闭它。

打开游标涉及创建称为门户的服务器内部数据结构,它保存游标查询的执行状态。门户有一个名称,该名称在门户存在期间在会话中必须是唯一的。默认情况下,PL/pgSQL将为其创建的每个门户分配一个唯一名称。但是,如果向游标变量分配一个非空字符串值,则该字符串将用作其门户名称。此功能可按第 43.7.3.5 节中所述使用。

43.7.2.1.OPEN FORquery#

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;

43.7.2.2.OPEN FOR EXECUTE#

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR EXECUTE query_string
                                     [ USING expression [, ... ] ];

打开游标变量并为其提供要执行的指定查询。游标不能已经打开,并且必须已声明为未绑定游标变量(即,作为简单的refcursor变量)。查询指定为字符串表达式,方式与EXECUTE命令中相同。与往常一样,这提供了灵活性,因此查询计划可以因一次运行而异(请参阅第 43.11.2 节),并且还意味着不会对命令字符串执行变量替换。与EXECUTE一样,可以通过format()USING将参数值插入动态命令。SCROLLNO SCROLL选项的含义与绑定游标相同。

示例

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

在此示例中,表名称通过format()插入到查询中。col1的比较值通过USING参数插入,因此无需引用。

43.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;

43.7.3. 使用游标#

打开游标后,可以使用此处描述的语句对其进行操作。

这些操作不必在最初打开游标的同一函数中发生。你可以从函数中返回一个refcursor值,并让调用者对游标进行操作。(在内部,refcursor值只是包含游标活动查询的门户的字符串名称。此名称可以传递,分配给其他refcursor变量,等等,而不会干扰门户。)

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

43.7.3.1.FETCH#

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

FETCH将游标中的下一行检索到目标中,目标可以是行变量、记录变量或逗号分隔的简单变量列表,就像SELECT INTO一样。如果没有下一行,则目标将设置为 NULL。与SELECT INTO一样,可以检查特殊变量FOUND以查看是否获取了行。

direction子句可以是 SQLFETCH命令中允许的任何变体,但不能获取多于一行的变体;即,它可以是NEXTPRIORFIRSTLASTABSOLUTEcountRELATIVEcountFORWARDBACKWARD。省略*direction与指定NEXT相同。在使用count的形式中,count可以是任何整数值表达式(与仅允许整数常量的 SQLFETCH命令不同)。除非使用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;

43.7.3.2.MOVE#

MOVE [ direction { FROM | IN } ] cursor;

MOVE重新定位游标,而不检索任何数据。MOVE的工作方式与FETCH命令完全相同,只是它只重新定位游标,而不返回已移动到的行。与SELECT INTO一样,可以检查特殊变量FOUND,以查看是否有下一行可移动到。

示例

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;

43.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;

43.7.3.4.CLOSE#

CLOSE cursor;

CLOSE关闭打开游标下的门户。这可用于在事务结束之前释放资源,或释放游标变量以再次打开。

示例

CLOSE curs1;

43.7.3.5. 返回游标#

PL/pgSQL函数可以将游标返回给调用者。这对于返回多行或多列非常有用,尤其对于非常大的结果集。为此,函数打开游标并将游标名称返回给调用者(或仅使用由调用者指定或以其他方式已知的门户名称打开游标)。然后,调用者可以从游标中获取行。游标可以由调用者关闭,也可以在事务关闭时自动关闭。

光标使用的门户名称可以由程序员指定或自动生成。要指定门户名称,只需在打开refcursor变量之前为其分配一个字符串。refcursor变量的字符串值将由OPEN作为底层门户的名称使用。但是,如果refcursor变量的值为 null(默认情况下为 null),则OPEN会自动生成一个与任何现有门户不冲突的名称,并将其分配给refcursor变量。

注意

在PostgreSQL16 之前,绑定光标变量被初始化为包含它们自己的名称,而不是保留为 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;

43.7.4. 遍历光标的结果#

有一个FOR语句的变体,它允许遍历光标返回的行。语法为

[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ] LOOP
    statements
END LOOP [ label ];

光标变量在声明时必须绑定到某个查询,并且不能已打开。FOR语句会自动打开光标,并在退出循环时再次关闭光标。当且仅当声明光标需要参数时,才必须出现实际参数值表达式的列表。这些值将以与OPEN期间完全相同的方式替换到查询中(请参阅第 43.7.2.3 节)。

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