与一次性执行整个查询不同,可以设置一个封装了该查询的游标,然后一次读取几行查询结果。这样做的一个原因是避免当结果包含大量行时内存溢出。(然而,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 被称为未绑定,因为它没有绑定到任何特定的查询。
SCROLL 选项不能与使用 FOR UPDATE/SHARE 的游标查询一起使用。另外,最好将 NO SCROLL 用于涉及易变函数的查询。 SCROLL 的实现假定重新读取查询的输出会得到一致的结果,而易变函数可能不会如此。
在使用游标检索行之前,必须先打开它。(这是 SQL 命令 DECLARE CURSOR 的等效操作。)PL/pgSQL 有三种形式的 OPEN 语句,其中两种使用未绑定的游标变量,第三种使用绑定的游标变量。
绑定游标变量也可以通过 FOR 语句在不显式打开游标的情况下使用,该语句在 第 41.7.4 节 中进行了描述。 FOR 循环会打开游标,并在循环完成后将其关闭。
打开游标涉及创建一个服务器内部数据结构,称为portal,它保存游标查询的执行状态。Portal 有一个名称,在 portal 存在期间必须在会话中唯一。默认情况下,PL/pgSQL 会为其创建的每个 portal 分配一个唯一名称。但是,如果将非空字符串值赋给游标变量,该字符串将用作其 portal 名称。此功能可以按 第 41.7.3.5 节 中描述的方式使用。
OPEN FOR query #OPENunbound_cursorvar[ [ NO ] SCROLL ] FORquery;
游标变量被打开并给定指定的查询以执行。游标不能已打开,并且必须声明为未绑定的游标变量(即,作为一个简单的 refcursor 变量)。查询必须是 SELECT,或任何其他返回行的内容(如 EXPLAIN)。查询的处理方式与其他 SQL 命令在 PL/pgSQL 中的处理方式相同: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 命令相同。通常,这提供了灵活性,因此查询计划可以因运行而异(参见 第 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 用于打开其查询在声明时已绑定到它的游标变量。游标不能已打开。如果游标声明为接受参数,则必须提供实际参数值表达式列表。这些值将被替换到查询中,就像在 OPEN 时一样(参见 第 41.7.2.3 节)。
绑定游标的查询计划始终被认为是可缓存的;在这种情况下没有 EXECUTE 的等价物。请注意,SCROLL 和 NO SCROLL 不能在 OPEN 中指定,因为游标的滚动行为已经确定。
可以使用位置或命名表示法传递参数值。在位置表示法中,所有参数按顺序指定。在命名表示法中,使用 := 或 => 将每个参数的名称与其参数表达式分隔开。类似于调用函数,如 第 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 的字符串名称。此名称可以传递,赋给其他 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 命令不同)。需要向后移动的 direction 值很可能失败,除非游标在声明或打开时带有 SCROLL 选项。
cursor 必须是一个 refcursor 变量的名称,该变量引用了一个已打开的游标 portal。
示例
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;
-- 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 时一样(参见 第 41.7.2.3 节)被替换到查询中。
变量 recordvar 会自动定义为 record 类型,并且只在循环内部存在(在循环内部,任何已有的变量名定义都会被忽略)。游标返回的每一行都会依次赋给这个记录变量,然后执行循环体。