和一次执行整个查询不同,可以建立一个游标来封装该查询,并且接着一次读取该查询结果的一些行。这样做的原因之一是在结果中包含大量行时避免内存不足(不过,PL/pgSQL用户通常不需要担心这些,因为FOR循环在内部会自动使用一个游标来避免内存问题)。一种更有趣的用法是返回一个函数已经创建的游标的引用,允许调用者读取行。这提供了一种有效的方法从函数中返回大型行集。
所有在PL/pgSQL中对游标的访问都会通过游标变量,它总是特殊的数据类型refcursor。创建游标变量的一种方法是把它声明为一个类型为refcursor的变量。另外一种方法是使用游标声明语法,通常是:
name[ [ NO ] SCROLL ] CURSOR [ (arguments) ] FORquery;
(为兼容 Oracle,可以用 IS 代替 FOR。)如果指定了 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命令,其中两种用于未绑定游标变量,另一种用于已绑定游标变量。
可以通过Section 41.7.4中描述的FOR语句在不显式打开游标的情况下使用已绑定的游标变量。FOR 循环会打开该游标,并在循环完成时再次关闭它。
打开游标会创建一个称为 portal 的服务器内部数据结构,它保存游标查询的执行状态。portal 具有一个名称,在其存在期间该名称在会话内必须唯一。默认情况下,PL/pgSQL 会为其创建的每个 portal 分配唯一名称。不过,如果你给游标变量赋一个非空字符串值,该字符串会被用作 portal 名称。这个特性可用于Section 41.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命令相同。像往常一样,这提供了灵活性,因此查询计划可以在不同执行之间变化(见Section 41.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对应的形式。注意,不能在OPEN中指定SCROLL和NO SCROLL,因为游标的滚动行为已经确定。
使用位置或命名记号可以传递参数值。在位置记号中,所有参数都必须按照顺序指定。在命名记号中,每一个参数的名字使用:=或=>与参数表达式分隔。类似于Section 4.3中描述的调用函数,也允许混合位置和命名记号。
示例(这些示例使用上面示例中的游标声明):
OPEN curs2; OPEN curs3(42); OPEN curs3(key := 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值只是一个所谓 portal 的字符串名称,该 portal 包含了该游标活动查询的状态。这个名称可以被传递、赋给其他refcursor变量等等,而不会干扰该 portal。)
所有 portal 都会在事务结束时被隐式关闭。因此,refcursor值只能在事务结束之前用于引用一个打开的游标。
FETCH #FETCH [direction{ FROM | IN } ]cursorINTOtarget;
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不一样,FETCH仅允许整数常量)。除非游标被使用SCROLL选项声明或打开,否则要求反向移动的direction值很可能会失败。
cursor必须是一个引用已打开游标 portal 的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类似,但只重新定位游标且不返回移动到的行。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;
UPDATE/DELETE WHERE CURRENT OF #UPDATEtableSET ... WHERE CURRENT OFcursor; DELETE FROMtableWHERE CURRENT OFcursor;
当游标定位在某个表行上时,可以使用该游标来标识该行,并对其执行更新或删除。游标查询的形式有一些限制(尤其不能包含分组),并且在这类场景中最好对游标使用 FOR UPDATE。详见 DECLARE 参考页。
一个示例:
UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;
CLOSE #
CLOSE cursor;
CLOSE关闭打开游标所基于的 portal。这样就可以在事务结束之前提前释放资源,或者释放该游标变量以便再次打开。
一个示例:
CLOSE curs1;
PL/pgSQL函数可以向调用者返回游标。这对于返回多行或多列,特别是非常大的结果集时很有用。要做到这一点,函数需要打开游标,并把游标名返回给调用者(或者直接使用调用者指定或已知的 portal 名称来打开游标)。随后调用者就可以从该游标中提取行。游标既可以由调用者关闭,也会在事务结束时自动关闭。
游标使用的 portal 名称既可以由程序员指定,也可以自动生成。要指定 portal 名称,只需在打开refcursor变量之前给它赋一个字符串值。OPEN会把该refcursor变量的字符串值用作底层 portal 的名称。不过,如果refcursor变量为 null,OPEN就会自动生成一个与任何现有 portal 都不冲突的名称,并把它赋回给refcursor变量。
在 PostgreSQL 16 之前,已绑定游标变量会被初始化为其自身名称,而不是保持为空,因此默认情况下底层 portal 名称与游标变量名相同。之所以改动,是因为该行为在不同函数里同名游标之间带来了过高的名称冲突风险。
下面的示例显示了一个调用者提供游标名字的方法:
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;
-- 需要在一个事务中使用游标。
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;
-- 需要在一个事务中使用游标。
BEGIN;
SELECT * FROM myfunc('a', 'b');
FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;
有一种FOR语句的变体,它允许通过游标返回的行进行迭代。语法是:
[ <<label>> ] FORrecordvarINbound_cursorvar[ ( [argument_name{ := | => } ]argument_value[, ...] ) ] LOOPstatementsEND LOOP [label];
该游标变量必须在声明时已经被绑定到某个查询,并且它不能已经被打开。FOR语句会自动打开游标,并且在退出循环时自动关闭游标。当且仅当游标被声明要使用参数时,才必须出现一个实际参数值表达式的列表。这些值会被替换到查询中,采用OPEN期间的方式(见Section 41.7.2.3)。
变量recordvar会被自动定义为record类型,并且只存在于循环内部(循环中该变量名任何已有定义都会被忽略)。每一个由游标返回的行都会被陆续地赋值给这个记录变量并且执行循环体。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。