ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostgreSQL 存储过程如何返回多个结果集:TaoToken 场景下的实战拆解

PostgreSQL 存储过程如何返回多个结果集:TaoToken 场景下的实战拆解 1. 为什么 PostgreSQL 存储过程返回多个结果集这么难写很多从 SQL Server 或 MySQL 转过来的开发者第一次在 PostgreSQL 里写「一个存储过程返回多张表」时都会卡住。原因很直接PostgreSQL 的函数FUNCTION在FROM子句里被调用时天生只能返回一张表结构而存储过程PROCEDUREPG 11 之后才有虽然能通过INOUT参数带回多个refcursor但调用方式和事务边界又和函数不一样。于是「PostgreSQL 存储过程如何返回多个结果集」这个问题在真实项目里往往变成三种写法之间的取舍REFCURSOR数组、RETURN NEXT逐行累积、SETOF复合类型。我先把场景说清楚。假设你在做一个订单后台前端一次请求要拿到「用户列表 该用户的订单列表 订单汇总统计」三块数据。如果拆成三次数据库往返网络开销和事务一致性都难受如果塞进一个 JSON 字段返回前端又得二次解析SQL 层没法直接过滤。这时候多结果集就是刚需。而当你把这类数据库能力接到 AI 编码助手或 Agent 工作流里时问题会再叠一层模型需要稳定的调用通道、统一的鉴权、可复现的请求结构。TaoToken 在这里扮演的角色就是统一 Key 和 API 通道——你用同一个 Key 就能访问不同模型把「生成 SQL」「解释执行计划」「排查报错」这些动作串起来而数据库本身的多结果集逻辑仍然跑在你的 PostgreSQL 里。两者是配合关系不是替代关系。这篇会给你三条可复制的实现路径每条都配建表 SQL、函数/过程定义、调用示例和逐条验证动作。你可以直接拿去改表名用。2. TaoToken 前置准备统一 Key 与 API 通道怎么配在动手写 SQL 之前先把调用侧准备好。因为后面验证多结果集时我会用 AI 助手帮你读报错、补 SQL所以需要一个稳定的模型通道。TaoToken 的定位是统一 Key/API 通道官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 Base URL。你需要拿到的三件套是Base URL、API Key、Model ID。这三者在任何 OpenAI 兼容客户端里都是必填项缺一个就会报鉴权或模型不存在的错。拿 Key 的路径是进控制台在 API Keys 页面创建然后复制出来。控制台地址带归因参数https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你用的是 Claude Code 这类编码工具接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有 Base URL 和 Key 的填法。想先验证模型通不通可以直接用模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期跑编码或 Agent 任务的话Coding Plan 更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这里要强调一点TaoToken 是模型调用通道不是数据库代理。你的 PostgreSQL 连接串、psql 客户端、JDBC/psycopg 驱动都还是直连你自己的库。多结果集的返回逻辑完全在数据库端完成TaoToken 只负责在你需要 AI 辅助生成或排障时提供模型能力。把这两层分清楚后面配置就不会混。配置时最容易踩的坑是把 Base URL 写成带/v1或不带/v1混用。建议统一用https://taotoken.net/api作为根具体路径由客户端拼接。Key 放在环境变量里别硬编码进 SQL 文件或提交到仓库。3. 可复制配置三种多结果集写法的完整 SQL这一节是核心给你三种写法从最推荐的REFCURSOR到RETURN NEXT再到SETOF。先建两张测试表后面所有例子都基于它们。-- 建表用户表 CREATE TABLE users ( id serial PRIMARY KEY, name varchar(50) NOT NULL, email varchar(100) ); -- 建表订单表 CREATE TABLE orders ( id serial PRIMARY KEY, user_id int REFERENCES users(id), total_amount numeric(10,2) NOT NULL, created_at timestamptz DEFAULT now() ); -- 造点数据 INSERT INTO users (name, email) VALUES (张三, zhangsanexample.com), (李四, lisiexample.com); INSERT INTO orders (user_id, total_amount) VALUES (1, 120.50), (1, 80.00), (2, 300.00);3.1 REFCURSOR 数组写法推荐这是最贴近「多结果集」语义的写法。函数返回一个refcursor数组每个游标对应一张结果集。调用方拿到游标名后逐个FETCH。CREATE OR REPLACE FUNCTION get_users_and_orders() RETURNS SETOF refcursor AS $$ DECLARE users_cursor refcursor; orders_cursor refcursor; BEGIN OPEN users_cursor FOR SELECT id, name, email FROM users ORDER BY id; OPEN orders_cursor FOR SELECT id, user_id, total_amount, created_at FROM orders ORDER BY id; RETURN NEXT users_cursor; RETURN NEXT orders_cursor; END; $$ LANGUAGE plpgsql;注意这里用的是RETURNS SETOF refcursor而不是RETURNS TABLE。RETURN NEXT每次吐出一个游标调用方按顺序接收。调用必须在同一个事务里否则游标会在事务结束时关闭。BEGIN; SELECT * FROM get_users_and_orders(); FETCH ALL IN unnamed portal 1; FETCH ALL IN unnamed portal 2; COMMIT;游标名是 PostgreSQL 自动生成的实际项目里建议显式命名方便FETCH时引用。3.2 RETURN NEXT 累积行写法如果你不想用游标而是想把多张表的数据「拼」成一个统一结构返回可以用RETURN NEXT逐行累积。适合结果集结构相似、需要合并展示的场景。CREATE OR REPLACE FUNCTION get_combined_report() RETURNS TABLE (source text, ref_id int, detail text, amount numeric) AS $$ BEGIN RETURN QUERY SELECT user::text, id, name || / || coalesce(email,), NULL::numeric FROM users; RETURN QUERY SELECT order::text, id, user_id || user_id, total_amount FROM orders; END; $$ LANGUAGE plpgsql;调用就是普通SELECTSELECT * FROM get_combined_report();这种写法返回的是单结果集但内容来自多张表。如果你的「多结果集」需求本质是「多来源合并」这条最省事。3.3 SETOF 复合类型写法当你想返回结构化的多组数据且每组有固定字段时可以定义复合类型用SETOF返回。CREATE TYPE user_order_summary AS ( user_name varchar, order_count int, total_amount numeric ); CREATE OR REPLACE FUNCTION get_user_summary() RETURNS SETOF user_order_summary AS $$ BEGIN RETURN QUERY SELECT u.name, count(o.id)::int, coalesce(sum(o.total_amount), 0) FROM users u LEFT JOIN orders o ON o.user_id u.id GROUP BY u.name ORDER BY u.name; END; $$ LANGUAGE plpgsql;调用SELECT * FROM get_user_summary();三种写法对照如下写法返回类型是否真多结果集事务要求适用场景REFCURSOR 数组SETOF refcursor是必须同事务多张独立表RETURN NEXT 累积TABLE(...)否合并无多来源合并SETOF 复合类型SETOF type否单集无结构化聚合选型建议真要「多结果集」用 3.1只是多来源合并用 3.2结构化聚合用 3.3。4. 验证请求与成功结果逐条 FETCH 确认写完 SQL 不算完得验证每个结果集真的能取到数据。下面是一套完整的验证动作。先确认函数存在SELECT proname, prorettype::regtype FROM pg_proc WHERE proname get_users_and_orders;预期看到prorettype是refcursor的集合类型。然后开事务调用BEGIN; SELECT * FROM get_users_and_orders();你会看到两行每行一个游标名类似unnamed portal 1和unnamed portal 2。接着逐个 FETCHFETCH ALL IN unnamed portal 1;预期返回 users 表的两行张三、李四。再取第二个FETCH ALL IN unnamed portal 2;预期返回 orders 表的三行。最后别忘了COMMIT;如果你在 psql 里操作FETCH之后游标就消耗完了想再取需要重新开事务调用。这是游标的正常行为不是 bug。对于 3.2 和 3.3 的写法验证更简单直接SELECT * FROM 函数名()看返回行数和字段即可。3.2 预期返回 5 行2 用户 3 订单3.3 预期返回 2 行两个用户的汇总。验证时建议把结果集行数和预期值写进测试脚本比如用pgTAP或简单的SELECT count(*)断言。这样改表结构时能第一时间发现回归。5. 本篇常见错排查401、local proxy failed、reading choices 等真实报错这一节对照真实会遇到的报错逐个拆。报错一cursor unnamed portal 1 does not exist原因通常是FETCH和函数调用不在同一个事务里或者事务已经COMMIT了。游标生命周期绑定事务跨事务就失效。解决把SELECT和所有FETCH包在同一个BEGIN ... COMMIT里。报错二function get_users_and_orders() does not exist检查 schema 是否在search_path里。如果你建在public但当前search_path不含它就会找不到。用SELECT * FROM public.get_users_and_orders();显式指定。报错三调用 AI 通道时401 Unauthorized这是 Key 的问题。检查三件套是否齐全Base URL 是否为https://taotoken.net/apiKey 是否从控制台正确复制注意别带空格Model ID 是否拼写正确。401 基本都是 Key 缺失或错误跟数据库无关。报错四local proxy failed这个报错通常出现在客户端配置了本地代理但代理没起来或者 Base URL 指向了本地地址。检查你的客户端配置里 Base URL 是否误填成localhost或某个本地端口。正确值应该是https://taotoken.net/api。同时确认没有多余的代理环境变量干扰。报错五reading choices相关解析错误这类报错一般是响应体不是预期的 JSON 结构常见于 Base URL 路径拼错比如多拼了/v1/v1或请求发到了非 API 端点。核对 Base URL 和客户端拼接规则确保最终请求路径正确。报错六OAuth相关鉴权失败如果你用的是 Claude Code 类工具鉴权走的是 Key 而非 OAuth 流程。出现 OAuth 报错说明客户端配置模式选错了应该切到 API Key 模式填入 Base URL、Key、Model ID 三件套。接入文档里有具体填法。报错七RETURN NEXT后结果为空检查RETURN QUERY里的SELECT是否真的返回了行。常见原因是WHERE条件过滤掉了全部数据或者JOIN类型写成了INNER JOIN导致左表无匹配时整行消失。用LEFT JOIN加coalesce兜底。排障时如果拿不准 SQL 哪里错可以把报错贴到模型对话里让 AI 帮你分析https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它能结合 PostgreSQL 语法给出修改建议但最终执行还是在你自己的库里。6. 把多结果集接进你的工作流CTA 与后续动作多结果集写完之后下一步通常是把它接进应用层。Java 用 JDBC 的CallableStatement配合getMoreResults()遍历Python 用 psycopg 的server-side cursor或直接执行FETCHNode 用pg的query逐条取。核心都是「先调用函数拿游标名再逐个 FETCH」。如果你在写这些调用代码时需要 AI 辅助生成模板或者想让 Agent 自动帮你补测试用例建议先把 Key 配好。API Keys 页面在这里https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各语言客户端的 Base URL 填法。长期跑编码任务的话Coding Plan 比按次调用更稳https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它适合那种「每天都要生成 SQL、写迁移脚本、排查慢查询」的节奏。最后给一个实用技巧把三种多结果集写法做成项目里的模板函数命名规范统一比如fn_xxx_multi。这样团队里谁要加新的多结果集直接复制改表名不用重新踩游标和事务的坑。验证脚本也一起进仓库CI 里跑一遍count(*)断言改表结构时能立刻发现哪个结果集空了。这比事后排查便宜得多。
返回列表