PIPE ROW 在 Oracle PL/SQL 中怎么写才不报错
直接说结论:pipe row 只能在定义为 pipelined 的函数里用,且函数返回类型必须是已声明的集合类型(比如 table of number),不能是 ref cursor 或标量。
常见错误现象:调用时抛 ORA-06553: PLS-382: expression is of wrong type,或者 ORA-06553: PLS-306: wrong number or types of arguments —— 大多因为返回类型没对上、忘了加 PIPELINED,或在普通函数里硬塞了 PIPE ROW。
- 函数头必须显式写
RETURN ... PIPELINED,不能只在 body 里用PIPE ROW - 返回集合类型需提前用
CREATE OR REPLACE TYPE建好,不能用匿名嵌套表(如RETURN SYS.ODCIVARCHAR2LIST是特例,但不推荐依赖) -
PIPE ROW每次只能传一个元素,类型必须和集合元素类型完全一致(包括精度、字符集、NULL 性) - 函数体内不能有
RETURN(除了最后的END RETURN),否则会提前退出,后续PIPE ROW不执行
示例(安全写法):
CREATE OR REPLACE TYPE num_list AS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION gen_nums(n IN NUMBER) RETURN num_list PIPELINED IS
BEGIN
FOR i IN 1..n LOOP
PIPE ROW(i); -- 类型匹配:num_list 元素是 NUMBER,i 是 NUMBER
END LOOP;
RETURN; -- 必须有,但不带值
END;
为什么用 PIPELINED 而不是普通函数返回集合
核心区别在执行模型:PIPELINED 函数是“边算边吐”,调用方(比如 SQL 查询)可以逐行消费结果;普通函数得等全部计算完、构造好整个集合再一次性返回,内存和延迟都高。
典型使用场景:处理大结果集但下游只取前几行(SELECT * FROM TABLE(gen_nums(1000000)) WHERE ROWNUM ),或需要流式 join / filter 的 ETL 场景。
- 非 pipelined 函数:Oracle 会把整个集合加载进 PGA,100 万行
NUMBER可能占几十 MB 内存 - Pipelined 函数:每调一次
PIPE ROW,就向调用方送一行,PGA 压力恒定(常驻约 1–2 行缓冲) - 兼容性注意:10g+ 支持,但 9i 不支持;如果函数被物化视图引用,部分版本要求必须
DETERMINISTIC(而 pipelined 函数通常不满足)
TABLE(gen_nums(...)) 在 SQL 里怎么避免 ORA-22905 错误
ORA-22905: cannot access rows from a non-nested table item 是最常卡住人的点 —— 根本原因:你用了 TABLE(),但 Oracle 没法确认这个函数返回的是合法嵌套表类型。
触发条件:函数返回类型是本地声明的类型(比如包里定义的 TYPE t IS TABLE OF ...),或用了未授权的系统类型(如 SYS.ODCIDATELIST)。
- 必须用
CREATE TYPE在 schema 级别创建集合类型,不能在包或函数内定义 - 确保函数签名里写的返回类型和
CREATE TYPE名字**完全一致**(大小写敏感,除非加双引号) - 如果函数属于某个 schema,调用方要带 schema 前缀:
SELECT * FROM TABLE(my_schema.gen_nums(10)) - 权限问题:调用方需对类型有
EXECUTE权限(GRANT EXECUTE ON num_list TO user_x)
PIPE ROW 性能陷阱:什么情况下反而比普通函数慢
不是所有场景都适合 pipelined。当数据量小、调用频繁、或下游必须全量消费时,函数调用开销(context switch、row-by-row 驱动)可能盖过内存优势。
容易踩的坑:
- 在循环里反复调用
PIPE ROW但每次只传 1 行,却没做批量缓冲(比如攒 100 行再 pipe 一次)—— 上下文切换成本远高于数据生成本身 - 函数里做了重 IO(如每次
PIPE ROW都查一次表),变成 N+1 查询,比一次性 bulk collect + return 更慢 - 忘记加
PARALLEL_ENABLE却在并行查询中使用,导致无法并行化,而普通函数有时反能被优化器自动并行 - 调试时加了
DBMS_OUTPUT.PUT_LINE,每行都触发一次输出,性能断崖下跌(生产务必关掉)
真正关键的复杂点在于:pipelined 不是银弹,它把控制权从 SQL 引擎移交给了 PL/SQL 过程逻辑。一旦你在里面混入阻塞操作、状态依赖或隐式锁,行为就很难预测 —— 这个边界,比语法难得多。










