PostgreSQL 16 的 CREATE PROCEDURE 不支持返回结果集,因其设计语义仅为执行动作而非提供数据;必须改用 CREATE FUNCTION(SETOF/TABLE)或 REFCURSOR 配合显式事务。

PostgreSQL 16 的 CREATE PROCEDURE 本身不能返回结果集 —— 它压根不支持 RETURN、OUT refcursor、SETOF 或任何数据返回机制。 想从过程里“拿数据出来”,必须绕道函数(CREATE FUNCTION)或用 REFCURSOR 配合显式事务控制。
为什么 CALL 过程无法像 SELECT 函数那样取数据
过程的设计语义就是“执行动作”,不是“提供数据”。即使你写一个空过程:
CREATE OR REPLACE PROCEDURE do_nothing() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;
执行 CALL do_nothing() 后,客户端收不到任何行。它没有返回类型、不参与 FROM 子句、不能被 SELECT * 调用。这是硬性限制,不是配置或版本问题。
常见错误现象:
-
ERROR: procedure xxx() does not exist—— 即使同名函数存在,CALL也不会自动 fallback 到函数 -
ERROR: RETURN QUERY is not allowed in procedures—— 过程体内禁止使用RETURN QUERY -
ERROR: query has no destination for result data—— 在过程里写裸SELECT会报这个错(除非加PERFORM或丢弃结果)
替代方案:用 FUNCTION + SETOF 或 TABLE 返回结果集
绝大多数需要“返回结果集”的场景,应直接改用 CREATE FUNCTION。PostgreSQL 16 完全支持以下方式:
-
RETURNS SETOF existing_table:复用表结构,字段/类型/NOT NULL 全继承,最省心 -
RETURNS TABLE(col1 int, col2 text):显式定义列,调用时无需AS别名,适合无对应物理表的聚合结果 -
RETURNS SETOF my_custom_type:配合CREATE TYPE定义命名行类型,类型安全且可复用
示例(SQL 函数,简洁):
CREATE OR REPLACE FUNCTION get_active_orders() RETURNS TABLE(order_id int, total numeric, created_at timestamptz) AS $$ SELECT id, total, created_at FROM orders WHERE status = 'active' AND created_at > now() - INTERVAL '7 days'; $$ LANGUAGE sql;
调用:SELECT * FROM get_active_orders();
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
注意:别写 RETURNS SETOF TABLE(...) —— 语法错误,SETOF 后不能跟 TABLE 关键字。
如果非要用 PROCEDURE(比如含复杂事务逻辑),只能靠 REFCURSOR
过程可以输出 OUT refcursor,但代价高:调用方必须手动管理事务、打开游标、再 FETCH。步骤缺一不可:
- 必须先
BEGIN显式开启事务(CALL不自动开事务) - 过程内用
OPEN curs FOR SELECT ...,curs必须是OUT refcursor参数 -
CALL执行后,立刻在同一个事务中FETCH ALL IN "curs"(名字大小写敏感) - 事务结束(
COMMIT或ROLLBACK)后,游标失效
示例:
CREATE OR REPLACE PROCEDURE fetch_recent_users(OUT users_curs refcursor)
AS $$
BEGIN
OPEN users_curs FOR
SELECT id, email FROM users WHERE last_login > now() - INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql;调用链:
BEGIN;
CALL fetch_recent_users('my_cursor');
FETCH ALL IN "my_cursor";
COMMIT;漏掉 BEGIN 就会报 cursor "my_cursor" does not exist;名字写成 my_cursor(没引号)或 MY_CURSOR(大小写错)也会失败。
真正容易被忽略的是:过程和函数的定位差异不是“语法糖”,而是设计契约。当你在写逻辑前就决定要用 PROCEDURE,就得接受它不返回数据的事实 —— 后续所有 workaround(临时表、refcursor、应用层缓存)都是为这个前提服务的,而不是去“突破”它。

















