尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Oracle 空游标判断的 3 种写法:从 %NOTFOUND 到显式游标属性配置

发布时间:2026/9/26 1:39:21

资讯中心
01
ARTICLE

Oracle 空游标判断的 3 种写法:从 %NOTFOUND 到显式游标属性配置

Oracle 空游标判断的 3 种写法:从 %NOTFOUND 到显式游标属性配置
1. 为什么“空游标判断”总在存储过程里翻车先说结论Oracle 里没有cursor%IS_EMPTY这种属性判断游标是否返回空结果集唯一可靠的路径是“先 FETCH再看%NOTFOUND或%ROWCOUNT”。很多人第一次写 PL/SQL 存储过程时会下意识地写IF cur%NOTFOUND THEN或者IF cur%ROWCOUNT 0 THEN然后发现结果和预期完全相反——明明表里没数据却走了“有数据”分支明明表里有数据却走了“没数据”分支。这个坑的根源在于游标在OPEN之后、FETCH之前它的属性状态是“未定义”的。%NOTFOUND在OPEN后默认是FALSE%ROWCOUNT在OPEN后默认是0。也就是说你还没取数据Oracle 根本不知道结果集里有没有行它只是把查询挂上去了。只有执行一次FETCHOracle 才会真正去定位第一行然后更新%NOTFOUND和%ROWCOUNT。所以本文聚焦的场景很具体你在匿名块或存储过程里用显式游标或REF CURSOR打开一个查询需要判断“这个查询到底有没有返回行”。我会把三种常见写法的实际表现拆开讲给出可以直接复制到 SQL*Plus 里跑的代码骨架并附上逐条验证输出的步骤。适合已经会写基本 PL/SQL、但在游标属性上踩过坑的开发者。2. 三种写法的实际表现对比2.1 写法一OPEN 后直接判断 %NOTFOUND这是最常见的错误写法。代码长这样DECLARE CURSOR c1 IS SELECT * FROM emp WHERE deptno 99; v_emp emp%ROWTYPE; BEGIN OPEN c1; IF c1%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(空结果集); ELSE DBMS_OUTPUT.PUT_LINE(有数据); END IF; CLOSE c1; END; /假设deptno 99在emp表里不存在。你期望输出“空结果集”但实际输出是“有数据”。原因就是OPEN之后%NOTFOUND被初始化为FALSEOracle 还没执行任何取行操作。这个写法在匿名块和存储过程里表现一致都是错的。2.2 写法二OPEN 后判断 %ROWCOUNT 0这个写法看起来更“合理”因为%ROWCOUNT表示已取行数0 就代表没取到。但问题在于OPEN之后%ROWCOUNT也是 0不管结果集里有没有行。代码DECLARE CURSOR c2 IS SELECT * FROM emp WHERE deptno 10; v_emp emp%ROWTYPE; BEGIN OPEN c2; IF c2%ROWCOUNT 0 THEN DBMS_OUTPUT.PUT_LINE(空结果集); ELSE DBMS_OUTPUT.PUT_LINE(有数据); END IF; CLOSE c2; END; /即使deptno 10有 3 行数据输出依然是“空结果集”。因为%ROWCOUNT统计的是“已经 FETCH 出来的行数”不是“结果集总行数”。你没 FETCH它就是 0。这个写法同样不可靠。2.3 写法三先 FETCH 一次再判断 %NOTFOUND这是正确写法。核心逻辑是OPEN之后立刻FETCH一行到记录变量里然后检查%NOTFOUND。如果%NOTFOUND为TRUE说明第一行就没取到结果集为空否则说明至少有一行。代码DECLARE CURSOR c3 IS SELECT * FROM emp WHERE deptno 10; v_emp emp%ROWTYPE; BEGIN OPEN c3; FETCH c3 INTO v_emp; IF c3%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(空结果集); ELSE DBMS_OUTPUT.PUT_LINE(有数据第一行 ename || v_emp.ename); END IF; CLOSE c3; END; /这个写法在匿名块和存储过程里都成立。注意FETCH之后%ROWCOUNT会变成 1如果有数据或保持 0如果没数据所以你也可以用%ROWCOUNT 0来判断但前提是已经FETCH过。不过%NOTFOUND语义更直接推荐优先用%NOTFOUND。2.4 三种写法对照表写法判断时机空结果集表现有数据表现是否可靠%NOTFOUNDOPEN 后直接判断误判为有数据正确否%ROWCOUNT 0OPEN 后直接判断正确误判为空否%NOTFOUNDFETCH 一次后判断正确正确是%ROWCOUNT 0FETCH 一次后判断正确正确是3. 可复制的游标声明与判断代码骨架下面给出一个完整的存储过程骨架包含显式游标和REF CURSOR两种形式。你可以直接复制到 SQL*Plus 里执行。3.1 显式游标版本CREATE OR REPLACE PROCEDURE check_emp_cursor(p_deptno IN NUMBER) IS CURSOR c_emp IS SELECT empno, ename, sal FROM emp WHERE deptno p_deptno; v_empno emp.empno%TYPE; v_ename emp.ename%TYPE; v_sal emp.sal%TYPE; BEGIN OPEN c_emp; FETCH c_emp INTO v_empno, v_ename, v_sal; IF c_emp%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(部门 || p_deptno || 没有员工记录); ELSE DBMS_OUTPUT.PUT_LINE(部门 || p_deptno || 有员工第一行: || v_ename || 薪资 || v_sal); -- 如果需要继续遍历剩余行可以在这里写 LOOP LOOP FETCH c_emp INTO v_empno, v_ename, v_sal; EXIT WHEN c_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE(后续行: || v_ename || 薪资 || v_sal); END LOOP; END IF; CLOSE c_emp; END; /3.2 REF CURSOR 版本REF CURSOR常用于存储过程返回结果集给调用方判断空游标的逻辑是一样的先FETCH再判断。CREATE OR REPLACE PROCEDURE check_ref_cursor(p_deptno IN NUMBER) IS TYPE t_ref_cur IS REF CURSOR; v_cur t_ref_cur; v_empno emp.empno%TYPE; v_ename emp.ename%TYPE; BEGIN OPEN v_cur FOR SELECT empno, ename FROM emp WHERE deptno p_deptno; FETCH v_cur INTO v_empno, v_ename; IF v_cur%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(REF CURSOR: 部门 || p_deptno || 为空); ELSE DBMS_OUTPUT.PUT_LINE(REF CURSOR: 部门 || p_deptno || 第一行 || v_ename); END IF; CLOSE v_cur; END; /3.3 匿名块版本如果你不想建存储过程匿名块也能验证SET SERVEROUTPUT ON; DECLARE CURSOR c_test IS SELECT * FROM emp WHERE deptno 99; v_emp emp%ROWTYPE; BEGIN OPEN c_test; FETCH c_test INTO v_emp; IF c_test%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(匿名块: 空结果集); ELSE DBMS_OUTPUT.PUT_LINE(匿名块: 有数据 || v_emp.ename); END IF; CLOSE c_test; END; /4. 在 SQL*Plus 中逐条验证输出结果下面是一套完整的验证步骤你可以按顺序执行观察每一步的输出。4.1 准备测试环境先确认emp表存在并查看部门分布SELECT deptno, COUNT(*) FROM emp GROUP BY deptno ORDER BY deptno;假设输出显示10有 3 行20有 5 行30有 6 行99不存在。这样我们就有了“有数据”和“空结果集”两种场景。4.2 验证错误写法一SET SERVEROUTPUT ON; DECLARE CURSOR c1 IS SELECT * FROM emp WHERE deptno 99; v_emp emp%ROWTYPE; BEGIN OPEN c1; IF c1%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(写法一: 空结果集); ELSE DBMS_OUTPUT.PUT_LINE(写法一: 有数据); END IF; CLOSE c1; END; /实际输出写法一: 有数据。但deptno 99明明没有数据说明这个判断是错的。4.3 验证错误写法二DECLARE CURSOR c2 IS SELECT * FROM emp WHERE deptno 10; v_emp emp%ROWTYPE; BEGIN OPEN c2; IF c2%ROWCOUNT 0 THEN DBMS_OUTPUT.PUT_LINE(写法二: 空结果集); ELSE DBMS_OUTPUT.PUT_LINE(写法二: 有数据); END IF; CLOSE c2; END; /实际输出写法二: 空结果集。但deptno 10有 3 行数据说明这个判断也是错的。4.4 验证正确写法DECLARE CURSOR c3 IS SELECT * FROM emp WHERE deptno 99; v_emp emp%ROWTYPE; BEGIN OPEN c3; FETCH c3 INTO v_emp; IF c3%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(写法三: 空结果集); ELSE DBMS_OUTPUT.PUT_LINE(写法三: 有数据); END IF; CLOSE c3; END; /实际输出写法三: 空结果集。正确。再把deptno改成10DECLARE CURSOR c4 IS SELECT * FROM emp WHERE deptno 10; v_emp emp%ROWTYPE; BEGIN OPEN c4; FETCH c4 INTO v_emp; IF c4%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(写法三: 空结果集); ELSE DBMS_OUTPUT.PUT_LINE(写法三: 有数据第一行 || v_emp.ename); END IF; CLOSE c4; END; /实际输出写法三: 有数据第一行 SMITH或对应第一行。正确。4.5 验证存储过程调用前面创建的存储过程EXEC check_emp_cursor(99); EXEC check_emp_cursor(10);预期输出分别为“没有员工记录”和“有员工第一行...”。如果输出符合预期说明存储过程里的判断逻辑正确。5. 本篇常见错排查5.1 报错 ORA-01001: invalid cursor这个错误通常是因为游标已经CLOSE了你还在访问它的属性。比如CLOSE c1; IF c1%NOTFOUND THEN ...CLOSE之后游标属性不可访问。解决办法把属性判断放在CLOSE之前。5.2 FETCH 之后 %ROWCOUNT 还是 0如果你FETCH了但%ROWCOUNT还是 0检查是不是FETCH语句写错了比如INTO的变量类型不匹配导致取行失败。另外%ROWCOUNT在FETCH成功后会递增如果第一行就%NOTFOUND它保持 0 是正常的。5.3 存储过程里 DBMS_OUTPUT 不输出在 SQL*Plus 里需要先执行SET SERVEROUTPUT ON否则DBMS_OUTPUT.PUT_LINE的内容不会显示。在 PL/SQL Developer 或 SQL Developer 里需要确保输出窗口已启用。5.4 REF CURSOR 判断空之后还能继续用吗可以。FETCH一次只是把游标位置移到第一行如果结果集有多行你可以继续FETCH取剩余行。但如果你判断为空之后直接CLOSE那就不能再取了。如果你需要把整个结果集返回给调用方通常的做法是先FETCH判断空如果不空把第一行和后续行一起处理或者用BULK COLLECT一次性取完再判断集合是否为空。5.5 用 SELECT COUNT(*) 代替游标判断有些场景下你可以先SELECT COUNT(*) INTO v_cnt FROM ... WHERE ...然后根据v_cnt判断。但这种方式多了一次查询而且在并发环境下可能和后续游标取到的数据不一致。如果只是判断空推荐还是用FETCH %NOTFOUND一次查询搞定。6. 接入与验证用 TaoToken 跑通你的 PL/SQL 调试链路如果你在本地没有可用的 Oracle 环境或者想快速验证一段 PL/SQL 逻辑可以借助 TaoToken 的模型对话能力来辅助排查。比如你把报错信息、游标声明片段贴进去让它帮你分析%NOTFOUND的判断时机是否正确。实测下来对于 ORA-01001、%ROWCOUNT 不更新这类问题它能给出比较具体的定位建议。需要先拿到 API Key入口在 console 的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapikeys 。拿到之后如果你只是临时问几个游标问题用模型对话页面就够了https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodelchat 。如果你打算长期用 AI 辅助写 PL/SQL、做代码审查可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcodingplan 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc API 基础地址是 https://taotoken.net/api 。配置的时候注意API Key 不要硬编码在存储过程里放在客户端环境变量或配置文件中更安全。最后提醒一句游标判断空的核心就一句话——先 FETCH再看 %NOTFOUND。别在 OPEN 之后直接判断那个状态是未定义的。把本文第 4 节的验证步骤在 SQL*Plus 里跑一遍你就能亲眼看到三种写法的差异。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。