PL/SQL プログラミング入門(2) > 表関数によるコレクションのネスト解除

表関数を手軽に使う活用例(コレクション・ネスト解除)

表関数は
select .. table(function_name) のように 表関数の引数に関数の結果を用いる方法 もあるが

select .. from table(collection) のように表に格納できるタイプの コレクション型 を用いて コレクションに対して様々な操作をすることも可能である。
(コレクション・ネスト解除、または、コレクション・アンネストと呼ばれる)

コレクション型の仮想テーブルに対して SQL 文を使用することで SQL の標準関数を利用することができる =処理プログラムを記述せずに済み SQL の膨大なライブラリ群を利用できる。つまり、配列を使って集計操作、分析関数、グループ化、並び替えまでも利用できるということである。

ここまで簡単に使うことができる表関数(コレクション・ネスト解除)機能であるが、場合によっては面倒な準備が1つ必要である。 それは、SQL 文に組み込むためには スキーマレベルのコレクションの定義 (CREATE TYPE) が必要であるということ。
これをローカルレベルの定義で行なうと PLS-00642: SQL文ではローカル・コレクション型は使用できません エラーが発生する。

スキーマレベルのコレクション定義には別の スキーマ・オブジェクト のメタ情報を使用できない。そのため 組み込みデータ型 を使って一から定義しなければならない。
オブジェクト表 (CRAETE TABLE OF type_name 〜) のような CREATE TYPE OF {table_name|subquery} があったら、うれしいところである。

IN 条件に複数(1000 要素以上)のバインド変数を割り当てる方法

IN 条件(IN 句)においてバインド変数を使用する場合、1つのプレースホルダには1つの値のみ割り当てることができ、複数の値をバインドすることができない。(⇒ 動的SQL(システム固有の動的 SQL)) しかし、以下の方法を使用することで IN 条件に複数の値をバインドすることと同じ結果(※)を実現できる。(この例では パラメータ付きのカーソル を使用しているが仕組みは一緒のはず)

ネストした表に TABLE ファンクションを使用することでコレクション属性のネスト解除してフラット化して IN に組み込んでいる。副問い合わせ式となっているために リテラル表現(スカラー式) の場合に制限される 1000 要素の制限にもかからない。(このサンプルでは 2000 個の要素を含めている)

IN において要素数が 1000を超えると ORA-01795: リストに指定できる式の最大数は1000です。というエラーが発生する。

(※) 結果は同じでもプロセスが異なる可能性がある。

IN (リテラル...) と OR は コスト計算次第で「OR 拡張」されることによって複数のステートメントに分割して UNION ALL 結合することで効率よい形に再構築される可能性があるが、IN (副問い合わせ) の場合にも 「OR 拡張」されるかまでは確認していません。(想像ですが OR 拡張にリライトする作業は相当難しそう)

  • IN 条件 + コレクション・アンネストによる副問い合わせの例
CREATE OR REPLACE TYPE RIVUS.tNumberList IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE RIVUS.COLLECTION_BIND
IS
  vNums  tNumberList;
 
  CURSOR cIDName(vNums tNumberList) IS
    SELECT USER_ID, USER_NAME
      FROM USER_MASTER
    WHERE
      USER_ID IN (SELECT * FROM TABLE(vNums))
    --            ↑ コレクション・アンネストによる副問い合わせ式
    ORDER BY USER_ID;
  TYPE tIDNAMES IS TABLE OF cIDName%ROWTYPE INDEX BY BINARY_INTEGER;
BEGIN
  vNums := tNumberList();
  vNums.EXTEND(2000);
  FOR i in 1..2000 LOOP
    vNums(i) := i;
  END LOOP;
 
  FOR vRec in cIDName(vNums)
  LOOP
    DBMS_OUTPUT.PUT_LINE(vRec.USER_ID || vRec.USER_NAME);
  END LOOP;
END;
/

配列に分析関数を適用する。配列をソートしてコレクションに戻す。

数列 vNums {100,50,30,15,-1,0} を昇順にソートし結果を vSorted 配列に代入する。 vTotal 配列にも同様に累積算の結果を代入する。 入力変数と出力変数を同じ変数にすると正しく動作しない。

CREATE OR REPLACE TYPE RIVUS.tNumberList IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE RIVUS.COLLECTION_SORT
IS
  vNums  tNumberList;
  vSorted  tNumberList;
  vTotal  tNumberList;
BEGIN
  vNums := tNumberList(100,50,30,15,1,0);
  SELECT COLUMN_VALUE, SUM(COLUMN_VALUE) OVER (ORDER BY COLUMN_VALUE)
    BULK COLLECT INTO vSorted, vTotal
    FROM TABLE(vNums)
  ORDER BY COLUMN_VALUE;
 
  FOR i in 1..vSorted.COUNT 
  LOOP
    DBMS_OUTPUT.PUT_LINE(TO_CHAR(vSorted(i),'000')||' / '||TO_CHAR(vTotal(i), '000'));
  END LOOP;
END;
/
SQL> CALL COLLECTION_SORT();
000 / 000
001 / 001
015 / 016
030 / 046
050 / 096
100 / 196
コールが完了しました。


関連事項

日本オラクル
■ 日本オラクル 株式会社
■ オラクルマスター資格 (オラクルマスターとは
■ Oracle のライセンスがわからない…
Oracle Direct (ネットで聞いても最後はここで要確認)