必须用sys_refcursor而非普通游标,因其是弱类型引用游标,可跨作用域传递并由调用方fetch;普通游标静态绑定、无法传出过程边界。

直接用 SYS_REFCURSOR 作 OUT 参数,就能让存储过程返回结果集——不是把数据复制出来,而是把游标句柄交出去,调用方自己 fetch。
为什么必须用 SYS_REFCURSOR 而不是普通游标变量
普通显式游标(比如 CURSOR c IS SELECT ...)是静态绑定的,只能在声明它的 PL/SQL 块内打开、fetch、close,不能传出过程边界。而 SYS_REFCURSOR 是弱类型引用游标,本质是个指针:它不关心查询结构,同一变量可先后 OPEN FOR SELECT * FROM t1 和 OPEN FOR SELECT x,y FROM t2;能跨作用域传递;客户端(JDBC、SQL Developer、SQL*Plus)能识别并展示其结果集。
常见错误现象:PLS-00382: expression is of wrong type —— 误把普通游标变量当 OUT 参数传;或声明成 IN OUT 却没初始化;或在声明区就写 OPEN(语法非法)。
-
SYS_REFCURSOR必须声明为OUT(不能是IN或IN OUT) -
OPEN ... FOR必须写在BEGIN块里,不能放在声明区 - 不能对
SYS_REFCURSOR直接FETCH—— 它本身不是结果,只是句柄,fetch 操作由调用方完成
CREATE OR REPLACE PROCEDURE 中怎么声明和打开 SYS_REFCURSOR
声明格式固定:p_result OUT SYS_REFCURSOR;OPEN p_result FOR 后跟任意合法 SELECT(支持绑定变量、子查询、甚至动态 SQL)。
示例:
CREATE OR REPLACE PROCEDURE get_user_data(
p_dept_id IN NUMBER,
p_result OUT SYS_REFCURSOR
) IS
BEGIN
OPEN p_result FOR
SELECT id, name, salary
FROM users
WHERE dept_id = p_dept_id AND status = 'ACTIVE';
END;
注意点:
- 查询语句中用
=而非:=—— 这是 SQL,不是赋值 - 若需动态 SQL(如拼表名),用
EXECUTE IMMEDIATE '...' INTO ...不适用;正确方式是OPEN p_result FOR sqlstr USING bind_var - Oracle 11g 支持
USING绑定变量,避免 SQL 注入,也提升硬解析复用率
调用时怎么拿到并遍历这个游标结果
不能在另一个存储过程中直接 FETCH —— 那是对本地游标的用法。这里要分两步:先执行过程获取句柄,再从该句柄读数据(通常在匿名块或客户端)。
PL/SQL 匿名块调用示例:
BEGIN get_user_data(p_dept_id => 10, p_result => :cur_out); END;
然后在 SQL Developer 或 PL/SQL Developer 的 Variables 面板里,把 cur_out 类型设为 Cursor,点 Value 栏右侧图标展开结果。
JDBC 中对应的是:
CallableStatement.registerOutParameter(?, Types.OTHER)cs.execute()ResultSet rs = cs.getResultSet()
容易踩的坑:
- SQL*Plus 默认不显示游标结果,需提前执行
SET SERVEROUTPUT ON并配合DBMS_OUTPUT.PUT_LINE手动输出(但一般不用——直接查游标更直观) - MyBatis 中若用
resultType="map"接收,需确保 mapper XML 中statementType="CALLABLE"且参数mode="OUT" - 游标未被消费完就断开连接?Oracle 会自动 close,但长期持有大结果集仍可能撑爆 PGA 内存
真正麻烦的从来不是写存储过程本身,而是调用方是否理解:这不是“返回一张表”,而是“返回一个可遍历的句柄”——它不带 schema 元信息,不自动转换类型,也不保证顺序,全靠调用方按约定处理。尤其在跨语言调用时,Types.OTHER 和 getResultSet() 的组合稍有错位,就会静默失败。











