ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Oracle合并多个sys_refcursor的三种方案与避坑指南

Oracle合并多个sys_refcursor的三种方案与避坑指南 简介面向Oracle开发人员针对存储过程中多个sys_refcursor动态游标难以合并的痛点提供了一套基于XML序列化与解析的解决方案。内容从实际业务背景出发对比了重写逻辑与复制代码的弊端重点介绍了利用xmltype构造函数将游标转为XML、通过DBMS_LOB操作CLOB数据、借助XPath提取ROW节点并最终用XMLTable还原为游标的完整流程。文中配有可直接参考的PL/SQL代码示例展示了合并两个同结构游标的具体写法与输出效果便于读者快速理解并迁移到自己的存储过程优化中。资源为PDF文档共1个文件压缩包大小约75KB内容紧凑实用。目前已有525人浏览学习适合需要处理复杂游标合并、想减少重复代码的Oracle中高级开发人员在日常开发与调优时参考。1. Oracle 如何合并多个 sys_refcursor存储过程里的高频需求真没有现成语法在 Oracle 存储过程里被问得最多的一个需求就是把两个甚至多个 sys_refcursor 结果集合并成一个返回给上层应用。做过报表接口的人都有体会业务方今天要 A 部门的数据明天要 AB 部门后天可能还加一个 C你不想给每个排列组合各写一个存储过程于是自然想到——把几个游标变量塞在一起返回。问题是Oracle 官方并没有提供 merge_cursor(c1, c2) 这类内置函数UNION ALL 这种 SQL 层操作在游标变量身上根本不成立。这个需求通常出现在三种场景里跨表合并不同来源的数据比如订单表和历史归档表各出一个游标同一个表按不同条件动态拼 SQL分别打开游标后想统一返回还有老系统改造上游模块已经把结果封装成 sys_refcursor 传了过来你只能在外面接住再合并。无论是哪种最终产出都是一个能被 JDBC 或 ODP.NET 正常读取的游标。这篇文章从游标变量的内存模型讲起给出三条可复现的合并路线管道函数、BULK COLLECT、全局临时表。再把列结构对不上、空游标、游标泄漏这些坑逐个点名。适合 PL/SQL 开发、做数据接口的工程师以及被“查不到数据”的报表折磨的 Java 后端同事。我会把参数和边界条件写清楚看完你可以直接按自己的表结构替换类型定义落地到项目里。2. 先搞懂 sys_refcursor 的游标变量模型为什么不能直接 UNION ALL2.1 sys_refcursor 是什么弱类型游标变量只是“结果集的指针”对 Oracle 入门阶段的朋友我把 sys_refcursor 比作一个黑匣子定义时不绑定任何结果集结构只有执行了OPEN v_cur FOR 一段 SQL之后它才指向会话内部私有 SQL 区里的一个游标状态。它不是一个独立存在的结果集更不像临时表那样把行拷贝到磁盘上。你只能向前 FETCH取过的行不会留在任何地方等你回头再查。DECLARE v_cur SYS_REFCURSOR; v_emp emp%ROWTYPE; BEGIN OPEN v_cur FOR SELECT * FROM emp WHERE deptno 10; LOOP FETCH v_cur INTO v_emp; EXIT WHEN v_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_emp.ename || - || v_emp.sal); END LOOP; CLOSE v_cur; END; /这段代码里的 v_cur 只能单向消费%NOTFOUND是判断结束的唯一可靠信号。理解了这一点你就明白合并多个 sys_refcursor 的本质不是把两块数据“粘”成一个表而是把两个游标的遍历过程归并到一个新的返回通道里。这里还有一个很多人踩过的认知坑你没法对游标变量做 COUNT(*)因为它不是表对象。想数两个游标各自多少行只能先 FETCH 一遍数完再重新 OPEN 一次消费过的数据已经回不去了。所以“合并前先确认数据量”这件事在纯游标场景下很难做到这也是后面选方案时要把数据量放在第一位的原因。2.2 UNION ALL 对游标变量不成立语法边界与快照语义SQL 层 UNION ALL 直接作用于两个游标变量Oracle 语法上不支持。你写SELECT * FROM c1 UNION ALL SELECT * FROM c2在解析阶段就会报错因为游标变量不是可引用的表对象。这不是写法问题是模型问题游标变量的数据流是单向的SQL 引擎无法把它当作一张物化视图。但很多人忽略了一个更简单的解法如果两个结果集都来自你能控制的 SQL直接在 SQL 层合并才是正道。OPEN v_cur FOR SELECT empno, ename, sal FROM emp WHERE deptno 10 UNION ALL SELECT empno, ename, sal FROM emp WHERE deptno 20;这条语句完成的事和合并两个游标完全一样而且它只有一个执行计划、一份快照、没有游标泄漏问题。只有当游标是外部传入比如上游模块已经 OPEN 好传给你、来源是动态 SQL 且无法拼到同一句、或者两个游标来自不同物理对象时才需要做游标层面的合并。这里有一个容易被忽略的语义问题两个游标是在不同时刻 OPEN 的对应的读一致性快照 SCN 可能不同。把它们合并后当同一批数据返回严格来说不是一致快照下的结果。普通报表影响不大但涉及金额统计、对账类接口尽量把两个 OPEN 放在同一个事务里或者让动态 SQL 带上 AS OF 闪回查询把快照对齐。2.3 动手前先回答三个问题列结构、数据量、去重与排序写合并代码之前我一般先确认三件事这三件事直接决定走哪条路。第一列结构是否固定。两个游标最终 SELECT 出来的列数、列类型、列顺序是否完全一致一致就用管道函数或者 BULK COLLECT不一致要先做列结构归一或者直接走全局临时表。第二数据量级。几百行和几百万行是两个世界。前者逐行 FETCH 都能接受后者逐行 PIPE ROW 会让接口慢到被业务方投诉。第三结果要不要去重、排序、分页。合并去重是这类需求的第一关如果业务上要求“同一工号出现两次只取一条”你不能只把两个游标接起来得在合并出口加一个去重层。这三个问题回答完方案基本就定了。下面两章分别给管道函数、BULK COLLECT、全局临时表的具体做法、参数和适用边界。3. 用管道函数合并 sys_refcursor列结构固定时的首选方案3.1 第一步在 SQL 层定义对象类型和嵌套表类型管道函数PIPELINED允许你把 FETCH 到的每一行通过PIPE ROW发出去消费方像查表一样从函数里读数据。但它有一个硬性前置条件返回类型必须能被 SQL 引擎识别所以要么是 SQL 层用 CREATE TYPE 建的对象类型要么是系统内置集合类型。CREATE OR REPLACE TYPE emp_rec AS OBJECT ( empno NUMBER, ename VARCHAR2(50), sal NUMBER(10,2) ); / CREATE OR REPLACE TYPE emp_tab AS TABLE OF emp_rec; /这两段代码分别做了什么emp_rec 定义了一行的结构三个属性对应我们要返回的三列emp_tab 是 emp_rec 的嵌套表类型也就是管道函数的返回载体。命名里的 emp 只是示例生产环境建议按业务对象起名比如 order_rec / order_tab 之类。为什么要放在 SQL 层而不是包里面因为如果把 emp_tab 定义在包 spec 里它就成了 PL/SQL 私有的局部集合类型SQL 引擎无法在SELECT ... FROM TABLE(...)里解析它运行时会直接报 PLS-00642。这是新手最容易翻车的地方后面避坑章节还会单独讲。3.2 第二步写 PIPELINED 管道函数逐个 FETCH 再 PIPE ROW假设两个游标的列结构完全一致都是 empno、ename、sal 三列。管道函数的核心逻辑就是“先取完第一个游标再取第二个每取到一行就发出去一行”。CREATE OR REPLACE FUNCTION merge_emp_cursors( p_c1 IN SYS_REFCURSOR, p_c2 IN SYS_REFCURSOR ) RETURN emp_tab PIPELINED IS TYPE t_emp_rec IS RECORD ( empno NUMBER, ename VARCHAR2(50), sal NUMBER ); v_row t_emp_rec; BEGIN IF p_c1 IS NOT NULL THEN LOOP FETCH p_c1 INTO v_row; EXIT WHEN p_c1%NOTFOUND; PIPE ROW (emp_rec(v_row.empno, v_row.ename, v_row.sal)); END LOOP; CLOSE p_c1; END IF; IF p_c2 IS NOT NULL THEN LOOP FETCH p_c2 INTO v_row; EXIT WHEN p_c2%NOTFOUND; PIPE ROW (emp_rec(v_row.empno, v_row.ename, v_row.sal)); END LOOP; CLOSE p_c2; END IF; RETURN; END merge_emp_cursors; /逐行解释一下函数内部定义了一个局部 RECORD 类型 t_emp_rec用来接收 FETCH 结果。FETCH 是按位置绑定的第一列进 empno第二列进 ename第三列进 sal跟列名无关。每取到一行就调用对象类型的构造器 emp_rec(...) 生成一个对象PIPE ROW 发出去。这里之所以不直接把 v_row 丢进 PIPE ROW是因为 v_row 是 PL/SQL RECORD不是 emp_rec 对象类型对不上。有两个细节值得注意。一是 CLOSE 的时机我在两个游标各自的循环结束后立即 CLOSE因为管道函数在消费方提前终止时可能根本跑不到 RETURN游标会在会话里一直挂着直到客户端关闭连接才释放。二是空游标保护IF p_c1 IS NOT NULL可以挡住从未 OPEN 过的游标变量避免 CLOSE 一个无效游标时抛 ORA-01001。3.3 第三步在存储过程里把合并结果打包成输出游标管道函数本身返回的是集合不是游标。要让上层 Java 代码像读取普通游标一样取数还需要一个存储过程把SELECT * FROM TABLE(merge_emp_cursors(...))包进输出游标。CREATE OR REPLACE PROCEDURE get_merged_emp( p_deptno1 IN NUMBER, p_deptno2 IN NUMBER, p_out OUT SYS_REFCURSOR ) IS v_c1 SYS_REFCURSOR; v_c2 SYS_REFCURSOR; BEGIN OPEN v_c1 FOR SELECT empno, ename, sal FROM emp WHERE deptno :d USING p_deptno1; OPEN v_c2 FOR SELECT empno, ename, sal FROM emp WHERE deptno :d USING p_deptno2; OPEN p_out FOR SELECT * FROM TABLE(merge_emp_cursors(v_c1, v_c2)); END get_merged_emp; /这里的核心写法是OPEN p_out FOR SELECT * FROM TABLE(fn(...))Oracle 会把管道函数的输出当作一张虚拟表而游标变量 v_c1、v_c2 作为函数入参传进去。由于管道函数是惰性执行的p_out 打开时函数还没有真正跑起来直到客户端开始 FETCH p_out函数才一行一行消费两个输入游标并产出合并结果。所以调用方的行为直接影响资源释放如果客户端只 FETCH 前 50 行就把输出游标关闭管道函数很可能没有机会把 v_c1、v_c2 的循环跑完CLOSE 语句不会执行。这个场景下就出现了“函数里明明写了 CLOSE游标还是泄漏”的奇怪现象后面避坑章节会展开讲。3.4 动态 SQL 列数不固定时怎么办先做列结构归一管道函数要求返回类型在编译时确定这跟动态 SQL 的“运行时才知道列数”天生冲突。实际项目里最常见的妥协方案是在拼动态 SQL 时就把每个游标的列结构强制归一到同一个形状。-- 第一个游标新表字段很规整 OPEN v_c1 FOR SELECT empno, ename, sal FROM emp WHERE deptno :d; -- 第二个游标历史归档表字段名和类型乱七八糟 OPEN v_c2 FOR SELECT TO_NUMBER(archive_no) AS empno, TO_CHAR(emp_name) AS ename, TO_NUMBER(total_sal) AS sal FROM emp_archive_2019 WHERE deptno :d;归一的原则很简单用 TO_NUMBER、TO_CHAR、TO_DATE 把每个游标的 SELECT 列表强制变成管道函数对象类型对应的三列。只要列数和类型对齐FETCH 就能稳定工作。别再依赖 Oracle 的隐式转换——生产环境下字段里混进一个非数字字符串整个合并结果就变成一个数字都取不出来的怪胎。如果连归一都做不到比如列数是随参数变化的管道函数就不是你的菜直接去看第四章的全局临时表方案。4. 数据量大或结构动态时的另外两条路BULK COLLECT 与全局临时表4.1 BULK COLLECT 批量收集用 LIMIT 控制内存适合十万行以内管道函数是逐行处理每次 PIPE ROW 都是一次过程调用行数上去之后开销不小。当单个游标的数据量在几千到十万行之间我一般改用 BULK COLLECT一次 FETCH 拿一批进内存攒完再整体返回。CREATE OR REPLACE PROCEDURE merge_emp_bulk( p_c1 IN SYS_REFCURSOR, p_c2 IN SYS_REFCURSOR, p_out OUT emp_tab ) IS v_acc emp_tab : emp_tab(); v_batch emp_tab : emp_tab(); BEGIN IF p_c1 IS NOT NULL THEN LOOP FETCH p_c1 BULK COLLECT INTO v_batch LIMIT 1000; EXIT WHEN v_batch.COUNT 0; FOR i IN 1 .. v_batch.COUNT LOOP v_acc.EXTEND; v_acc(v_acc.COUNT) : v_batch(i); END LOOP; END LOOP; CLOSE p_c1; END IF; IF p_c2 IS NOT NULL THEN LOOP FETCH p_c2 BULK COLLECT INTO v_batch LIMIT 1000; EXIT WHEN v_batch.COUNT 0; FOR i IN 1 .. v_batch.COUNT LOOP v_acc.EXTEND; v_acc(v_acc.COUNT) : v_batch(i); END LOOP; END LOOP; CLOSE p_c2; END IF; p_out : v_acc; END merge_emp_bulk; /关键参数是LIMIT 1000。LIMIT 决定每次批量取多少行也直接决定 PGA 里这一批数据占多大内存。取值太小退化成逐行失去批量的意义取值太大比如五万行一批遇到超宽表会撑爆 PGA。我常用的区间是 500 到 2000默认给 1000。项目里如果发现会话的 PGA 涨得离谱优先把这个数字降下来。注意一个行为细节每次 FETCH BULK COLLECT 都会重新填充 v_batch上一批数据会被覆盖所以循环里只要把当前这一批追加进 v_acc 就行。整个过程只在 PL/SQL 和 SQL 引擎之间做了几次上下文切换比逐行 FETCH 快一个量级。4.2 全局临时表列结构多变时的兜底方案碰到列结构动态变化、或者两个游标列数都不一致的需求管道函数和 BULK COLLECT 都使不上劲只能上全局临时表GTT。思路是把两个游标分别 FETCH 出来按统一的目标列 INSERT 进临时表最后返回一个查临时表的游标。CREATE GLOBAL TEMPORARY TABLE gtt_emp_merge ( empno NUMBER, ename VARCHAR2(50), sal NUMBER(10,2) ) ON COMMIT PRESERVE ROWS; / CREATE OR REPLACE PROCEDURE merge_emp_gtt( p_c1 IN SYS_REFCURSOR, p_c2 IN SYS_REFCURSOR, p_out OUT SYS_REFCURSOR ) IS v_row t_emp_rec; BEGIN DELETE FROM gtt_emp_merge; -- 清掉本会话上一次的残留 IF p_c1 IS NOT NULL THEN LOOP FETCH p_c1 INTO v_row; EXIT WHEN p_c1%NOTFOUND; INSERT INTO gtt_emp_merge VALUES (v_row.empno, v_row.ename, v_row.sal); END LOOP; CLOSE p_c1; END IF; IF p_c2 IS NOT NULL THEN LOOP FETCH p_c2 INTO v_row; EXIT WHEN p_c2%NOTFOUND; INSERT INTO gtt_emp_merge VALUES (v_row.empno, v_row.ename, v_row.sal); END LOOP; CLOSE p_c2; END IF; OPEN p_out FOR SELECT empno, ename, sal FROM gtt_emp_merge ORDER BY empno; END merge_emp_gtt; /GTT 有两种提交模式ON COMMIT PRESERVE ROWS 表示数据保留到会话结束适合“过程填充、调用方慢慢读游标”的场景ON COMMIT DELETE ROWS 表示提交后立刻清空如果过程内部有 COMMIT合并完打开输出游标时数据已经没了。所以我建议用 PRESERVE ROWS并在过程开头执行 DELETE避免同一个会话第二次调用时把旧数据累加进去。GTT 的数据是会话私有的两个会话各写各的不存在互相污染的问题这也让它成为并发接口里相对安全的兜底方案。代价是插入操作本身有写开销加上临时表空间的使用性能上线比管道函数低。但它在“列结构完全动态”这个场景下几乎没有替代品。4.3 三条路线怎么选内存、性能、灵活性的取舍把三条路放到一起对比选择困难症会轻很多维度管道函数BULK COLLECT全局临时表内存占用低流水式产出中按 LIMIT 攒批低但占用临时表空间列结构固定要求必须固定必须固定不要求INSERT 时映射适合数据量五千行以内十万行以内十万行以上或动态列实现复杂度中类型加函数中循环加追加低建表加插入游标关闭责任函数内部需 CLOSE过程内部需 CLOSE过程内部需 CLOSE是否消耗磁盘否否是我自己的选择习惯是列结构固定且行数少用管道函数列结构固定但行数上万用 BULK COLLECT列数不固定、类型混乱、或者上游游标来自完全不同的系统用全局临时表。数据量大时管道函数反而慢是因为 PIPE ROW 逐行调用开销被放大了全局临时表看起来简单但大批量插入一样会把临时表空间写满所以不是万能后悔药。5. 合并 sys_refcursor 避坑指南5 个真实踩坑记录5.1 ORA-01000游标没关干净跑几轮就爆现象接口调用十几轮之后数据库报ORA-01000: maximum open cursors exceeded应用日志里一把一把的异常。原因最常见的来源是管道函数的输入游标没被 CLOSE。你以为写了 CLOSE 就万事大吉但客户端只取了输出游标的前几十行就关闭管道函数来不及跑完循环后面的 CLOSE 根本执行不到。另外过程里如果出现异常没有 EXCEPTION 块兜底已经 OPEN 的 v_c1、v_c2 一样挂在那里。解决先分清是“没写 CLOSE”还是“写了但没机会执行”。没写就补上写了但客户端提前终止就把方案换成 BULK COLLECT 或 GTT让游标在过程内部被完整消费并关闭再输出最终结果。临时调大 OPEN_CURSORS 只能拖延问题不是解法。5.2 列顺序不一致合并结果张冠李戴现象合并结果能查出来但 empno 那一列显示的是字符串sal 列全是 0业务方一眼就看出数据乱了。原因FETCH 是按位置绑定不是按列名绑定。两个游标一个写SELECT empno, ename, sal另一个写SELECT empno, sal, ename管道函数完全不知道第二个游标的第二列其实是工资照样塞进对象类型的第二个属性。解决在动态 SQL 里统一显式列名和列序禁止用 SELECT *。如果两个游标来自不同历史时期的表字段命名都不一样就用 TO_NUMBER、TO_CHAR 强制归一。开发阶段可以先用 6.1 里的 DBMS_SQL 描述器把两个游标的列结构打出来对比别用眼睛去猜。5.3 包内类型不能进 TABLE()PLS-00642 与包状态被丢弃现象把 emp_tab 类型定义在包 spec 里编译没问题一执行SELECT ... FROM TABLE(merge_func(...))就报PLS-00642: local collection types not allowed in SQL statements。原因包里的集合类型是 PL/SQL 局部类型SQL 引擎认不出来凡是要在 FROM 子句里用管道函数返回类型必须是 CREATE TYPE 建出来的 SQL 类型。解决把对象类型和嵌套表类型放到 SQL 层。还有一个连锁坑如果你在开发环境反复 DROP TYPE 再重建依赖这些类型的函数会失效其他会话再调用可能遇到ORA-04068: existing state of packages has been discarded。这通常不是代码逻辑错了是类型对象被重建、包状态被清掉了重新编译一次包即可。5.4 空游标不等于“零行游标”ORA-01001 和 IS NULL 判断的坑现象上游传进来的游标参数有时根本没 OPEN你直接 CLOSE 它报ORA-01001: invalid cursor有时候游标 OPEN 了但查不到任何行合并结果比预期少调用方还怀疑你吞了数据。原因未 OPEN 的 sys_refcursor 变量是 NULL对它做 CLOSE 就是违法的但 OPEN 后零行数据的游标变量不是 NULLIS NULL 判断区分不了这两种状态。解决在管道函数和存储过程里统一加IF p_c1 IS NOT NULL THEN再 FETCHFETCH 循环靠%NOTFOUND退出然后 CLOSE。这两层逻辑缺一不可。更重要的约定在调用侧上游约定“没数据也要返回一个打开的空游标”而不是传 NULL这样下游代码就只需要处理一种形态。5.5 全局临时表越合并越多重复调用前没清理残留现象同一个存储过程在同一个会话里调用两次第二次返回的行数是第一次的两倍第三次是三倍。原因GTT 用了 ON COMMIT PRESERVE ROWS会话内的数据一直保留。过程开头没有 DELETE第二次调用又把新数据 INSERT 进去旧数据全留在表里。解决在过程开头加DELETE FROM gtt_emp_merge;。如果多个过程共用同一张 GTT更稳妥的做法是加一个 run_id 列每次调用生成新的 run_id输出游标只查当前 run_id 的数据这样即使并发调用也不会串数据。6. 验证合并结果与进阶技巧把这三个技巧沉淀成自己的轮子6.1 用 DBMS_SQL 描述器核对游标列结构前面反复强调列结构要一致但两个动态 SQL 拼出来到底是什么形状最好用工具验证而不是靠肉眼。DBMS_SQL 可以把 ref cursor 转成游标号然后 DESCRIBE 出每一列的元数据。CREATE OR REPLACE FUNCTION describe_columns( p_cur IN SYS_REFCURSOR ) RETURN VARCHAR2 IS v_cid NUMBER; v_cnt NUMBER; v_desc DBMS_SQL.DESC_TAB; v_result VARCHAR2(4000); BEGIN v_cid : DBMS_SQL.TO_CURSOR_NUMBER(p_cur); DBMS_SQL.DESCRIBE_COLUMNS(v_cid, v_cnt, v_desc); FOR i IN 1 .. v_cnt LOOP v_result : v_result || v_desc(i).col_name || ,; END LOOP; DBMS_SQL.CLOSE_CURSOR(v_cid); RETURN RTRIM(v_result, ,); END describe_columns; /调用后返回的是“列1,列2,列3”这样的字符串把两个游标的结果并排比对就能发现顺序差异。有一点必须提醒TO_CURSOR_NUMBER 转换后原来的 ref cursor 变量就不能再用了这个函数只适合开发联调阶段做检查不能在生产路径上随手调用。6.2 写一个可复用的回归断言脚本合并逻辑改一次就怕一次所以我把回归验证也做成固定脚本先算出预期行数再跑合并函数最后 FETCH 输出游标数一遍实际行数。DECLARE v_c1 SYS_REFCURSOR; v_c2 SYS_REFCURSOR; v_out SYS_REFCURSOR; v_expected NUMBER : 0; v_actual NUMBER : 0; v_rec emp_rec; BEGIN SELECT COUNT(*) INTO v_expected FROM emp WHERE deptno IN (10, 20); OPEN v_c1 FOR SELECT empno, ename, sal FROM emp WHERE deptno 10; OPEN v_c2 FOR SELECT empno, ename, sal FROM emp WHERE deptno 20; OPEN v_out FOR SELECT * FROM TABLE(merge_emp_cursors(v_c1, v_c2)); LOOP FETCH v_out INTO v_rec; EXIT WHEN v_out%NOTFOUND; v_actual : v_actual 1; END LOOP; CLOSE v_out; IF v_actual v_expected THEN DBMS_OUTPUT.PUT_LINE(PASS: merged rows || v_actual); ELSE DBMS_OUTPUT.PUT_LINE(FAIL: expected || v_expected || , actual || v_actual); END IF; END; /这个脚本的价值在于把“合并结果对不对”变成了一个可以反复执行的断言。改一列类型、调一下 LIMIT、换一种合并顺序跑一遍就知道有没有破坏行数。金额敏感的业务我还会再加一个 SUM(sal) 的断言合并后的总金额必须等于两个来源之和。6.3 进阶玩法合并去重、统一排序与分页合并之后往往还要处理业务上的重复。比如两个游标里都有工号 1001 的员工报表只想要一条这时候在 TABLE 外面套一层去重即可。OPEN p_out FOR SELECT empno, ename, sal FROM ( SELECT empno, ename, sal, ROW_NUMBER() OVER (ORDER BY empno, ename) AS rn FROM ( SELECT empno, ename, sal FROM TABLE(merge_emp_cursors(p_c1, p_c2)) GROUP BY empno, ename, sal ) ) WHERE rn BETWEEN :v_start AND :v_end;GROUP BY 在这里承担去重职责ROW_NUMBER 生成连续序号外层 WHERE 按序号做分页这属于 Oracle 分页的常见写法。如果要显式区分数据来源可以在管道函数里多输出一个 source_flag 列第一个游标给 1第二个给 2排序时ORDER BY source_flag, empno这样合并结果保留了来源信息排查问题时不至于抓瞎。最后一页的分页有个性能真相必须说清楚管道函数要先把所有行都产出来ROW_NUMBER 才有办法排序编号。所以这个写法不会因为“只要第一页”就少干活资源在管道函数这一层已经全部花完了。真有大结果集分页需求把管道函数换成 BULK COLLECT 或者 GTT先落库再分页内存压力会小很多。我现在的习惯是接到合并游标的需求先问三件事——列结构能不能固定、调用方会不会只取一部分行就关闭、有没有去重分页的附加条件。问完再决定写管道函数还是走临时表。这三步让我少熬了很多夜也少接了很多线上半夜的告警电话。这套验证脚本和避坑清单希望帮到你。本文还有配套的精品资源点击获取
返回列表