傳統 PL/SQL 函數返回集合(Nested Table / VARRAY)需完全構建內存集合後一次性返回,缺點明顯:
管道化表函數 通過 `PIPE ROW` 逐行輸出,實現流式處理、即時返回、恆定內存佔用,並可直接在 SQL `FROM` 子句中通過 `TABLE()` 操作符調用,如同查詢普通表/視圖。
-- 單行輸出結構
CREATE OR REPLACE TYPE obj_dept_sal AS OBJECT (
deptno NUMBER,
max_sal NUMBER,
min_sal NUMBER
);
/
-- 集合類型 (必須為 Nested Table 或 VARRAY,關聯表函數返回類型)
CREATE OR REPLACE TYPE tab_dept_sal AS TABLE OF obj_dept_sal;
/
CREATE OR REPLACE FUNCTION get_dept_sal_range (p_deptno IN NUMBER)
RETURN tab_dept_sal PIPELINED -- 關鍵字:聲明為管道化
AUTHID DEFINER -- 或 CURRENT_USER,根據權限模型選擇
IS
v_rec obj_dept_sal := obj_dept_sal(NULL, NULL, NULL); -- 預實例化對象,減少分配開銷
v_err_code NUMBER;
v_err_msg VARCHAR2(4000);
BEGIN
-- 1. 遊標遍歷源數據 (此處為示例單行聚合,實際可為複雜邏輯/多表聯接)
FOR c IN (
SELECT deptno, MAX(sal) max_sal, MIN(sal) min_sal
FROM emp
WHERE deptno = p_deptno
GROUP BY deptno
) LOOP
-- 2. 填充對象屬性
v_rec.deptno := c.deptno;
v_rec.max_sal := c.max_sal;
v_rec.min_sal := c.min_sal;
-- 3. 核心:管道輸出單行,即時返回給調用者
PIPE ROW (v_rec);
END LOOP;
-- 4. 必須以空 RETURN 結束 (不返回值)
RETURN;
EXCEPTION
WHEN OTHERS THEN
v_err_code := SQLCODE;
v_err_msg := SUBSTR(SQLERRM, 1, 4000);
-- 記錄錯誤日誌 (自主事務避免回滾主事務)
INSERT INTO etl_error_log (proc_name, err_code, err_msg, err_time, input_params)
VALUES ('GET_DEPT_SAL_RANGE', v_err_code, v_err_msg, SYSTIMESTAMP, 'DEPTNO='||p_deptno);
COMMIT; -- 自主事務需在獨立過程中提交,此處簡化
-- 重新拋出或靜默結束,視業務而定
RAISE; -- 讓調用者感知異常
END get_dept_sal_range;
/
-- 基礎調用
SELECT * FROM TABLE(get_dept_sal_range(10));
-- 進階:聯合查詢、篩選、排序
SELECT d.dname, f.max_sal, f.min_sal
FROM dept d,
TABLE(get_dept_sal_range(d.deptno)) f
WHERE d.loc = 'NEW YORK'
ORDER BY f.max_sal DESC;
-- 並行化 (需函數無副作用、無會話狀態依賴)
SELECT /*+ PARALLEL(f, 4) */ * FROM TABLE(get_dept_sal_range(20)) f;
| 機制 | 說明 | 最佳實踐 |
|---|---|---|
| PIPE ROW | 將單行對象推入管道輸出流。每次調用觸發一次上下文切換 (PL/SQL → SQL 引擎) | 批量處理場景考慮批量綁定:在循環中累積 N 行 (如 1000) 再 `PIPE ROW`,但標準管道函數不支援批量 PIPE。若極度追求吞吐,改用 `DBMS_SQL.RETURN_RESULT` (12c+) 或客戶端數組獲取。 |
| 流式處理 | 調用者 (SQL 引擎) 按需拉取行,函數暫停/恢復執行 (協程模式) | 避免在函數中做全表掃描後再 PIPE;改用遊標循環邊讀邊 PIPE,實現真正流式。 |
| 內存恆定 | PGA 僅佔用單行對象 + 遊標上下文,不隨結果集增長 | 適用於百萬行以上大結果集導出/轉換/ETL 推送。 |
| 並行執行 | 12c+ 支持 `PARALLEL_ENABLE` 屬性 + `PARTITION BY` 實現函數並行 | |
| 自主事務日誌 | 異常處理中寫日誌不影響主事務 | 使用 `PRAGMA AUTONOMOUS_TRANSACTION` 在獨立過程中記錄錯誤。 |
CREATE OR REPLACE FUNCTION query_to_pipe (p_sql IN VARCHAR2)
RETURN SYS_REFCURSOR PIPELINED -- 12c+ 支持隱式轉換,或定義 ANYDATA 集合
IS
c SYS_REFCURSOR;
v ANYDATA; -- 通用容器
BEGIN
OPEN c FOR p_sql; -- 動態 SQL 注入風險極高,生產嚴禁!僅內部受控工具使用
LOOP
FETCH c INTO v;
EXIT WHEN c%NOTFOUND;
PIPE ROW (v); -- 需目標類型為 ANYDATA 集合
END LOOP;
CLOSE c;
RETURN;
END;
⚠ 安全警告: 動態 SQL 管道函數極易導致 SQL 注入,**嚴禁在面向外部輸入的接口使用**。僅限內部受控報表引擎、元數據驅動 ETL 等可信場景。
| 版本/方案 | 特性 | 適用場景 |
|---|---|---|
| 9i+ PIPELINED | 流式輸出、TABLE() 調用 | 核心報表、ETL 轉換、複雜邏輯封裝為關係視圖 |
| 12c+ POLYMORPHIC TABLE FUNCTIONS (PTF) | 無需預定義返回類型、支持行/列動態生成、描述接口 | 動態轉置、JSON 展開、通用數據掩碼、可擴展分析函數 |
| 12c+ DBMS_SQL.RETURN_RESULT | 隱式游標直接返回客戶端、無需 TABLE() | 存儲過程返回多結果集給 Java/.NET 客戶端、避免 REF CURSOR 定義 |
| 18c+ SQL MACROS (SCALAR/TABLE) | 編譯期展開為內聯 SQL、零上下文切換、最優性能 | 參數化視圖、通用過濾邏輯、替代簡單管道函數 |