news 2026/9/26 19:47:28

Oracle 游标循环两种写法:open cursor loop fetch into 与 for in cursor loop 的配置与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Oracle 游标循环两种写法:open cursor loop fetch into 与 for in cursor loop 的配置与验证

1. 从一次数据迁移踩坑说起:两种游标循环到底差在哪

如果你正在做 Oracle 到其他库的迁移,或者维护一套跑了多年的 PL/SQL 批处理,大概率绕不开显式游标。open cursor loop fetch into和for in cursor loop这两种写法,表面看只是代码风格差异,实际在异常处理、资源释放、执行计划复用上完全是两回事。我见过太多迁移脚本因为混用这两种写法,导致游标泄漏、结果集少一行、或者%NOTFOUND判断失效。

先说结论:for in cursor loop是语法糖,Oracle 自动帮你做了OPEN、FETCH、EXIT WHEN %NOTFOUND、CLOSE四件事,代码短、不容易漏关游标;而open fetch into是手动挡,你需要自己声明变量、自己控制退出条件、自己保证CLOSE被执行。手动挡灵活,但每个环节都可能出错。

这篇面向数据库开发和迁移场景,交付可复制的游标声明、循环骨架、异常处理配置,并给出执行计划与结果集一致性的验证动作。适合已经会写基本 SQL、但想在迁移或重构时把游标逻辑写扎实的读者。下面所有代码都可以直接在 SQL*Plus 或 SQL Developer 里跑,我用的是 Oracle 19c 的HR示例 schema。

2. 前置准备:TaoToken 接入与 SQL 客户端环境

在开始写游标之前,先把执行环境理顺。我平时调试 PL/SQL 会用两种方式:一种是在本地 SQL 客户端里直接跑,另一种是通过 API 把生成的 SQL 或 PL/SQL 块发给模型做审查和改写。后者在迁移场景特别有用,因为不同数据库的游标语法差异大,让模型帮你做语法映射能省不少时间。

如果你也想用 API 方式做 SQL 审查,可以先去 TaoToken 拿一个 Key。地址是 https://taotoken.net/api ,注册后在控制台创建 API Key,接入文档在 https://taotoken.net/doc 。拿到 Key 之后,你可以把下面这段游标代码发给模型,让它帮你检查%NOTFOUND的位置是否正确、CLOSE是否在所有分支都被执行。

需要说明的是,TaoToken 在这里的角色是帮你做代码审查和语法迁移的辅助工具,不是替代你的数据库客户端。真正的执行、执行计划查看、结果集比对,还是要在 SQL*Plus 或 SQL Developer 里完成。另外,如果你长期要做 PL/SQL 迁移和批量改写,可以了解一下 Coding Plan,适合需要反复调用模型做代码审查的场景。

环境方面,你需要:

  • Oracle 数据库 11g 及以上(%ROWTYPE和FOR ... IN游标在 11g 都支持)
  • 有HRschema 的读权限,或者换成你自己的表
  • DBMS_OUTPUT已启用,否则看不到输出

启用DBMS_OUTPUT的命令:

SET SERVEROUTPUT ON SIZE UNLIMITED;

3. 可复制配置:两种游标循环的完整骨架

3.1 open cursor loop fetch into 手动挡写法

先看手动挡。核心是四步:声明游标、声明接收变量、OPEN、循环FETCH并判断%NOTFOUND、最后CLOSE。

DECLARE CURSOR emp_cur IS SELECT first_name, last_name, salary FROM hr.employees WHERE department_id = 50; v_first_name hr.employees.first_name%TYPE; v_last_name hr.employees.last_name%TYPE; v_salary hr.employees.salary%TYPE; v_count PLS_INTEGER := 0; BEGIN OPEN emp_cur; LOOP FETCH emp_cur INTO v_first_name, v_last_name, v_salary; EXIT WHEN emp_cur%NOTFOUND; v_count := v_count + 1; DBMS_OUTPUT.PUT_LINE( v_count || ': ' || v_first_name || ' ' || v_last_name || ' salary=' || v_salary ); END LOOP; CLOSE emp_cur; DBMS_OUTPUT.PUT_LINE('total rows = ' || v_count); EXCEPTION WHEN OTHERS THEN IF emp_cur%ISOPEN THEN CLOSE emp_cur; END IF; DBMS_OUTPUT.PUT_LINE('error: ' || SQLERRM); RAISE; END; /

这里有几个关键点。第一,EXIT WHEN emp_cur%NOTFOUND必须放在FETCH之后、处理逻辑之前,否则最后一行会被漏掉或者多处理一次。第二,%NOTFOUND在FETCH之前是NULL,所以不能提前判断。第三,异常处理里用%ISOPEN判断游标是否还开着,避免重复CLOSE报ORA-01001。

如果你用%ROWTYPE接收整行,写法会更简洁:

DECLARE CURSOR emp_cur IS SELECT first_name, last_name, salary FROM hr.employees WHERE department_id = 50; v_emp emp_cur%ROWTYPE; BEGIN OPEN emp_cur; LOOP FETCH emp_cur INTO v_emp; EXIT WHEN emp_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_emp.first_name || ' ' || v_emp.last_name); END LOOP; CLOSE emp_cur; END; /

注意v_emp emp_cur%ROWTYPE这种写法,变量类型直接绑定游标的返回结构,迁移时如果改了SELECT列表,变量声明不用动,这是手动挡里比较省心的一个技巧。

3.2 for in cursor loop 自动挡写法

自动挡就短很多:

BEGIN FOR v_emp IN ( SELECT first_name, last_name, salary FROM hr.employees WHERE department_id = 50 ) LOOP DBMS_OUTPUT.PUT_LINE(v_emp.first_name || ' ' || v_emp.last_name); END LOOP; END; /

FOR ... IN后面可以直接跟子查询,这叫匿名游标,不用提前声明。循环变量v_emp是隐式声明的%ROWTYPE,作用域只在循环体内。Oracle 自动处理OPEN、FETCH、EXIT WHEN %NOTFOUND、CLOSE,你不需要写任何一句。

如果你已经有声明好的游标,也可以直接FOR v_emp IN emp_cur LOOP,效果一样。两种写法在 11g 之后执行计划基本一致,优化器都会做游标共享。

3.3 两种写法的对照表

维度open fetch intofor in cursor loop
游标变量声明必须手动声明隐式声明,无需声明
OPEN/CLOSE必须手动写自动完成
%NOTFOUND 判断必须手动写自动完成
异常时游标释放需手动%ISOPEN判断自动释放
循环内修改游标可以,灵活不可以,游标已固定
代码行数多少
适合场景需要精细控制、动态游标常规遍历、迁移脚本

4. 验证请求与成功结果:执行计划与结果集一致性

写完游标只是第一步,迁移场景最怕的是两种写法结果不一致。下面给出三个验证动作。

4.1 结果集行数比对

先跑一个基准查询,拿到期望行数:

SELECT COUNT(*) FROM hr.employees WHERE department_id = 50;

假设返回 45。然后分别跑两种游标写法,在循环里累加计数,最后输出total rows。两次输出必须都是 45。如果手动挡输出 44,大概率是EXIT WHEN位置写错了;如果输出 46,可能是FETCH写在了EXIT后面。

4.2 执行计划查看

在 SQL*Plus 里用EXPLAIN PLAN看游标对应的查询计划:

EXPLAIN PLAN FOR SELECT first_name, last_name, salary FROM hr.employees WHERE department_id = 50; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

两种游标写法对应的查询计划应该完全一样,都是对employees表的全表扫描或索引扫描。如果不一样,说明你在手动挡里加了额外的WHERE或者ORDER BY,需要对齐。

4.3 游标泄漏检查

跑完手动挡之后,查一下当前会话打开的游标:

SELECT sql_text, cursor_type FROM v$open_cursor WHERE sid = SYS_CONTEXT('USERENV', 'SID');

如果看到emp_cur对应的 SQL 还在列表里,说明CLOSE没执行到。正常情况下,CLOSE之后这条记录应该消失。自动挡不需要检查,Oracle 保证循环结束就释放。

5. 本篇常见错排查

5.1 ORA-01001: invalid cursor

这个错基本出现在手动挡。原因通常是CLOSE执行了两次,或者OPEN之前就FETCH。检查你的异常处理块,如果WHEN OTHERS里写了CLOSE emp_cur,但正常流程也CLOSE了,异常触发时就会重复关闭。正确做法是用%ISOPEN判断:

IF emp_cur%ISOPEN THEN CLOSE emp_cur; END IF;

5.2 结果集少一行

手动挡里EXIT WHEN emp_cur%NOTFOUND如果写在FETCH之前,第一行还没取就退出了。如果写在处理逻辑之后,最后一行处理完FETCH返回%NOTFOUND为TRUE,但那一行已经被处理过了,不会少。少一行通常是EXIT写在了FETCH和DBMS_OUTPUT之间,导致最后一行没输出。

5.3 for 循环里改游标变量报错

FOR v_emp IN emp_cur LOOP里的v_emp是只读的,你不能在循环体里给它赋值。如果你需要修改行数据,得用UPDATE ... WHERE CURRENT OF emp_cur,但前提是游标声明时带了FOR UPDATE。自动挡不支持WHERE CURRENT OF,这是它相比手动挡的一个硬限制。

5.4 迁移到其他数据库时语法不兼容

FOR ... IN子查询这种写法在 PostgreSQL 里对应FOR rec IN SELECT ... LOOP,在 MySQL 里没有直接对应,需要改成DECLARE ... CURSOR ... HANDLER。如果你在做跨库迁移,建议先用 API 把 PL/SQL 块发给模型做语法映射,接入文档在 https://taotoken.net/doc ,模型对话入口在 https://taotoken.net/chat 。把两种写法的代码贴进去,让它输出目标库的等价写法,比手动查文档快很多。

6. 按场景选型与后续动作

选型其实很简单。如果你只是遍历一个固定查询的结果集,不需要在循环里动态改游标,直接用FOR ... IN子查询,代码短、不容易漏CLOSE、迁移时也好看。如果你需要FOR UPDATE加WHERE CURRENT OF做行级更新,或者需要在循环中途根据条件重新OPEN游标,那就用手动挡,但务必把%ISOPEN判断和异常处理写全。

迁移场景还有一个坑:老代码里经常用open fetch into配合%ROWCOUNT做分批提交。%ROWCOUNT在自动挡里也能用,但语义是当前循环已处理的行数,不是游标总行数。如果你要每 1000 行COMMIT一次,两种写法都可以:

BEGIN FOR v_emp IN (SELECT * FROM hr.employees WHERE department_id = 50) LOOP -- 处理逻辑 IF MOD(v_emp.rn, 1000) = 0 THEN COMMIT; END IF; END LOOP; COMMIT; END; /

注意自动挡里没有rn这个列,你需要自己在子查询里加ROWNUM或者在循环里用计数器变量。手动挡直接用emp_cur%ROWCOUNT就行,这是它更方便的地方。

最后给一个实操建议:迁移前先把两种写法各跑一遍,用v$open_cursor确认没有泄漏,用COUNT(*)确认行数一致,用EXPLAIN PLAN确认计划一致。三个验证都过了,再往生产脚本里合。如果你需要批量审查迁移脚本里的游标写法,可以把脚本拆成小块发给模型做静态检查,API Key 在 https://taotoken.net/api-keys 创建,配合 Coding Plan 做长期迁移项目会更顺。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 19:46:59

YOLOv5+OpenPose摔倒检测:毕业设计实战与调参指南

简介:这份资源面向计算机视觉方向的本科毕业生与深度学习入门者,提供一套可直接运行的摔倒检测完整方案,解决从人体关键点提取到动作分类的工程落地问题。项目以YOLOv5完成人体检测,结合OpenPose提取骨骼关键点,再通过…

作者头像 李华
网站建设 2026/9/26 19:45:16

基于PyTorch的多模态虚假新闻检测:BERT+ResNet+对比学习实战

简介:一套基于PyTorch实现的多模态虚假新闻检测系统源码,面向深度学习研究者、NLP方向学生及舆情分析开发者。方案融合BERT预训练模型与ResNet卷积神经网络,分别提取文本深层语义与图像视觉特征,并在微博谣言数据集上完成训练与评…

作者头像 李华
网站建设 2026/9/26 19:44:19

《WiFi 嵌入式物联网开发全套实战》| 第 20 章 WiFi 低功耗休眠、定时唤醒、保活机制(电池设备必备)

专栏:《WiFi 嵌入式物联网开发全套实战》 专栏定位:嵌入式 Linux/ESP32 WiFi 从原理→驱动→配网→协议→稳定性→抓包调试→量产优化全套工业实战 适配:物联网设备、智能家居、工控网关、无线透传设备、4GWiFi 双模设备 💖 点赞 …

作者头像 李华
网站建设 2026/9/26 19:43:23

莆仙话语音翻译应用的网页与微信小程序双端设计实践

莆仙话属于低资源方言。与普通话相比,可直接用于语音识别、文本归一化和语音合成的数据更少,莆田、仙游等地区的口音差异也会影响识别结果。因此,把方言语音翻译做成可日常使用的产品,难点不只在模型,还包括录音交互、…

作者头像 李华
网站建设 2026/9/26 19:42:38

Substrate区块链开发框架入门:从核心概念到本地链实操

1. 从零认识 Substrate:它到底是什么,能解决什么问题第一次听到 Substrate 这个词,很多人会以为是某个前端框架或者构建工具。其实不是。Substrate 是一个用于构建区块链的开发框架,由 Parity Technologies 团队打造,最…

作者头像 李华