ARTICLE DETAIL

资讯详情

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

Oracle存储过程中显示游标(cursor)的使用:从声明到循环取数的完整实践

Oracle存储过程中显示游标(cursor)的使用:从声明到循环取数的完整实践 1. 为什么你的存储过程一取多行数据就报错显式游标到底解决什么问题刚接触 PL/SQL 的人常遇到一个场景写了个存储过程想从t1里把符合条件的记录一条条拿出来处理结果要么只处理了第一行要么直接抛ORA-01422: exact fetch returns more than requested number of rows。原因很简单——SELECT ... INTO这种写法只接受单行结果一旦查询返回多行PL/SQL 就不知道该怎么办了。这时候就需要显式游标explicit cursor。你可以把它理解成一个指向结果集的“指针”查询语句执行后Oracle 在内存里维护一块私有工作区游标就是这块区域的句柄。你通过OPEN打开它、FETCH一行行取、CLOSE释放它。相比隐式游标每次 DML 或单行 SELECT 自动创建显式游标让你能精确控制多行结果集的遍历节奏。它适合谁适合需要在存储过程里做批量数据处理的人比如给一批订单逐条算折扣、把临时表数据清洗后写入正式表、按部门循环发通知。这些场景的共同点是——结果集行数不确定且每行都要执行一段逻辑。我试过在数据迁移脚本里用显式游标逐行校验配合%ROWCOUNT和%FOUND做进度控制比一次性INSERT ... SELECT更容易定位脏数据。下面从声明到循环取数把完整流程拆开讲代码都能直接复制到你的环境里跑。2. TaoToken 前置准备把模型对话和 API Key 配好再动手写游标写游标本身不需要联网但调试过程中如果想让 AI 帮你解释报错、生成测试数据、或者把一段游标逻辑改写成FOR循环版本有个顺手的模型入口会省很多时间。我平时用 TaoToken 做这类辅助它的模型对话入口可以直接贴 PL/SQL 代码问问题接入文档里也有标准的 Base URL 和 Key 配置方式。先把三件套准备好后面调试游标时随时能调用配置项值Base URLhttps://taotoken.net/apiAPI Key在控制台创建形如sk-...Model ID按你订阅的模型填写如claude-sonnet-4-5等获取 Key 的路径打开 TaoToken 控制台 → API Keys → 新建。如果你更习惯在编辑器里直接对话可以看 模型对话 页面长期做编码和 Agent 任务的Coding Plan 更划算。接入细节都在 接入文档 里。注意TaoToken 只是模型调用入口不替代你的 Oracle 客户端SQL*Plus、SQL Developer、DBeaver 等。游标的编译和执行还是在数据库侧完成。如果你用的是 Claude Code 这类命令行工具配置通常写在settings.json里把 Base URL 和 Key 填进去即可Cline 的 MCP 配置则在cline_mcp_settings.json中声明服务地址。无论哪种核心都是Base URL Key Model ID三件套对齐缺一个就会在请求时报 401 或 model not found。3. 可复制配置显式游标的声明、OPEN/FETCH/CLOSE 与 FOR 循环两种写法先建一张测试表后面所有例子都基于它CREATE TABLE t1 ( id NUMBER, sname VARCHAR2(50), dept VARCHAR2(30) ); INSERT INTO t1 VALUES (1, 张三, 研发); INSERT INTO t1 VALUES (2, 李四, 销售); INSERT INTO t1 VALUES (3, 王五, 研发); COMMIT;3.1 完整四步写法声明 → OPEN → FETCH → CLOSE这是最“原始”也最能看清游标生命周期的写法。适合你需要在循环中间做复杂判断、或者手动控制关闭时机的场景。CREATE OR REPLACE PROCEDURE proc_cursor_manual IS CURSOR cur IS SELECT id, sname, dept FROM t1 WHERE dept 研发; v_id t1.id%TYPE; v_name t1.sname%TYPE; v_dept t1.dept%TYPE; BEGIN OPEN cur; LOOP FETCH cur INTO v_id, v_name, v_dept; EXIT WHEN cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(行号 || cur%ROWCOUNT || , 编号 || v_id || , 姓名 || v_name || , 部门 || v_dept); END LOOP; CLOSE cur; END; /几个关键点%NOTFOUND在 FETCH 之后判断顺序不能反%ROWCOUNT统计的是已成功取出的行数CLOSE必须执行否则游标一直占着内存同一会话里重复 OPEN 会报ORA-06511: PL/SQL: cursor already open。3.2 FOR 循环写法自动 OPEN/FETCH/CLOSE如果你不需要手动干预游标状态FOR ... IN ... LOOP是最省事的。Oracle 会自动完成打开、逐行取、循环结束关闭代码量少一半。CREATE OR REPLACE PROCEDURE proc_cursor_for IS CURSOR cur IS SELECT id, sname, dept FROM t1; BEGIN FOR rec IN cur LOOP DBMS_OUTPUT.PUT_LINE(行号 || cur%ROWCOUNT || , 编号 || rec.id || , 姓名 || rec.sname || , 部门 || rec.dept); END LOOP; END; /注意rec是记录变量字段直接用rec.id访问不需要提前声明。这里有个坑FOR 循环里不能再写 OPEN、FETCH、CLOSE否则编译能过但运行时报错因为 Oracle 已经隐式管理了这些操作。3.3 带参数的游标实际业务里查询条件往往是变量游标支持传参CREATE OR REPLACE PROCEDURE proc_cursor_param(p_dept IN VARCHAR2) IS CURSOR cur(p VARCHAR2) IS SELECT id, sname FROM t1 WHERE dept p; BEGIN FOR rec IN cur(p_dept) LOOP DBMS_OUTPUT.PUT_LINE(编号 || rec.id || , 姓名 || rec.sname); END LOOP; END; /参数只在 OPEN或 FOR 循环首次进入时绑定循环过程中改参数值不会影响已打开的结果集。3.4 用 JSON 片段记录你的连接配置如果你在脚本或工具里管理数据库连接和模型调用配置可以用一段 JSON 把两边都记下来避免每次翻文档{ oracle: { host: 127.0.0.1, port: 1521, service: ORCLPDB1, user: scott, role: normal }, taotoken: { base_url: https://taotoken.net/api, api_key: sk-你的Key, model_id: claude-sonnet-4-5 } }提示Oracle 连接串格式为user/passwordhost:port/service在 SQL*Plus 里用conn scott/tiger127.0.0.1:1521/ORCLPDB1即可登录。4. 验证请求与成功结果编译、执行、看输出写完存储过程先编译再执行。在 SQL*Plus 或 SQL Developer 里依次跑-- 编译 ALTER PROCEDURE proc_cursor_manual COMPILE; -- 查看编译错误如果有 SHOW ERRORS PROCEDURE proc_cursor_manual; -- 打开输出 SET SERVEROUTPUT ON SIZE UNLIMITED; -- 执行 EXEC proc_cursor_manual;成功时你会看到类似输出行号1, 编号1, 姓名张三, 部门研发 行号2, 编号3, 姓名王五, 部门研发proc_cursor_for执行后应输出全部三行。proc_cursor_param(销售)则只输出李四那一行。如果你想验证游标是否真的逐行处理可以在循环里加一个计数器变量循环结束后打印总数和SELECT COUNT(*)的结果对比。两者一致说明 FETCH 没有漏行也没有重复。对于%ROWCOUNT有个细节值得注意在 FOR 循环中它同样可用但统计的是当前已取出的行数循环结束后等于总行数。如果你在循环体内提前EXIT%ROWCOUNT就停在退出时的值。5. 本篇常见错排查401、ORA-01001、ORA-06511 逐个对照报错一ORA-01001: invalid cursor原因通常是没 OPEN 就 FETCH或者 CLOSE 之后又 FETCH。检查你的代码顺序OPEN → LOOP → FETCH → EXIT WHEN %NOTFOUND → 处理 → END LOOP → CLOSE。少任何一步都可能触发。报错二ORA-06511: PL/SQL: cursor already open同一个游标被 OPEN 了两次还没 CLOSE。常见于异常处理里忘了关闭或者循环中重复 OPEN。解决办法在EXCEPTION块里补IF cur%ISOPEN THEN CLOSE cur; END IF;。报错三ORA-01422: exact fetch returns more than requested number of rows这不是游标本身的错而是你用了SELECT ... INTO却返回多行。改成显式游标 循环即可。报错四401 Unauthorized模型调用侧如果你在调试时用 TaoToken 的 API 辅助分析报错遇到 401 说明 Key 无效或没带上。检查请求头里Authorization: Bearer sk-...是否完整Base URL 是否为https://taotoken.net/api。Key 过期就去 API Keys 页面重新生成。报错五local proxy failed / reading choices这类错误一般出现在客户端配置了本地代理但代理没启动或者返回体解析失败。先确认网络能直连taotoken.net再检查 Model ID 是否拼写正确。如果返回体里没有choices字段多半是模型名写错了。报错六OAuth 相关错误部分命令行工具用 OAuth 方式登录如果 token 过期会提示重新授权。按工具提示走一遍授权流程即可和游标逻辑无关。报错七DBMS_OUTPUT 没输出不是游标的问题是SERVEROUTPUT没打开。执行SET SERVEROUTPUT ON再跑一次。6. 继续深入从显式游标到 REF CURSOR 与批量处理掌握基础显式游标后你可能会遇到两个进阶需求一是查询语句在运行时才能确定动态 SQL二是需要把结果集返回给调用方。前者用EXECUTE IMMEDIATE配合游标变量后者用REF CURSOR。CREATE OR REPLACE PROCEDURE proc_ref_cursor(p_dept IN VARCHAR2, p_out OUT SYS_REFCURSOR) IS BEGIN OPEN p_out FOR SELECT id, sname FROM t1 WHERE dept p_dept; END; /调用方拿到SYS_REFCURSOR后自行 FETCH适合存储过程之间传递结果集。另一个实用技巧是BULK COLLECT一次性把结果集批量取到集合里减少上下文切换DECLARE TYPE t_rec IS RECORD (id t1.id%TYPE, sname t1.sname%TYPE); TYPE t_tab IS TABLE OF t_rec; v_tab t_tab; BEGIN SELECT id, sname BULK COLLECT INTO v_tab FROM t1; FOR i IN 1 .. v_tab.COUNT LOOP DBMS_OUTPUT.PUT_LINE(v_tab(i).id || - || v_tab(i).sname); END LOOP; END; /数据量大时BULK COLLECT配合LIMIT分批次取比逐行 FETCH 快很多。但如果你需要在每行之间做复杂业务判断显式游标的逐行控制反而更清晰。最后提醒一句游标用完一定要关。我见过生产环境因为漏写 CLOSE 导致OPEN_CURSORS耗尽整个会话卡死。养成习惯——手动写法必配 CLOSEFOR 循环写法别画蛇添足加 OPEN。
返回列表