拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Oracle 游标的使用:从显式游标到游标变量,一份可复用的 PL/SQL 配置骨架

Oracle 游标的使用:从显式游标到游标变量,一份可复用的 PL/SQL 配置骨架 1. 为什么你的 PL/SQL 里总在重复写游标日常做 Oracle 存储过程或批处理绕不开一个动作把一张表里符合条件的行捞出来逐行加工。很多人第一次写的时候习惯用SELECT ... INTO加循环结果一遇到多行就报TOO_MANY_ROWS或者干脆漏掉最后一行。游标就是为这个场景准备的它相当于给结果集装了一个可移动的指针你可以一行一行地取取完自动停。这篇聚焦 Oracle PL/SQL 游标的核心用法覆盖显式游标、隐式游标、参数化游标和 REF CURSOR 游标变量。面向的是每天写存储过程、做数据批处理的开发同学。我会给出可以直接复制的声明与循环骨架、异常处理模板以及在 SQL*Plus 里执行验证的具体步骤和预期输出。你照着敲一遍基本就能把游标这套东西落到自己的脚本里。先明确一个检索词Oracle 游标Cursor是 PL/SQL 中处理多行查询结果集的机制分为显式游标和隐式游标两大类。显式游标由你手动声明、打开、提取、关闭隐式游标由 Oracle 在每条 DML 或单行 SELECT 时自动维护通过SQL%属性访问。适合谁适合已经会写基础 PL/SQL、但游标属性老是记混、循环边界总写错的人。2. 前置准备环境与 TaoToken 接入在开始写游标之前先把执行环境理顺。你需要一个能跑 PL/SQL 的 Oracle 实例SQLPlus 或 SQL Developer 都行。我下面用 SQLPlus 演示因为它输出干净适合验证。如果你本地没有现成库或者想让 AI 帮你生成/审查游标代码可以用 TaoToken 的模型对话能力来辅助。接入方式很简单先拿一个 API Key打开 https://taotoken.net/api-keys 登录后创建一个 Key复制保存。模型对话入口在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 把 Key 填进去就能对话。如果你要长期做编码和 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 里面有完整的参数说明。注意TaoToken 是模型调用与编码辅助平台不是数据库客户端也不替代你的 Oracle 实例。游标代码最终还是在你的库里执行。环境侧确认两件事一是SET SERVEROUTPUT ON打开否则DBMS_OUTPUT.PUT_LINE什么都不显示二是你有一张可操作的测试表。下面我用经典的EMP表结构字段包括EMPNO、ENAME、JOB、SAL、DEPTNO、HIREDATE、COMM。如果你的库没有先建一张CREATE TABLE emp ( empno NUMBER(4) PRIMARY KEY, ename VARCHAR2(20), job VARCHAR2(20), sal NUMBER(7,2), deptno NUMBER(2), hiredate DATE, comm NUMBER(7,2) ); INSERT INTO emp VALUES (7369,SMITH,CLERK,800,20,TO_DATE(1980-12-17,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7499,ALLEN,SALESMAN,1600,30,TO_DATE(1981-02-20,YYYY-MM-DD),300); INSERT INTO emp VALUES (7566,JONES,MANAGER,2975,20,TO_DATE(1981-04-02,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7698,BLAKE,MANAGER,2850,30,TO_DATE(1981-05-01,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7782,CLARK,MANAGER,2450,10,TO_DATE(1981-06-09,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7788,SCOTT,ANALYST,3000,20,TO_DATE(1987-04-19,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7839,KING,PRESIDENT,5000,10,TO_DATE(1981-11-17,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7844,TURNER,SALESMAN,1500,30,TO_DATE(1981-09-08,YYYY-MM-DD),0); INSERT INTO emp VALUES (7876,ADAMS,CLERK,1100,20,TO_DATE(1987-05-23,YYYY-MM-DD),NULL); INSERT INTO emp VALUES (7900,JAMES,CLERK,950,30,TO_DATE(1981-12-03,YYYY-MM-DD),NULL); COMMIT;3. 可复制配置四类游标骨架3.1 显式游标 FETCH 循环最基础显式游标四步走声明、打开、提取、关闭。这是理解所有游标变体的地基。SET SERVEROUTPUT ON DECLARE CURSOR c_job IS SELECT empno, ename, job, sal FROM emp WHERE job MANAGER; v_row c_job%ROWTYPE; BEGIN OPEN c_job; LOOP FETCH c_job INTO v_row; EXIT WHEN c_job%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_row.empno || - || v_row.ename || - || v_row.job || - || v_row.sal); END LOOP; CLOSE c_job; END; /关键点%ROWTYPE让变量自动匹配游标列结构不用手写每个字段类型。EXIT WHEN c_job%NOTFOUND必须放在FETCH之后、处理逻辑之前否则会多处理一行空数据。3.2 FOR 循环游标推荐日常用FOR 循环把打开、提取、关闭全包了代码短还不容易忘关游标。DECLARE CURSOR c_job IS SELECT empno, ename, job, sal FROM emp WHERE job MANAGER; BEGIN FOR r IN c_job LOOP DBMS_OUTPUT.PUT_LINE(r.empno || - || r.ename || - || r.job || - || r.sal); END LOOP; END; /r是隐式声明的记录变量作用域只在循环内。你不需要OPEN、FETCH、CLOSEOracle 自动处理。实测下来日常批处理优先用这种写法。3.3 参数化游标按条件复用把过滤条件做成参数一个游标声明可以服务多次调用。DECLARE CURSOR c_dept(p_deptno NUMBER) IS SELECT empno, ename, sal FROM emp WHERE deptno p_deptno; BEGIN FOR r IN c_dept(20) LOOP DBMS_OUTPUT.PUT_LINE(员工号 || r.empno || 员工名 || r.ename || 工资 || r.sal); END LOOP; END; /参数语法是cursor_name(param_name [IN] data_type [{:|DEFAULT} value])。注意参数只写类型不写长度比如p_deptno NUMBER不能写NUMBER(2)。3.4 REF CURSOR 游标变量跨程序传递REF CURSOR 是游标变量可以在存储过程之间传递结果集适合做通用查询接口。DECLARE TYPE t_emp_cursor IS REF CURSOR; v_cur t_emp_cursor; v_empno emp.empno%TYPE; v_ename emp.ename%TYPE; BEGIN OPEN v_cur FOR SELECT empno, ename FROM emp WHERE deptno 30; LOOP FETCH v_cur INTO v_empno, v_ename; EXIT WHEN v_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_empno || - || v_ename); END LOOP; CLOSE v_cur; END; /REF CURSOR 的OPEN ... FOR后面可以跟动态 SQL 字符串这是它比静态游标灵活的地方。但灵活也意味着编译期检查少字段对不上要到运行才报错。3.5 隐式游标属性速查每条 DML 和单行 SELECT 都会产生隐式游标用SQL%访问属性含义典型用途SQL%FOUND是否有行受影响判断 UPDATE 是否命中SQL%NOTFOUND是否无行受影响判断查询是否为空SQL%ROWCOUNT受影响行数统计批量操作结果SQL%ISOPEN游标是否打开隐式游标总是 FALSEBEGIN UPDATE emp SET sal sal * 1.1 WHERE deptno 20; IF SQL%FOUND THEN DBMS_OUTPUT.PUT_LINE(更新了 || SQL%ROWCOUNT || 行); ELSE DBMS_OUTPUT.PUT_LINE(没有匹配的行); END IF; END; /注意隐式游标的SQL%ROWCOUNT在SELECT INTO后是 1 或 0在UPDATE/DELETE后是实际影响行数。别把它和显式游标的%ROWCOUNT混用。4. 验证请求与成功结果把上面代码在 SQL*Plus 里跑一遍确认输出符合预期。第一步打开输出SET SERVEROUTPUT ON SIZE UNLIMITED;第二步执行 3.2 的 FOR 循环游标预期输出三行 MANAGER 记录7566-JONES-MANAGER-2975 7698-BLAKE-MANAGER-2850 7782-CLARK-MANAGER-2450第三步执行 3.3 参数化游标传 20预期输出部门 20 的员工员工号7369 员工名SMITH 工资800 员工号7566 员工名JONES 工资2975 员工号7788 员工名SCOTT 工资3000 员工号7876 员工名ADAMS 工资1100第四步执行 3.5 隐式游标预期输出更新了 4 行如果输出为空先检查SET SERVEROUTPUT ON是否执行再检查DBMS_OUTPUT缓冲区大小。SQL*Plus 默认缓冲区可能不够用SIZE UNLIMITED保险。5. 本篇常见错排查5.1 ORA-01001: invalid cursor原因对已关闭或未打开的游标执行FETCH。显式游标必须先OPEN再FETCHCLOSE之后不能再取。排查检查OPEN和CLOSE是否配对循环里有没有提前CLOSE。5.2 ORA-06502: numeric or value error原因FETCH的变量类型和游标列类型不匹配或者%ROWTYPE用错了游标。排查确认v_row声明的是c_job%ROWTYPE不是别的游标。参数化游标传参时类型也要对上。5.3 循环多执行一次或漏掉最后一行原因EXIT WHEN位置放错。放在FETCH之前第一次判断时%NOTFOUND还是初始值会多跑一轮放在处理逻辑之后最后一行可能被跳过。正确顺序永远是FETCH→EXIT WHEN %NOTFOUND→ 处理逻辑。5.4 FOR 循环里修改游标基表原因FOR 循环游标在打开时结果集已固定循环中修改基表不会反映到当前循环。排查如果需要在循环中更新并影响后续行用FOR UPDATE加WHERE CURRENT OF或者改用显式游标配合%ROWCOUNT控制。5.5 REF CURSOR 字段对不上原因OPEN ... FOR的 SELECT 列数和FETCH INTO的变量数不一致或者类型不兼容。排查把 SELECT 的列和 INTO 的变量一一列出来核对。REF CURSOR 编译期不检查只能靠运行时验证。5.6 SQL%ROWCOUNT 返回 0原因在SELECT INTO之前读取或者 DML 没有匹配行。排查SQL%ROWCOUNT只在 DML 或SELECT INTO之后有效。SELECT INTO没查到数据会抛NO_DATA_FOUND不会走到SQL%ROWCOUNT。6. 把游标骨架用起来游标这东西写多了会发现套路固定声明结果集、决定用 FOR 还是 FETCH、处理边界、关掉。真正容易翻车的是异常分支和循环边界。我自己的习惯是凡是批处理先写 FOR 循环游标跑通逻辑遇到需要跨过程传递结果集再换 REF CURSOR。如果你在写游标时想让 AI 帮你审查%NOTFOUND位置或者生成异常处理模板可以用模型对话快速过一遍https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。长期做存储过程和 Agent 编排的Coding Plan 会更顺手https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。接入细节和参数说明都在文档里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。最后留一个实用技巧在 SQL*Plus 里调试游标时把DBMS_OUTPUT.PUT_LINE换成往临时表插日志比看屏幕输出更可靠尤其是循环几千行的时候。临时表加个SERIAL列跑完直接SELECT排序看哪一行出的问题一目了然。
返回列表