過去実行したSQLはV$SQLやV$SQLAREAのSQL_TEXTから取得する事できますが、1000バイトを超えるSQLはSQL_FULLTEXTを参照する必要があります。ただしSQL_TEXTと異なり改行コードが含まれている事や、SQL発行ツール自体が VARCHAR2(4000)を 超える文字列を表示できない制限があって期待通りの表示ができません。これら課題をクリアするため以下テーブルファンクション(ストアドパッケージ)を作成しました。
SQL_IDをパラメータにSQL_TEXTを表示するテーブルファンクション
以下実行イメージです。
select * from table(devtool.PICKUP_SQL('0pyq96jtkdr31'))
;
実行SQLテキスト
select '伊集院' as "'--姓--'"
/* '20/06/01 訂正
,'光' as "名"
*
*/
,'ヒカル' as "/* 名 */"
,'45' "年齢" -- 2020/07/01 追加
-- '男' as "性別"
,'1行目
2行目
-- 3行目 ここは有効
/*
4行目 ここも有効
*/
5行目' as "備考"
from dual
実行結果

テーブルファンクションの登録
事前作業としての権限付与
今回作成するプロシージャは内部でSYS.GV_$SQLAREAへアクセスするため作成するスキーマ(今回の例ではSOONIとします)には SYS.GV_$SQLAREA へのSELECT権限付与が必要です。注意点としてスキーマへ直接権限付与する必要があります。ロール経由での付与ではプロシージャのコンパイルエラーが発生します。ストアドプロシージャのコンパイルはスキーマが直接保持している権限しか確認しないためです。
-- 案1 オブジェクト権限付与 sysスキーマから以下実行
SQL> grant select on GV_$SQLAREA to SOONI;
権限付与が成功しました。
SQL>
-- 案2 システム権限付与 sysもしくはsystemスキーマから実行
SQL> grant SELECT ANY DICTIONARY to SOONI
2 ;
権限付与が成功しました。
SQL>
ストアドパッケージの登録
以下ストアドパッケージ(ヘッダー/ボディー)を適切なスキーマ(今回はSOONI)へ登録してください。
ストアドパッケージ(HEADER)
create or replace PACKAGE DEVTOOL
IS
/*
* レコード型の宣言 (テーブル・ファンクションの行の型)
*/
TYPE LIST_SQL_TYPE IS RECORD
(
SQL_ID VARCHAR2(13)
,RID NUMBER
,LSQL VARCHAR2(4000)
);
-- コレクション型の宣言 (テーブル・ファンクションの戻り値)
TYPE LIST_SQL_RETURN IS TABLE OF LIST_SQL_TYPE;
-- SQL_IDで指定するSQLをVARCHAR型レコードで返す(テーブルファンクション)
FUNCTION PICKUP_SQL(P_SQL_ID IN VARCHAR2) RETURN LIST_SQL_RETURN PIPELINED;
END;
ストアドパッケージ(BODY)
CREATE OR REPLACE PACKAGE BODY DEVTOOL AS
FUNCTION get_sql_text(P_SQL_ID IN VARCHAR2) RETURN CLOB;
/* ---------------------------------------------------------------------
* (ファンクション名): PICKUP_SQL
*
* (機能概要): SQL_IDで指定するSQLをVARCHAR型レコードで返す
*
* 1. 空行の削除
* 2. 「,」の後ろに2つ以上の半角スペースが続いた場合は1つにする
* 3. 1行あたりの最大文字数を超える場合に限り、対象行をカンマで改行する
*
* --------------------------------------------------------------------- */
FUNCTION PICKUP_SQL
(
P_SQL_ID IN VARCHAR2
) RETURN LIST_SQL_RETURN PIPELINED
IS
-- 1行あたり最大文字数(越えた場合カンマで改行)
SEPSIZE constant number := 200;
vrec LIST_SQL_TYPE; -- 1行のレコード変数
qlno number; -- 行番号
vsql clob;
xbuff clob;
mbuff clob; -- バッファ
msize number; -- 1行サイズ
mpos number; -- 処理対象ポイント
sbuff VARCHAR2(4000); -- 最大文字数越時バッファ
ssize number; -- 最大文字数越時1行サイズ
spos number; -- 最大文字数越時処理対象ポイント
asize number;
BEGIN
vsql := get_sql_text(P_SQL_ID);
if ( vsql is null ) then
RETURN ;
end if;
-- 「,」の後ろに2つ以上の半角スペースが続いた場合1つにする
vsql := REGEXP_REPLACE(vsql, ', {2,}',', ',1,0,'i');
qlno := 0; -- 行番号
mpos := 0; -- 処理対象ポイント
msize := 0; -- 1行のサイズ
asize := DBMS_LOB.GETLENGTH(vsql); -- 全体のサイズ
loop
qlno := qlno + 1;
mpos := mpos + msize + 1;
mbuff := REGEXP_SUBSTR(vsql,'.*',mpos,1,'i'); -- 1行読み込み
msize := DBMS_LOB.GETLENGTH(mbuff); -- 1行のサイズ
-- dbms_output.put_line('msize:'||msize||' :'||mbuff);
exit when msize <=0;
if ( msize > SEPSIZE and REGEXP_INSTR(mbuff,'\,',1,2,1,'i') > 0 ) then
-- 1行のサイズが大きく2つ以上のカンマを含む場合
-- カンマで改行(但し先頭にカンマがある場合は対象外)
xbuff := REGEXP_REPLACE(mbuff, ',',CHR(10)||', ',2,0,'i');
spos := 0;
ssize := 0;
loop
spos := spos + ssize + 1;
sbuff := REGEXP_SUBSTR(xbuff,'.*',spos,1,'i');
ssize := length(sbuff);
exit when ssize <= 0 or ssize is null;
vrec.SQL_ID := to_char(ssize);
vrec.RID := qlno; -- 行番号をセット
vrec.LSQL := sbuff; -- 1行分をセット
PIPE ROW(vrec); -- 結果セットをパイプへ
qlno := qlno +1;
end loop;
-- dbms_output.put_line('ssize:'||ssize||' qlno:'||qlno);
continue;
end if;
-- dbms_output.put_line('mpos:'||mpos||' msize:'||msize);
vrec.SQL_ID := P_SQL_ID;
vrec.RID := qlno; -- 行番号をセット
vrec.LSQL := mbuff; -- 1行分をセット
PIPE ROW(vrec); -- 結果セットをパイプへ
end loop;
RETURN ;
END PICKUP_SQL;
/* ---------------------------------------------------------------------
* (ファンクション名): get_sql_text
*
* (機能概要):SQL_IDで指定するSQL_FULLTEXTをV$SQLAREAより取得する。
*
* --------------------------------------------------------------------- */
FUNCTION get_sql_text(
P_SQL_ID IN VARCHAR2
)
return clob
is
vsql clob;
BEGIN
/* RACで動作している場合を想定 */
select w.SQL_FULLTEXT into vsql from
(
select SQL_FULLTEXT
,row_number() over( partition by SQL_ID order by INST_ID) j
FROM SYS.GV_$SQLAREA
WHERE SQL_ID = P_SQL_ID
) w where j=1;
return(vsql) ;
END;
END DEVTOOL;
/
