新闻详情

新闻详情

首页 / 资讯中心 / 详情

Oracle游标的使用详解:从显式游标到游标变量的完整实践

发布时间:2026/10/2 17:02:56来源:尧图网络
Oracle游标的使用详解:从显式游标到游标变量的完整实践
1. Oracle 游标到底解决什么问题从一次批量更新说起如果你写过 PL/SQL大概率遇到过这种场景一张订单表里有几十万行数据需要按行做业务判断比如给某类商品统一打折、给逾期订单逐条发提醒、把明细表的数据汇总到报表表。用一条 UPDATE 能搞定的当然最好但现实里往往每一行的处理逻辑都不一样这时候就需要一个能逐行取数、逐行处理的工具这个工具就是游标。游标CURSOR本质上是一块内存工作区Oracle 把 SELECT 语句查出来的结果集放在这块内存里然后用一个指针指向当前行。你每次 FETCH指针就往下走一行直到取完为止。可以把它理解成一个只进不退的队列读取器初始时指针指向首记录之前第一次 FETCH 拿到第一行指针移到第二行依此类推。指针只能向下移动不能回退这也是为什么游标适合顺序遍历而不适合随机跳转。游标能做什么简单说三件事第一把多行查询结果逐条交给 PL/SQL 处理实现行级业务逻辑第二通过属性%FOUND、%NOTFOUND、%ROWCOUNT、%ISOPEN实时感知 SQL 执行状态第三参数化游标可以让同一段逻辑复用于不同的查询条件减少重复代码。它适合谁适合已经会写基本 SELECT 和 PL/SQL 块、但一遇到多行逐条处理就卡壳的 Oracle 开发者也适合从 MySQL、PostgreSQL 转过来、发现存储过程写法差异较大的同学。本文会从显式游标、隐式游标讲到游标变量REF CURSOR每一步都给可复制的建表脚本、游标脚本以及在 SQL*Plus 里逐步验证输出的操作步骤。实测下来把这几类游标吃透日常存储过程开发里 80% 的逐行处理需求都能覆盖。先明确一个概念边界隐式游标是 Oracle 在执行 DML 或单行 SELECT 时自动创建的游标名固定为 SQL你不需要声明和打开显式游标需要你自己声明、打开、提取、关闭游标变量则是不与特定查询绑定的动态指针打开时才决定查什么。三者用途不同下面逐一展开。2. 环境准备与建表脚本让游标有数据可查在写游标之前得先有一张能跑出多行结果的表。这里用经典的 products 表做演示字段包括产品编号、产品名称、供应商编号、类别编号、单价。为了让后面的游标例子都能验证出结果我准备了 8 条测试数据覆盖多个供应商和类别。-- 建表 CREATE TABLE products ( productid NUMBER(10) PRIMARY KEY, productname VARCHAR2(100), supplierid NUMBER(10), categoryid NUMBER(10), unitprice NUMBER(10,2) ); -- 插入测试数据 INSERT INTO products VALUES (1, 机械键盘, 6, 1, 299.00); INSERT INTO products VALUES (2, 无线鼠标, 6, 1, 89.00); INSERT INTO products VALUES (3, 显示器, 7, 1, 1299.00); INSERT INTO products VALUES (4, USB集线器, 7, 2, 45.00); INSERT INTO products VALUES (5, 笔记本支架, 8, 2, 129.00); INSERT INTO products VALUES (6, 摄像头, 8, 1, 199.00); INSERT INTO products VALUES (7, 移动硬盘, 6, 2, 499.00); INSERT INTO products VALUES (8, 网线, 7, 2, 15.00); COMMIT;建好之后先在 SQL*Plus 里确认数据没问题SELECT productid, productname, supplierid, categoryid, unitprice FROM products ORDER BY productid;你应该能看到 8 行记录。这一步很关键因为后面所有游标脚本的输出都要和这张表对得上如果数据不对排障时会分不清是游标写错了还是数据本身有问题。接下来打开 SQL*Plus 的输出开关否则 dbms_output.put_line 的内容不会显示SET SERVEROUTPUT ON SIZE UNLIMITED;SIZE UNLIMITED是为了防止输出内容过多被截断处理大批量数据时尤其有用。如果你用的是 SQL Developer 或 PL/SQL Developer也要确认输出窗口已启用 DBMS Output。关于连接方式如果你在本地用 SQL*Plus 直连数据库命令大致是sqlplus 用户名/密码服务名。有些同学会在本地做一层 API 网关来统一管理数据库连接和密钥比如把连接信息收敛到 https://taotoken.net/api 这类统一入口再在客户端配置 Base URL 和 Key。这种做法的好处是密钥不散落在各个脚本里团队协作时切换环境也方便。不过要注意数据库连接本身和模型 API 是两回事别把两者的配置混在一起。环境就绪后我们进入正题。先看隐式游标因为它最容易被忽略却天天在用。3. 隐式游标与显式游标声明、打开、提取、关闭全流程3.1 隐式游标不用声明但属性必须会用每次执行 UPDATE、DELETE、INSERT 或者只返回一行的 SELECT INTOOracle 都会自动创建一个隐式游标名字固定叫 SQL。你不需要 OPEN、FETCH、CLOSE但可以通过 SQL%FOUND、SQL%NOTFOUND、SQL%ROWCOUNT、SQL%ISOPEN 来获取执行状态。看一个批量打折的例子把类别为 1 的产品单价打 9 折并显示影响行数BEGIN UPDATE products SET unitprice unitprice * 0.9 WHERE categoryid 1; IF SQL%FOUND THEN dbms_output.put_line(更新了 || SQL%ROWCOUNT || 条记录); ELSE dbms_output.put_line(没有更新记录); END IF; END; /执行后输出应该是更新了 4 条记录因为类别为 1 的产品有 4 个编号 1、2、3、6。这里有个细节SQL%ROWCOUNT 返回的是最近一次 SQL 语句影响的行数如果你在 IF 之前又执行了别的 DML这个值会被覆盖所以要在 DML 之后立刻读取。隐式游标的 %ISOPEN 对用户而言永远是 FALSE因为 Oracle 在执行完 SQL 后自动关闭了它你没法手动干预。这一点和显式游标完全不同别想着去 OPEN SQL 或者 CLOSE SQL会直接报错。3.2 显式游标四步走的标准流程显式游标需要你完整地走四步声明DECLARE、打开OPEN、提取FETCH、关闭CLOSE。声明时把 SELECT 语句绑定进去打开时 Oracle 执行查询并把结果集放入内存提取时逐行取数关闭时释放资源。先看无参游标查询供应商编号为 6 的所有产品DECLARE CURSOR prod_cursor IS SELECT * FROM products WHERE supplierid 6; prod_record products%ROWTYPE; BEGIN OPEN prod_cursor; LOOP FETCH prod_cursor INTO prod_record; EXIT WHEN prod_cursor%NOTFOUND; dbms_output.put_line(产品编号: || prod_record.productid); dbms_output.put_line(产品名称: || prod_record.productname); dbms_output.put_line(供应商编号: || prod_record.supplierid); END LOOP; CLOSE prod_cursor; END; /这段脚本会输出供应商 6 的 3 个产品机械键盘、无线鼠标、移动硬盘。注意prod_record products%ROWTYPE这行它声明了一个和 products 表行结构完全一致的变量FETCH 时整行数据一次性赋给它字段顺序和类型自动对齐不用手动一个个声明变量。参数化游标在此基础上增加了灵活性把查询条件做成参数DECLARE CURSOR prod_cursor (suppID IN NUMBER DEFAULT 1) IS SELECT * FROM products WHERE supplierid suppID; prod_record products%ROWTYPE; BEGIN OPEN prod_cursor(7); LOOP FETCH prod_cursor INTO prod_record; EXIT WHEN prod_cursor%NOTFOUND; dbms_output.put_line(产品编号: || prod_record.productid || 产品名称: || prod_record.productname || 供应商编号: || prod_record.supplierid); END LOOP; CLOSE prod_cursor; END; /这里 OPEN prod_cursor(7) 传入供应商 7会输出显示器、USB 集线器、网线三条记录。参数游标有个坑定义参数时不能指定长度约束比如suppID IN NUMBER(10)是非法的只能写suppID IN NUMBER。原因是参数类型在编译期只需要知道数据类型长度由实际传入值决定。3.3 游标 FOR 循环最省事的写法如果你不需要手动控制 OPEN、FETCH、CLOSE游标 FOR 循环是最推荐的方式。Oracle 会自动帮你打开游标、逐行提取、循环结束后自动关闭连循环变量都不用声明DECLARE CURSOR prod_cursor (suppID IN NUMBER DEFAULT 1) IS SELECT * FROM products WHERE supplierid suppID; BEGIN FOR v_pr IN prod_cursor(8) LOOP dbms_output.put_line(产品编号: || v_pr.productid || 产品名称: || v_pr.productname || 供应商编号: || v_pr.supplierid); END LOOP; END; /输出供应商 8 的两个产品笔记本支架、摄像头。注意 FOR 循环里绝对不能再用 OPEN、FETCH、CLOSE否则会报游标已打开之类的错误。循环变量 v_pr 的作用域只在循环体内出了循环就访问不到了。3.4 显式游标与隐式游标对比对比项显式游标隐式游标是否需要声明需要不需要Oracle 自动创建游标名自定义固定为 SQL打开/关闭手动或 FOR 循环自动自动适用场景返回多行的 SELECTDML 或单行 SELECT INTO%ISOPEN打开后为 TRUE永远为 FALSE能否参数化可以不可以选型建议很简单多行查询逐条处理用显式游标或游标 FOR 循环只是想知道 DML 影响了几行用隐式游标的 SQL%ROWCOUNT 就够了。别为了用游标而用游标单行查询直接 SELECT INTO 更清晰。4. 游标变量REF CURSOR动态结果集与多查询复用显式游标在声明时就绑定了查询结构固定所以叫静态游标。游标变量REF CURSOR则不同它声明时只定义类型打开时才决定查什么同一个变量可以先后指向不同结构的查询结果集。这在需要动态拼接 SQL、或者把结果集返回给调用方比如存储过程返回结果集给应用层时特别有用。使用游标变量分五步定义 REF CURSOR 类型、声明游标变量、OPEN ... FOR 打开、FETCH INTO 提取、CLOSE 关闭。注意检索游标变量只能用简单 LOOP 或 WHILE LOOP不能用 FOR 循环因为 FOR 循环要求游标在声明时就绑定查询。先看强类型游标变量返回值用 %ROWTYPE 约束DECLARE TYPE t_productsRef IS REF CURSOR RETURN products%ROWTYPE; v_cur t_productsRef; v_rec products%ROWTYPE; BEGIN OPEN v_cur FOR SELECT * FROM products WHERE categoryid 1; LOOP FETCH v_cur INTO v_rec; EXIT WHEN v_cur%NOTFOUND; dbms_output.put_line(产品名: || v_rec.productname || 类别: || v_rec.categoryid || 单价: || v_rec.unitprice); END LOOP; CLOSE v_cur; END; /也可以自定义记录类型作为返回值DECLARE TYPE t_prodRecord IS RECORD ( prodid products.productid%TYPE, prodname products.productname%TYPE ); TYPE t_prodRef IS REF CURSOR RETURN t_prodRecord; v_cur t_prodRef; v_rec t_prodRecord; BEGIN OPEN v_cur FOR SELECT productid, productname FROM products WHERE categoryid 2; LOOP FETCH v_cur INTO v_rec; EXIT WHEN v_cur%NOTFOUND; dbms_output.put_line(编号: || v_rec.prodid || 名称: || v_rec.prodname); END LOOP; CLOSE v_cur; END; /弱类型游标变量不指定 RETURN灵活性最高同一个变量可以打开多个不同结构的查询DECLARE TYPE prod_cursor IS REF CURSOR; v_cur prod_cursor; v_rec products%ROWTYPE; BEGIN -- 第一次打开类别为 1 OPEN v_cur FOR SELECT * FROM products WHERE categoryid 1; LOOP FETCH v_cur INTO v_rec; EXIT WHEN v_cur%NOTFOUND; dbms_output.put_line(第一次: || v_rec.productname || 单价 || v_rec.unitprice); END LOOP; CLOSE v_cur; -- 第二次打开单价小于 100 OPEN v_cur FOR SELECT * FROM products WHERE unitprice 100; LOOP FETCH v_cur INTO v_rec; EXIT WHEN v_cur%NOTFOUND; dbms_output.put_line(第二次: || v_rec.productname || 单价 || v_rec.unitprice); END LOOP; CLOSE v_cur; END; /这里有个关键点同一个游标变量在重新 OPEN 之前必须先 CLOSE否则会报 ORA-06511游标已打开。弱类型虽然灵活但编译期不做结构检查字段对不上要到运行期才报错所以生产代码里更推荐强类型除非确实需要动态结构。游标变量最常见的生产用途是存储过程返回结果集。比如定义一个过程把某个供应商的产品列表返回给调用方CREATE OR REPLACE PROCEDURE get_products_by_supplier ( p_supplierid IN NUMBER, p_cursor OUT SYS_REFCURSOR ) AS BEGIN OPEN p_cursor FOR SELECT productid, productname, unitprice FROM products WHERE supplierid p_supplierid; END; /调用时在匿名块里接收DECLARE v_cur SYS_REFCURSOR; v_id products.productid%TYPE; v_nm products.productname%TYPE; v_up products.unitprice%TYPE; BEGIN get_products_by_supplier(6, v_cur); LOOP FETCH v_cur INTO v_id, v_nm, v_up; EXIT WHEN v_cur%NOTFOUND; dbms_output.put_line(v_id || | || v_nm || | || v_up); END LOOP; CLOSE v_cur; END; /SYS_REFCURSOR 是 Oracle 预定义的弱类型游标变量省去了自己定义 TYPE 的麻烦做返回结果集的存储过程时基本都用它。5. 常见报错排查从 ORA-01001 到 ORA-06511游标相关的报错大多集中在打开状态不对和字段不匹配两类。下面按真实报错逐个拆解。ORA-01001: invalid cursor无效的游标这个错误通常出现在三种情况没 OPEN 就 FETCH、已经 CLOSE 又 FETCH、或者游标变量根本没赋值。比如DECLARE CURSOR c IS SELECT * FROM products; r products%ROWTYPE; BEGIN FETCH c INTO r; -- 报错没打开 END; /修复方式就是老老实实按 OPEN → FETCH → CLOSE 的顺序来。如果用了 FOR 循环就别再手动 OPEN。ORA-06511: PL/SQL: cursor already open游标已打开重复 OPEN 同一个游标会触发这个错误。常见于循环里反复 OPEN 却没 CLOSE-- 错误示范 OPEN v_cur FOR SELECT * FROM products WHERE categoryid 1; OPEN v_cur FOR SELECT * FROM products WHERE categoryid 2; -- 报错修复第二次 OPEN 之前先 CLOSE v_cur。或者干脆用 FOR 循环让 Oracle 自动管理生命周期。ORA-01002: fetch out of sequence提取顺序错误在 FOR 循环里手动 FETCH或者游标已经取完还继续 FETCH都可能触发。比如BEGIN FOR v IN (SELECT * FROM products) LOOP FETCH ... -- 报错FOR 循环内不能手动 FETCH END LOOP; END;记住一条铁律FOR 循环和手动 OPEN/FETCH/CLOSE 不能混用。ORA-06550 / PLS-00382: expression is of wrong type类型不匹配FETCH INTO 的变量顺序、类型、个数必须和 SELECT 列表一一对应。比如 SELECT 返回 3 列你 INTO 只给 2 个变量就会报错。用 %ROWTYPE 可以规避大部分这类问题因为整行结构自动对齐。ORA-01422: exact fetch returns more than requested number of rows这个不是游标本身的错而是 SELECT INTO 返回了多行。隐式游标只适合单行查询多行必须用显式游标或游标变量。排查时先确认 WHERE 条件是否能唯一确定一行。local proxy failed / 401 类连接错误如果你在客户端通过统一网关访问数据库或 API遇到 401 或 local proxy failed先检查三件套是否齐全Base URL、Key、Model ID或对应的服务标识。以 API 网关为例Base URL 填 https://taotoken.net/apiKey 填控制台生成的密钥模型标识按文档填。三者缺一不可顺序也不能错。配置片段大致如下{ base_url: https://taotoken.net/api, api_key: sk-你的密钥, model: 你的模型ID }如果是 Claude Code 这类工具接入配置通常放在 settings.json 里Base URL、Key、Model ID 同样要写全。改完配置记得重启客户端否则旧配置还在内存里。OAuth 相关报错有些工具用 OAuth 方式授权token 过期后会报认证失败。这时候去控制台重新生成或刷新凭证即可。注意别把 OAuth token 和 API Key 搞混两者用途不同。排障的通用思路先看报错号ORA- 开头的是数据库层PLS- 开头的是编译层再看是打开状态问题还是数据匹配问题最后用最小可复现脚本验证。把上面这些错误对照一遍基本能覆盖游标开发中 90% 的坑。6. 把游标用进真实项目从脚本到可维护的存储过程前面几节把语法和排障讲完了这一节说说怎么把游标用得像个老手。游标本身不难难的是在真实项目里控制好资源、异常和性能。第一优先用游标 FOR 循环。它自动处理 OPEN、FETCH、CLOSE代码短出错概率低。只有需要手动控制提取节奏比如每 N 行提交一次时才用手动四步走。第二异常处理一定要写。游标打开后如果中途抛异常不 CLOSE 会一直占着内存工作区。标准模板是DECLARE CURSOR c IS SELECT * FROM products; r products%ROWTYPE; BEGIN OPEN c; LOOP FETCH c INTO r; EXIT WHEN c%NOTFOUND; -- 业务处理 END LOOP; CLOSE c; EXCEPTION WHEN OTHERS THEN IF c%ISOPEN THEN CLOSE c; END IF; RAISE; END; /用 %ISOPEN 判断再关闭避免关闭未打开的游标二次报错。第三批量处理时注意提交频率。游标里逐行 UPDATE 会产生大量 undo数据量大时容易撑爆回滚段。可以每 1000 行 COMMIT 一次但要注意 COMMIT 会释放游标以外的锁事务一致性要自己权衡。第四能用 SQL 解决的别用游标。游标是逐行处理性能天然不如集合操作。比如给类别 1 打 9 折这种需求一条 UPDATE 就够别写成游标循环。游标适合的是每行逻辑不同、需要调用其他过程、需要复杂条件分支的场景。第五游标变量返回结果集时记得在文档里写清楚返回的列结构。弱类型 SYS_REFCURSOR 编译期不检查调用方如果按错误的列顺序 FETCH运行期才报错排查成本高。如果你在团队里做数据库开发建议把常用的游标模板沉淀成代码片段库新人直接套用减少低级错误。需要查 API 文档或生成密钥时可以去控制台的 API Keys 页面https://taotoken.net/api-keys和接入文档https://taotoken.net/doc对照配置想先验证模型或 SQL 逻辑用模型对话页面https://taotoken.net/chat快速试跑如果是长期做编码和 Agent 类任务Coding Planhttps://taotoken.net/coding-plan会更合适。这些入口按需取用别一股脑全配上配置越多越容易乱。最后留一个实操建议把本文的建表脚本和游标例子在 SQL*Plus 里从头跑一遍每跑一个就对照输出确认行数和字段值。游标这东西看十遍不如亲手 FETCH 一遍。跑通之后试着把参数游标改成存储过程再用 SYS_REFCURSOR 返回结果集你就完成了从会用游标到会用游标做接口的跨越。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

智慧养老与智能家居融合方案:从感知层选型到AI告警调参的工程实践 2026/10/2 18:42:15

智慧养老与智能家居融合方案:从感知层选型到AI告警调参的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
PyTorch损失函数详解:BCELoss与BCEWithLogitsLoss的对比与应用 2026/10/2 18:42:09

PyTorch损失函数详解:BCELoss与BCEWithLogitsLoss的对比与应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ROS2机械臂MPC轨迹规划:从MoveIt2底层链路到真实硬件闭环 2026/10/2 18:42:08

ROS2机械臂MPC轨迹规划:从MoveIt2底层链路到真实硬件闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Qt操作Word:基于COM接口的QAxObject实战与避坑指南 2026/10/2 18:41:55

Qt操作Word:基于COM接口的QAxObject实战与避坑指南

简介:面向需要在Qt应用中操作Word、Excel文档的C开发者,这套QtOffice轻量代码包围绕QtWord与QTOffice的常用接口做了二次封装,解决直接调用Office COM、OLE等复杂机制带来的高门槛问题。压缩包体积仅5KB,内部共5个文件&#xff0c…

阅读更多 →
Python解析通达信.day文件,结合pytdx搭建本地量化数据链 2026/10/2 18:41:54

Python解析通达信.day文件,结合pytdx搭建本地量化数据链

做量化的人,几乎都会遇到同一个坎:数据从哪来。市面上的免费数据接口今天能用明天就挂,付费终端一年好几千,对一个还没跑通策略的新手来说实在肉疼。后来我发现,电脑里装的通达信软件本来就在本地落地了一份完整的行情…

阅读更多 →
OpenShell:开源免费的MobaXterm平替,SSH/SFTP全能终端实战指南 2026/10/2 18:41:54

OpenShell:开源免费的MobaXterm平替,SSH/SFTP全能终端实战指南

去年年底,我把电脑上用了快三年的MobaXterm卸载了,换上了开源工具OpenShell。原因很简单:我手头维护的服务器越来越多,每次在Windows和远程Linux之间来回切窗口、传文件、开多个SSH会话,工具用得越来越别扭。OpenShell…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉