カーソル変数を使うと、動的に編集したSELECT文をカーソル処理することができます。
DECLARE
type cur_type IS REF CURSOR;
cur cur_type;
.
.
.
BEGIN
OPEN cur FOR 'SELECT ... FROM ...';
LOOP
FETCH cur INTO ...;
EXIT WHEN cur%NOTFOUND;
.
.
.
END LOOP;
CLOSE cur;
END;
/
条件に応じてWHERE句やORDER BY句などを変えたい場合などに便利な機能です。
しかし、カーソル変数を使っても、FETCH文のINTO句を動的に編集することはできないため、取得するカラム数が不定のSELECT文には対応できません。
そもそも、取得するカラム数が不定のSELECT文を使用すること自体極めて稀かとは思いますが、どうしてもそういうことをしたいという場合はDBMS_SQLパッケージを使えば実現可能なようです。
試しに、指定したテーブルのデータをCSVファイルに出力するというプロシージャを作成してみました。
CREATE OR REPLACE PROCEDURE OUTPUT_TABLE_AS_CSV
(
p_table_name IN VARCHAR2,
p_directory IN VARCHAR2
)
IS
TYPE table_type IS TABLE OF VARCHAR2(32767)
INDEX BY BINARY_INTEGER;
wk_column_name table_type;
wk_column_value table_type;
wk_column_count NUMBER;
wk_sql VARCHAR2(2000);
CURSOR column_cur IS
SELECT COLUMN_NAME
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = p_table_name
ORDER BY COLUMN_ID;
table_cur INTEGER;
wk_file UTL_FILE.FILE_TYPE;
BEGIN
--USER_TAB_COLUMNS から指定したテーブルのカラム名を取得し、SELECT文を編集する。
wk_column_count := 0;
OPEN column_cur;
LOOP
FETCH column_cur INTO wk_column_name( wk_column_count + 1 );
EXIT WHEN column_cur%NOTFOUND;
wk_column_count := wk_column_count + 1;
IF wk_sql IS NULL THEN
wk_sql := 'SELECT ' || wk_column_name( wk_column_count );
ELSE
wk_sql := wk_sql || ', ' || wk_column_name( wk_column_count );
END IF;
wk_column_value( wk_column_count ) := null;
END LOOP;
CLOSE column_cur;
--カラムが1つでも存在する場合
IF wk_column_count > 0 THEN
--カーソルをオープンする。
table_cur := DBMS_SQL.OPEN_CURSOR;
--SQL文を解析する。
wk_sql := wk_sql || ' FROM ' || p_table_name;
DBMS_SQL.PARSE( table_cur, wk_sql, DBMS_SQL.NATIVE );
--カラムを定義する。
FOR i IN 1..wk_column_count LOOP
DBMS_SQL.DEFINE_COLUMN( table_cur, i, wk_column_value( i ), 4000 );
END LOOP;
--SELECT文を実行する。
IF ( DBMS_SQL.EXECUTE( table_cur ) = 0 ) THEN
--CSVファイルをオープンする。
wk_file := UTL_FILE.FOPEN( p_directory, p_table_name || '.csv', 'w', 32767 );
--カラムヘッダーを出力する。
FOR i IN 1..wk_column_count LOOP
--CSVファイルに出力する。
IF i = 1 THEN
UTL_FILE.PUT( wk_file, wk_column_name( i ) );
ELSE
UTL_FILE.PUT( wk_file, ',' || wk_column_name( i ) );
END IF;
END LOOP;
UTL_FILE.NEW_LINE( wk_file );
--データ行を出力する。
WHILE DBMS_SQL.FETCH_ROWS( table_cur ) > 0 LOOP
FOR i IN 1..wk_column_count LOOP
--カラム値を取得する。
DBMS_SQL.COLUMN_VALUE( table_cur, i, wk_column_value( i ) );
--CSVファイルに出力する。
IF i = 1 THEN
UTL_FILE.PUT( wk_file, wk_column_value( i ) );
ELSE
UTL_FILE.PUT( wk_file, ',' || wk_column_value( i ) );
END IF;
END LOOP;
UTL_FILE.NEW_LINE( wk_file );
END LOOP;
--CSVファイルをクローズする。
UTL_FILE.FCLOSE( wk_file );
END IF;
--カーソルをクローズする。
DBMS_SQL.CLOSE_CURSOR(table_cur);
END IF;
END;
/
カーソルをオープン(OPEN_CURSOR)して、解析(PARSE)して、カラムを定義(DEFINE_COLUMN)して、実行(EXECUTE)して、フェッチ(FETCH_ROWS)して、値を取得(COLUMN_VALUE)して、クローズ(CLOSE)する...と手順は煩雑ですが、このようにカラムの値を受け取る変数にPL/SQL表などを使えばカラム数が不定でも対応することができます。
ただし、上記の例ではすべてのカラムを文字列型として処理していますが、データ型を区別して値を取得したいという場合にはもう一工夫必要です。
さすがにそこまでやってみる気はありませんが...(笑)
【関連エントリ】
[Oracle] 動的に編集したカラム数が不定なSELECT文をカーソル処理してみる。 2005/09/21
[Oracle] DBMS_SQLパッケージでORA-04030エラーが発生 2006/09/29