与其一次执行整个查询,可以设置一个封装查询的游标,然后一次读取几行查询结果。这样做的一个原因是避免在结果包含大量行时出现内存溢出。(但是,PL/pgSQL 用户通常不必担心这一点,因为FOR
循环在内部自动使用游标来避免内存问题。)一个更有趣的使用方法是返回函数创建的游标的引用,允许调用者读取行。这提供了一种从函数返回大型行集的有效方法。
PL/pgSQL 中对游标的所有访问都通过游标变量进行,游标变量始终为特殊数据类型refcursor
。创建游标变量的一种方法是将其声明为类型为refcursor
的变量。另一种方法是使用游标声明语法,通常为
name
[ [ NO ] SCROLL ] CURSOR [ (arguments
) ] FORquery
;
(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
的实现假设重新读取查询的输出将给出一致的结果,而这是不稳定函数可能做不到的。
在游标可用于检索行之前,必须 打开 它。(这相当于 SQL 命令 DECLARE CURSOR
。) PL/pgSQL 有三种形式的 OPEN
语句,其中两种使用未绑定游标变量,而第三种使用绑定游标变量。
绑定游标变量也可以通过 第 43.7.4 节 中描述的 FOR
语句在不显式打开游标的情况下使用。 FOR
循环将打开游标,然后在循环完成后再次关闭它。
打开游标涉及创建称为 门户 的服务器内部数据结构,它保存游标查询的执行状态。门户有一个名称,该名称在门户存在期间在会话中必须是唯一的。默认情况下,PL/pgSQL 将为其创建的每个门户分配一个唯一名称。但是,如果向游标变量分配一个非空字符串值,则该字符串将用作其门户名称。此功能可按 第 43.7.3.5 节 中所述使用。
OPEN FOR
query
#OPENunbound_cursorvar
[ [ NO ] SCROLL ] FORquery
;
打开游标变量并为其提供要执行的指定查询。游标不能已经打开,并且必须已声明为未绑定游标变量(即,作为简单的 refcursor
变量)。查询必须是 SELECT
,或其他返回行内容(例如 EXPLAIN
)。查询的处理方式与 PL/pgSQL 中的其他 SQL 命令相同:替换 PL/pgSQL 变量名称,并缓存查询计划以备可能重复使用。当将 PL/pgSQL 变量替换到游标查询中时,替换的值是 OPEN
时具有的值;对变量的后续更改不会影响游标的行为。SCROLL
和 NO SCROLL
选项的含义与绑定游标相同。
示例
OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;
OPEN FOR EXECUTE
#OPENunbound_cursorvar
[ [ NO ] SCROLL ] FOR EXECUTEquery_string
[ USINGexpression
[, ... ] ];
打开游标变量并为其提供要执行的指定查询。游标不能已经打开,并且必须已声明为未绑定游标变量(即,作为简单的 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
参数插入,因此无需引用。
OPENbound_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;
打开游标后,可以使用此处描述的语句对其进行操作。
这些操作不必在最初打开游标的同一函数中发生。你可以从函数中返回一个 refcursor
值,并让调用者对游标进行操作。(在内部,refcursor
值只是包含游标活动查询的门户的字符串名称。此名称可以传递,分配给其他 refcursor
变量,等等,而不会干扰门户。)
所有门户在事务结束时都会隐式关闭。因此,refcursor
值只能用于引用打开的游标,直到事务结束。
FETCH
#FETCH [direction
{ FROM | IN } ]cursor
INTOtarget
;
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
命令不同)。除非使用 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;
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;
UPDATE/DELETE WHERE CURRENT OF
#UPDATEtable
SET ... WHERE CURRENT OFcursor
; DELETE FROMtable
WHERE CURRENT OFcursor
;
当游标定位在表行上时,可以使用游标识别行来更新或删除该行。对游标的查询可以是什么存在限制(特别是,没有分组),并且最好在游标中使用 FOR UPDATE
。有关更多信息,请参见 DECLARE 参考页面。
示例
UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;
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;
有一个 FOR
语句的变体,它允许遍历光标返回的行。语法为
[ <<label
>> ] FORrecordvar
INbound_cursorvar
[ ( [argument_name
:= ]argument_value
[, ...] ) ] LOOPstatements
END LOOP [label
];
光标变量在声明时必须绑定到某个查询,并且 不能 已打开。 FOR
语句会自动打开光标,并在退出循环时再次关闭光标。当且仅当声明光标需要参数时,才必须出现实际参数值表达式的列表。这些值将以与 OPEN
期间完全相同的方式替换到查询中(请参阅 第 43.7.2.3 节)。
变量 recordvar
自动定义为类型 record
,并且仅存在于循环内(循环内将忽略变量名称的任何现有定义)。光标返回的每一行都会依次分配给此记录变量,然后执行循环体。