1. 从一次实验课翻车说起:Oracle 游标到底解决什么问题
如果你正在上 Oracle 数据库实验课,看到cursor、open、fetch、%rowcount这些词就头大,这篇就是写给你的。Oracle 游标(cursor)本质上是 PL/SQL 里用来逐行处理查询结果集的一个指针,它让你能把select出来的多行数据一行一行拿出来做判断、计算、更新。适合谁?适合正在做数据库实验、准备课程设计、或者第一次接触 PL/SQL 存储过程的人。
我见过太多同学在实验课上卡住,不是因为游标语法难,而是因为环境没连上、dbms_output没开、或者scott用户被锁了,结果代码明明抄对了却一直报错。这篇手册会先把游标四步走讲透,再给参数化游标、游标 FOR 循环、for update游标的完整可粘贴脚本,最后用 TaoToken 统一 Key 通道帮你排查 ORA-01031、连接失败这类环境问题。你不需要装一堆客户端,SQL*Plus 或 SQL Developer 都能跑。
先说清楚游标的核心价值:普通select是一次性把结果集返回给客户端,而游标是在服务端维护一个结果集指针,你可以控制每次取几行、取到哪一行、什么时候停。这在做批量更新、逐行校验、统计汇总时特别有用。实验里常见的「统计各部门平均工资」「按部门号动态查询员工」「工资低于 1000 调到 1500」都是游标的典型场景。
游标分两类:显式游标和隐式游标。显式游标是你自己declare cursor声明、自己open/fetch/close的;隐式游标是 Oracle 为每条 DML 语句自动开的,你用SQL%ROWCOUNT、SQL%FOUND这些属性去读它的状态。实验里两种都会考,下面逐个拆。
2. TaoToken 前置准备:统一 Key 通道与连接报错排查
做游标实验之前,先把连接问题解决掉,否则你会在ORA-01031: insufficient privileges或者ORA-12541: TNS:no listener上耗掉半节课。我的做法是用 TaoToken 做一个统一的 Key 通道,把模型对话、编码辅助、接口调试都走同一个入口,这样排查连接类报错时不用在多个平台之间来回切换。
TaoToken 官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。它的作用是给你一个统一的 Key 和 Base URL,让你在写 PL/SQL 实验报告、调试连接脚本、或者让 AI 帮你解释报错时,有一个稳定的通道。注意,它不是数据库客户端,也不替代 SQL Developer,它只是帮你把「查文档、问报错、生成脚本」这些动作串起来。
具体怎么用?第一步,去控制台创建一个 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建完把 Key 复制下来,后面配置里要用。第二步,如果你要验证模型通道是否通,可以用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 发一条测试消息。第三步,如果你打算长期做编码和 Agent 类任务,可以看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
这里要强调一个排查思路:当你遇到ORA-01031时,先别急着改游标代码,先确认三件事——数据库用户有没有create session权限、scott用户是不是被锁、dbms_output有没有开。这三件事用 SQL*Plus 几条命令就能查。而如果你是在用 AI 辅助生成游标脚本时遇到接口报错,比如401、local proxy failed、reading choices这类,那就要检查你的 Base URL 和 Key 是否配对。TaoToken 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有完整的 Base URL、Key、Model ID 三件套说明。
我实测下来,把 Key 通道统一之后,最大的好处是排查报错时不用猜「到底是数据库的问题还是工具的问题」。你可以在同一个通道里先验证模型能不能正常返回,再去跑游标脚本,这样问题定位快很多。API Keys 管理页面在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,建议把实验用的 Key 单独建一个,方便轮换。
3. 可复制配置:显式游标四步与参数化游标完整脚本
这一节给你可以直接粘贴运行的脚本。先建实验表,再跑游标。我用xs(学生)表和scott.emp表做例子,你可以按自己实验指导书替换表名。
先建xs表并插数据:
-- 建表 create table xs ( xh varchar2(10), xm varchar2(20), xb varchar2(4) ); -- 插入测试数据 insert into xs values ('001', '张三', '男'); insert into xs values ('002', '李四', '女'); insert into xs values ('003', '王五', '男'); commit;显式游标的标准四步是:声明(declare)、打开(open)、提取(fetch)、关闭(close)。下面是最小可运行版本:
set serveroutput on; declare cursor c_1 is select xm from xs; v_1 xs.xm%type; begin open c_1; fetch c_1 into v_1; dbms_output.put_line(v_1); close c_1; end; /注意set serveroutput on;必须加,否则dbms_output.put_line的输出你看不到。这是实验课最常见的「代码没错但没输出」原因。
用%rowtype一次取整行:
declare cursor c_1 is select * from xs; v_1 xs%rowtype; begin open c_1; fetch c_1 into v_1; dbms_output.put_line(v_1.xh || ' ' || v_1.xm); close c_1; end; /带参数的游标,参数在open时传入:
declare cursor c_1(v_xb xs.xb%type) is select * from xs where xb = v_xb; v_1 xs%rowtype; begin open c_1('男'); fetch c_1 into v_1; dbms_output.put_line(v_1.xh || ' ' || v_1.xm); close c_1; end; /多表查询游标,用%rowtype承接:
declare cursor c_4 is select empno, ename, emp.deptno, dname from scott.emp, scott.dept where emp.deptno = dept.deptno; v_4 c_4%rowtype; begin open c_4; fetch c_4 into v_4; dbms_output.put_line(v_4.empno || ' ' || v_4.ename || ' ' || v_4.deptno || ' ' || v_4.dname); close c_4; end; /用%rowcount显示当前取到第几行:
declare cursor c_1 is select xm from xs; v_1 xs.xm%type; begin open c_1; fetch c_1 into v_1; dbms_output.put_line(v_1 || ' ' || c_1%rowcount); close c_1; end; /如果你要用 AI 辅助生成这些脚本,可以在 TaoToken 的模型对话里贴报错信息,让它帮你定位。配置时记住三件套:Base URL 用https://taotoken.net/api,Key 用你在控制台创建的那串,Model ID 按文档里写的填。这三样缺一不可,配错了就会报401或reading choices类错误。
4. 验证请求与成功结果:循环、FOR 循环与 for update 游标
游标真正好用的地方在于循环处理多行。先看用loop+exit when %notfound的写法,这是实验里最常考的:
declare v_deptno scott.emp.deptno%type; cursor c_emp is select * from scott.emp where scott.emp.deptno = v_deptno; v_emp c_emp%rowtype; begin v_deptno := &x; open c_emp; loop fetch c_emp into v_emp; exit when c_emp%notfound; dbms_output.put_line(v_emp.empno || ' ' || v_emp.ename || ' ' || v_emp.deptno || ' ' || v_emp.sal); end loop; close c_emp; end; /运行时会提示你输入x的值,输入10就能看到 10 号部门的员工。这里&x是 SQL*Plus 的替换变量,SQL Developer 里也能用。
统计各部门平均工资,用简单循环:
declare cursor c_dept is select deptno, avg(sal) avgsal from scott.emp group by deptno; v_dept c_dept%rowtype; begin open c_dept; loop fetch c_dept into v_dept; exit when c_dept%notfound; dbms_output.put_line(v_dept.deptno || ' ' || v_dept.avgsal); end loop; close c_dept; end; /用while循环改写,注意fetch要写两次,一次在循环前,一次在循环体末尾:
declare cursor c_dept is select deptno, avg(sal) avgsal from scott.emp group by deptno; v_dept c_dept%rowtype; begin open c_dept; fetch c_dept into v_dept; while c_dept%found loop dbms_output.put_line(v_dept.deptno || ' ' || v_dept.avgsal); fetch c_dept into v_dept; end loop; close c_dept; end; /最简洁的是游标 FOR 循环,不用手动open/fetch/close,Oracle 自动帮你做:
declare cursor c_dept is select deptno, avg(sal) avgsal from scott.emp group by deptno; begin for v_dept in c_dept loop dbms_output.put_line(v_dept.deptno || ' ' || v_dept.avgsal); end loop; end; /for update游标用于边遍历边更新,配合where current of定位当前行:
declare cursor c_emp is select * from scott.emp for update; v_zl number; begin for v_emp in c_emp loop case v_emp.deptno when 10 then v_zl := 100; when 20 then v_zl := 150; when 30 then v_zl := 200; else v_zl := 250; end case; update scott.emp set sal = sal + v_zl where current of c_emp; end loop; commit; end; /注意原实验代码里case三个分支都写成了when 10,那是笔误,正确写法是when 10 / when 20 / when 30。这个坑我在批改实验报告时见过好几次。
再看一个带条件判断的工资调整:
declare cursor c_1 is select empno, sal from scott.emp for update of sal; v_sal scott.emp.sal%type; begin for v_1 in c_1 loop if v_1.sal <= 1000 then v_sal := 1500; else v_sal := v_1.sal * 1.5; if v_sal > 10000 then v_sal := 10000; end if; end if; update scott.emp set sal = v_sal where current of c_1; end loop; commit; end; /成功运行的标志是:set serveroutput on之后能看到逐行输出,for update脚本执行完commit后查表数据确实变了。如果没输出,先检查serveroutput;如果报ORA-01031,检查权限;如果报ORA-00942,检查表名和 schema 前缀。
5. 本篇常见报错排查:ORA-01031、连接失败与游标属性误用
实验里最容易卡住的不是游标逻辑,而是环境和属性误用。下面按真实报错逐个说。
ORA-01031: insufficient privileges通常出现在你试图访问scott.emp但没有权限,或者scott用户被锁。解决动作:用 DBA 账号执行alter user scott account unlock;和grant connect, resource to scott;。如果你用的是自己的用户,确认有create session和select权限。
ORA-12541: TNS:no listener或连接失败,先确认监听服务起了没,lsnrctl status看一眼。如果是用 AI 工具辅助时遇到local proxy failed,那多半是 Base URL 配错了。TaoToken 的 Base URL 是https://taotoken.net/api,不要多加路径,也不要漏掉/api。Key 和 Model ID 要跟文档一致,三件套配齐。
ORA-06550或PLS-00201一般是变量类型声明不对,比如v_1 xs.xm%type里表名或列名拼错。检查%type前面的表列是否存在。
ORA-01001: invalid cursor是游标没open就fetch,或者close之后又fetch。显式游标必须严格按 open→fetch→close 顺序。
%rowcount返回 0 或不对,是因为你在fetch之前就读了它。%rowcount表示当前已经取到的行数,必须在fetch之后读。
%notfound和%found用反了会导致循环多跑一次或少跑一次。记住:exit when c_emp%notfound是取不到数据时退出,while c_dept%found是有数据时继续。
dbms_output没输出,99% 是忘了set serveroutput on;。在 SQL Developer 里还要确认「DBMS Output」面板已经启用。
如果你在配置 AI 辅助通道时遇到401,去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 确认 Key 没过期。遇到reading choices类报错,检查 Model ID 是否填对。接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里有完整的参数对照表。
6. 把游标实验跑通之后:统一通道与长期编码习惯
游标实验跑通只是第一步。真正让你省时间的是把「写脚本、查报错、验证结果」这条链路固定下来。我的习惯是:数据库连接用 SQL Developer,脚本生成和报错解释走 TaoToken 的统一 Key 通道,这样不用在多个工具之间复制粘贴。
如果你后面还要做存储过程、触发器、包这些 PL/SQL 内容,建议把 Coding Plan 用起来,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它适合长期编码和 Agent 类任务,比每次单独配 Key 省事。模型对话入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,验证模型是否正常返回就用它。
最后给你一个实用技巧:把常用的游标模板存成一个.sql文件,每次实验改表名和条件就行。for update游标记得加commit,否则数据不落库。%rowtype和%type能少写很多类型声明,但表结构变了要重新编译。游标 FOR 循环最省代码,但如果你需要在循环中间做复杂控制,还是用显式游标更灵活。
实验报告里如果要求写「游标四步」,就按 declare→open→fetch→close 写;要求「隐式游标属性」,就写SQL%ROWCOUNT、SQL%FOUND、SQL%NOTFOUND;要求「参数化游标」,就在cursor声明里加参数,open时传值。这些点覆盖了绝大多数 Oracle 游标实验的评分项。