☰
Oracle 游标的使用:从显式游标到游标变量,一份可复用的 PL/SQL 配置骨架
2026/9/29 6:22:38 网站建设 项目流程

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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,把 Key 填进去就能对话。
  • 如果你要长期做编码和 Agent 类任务,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
  • 接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,里面有完整的参数说明。

注意: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、CLOSE,Oracle 自动处理。实测下来,日常批处理优先用这种写法。

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游标是否打开隐式游标总是 FALSE
BEGIN 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再FETCH,CLOSE之后不能再取。

排查:检查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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。长期做存储过程和 Agent 编排的,Coding Plan 会更顺手:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。接入细节和参数说明都在文档里:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。

最后留一个实用技巧:在 SQL*Plus 里调试游标时,把DBMS_OUTPUT.PUT_LINE换成往临时表插日志,比看屏幕输出更可靠,尤其是循环几千行的时候。临时表加个SERIAL列,跑完直接SELECT排序看,哪一行出的问题一目了然。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询