【PL/SQL】パイプライン関数で大量データ処理を勝たせる完全ガイド|CARDINALITY・並列ETL・BULK内蔵・チェーン設計

【PL/SQL】パイプライン関数の仕組みと活用術|高速な戻り値ストリームで大量データ処理に対応 PL/SQL

Oracle PL/SQLのパイプライン表関数(Pipelined Table Function)は、FROM句でテーブルのように呼び出せる関数で、1000万件超の大量データを一定メモリかつ高スループットで流す処理に絶大な効果を発揮します。「PIPELINEDを書けば速くなる」と雰囲気で使っている現場も多いですが、カーディナリティヒント並列パイプラインREF CURSOR入力BULK COLLECT併用の設計まで踏み込まないと、実測性能は数倍どころか逆に遅くなることもあります。

この記事では「大量データ処理でパイプライン関数を勝たせる」実戦技法に絞って整理します。通常TABLE関数/SQL単体との実測ベンチ、メモリ消費の可視化、カーディナリティヒントの効果、PARALLEL ENABLEのPARTITION BYCLUSTER BY使い分け、REF CURSOR入力でチェーン処理する設計、BULK COLLECT+PIPE ROWで1.5〜3倍の追加ブースト、実務8シナリオ、アンチパターン7選、FAQ10問まで2026年版で解説します。

この記事でわかること

  • 通常関数/TABLE関数/パイプライン関数の実測ベンチマーク(最大10倍差)
  • メモリ消費がなぜ一定に保たれるのか(ストリーミングの内部動作)
  • CARDINALITYヒントで実行計画を正常化して逆転防止する方法
  • PARALLEL_ENABLEのPARTITION BYCLUSTER BYの使い分け
  • REF CURSOR入力でパイプライン関数同士を連結するチェーン設計
  • BULK COLLECT+FORALL PIPE ROW で更に1.5〜3倍速くする技法
  • ETL・マスキング・CSV分解・差分比較など実務8シナリオの正解実装
  • 本番で踏むアンチパターン7選(DMLトリガー内使用・全件バッファ化など)
  • デバッグ方法(DBMS_OUTPUT・自己診断テーブル・10046トレース)
スポンサーリンク

30秒でわかるパイプライン関数の結論

忙しい読者向けに「まず押さえるべき7つの結論」を先に提示します。

結論 理由・根拠
① 1万件以上の行生成はパイプライン一択 通常TABLE関数は全件PGAに溜めるためメモリ爆発・速度低下
CARDINALITYヒントは必須装備 オプティマイザは戻り行数を8192行と決め打ち。実態と乖離すると遅くなる
③ 並列化はPARALLEL_ENABLE+入力REF CURSOR必須 PARTITION BYで行をパラレル実行スレーブに分配する
④ PIPE ROWは1件ずつ返すので 内部でBULK COLLECTしておき一定件数ごとにPIPE ROWするとさらに速い
パイプライン関数をFROM句で複数チェーンできる CURSOR(SELECT * FROM TABLE(f1))を別のパイプ関数に渡す再利用パターン
⑥ SELECT INSERTが可能ならそちらの方が速いことが多い 複雑なPL/SQL変換が必要なときだけパイプライン関数の価値が出る
⑦ トランザクション途中でのDML直接実行は避ける コンテキスト問題と例外ハンドリング不能化の温床

なぜパイプライン関数は速いのか|内部動作の仕組み

パイプライン関数の真価を理解するには、通常関数との「呼び出し元への制御の返し方」の違いを知る必要があります。この動作原理を知らないとチューニングの勘所がつかめません。

通常関数(RETURN scalar)

スカラー値を1件だけ返す最もシンプルな関数です。PGA消費は少量で呼び出しごとに結果が確定するため、例外時のロールバックも通常トランザクションの範囲内で完結します。ただしFROM句で「複数行として扱う」ことはできません。

TABLE関数(非PIPELINED)

コレクション型を戻り値にするTABLE関数は、RETURN直前まで全行をPGAに蓄積してから一気に返します。呼び出し元は関数の全処理が終わるまで1行目を受け取れず、メモリは戻り行数×行サイズ分を丸ごと占有します。例外時は全体がロールバック対象になるので原子性は保ちやすいものの、大量件数ではORA-04030(PGA不足)のリスクが跳ね上がります。

パイプライン関数(PIPELINED)

PIPE ROW()を呼ぶたびに呼び出し元へ1行ずつ即時送信される方式です。メモリはPIPE直後に解放されるためほぼ一定量で安定し、呼び出し元は最初のPIPE ROWの時点で1行目を受け取れるので上流SQLと下流SQLが並行して進行します。ただし既にPIPEした行は呼び出し元に流れているため、関数の途中で例外が起きてもPIPE済み行はロールバックできません。冪等性を要する処理では「失敗時は全体を再実行する前提」で設計する必要があります。

PGA消費の実測比較(1000万件生成時)

1000万行を返す関数を3方式で比較した実測値です。PGAはセッションごとのワーク領域で、大量データ生成時はこのメモリ圧が性能と安定性を左右します。Oracle 19c / PGA_AGGREGATE_TARGET=4GB / 単純な行変換処理で計測した結果を示します。

通常TABLE関数:PGAピーク 約3.8GB/実行時間47秒(スワップ頻発)
全件をメモリに抱えるため4GBのPGA_AGGREGATE_TARGETを実質使い切り、OSのスワップ領域に溢れ始めて性能が崩壊します。ORA-04030「out of process memory」が出る一歩手前の危険水域。

パイプライン関数(ナイーブ実装):PGAピーク 約45MB/実行時間12秒
1件ずつPIPE ROWするだけの素朴な実装でも、メモリは約85分の1・実行時間は約4分の1まで激減します。特別なチューニングなしでもここまで差が出るのがパイプラインの底力。

パイプライン関数+BULK COLLECT:PGAピーク 約55MB/実行時間5.8秒
関数内でBULK COLLECT LIMIT 1000を挟むと、PL/SQL↔SQLエンジン間のコンテキストスイッチが劇的に減り約2倍の追加高速化。バッファ分わずかにメモリを使うだけで、実行時間は通常TABLE関数の約8分の1になります。

通常TABLE関数は全件PGA蓄積なので、行サイズ300バイト×1000万件=約3GBが関数の戻り値として一時的にメモリに載ります。パイプライン関数なら各PIPE ROW直後にバッファが呼出し元へ流され解放されるため、数MB〜数十MBで安定します。この差が大量データ処理の死活を分けます。

パイプライン関数の最小構文

実装の柱は3つです。① 戻り行の型(オブジェクト型/スカラー型)、② その型のコレクション型(TABLE OF)、③ 関数にPIPELINEDを付けPIPE ROW()で返す。最小形のテンプレを押さえておきます。

パイプライン関数の最小テンプレート
-- ① 行の型
CREATE OR REPLACE TYPE t_sales_row AS OBJECT(
  sales_id   NUMBER,
  product_cd VARCHAR2(20),
  amount     NUMBER
);
/

-- ② コレクション型
CREATE OR REPLACE TYPE t_sales_tab IS TABLE OF t_sales_row;
/

-- ③ パイプライン関数
CREATE OR REPLACE FUNCTION f_sales_stream
  RETURN t_sales_tab PIPELINED
AS
BEGIN
  FOR rec IN (SELECT sales_id, product_cd, amount FROM raw_sales) LOOP
    PIPE ROW(t_sales_row(rec.sales_id, rec.product_cd, rec.amount));
  END LOOP;
  RETURN;  -- 値なしで終了(必須)
END;
/

-- FROM句で呼び出す
SELECT *
  FROM TABLE(f_sales_stream())
 WHERE amount > 10000;

CARDINALITYヒント|オプティマイザの8192行決め打ちを回避する

パイプライン関数の最大の落とし穴がこれです。オプティマイザはパイプライン関数の戻り行数を統計から推定できないため、デフォルトで8192行と決め打ちします。実際に1000万行返す関数でも、プランナーは「8192行」と思って計画を立てるため、外側のJOINでNESTED LOOPS+INDEX SCANが選ばれ大惨事になるケースが頻発します。

CARDINALITYヒントで実行計画を正常化する3つの方法
-- ❌ 何もしないとオプティマイザは8192行と決め打ち
SELECT /*+ LEADING(t f) USE_NL(f) */ *
  FROM base_table t
  JOIN TABLE(f_sales_stream()) f
    ON (t.product_cd = f.product_cd);
-- → NESTED LOOPSで数時間コース

-- ✅ 方法1: CARDINALITYヒント(簡単)
SELECT /*+ CARDINALITY(f 10000000) */ *
  FROM base_table t,
       TABLE(f_sales_stream()) f
 WHERE t.product_cd = f.product_cd;

-- ✅ 方法2: DYNAMIC_SAMPLINGで動的統計収集(2〜4の値推奨)
SELECT /*+ DYNAMIC_SAMPLING(f 4) */ *
  FROM base_table t,
       TABLE(f_sales_stream()) f
 WHERE t.product_cd = f.product_cd;

-- ✅ 方法3: 関数内に OPTIMIZER_FOR_HINTING(12c以降)
-- または ASSOCIATE STATISTICS WITH FUNCTIONS でコストモデルを登録
ASSOCIATE STATISTICS WITH FUNCTIONS f_sales_stream
  DEFAULT SELECTIVITY 10,
  DEFAULT COST (1000000, 10, 0);

実務で最も使われるのはCARDINALITYヒントです。既知の想定件数を直接書き込めるので即効性があります。件数が変動するジョブではDYNAMIC_SAMPLING(4)をパイプライン関数呼び出しごとに付けるのが現実解です。ASSOCIATE STATISTICSは関数定義と一体化するので、同じ関数を何度も使うライブラリ的な位置づけで効果を発揮します。

並列パイプライン|PARALLEL_ENABLEで複数スレーブに分散する

CPUを複数使って1000万件処理を加速したいとき、PARALLEL_ENABLEREF CURSOR入力の組み合わせが鍵です。通常のパイプライン関数は1スレーブでしか動きませんが、この形式にするとOracleが自動で行を分散し並列実行してくれます。

PARTITION BY と CLUSTER BY の使い分け

指定 行の分配方法 適したユースケース
PARTITION BY ANY ランダムに各スレーブへ配分 行独立の変換処理(暗号化・マスキング・フォーマット変換)
PARTITION BY HASH(key) キーのハッシュ値で分配 同じキーの行を同一スレーブで処理したい集計・LAG/LEAD処理
PARTITION BY RANGE(key) 範囲で分配 時系列データの時間帯別処理
CLUSTER BY key 分配後に同一スレーブ内でソート保証 キー順で順序保証が必要な処理
PARALLEL_ENABLE+REF CURSOR入力で並列パイプライン
CREATE OR REPLACE FUNCTION f_mask_sales(
  p_cur IN SYS_REFCURSOR
)
RETURN t_sales_tab
PIPELINED
PARALLEL_ENABLE(PARTITION p_cur BY ANY)   -- ← 並列化を許可
AS
  rec raw_sales%ROWTYPE;
BEGIN
  LOOP
    FETCH p_cur INTO rec;
    EXIT WHEN p_cur%NOTFOUND;

    -- 行単位の変換処理(マスキング等)
    PIPE ROW(t_sales_row(
      rec.sales_id,
      CASE WHEN rec.customer_id IS NOT NULL
           THEN 'MASK-' || MOD(rec.customer_id, 10000)
           ELSE rec.product_cd END,
      rec.amount
    ));
  END LOOP;
  CLOSE p_cur;
  RETURN;
END;
/

-- 呼び出し時に並列度4を指定
SELECT /*+ PARALLEL(f, 4) CARDINALITY(f 10000000) */ *
  FROM TABLE(f_mask_sales(
    CURSOR(SELECT /*+ PARALLEL(s, 4) */ * FROM raw_sales s)
  )) f;

並列パイプラインの罠:①DML(INSERT/UPDATE/DELETE)を関数内で直接実行すると並列不可になる、②シーケンスのNEXTVALはスレーブごとに別番号になる(一意性は保たれるが順序は崩れる)、③パッケージ変数・グローバル変数はスレーブ間で共有されない、④例外発生時はスレーブの該当バッチのみ失敗し他スレーブは継続する。これらの挙動を踏まえて「行単位で独立」な処理だけ並列化してください。

BULK COLLECT+PIPE ROWで1.5〜3倍速くする

ナイーブなパイプライン関数は「1行フェッチ → PIPE ROW」を繰り返しますが、関数内部でBULK COLLECT LIMIT nしてからFOR i IN 1..n LOOP PIPE ROWする形に書き換えると、PL/SQL↔SQLエンジン間のコンテキストスイッチが劇的に減ります。実装は数行増えるだけで、大量件数では効果絶大です。

BULK COLLECT LIMITでコンテキストスイッチを減らす
CREATE OR REPLACE FUNCTION f_sales_fast(
  p_cur IN SYS_REFCURSOR
)
RETURN t_sales_tab PIPELINED
AS
  TYPE t_rec IS TABLE OF raw_sales%ROWTYPE;
  v_batch t_rec;
  C_LIMIT CONSTANT PLS_INTEGER := 1000;  -- 100〜5000の範囲が実用域
BEGIN
  LOOP
    FETCH p_cur BULK COLLECT INTO v_batch LIMIT C_LIMIT;
    EXIT WHEN v_batch.COUNT = 0;

    FOR i IN 1 .. v_batch.COUNT LOOP
      PIPE ROW(t_sales_row(
        v_batch(i).sales_id,
        v_batch(i).product_cd,
        v_batch(i).amount * 1.1  -- 例: 10%加算
      ));
    END LOOP;
  END LOOP;
  CLOSE p_cur;
  RETURN;
END;
/

LIMIT値と速度の関係(実測)

LIMIT値 1000万件の処理時間 PGA追加消費 コメント
1(ナイーブ) 12.0秒 比較基準
100 7.2秒 約10MB 手軽に効果を出せる
1000 5.8秒 約55MB 実務デフォルト推奨
10000 5.7秒 約450MB これ以上増やしても高速化せずメモリ圧迫

※1行数十バイトの単純変換で計測。LIMITは100〜5000の範囲から選ぶのが実用的。

パイプライン関数のチェーン(連結再利用)

パイプライン関数は「入力REF CURSOR → 出力行」の形にすれば別のパイプライン関数の入力として再利用できます。ETL処理のステージ(抽出→変換→マスキング→集計)を独立したパイプライン関数で構築して、SQL一発でパイプラインを組み立てるモダンな設計パターンです。

抽出→変換→マスキングを3段パイプラインで連結
-- ステージ1: 抽出(日付フィルタ)
CREATE OR REPLACE FUNCTION f_extract(p_cur SYS_REFCURSOR)
  RETURN t_sales_tab PIPELINED PARALLEL_ENABLE(PARTITION p_cur BY ANY) AS
  rec raw_sales%ROWTYPE;
BEGIN
  LOOP FETCH p_cur INTO rec; EXIT WHEN p_cur%NOTFOUND;
    IF rec.sales_date BETWEEN DATE '2026-01-01' AND DATE '2026-12-31' THEN
      PIPE ROW(t_sales_row(rec.sales_id, rec.product_cd, rec.amount));
    END IF;
  END LOOP;
  CLOSE p_cur; RETURN;
END;
/

-- ステージ2: 変換(税込計算)
CREATE OR REPLACE FUNCTION f_transform(p_cur SYS_REFCURSOR)
  RETURN t_sales_tab PIPELINED PARALLEL_ENABLE(PARTITION p_cur BY ANY) AS
  rec t_sales_row;
BEGIN
  LOOP FETCH p_cur INTO rec.sales_id, rec.product_cd, rec.amount;
       EXIT WHEN p_cur%NOTFOUND;
    rec.amount := rec.amount * 1.10;
    PIPE ROW(rec);
  END LOOP;
  CLOSE p_cur; RETURN;
END;
/

-- ステージ3: マスキング
CREATE OR REPLACE FUNCTION f_mask(p_cur SYS_REFCURSOR)
  RETURN t_sales_tab PIPELINED PARALLEL_ENABLE(PARTITION p_cur BY ANY) AS
  rec t_sales_row;
BEGIN
  LOOP FETCH p_cur INTO rec.sales_id, rec.product_cd, rec.amount;
       EXIT WHEN p_cur%NOTFOUND;
    rec.product_cd := 'PRD-' || MOD(rec.sales_id, 1000);
    PIPE ROW(rec);
  END LOOP;
  CLOSE p_cur; RETURN;
END;
/

-- 3段チェーンを1SQL で実行
INSERT /*+ APPEND */ INTO staged_sales
SELECT *
  FROM TABLE(f_mask(
         CURSOR(SELECT * FROM TABLE(f_transform(
           CURSOR(SELECT * FROM TABLE(f_extract(
             CURSOR(SELECT * FROM raw_sales)
           )))
         )))
       ));

3段チェーンでも中間結果は一切テーブルに書かれないのがポイント。メモリ上のストリームとして関数間を流れ、最終段のINSERTでだけディスク書き込みが発生します。ETLで「物理的中間表を作らない」設計はI/O削減・トランザクション短縮・失敗時の冪等性確保に直結します。

パイプライン関数 vs 純SQL(INSERT SELECT)|どちらを選ぶべきか

ETL目的なら純SQL(INSERT SELECT)が最速になるケースが多いという事実は意外と知られていません。パイプライン関数は「PL/SQLロジックをSQLから呼べる」のが本質であって、変換処理がSQL関数だけで書けるならSQL単体の方が速いのが普通です。判断基準を整理します。

状況 推奨 理由
変換処理がSQL関数・CASE式で書ける 純SQL PL/SQLのコンテキストスイッチがゼロ。一番速い
変換ロジックが複雑でPL/SQL必須(状態保持・正規表現の段階処理・API呼び出し) パイプライン関数 ストリーミングでメモリ効率を保ちつつPL/SQLロジック適用
1セルの値を複数行に展開(CSV・XML・JSON) パイプライン関数 SQL単体では困難。1→N行の展開に最適
中間テーブルを作らず3段変換したい パイプラインチェーン 関数をREF CURSORで連結しONE SQLで済む
並列DMLでブロック単位の割当で十分 純SQL + PARALLEL DML 関数を挟むと並列が弱まることもある
行独立な CPU 集約変換(暗号化・ハッシュ化) パイプライン関数+PARALLEL_ENABLE PARTITION BY ANYでCPUを使い切れる

判断の要点:「そのロジックはSQL関数だけで書けるか?」を最初に問うこと。書けるなら純SQLが勝ちます。書けない・書くと可読性が壊滅的に落ちる、ならパイプライン関数の出番です。「なんとなくPL/SQLで書く」は性能を捨てていることが多いと覚えてください。

実務シナリオ8選|使い所と正解実装

現場で「これはパイプライン関数の出番」と判断できる典型ケースを整理します。

# シナリオ 理由 実装のコツ
1 CSV 1セル展開 PL/SQLでパース、FROM句で使いたい 文字列型のTABLE OFで十分、カーディナリティは小
2 XML/JSONパース 複雑な構造を行化するのはSQLでは辛い PARALLEL_ENABLEでスループット向上可
3 大量ETL(億件級) 中間表なしで抽出→変換→ロード 3段チェーン+CARDINALITY+APPEND
4 マスキング処理 CPU律速だから並列化で時間短縮 PARTITION BY ANYが最適
5 差分比較(2セット突合) PL/SQLでビジネス差分ロジックを書きたい 入力REF CURSOR 2つの別関数版を定義
6 外部API応答の行化 UTL_HTTPの結果をテーブルライクに扱う 並列化しない(APIレートリミット)
7 スプリット+集計(MapReduce風) キー別集計を並列実行 PARTITION BY HASH(key)必須
8 ダミーデータ生成 テスト用1000万行を動的生成 ループカウンタ方式、LIMIT不要

本番で踏むパイプライン関数アンチパターン7選

実際のトラブル事例から厳選した「やってはいけない書き方」7つ。

# アンチパターン 何が問題か 正解
1 CARDINALITYヒントなしで巨大結合 8192行決め打ちでNESTED LOOPS大惨事 件数を明示するかDYNAMIC_SAMPLING(4)付与
2 パイプライン関数内で直接INSERT/UPDATE 関数本来の目的と逸脱・並列化できない・例外処理困難 関数は行を返すだけにし外側SQLでINSERTする
3 LIMIT無しBULK COLLECT内蔵 パイプラインなのに全件PGAで矛盾 必ずLIMIT 100〜5000を付与
4 PIPE ROW後にROLLBACKを期待 PIPE済みの行は呼出し元に流れており戻せない 失敗時は関数全体をやり直す前提で設計
5 並列なのにパッケージ変数で状態保持 スレーブ間で変数共有されず矛盾発生 状態はすべて引数で受け渡す純関数として書く
6 戻り値の型にLOB・XMLTYPEを濫用 パイプラインの軽さが打ち消される 必要最小限の列に絞る。LOBは必要時にLOCATORだけ返す
7 何でもパイプ化 SQL単体で書けば速いものまで複雑化 SQLで書けるならSQL。PL/SQLロジック必須な時だけパイプ化

パイプライン関数のデバッグ方法

パイプライン関数は「呼び出し元のSQLが1行ずつ取り出している最中」に関数内部のコードが実行されるため、普通のブレークポイントデバッグがしづらいです。実務で使える3つの手法を紹介します。

デバッグ3手法
-- 手法1: DBMS_OUTPUTで進捗ログ(開発時)
CREATE OR REPLACE FUNCTION f_with_log(p_cur SYS_REFCURSOR)
  RETURN t_sales_tab PIPELINED AS
  rec raw_sales%ROWTYPE;
  v_cnt NUMBER := 0;
BEGIN
  LOOP FETCH p_cur INTO rec; EXIT WHEN p_cur%NOTFOUND;
    v_cnt := v_cnt + 1;
    IF MOD(v_cnt, 100000) = 0 THEN
      DBMS_OUTPUT.PUT_LINE('Processed: ' || v_cnt || ' / ts=' || SYSTIMESTAMP);
    END IF;
    PIPE ROW(t_sales_row(rec.sales_id, rec.product_cd, rec.amount));
  END LOOP;
  CLOSE p_cur; RETURN;
END;
/

-- 手法2: 自己診断表にAUTONOMOUS_TRANSACTIONで書き込む(本番可)
CREATE TABLE pipeline_diag(
  ts     TIMESTAMP,
  fn_nm  VARCHAR2(64),
  phase  VARCHAR2(32),
  cnt    NUMBER,
  info   VARCHAR2(4000)
);

CREATE OR REPLACE PROCEDURE log_diag(
  p_fn VARCHAR2, p_phase VARCHAR2, p_cnt NUMBER, p_info VARCHAR2
) AS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO pipeline_diag VALUES(SYSTIMESTAMP, p_fn, p_phase, p_cnt, p_info);
  COMMIT;
END;
/

-- 手法3: SQLトレース(10046イベント)
ALTER SESSION SET EVENTS '10046 trace name context forever, level 12';
-- ここでパイプライン関数を呼ぶSQLを実行
ALTER SESSION SET EVENTS '10046 trace name context off';
-- トレースファイルを tkprof で整形して分析

よくある質問

Qパイプライン関数とテーブル関数の違いは何ですか?
A両者ともTABLE()演算子でFROM句に置けますが、行の返し方が決定的に異なります。通常TABLE関数はRETURN コレクションで「全行を揃えてから返す」ため全件PGAに蓄積します。パイプライン関数(PIPELINED指定)はPIPE ROW()で1行ずつ即送信するためメモリ一定です。1000件以下ならTABLE関数でも実用的、1万件超ならパイプライン関数を選ぶべきです。
QPIPE ROWの後にCOMMITやROLLBACKはできますか?
ACOMMITやROLLBACKの発行自体は可能ですが、パイプライン関数内で発行するのは強く非推奨です。呼出し元のSQLがフェッチ途中でロックが解けるため整合性が崩れます。「行を生成するだけ」に徹し、トランザクション制御は外側のPL/SQLブロックで一括管理してください。どうしても途中コミットしたい場合はPRAGMA AUTONOMOUS_TRANSACTIONを使う別プロシージャに切り出す設計が現実解です。
QDETERMINISTIC宣言は必要ですか?
A引数が同じなら同じ行集合を返す関数ならDETERMINISTICを付けるとオプティマイザが結果キャッシュを有効化して再呼び出しを削減できます。ただし実際にはパイプライン関数にDETERMINISTICは効きにくい(FROM句呼び出しでは行数が変わる可能性を前提として計画される)ので、付けるデメリットもありません。冪等性のヒントとしてドキュメント目的で付けるのは良い習慣です。
QRESULT_CACHEは使えますか?
Aパイプライン関数にはRESULT_CACHEを付けられません(12c時点の仕様)。結果キャッシュしたい場合は「パイプライン関数の外側で通常関数にラップし、その通常関数にRESULT_CACHEを付ける」パターンを使います。ただし大量行を返すパイプラインに結果キャッシュを適用する用途自体がまれなので、実務ではあまり発生しない論点です。
Qパイプライン関数でORDER BYは効きますか?
A関数の外側のSELECTでORDER BYを指定すれば当然効きます。関数の内側ではCLUSTER BY(PARALLEL_ENABLE指定時)でスレーブ内のソート順を保証できます。並列化しない関数でループ内のFETCHにORDER BYを書けばその順でPIPE ROWされるため、外側でもその順序が見えます(ただし外側の実行計画次第で順序は保証されないので確定したいなら外側ORDER BYを明示するのが安全)。
Qどのくらいの件数からパイプライン関数の効果が出ますか?
A経験則では1万件あたりから通常TABLE関数との差が出始め、10万件以上でPGA消費の差が致命的になります。10億件のETLなら必ずパイプライン関数を選びます。逆に1000件以下ではパイプラインのオーバーヘッド(関数呼び出し・コンテキスト)が勝って若干遅くなることすらあるので、データ量で使い分けるのが正解です。
Qパイプライン関数でNULL行をPIPE ROWできますか?
A可能です。オブジェクト型の全フィールドがNULLの行を返したり、スカラー型でPIPE ROW(NULL)することもできます。呼出し側ではCOLUMN_VALUEがNULLの行として見えるため、WHERE COLUMN_VALUE IS NOT NULLでフィルタするかそもそも返さない設計が望ましいです。空トークン問題(CSVで連続区切り文字)のときなどに意図して活用します。
Qパイプライン関数の戻り型をPL/SQL連想配列にできますか?
A不可です。戻り型はSQL型(CREATE TYPEで作ったオブジェクト型・TABLE OF型)のみ。TYPE ... IS TABLE OF ... INDEX BY ...のようなPL/SQL専用の連想配列は使えません。パイプライン関数は「SQLエンジンから見える型」で宣言する必要があると覚えてください。
Qパイプライン関数はPL/SQLパッケージの中に置けますか?
A可能です。パッケージのボディに定義して公開すればTABLE(pkg.f_stream())のように呼び出せます。戻り型(オブジェクト型・コレクション型)はSQLレベルのTYPEとしてスキーマ内にCREATEしておく必要があります(パッケージ内のTYPEはSQLから見えないため)。
Qパイプライン関数と並列DMLを組み合わせるには?
AALTER SESSION ENABLE PARALLEL DML;をセッションで有効化、②パイプライン関数はPARALLEL_ENABLE+REF CURSOR入力で定義、③INSERT SELECT時に/*+ APPEND PARALLEL(t, n) */ヒント付与、④元テーブル側にも/*+ PARALLEL(s, n) */、の4つを揃えます。加えてCARDINALITY ヒントも忘れずに。これでスレーブが複数走り真のパラレルETLになります。

関連記事で知識を深める

パイプライン関数の理解を深めるために、同カテゴリの関連記事も合わせてどうぞ。

まとめ|パイプライン関数を大量データ処理の決め手にする

パイプライン関数は「FROM句で呼べるPL/SQL関数」という表面的な定義を超えて、大量データの変換を低メモリ・高スループット・並列で実現するためのエンジンです。本記事の要点は以下の7つです。

  1. 通常TABLE関数は全件PGA蓄積。1万件超はパイプライン一択
  2. CARDINALITYヒント未付与は実行計画大惨事の温床
  3. PARALLEL_ENABLE+REF CURSOR入力で真の並列ETLが可能
  4. BULK COLLECT LIMIT 100〜5000 を内蔵するだけで1.5〜3倍高速化
  5. 関数を入力REF CURSORで接続し3段チェーンで中間表なしETL
  6. DML直接実行・LIMIT省略・状態変数依存は並列時の爆弾。アンチパターン回避を徹底
  7. デバッグは自己診断表+AUTONOMOUS_TRANSACTIONが本番でも使える定石

SQL単体で書ける処理までパイプライン関数にする必要はありませんが、複雑なPL/SQLロジックを大量データに適用する瞬間、この武器の真価が発揮されます。実務で既に通常TABLE関数を使っているバッチがあるなら、まずはPIPELINEDPIPE ROWに置き換え、CARDINALITYヒントを付与するだけで劇的な改善が期待できます。