可以设置一个封装查询的游标,
然后每次读取少量查询结果行,而不是一次执行整个查询。
这样做的一个原因是,当结果包含大量行时避免内存耗尽。
(不过,PL/pgSQL 用户通常不需要为此担心,
因为 FOR 循环内部会自动使用游标来避免内存问题。)
一种更有意思的用法是返回函数所创建游标的引用,
让调用者去读取其中的行。这为从函数返回大行集合
提供了一种高效的方式。
在 PL/pgSQL 中对游标的所有访问
都通过游标变量进行,游标变量始终具有特殊数据类型
refcursor。创建游标变量的一种方式
就是把它声明为 refcursor 类型的变量。
另一种方式是使用游标声明语法,其一般形式是:
name[ [ NO ] SCROLL ] CURSOR [ (arguments) ] FORquery;
(为了与 Oracle 兼容,
FOR 可以替换为 IS。)
如果指定了 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 选项。此外,
对于涉及易变(volatile)函数的查询,最好使用
NO SCROLL。SCROLL
的实现假定重新读取查询的输出会给出一致的结果,
而易变函数可能做不到这一点。
在游标可以用来检索行之前,它必须被
打开。(这相当于 SQL 命令
DECLARE
CURSOR。)
PL/pgSQL 有三种形式的
OPEN 语句,其中两种使用未绑定游标变量,
第三种使用绑定游标变量。
绑定游标变量也可以不显式打开游标就使用,
具体做法是使用 Section 41.7.4
中描述的 FOR 语句。
FOR 循环会打开游标,并在循环结束时再次关闭它。
打开游标涉及创建一个称为门户(portal) 的服务器内部数据结构,它保存游标查询的执行状态。 门户有一个名字,该名字在门户存在的整个期间内必须在会话内唯一。 默认情况下,PL/pgSQL 会为它创建的每个门户赋予一个唯一的名字。 不过,如果你给游标变量赋了一个非空的字符串值, 该字符串将被用作它的门户名。此特性可以按照 Section 41.7.3.5 中描述的方式使用。
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;
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 等价的机制。
注意 SCROLL 和 NO SCROLL
不能在 OPEN 中指定,
因为游标的滚动行为已经被确定了。
参数值既可以使用位置记法传递,
也可以使用命名记法传递。
在位置记法中,所有参数按顺序指定。在命名记法中,
每个参数的名字使用 := 或
=> 与参数表达式分隔。
与调用函数类似(见 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 值只是包含该游标活动查询的
门户的字符串名字。这个名字可以被传递、赋给其他
refcursor 变量等等,而不会扰动该门户。)
所有门户都会在事务结束时被隐式关闭。因此,
refcursor 值只能用来引用一个打开的游标
直到事务结束。
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 命令不同,
后者只允许整数常量)。
需要向后移动的 direction 值
很可能会失败,除非游标是用 SCROLL 选项
声明或打开的。
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 [direction{ FROM | IN } ]cursor;
MOVE 在不检索任何数据的情况下重新定位游标。
MOVE 的工作方式与 FETCH
命令类似,区别是它只重新定位游标,而不返回移动到的那一行。
direction 子句可以是 SQL
FETCH 命令允许的任何变体,
包括那些一次能抓取多行的;游标会被定位到最后那样的一行。
(不过,direction 子句只是
一个不带关键字的 count 表达式的情况
在 PL/pgSQL 中已被弃用。
这种语法与完全省略 direction 子句的
情况存在歧义,因此在 count
不是常量时可能会失败。)
与 SELECT INTO 一样,
可以检查特殊变量 FOUND 来确定是否有要移动到的行。
如果没有这样的行,游标将被定位在最后一行之后或第一行之前,
取决于移动的方向。
示例:
MOVE curs1; MOVE LAST FROM curs3; MOVE RELATIVE -2 FROM curs4; MOVE FORWARD 2 FROM curs4;
UPDATEtableSET ... WHERE CURRENT OFcursor; DELETE FROMtableWHERE CURRENT OFcursor;
当游标定位在某个表行上时,可以使用该游标来标识这一行,
从而更新或删除它。对游标的查询有一些限制
(特别是不能有分组),而且最好在游标中使用
FOR UPDATE。更多信息见
DECLARE
参考页。
示例:
UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;
CLOSE cursor;
CLOSE 关闭一个打开的游标底层的门户。
这可以用来在事务结束之前释放资源,
或者释放游标变量以便再次打开。
示例:
CLOSE curs1;
PL/pgSQL 函数可以把游标返回给调用者。 这对于返回多行或多列非常有用,尤其是在结果集非常大的时候。 为此,函数打开游标并把游标名返回给调用者 (或者直接使用调用者指定或以其他方式已知的门户名打开游标)。 然后调用者可以从游标中抓取行。游标可以由调用者关闭, 也可以在事务关闭时被自动关闭。
用于游标的门户名可以由程序员指定,也可以自动生成。
要指定门户名,只需在打开之前给 refcursor
变量赋一个字符串即可。refcursor 变量的字符串值
将被 OPEN 用作底层门户的名字。
不过,如果 refcursor 变量的值是空的
(默认情况下就是如此),那么
OPEN 会自动生成一个不与任何现有门户冲突的名字,
并把它赋给该 refcursor 变量。
在 PostgreSQL 16 之前, 绑定游标变量会被初始化为包含它们自己的名字, 而不是保留为空,因此默认情况下底层门户名会与游标变量的名字相同。 这一行为被改变了,因为它在具有相似名字的游标之间 带来了过多的冲突风险。
下面的示例展示了调用者提供游标名的一种方式:
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>> ] FORrecordvarINbound_cursorvar[ ( [argument_name{ := | => } ]argument_value[, ...] ) ] LOOPstatementsEND LOOP [label];
游标变量必须在声明时已绑定到某个查询,而且它
不能已经处于打开状态。
FOR 语句会自动打开游标,并在循环退出时
再次关闭它。当且仅当游标被声明为带参数时,
才必须给出实际参数值表达式的列表。
这些值将被替换进查询中,方式与 OPEN 期间完全一样
(见 Section 41.7.2.3)。
变量 recordvar 被自动定义为
record 类型,并且只存在于循环内部
(该变量名的任何现有定义在循环内都会被忽略)。
游标返回的每一行都会被依次赋给这个记录变量,
循环体对每一行执行一次。