
43.7. 游标#
与其一次执行整个查询,可以设置一个封装查询的游标,然后一次读取几行查询结果。这样做的一个原因是避免在结果包含大量行时出现内存溢出。(但是,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 SCROLL
。SCROLL
的实现假设重新读取查询的输出将给出一致的结果,而这是不稳定函数可能做不到的。
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 FOR
query
#
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;
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
将参数值插入动态命令。SCROLL
和NO 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
。请注意,SCROLL
和NO 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命令中允许的任何变体,但不能获取多于一行的变体;即,它可以是NEXT
、PRIOR
、FIRST
、LAST
、ABSOLUTE
count
、RELATIVE
count
、FORWARD
或BACKWARD
。省略*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
,并且仅存在于循环内(循环内将忽略变量名称的任何现有定义)。光标返回的每一行都会依次分配给此记录变量,然后执行循环体。