SQL*Plus にてCOLUMN FORMAT文を自動生成する方法

この記事ではSQL*PlusでCOLUMN FORMATを自動で実行する方法を解説します。

 自動でCOLUMN FORMAT文を実行する方法

SQL*PlusでSELECT文を実行するとデフォルトだと列の表示幅が狭く検索結果が見にくかったりします。

col for文を使うと列の表示幅を変更できますが、いちいち打つのも面倒ですよね。

そんなときに便利なのが、実行したselect 文に対して自動でcol for文を実行してくれるSQLスクリプトです。

スクリプトの内容は下記の参考にした記事まんまです。

参考にした記事

https://qiita.com/sowd/items/34da8eb99ec5f22fd7a4

set linesizeがうまく動作していなかったので、最後にset linesize を加えて自動でset linesize もされるように変更しています。


 SQLスクリプト

下記の内容をcolfmt.sqlで保存し、SQL*Plusを実行するホームディレクトリに配置しましょう。

4~6行目は使用するクライアントの環境に合わせてパスを設定してください。

SET TERMOUT OFF

-- 一時ファイル名の設定 (環境に合わせて設定してください)
DEF _MY_TMPFILE_PATH_SAVE  = "/tmp/__colfmtstr_save.tmp"
DEF _MY_TMPFILE_PATH_STORE = "/tmp/__colfmtstr_store.tmp"
DEF _MY_TMPFILE_PATH_SPOOL = "/tmp/__colfmtstr_spool.tmp"

-- バッファ・環境設定の保存
SAVE &_MY_TMPFILE_PATH_SAVE REPLACE
STORE SET &_MY_TMPFILE_PATH_STORE REPLACE

-- バッファの内容を取得
VAR MY_BUFFSTR CLOB
0 :MY_BUFFSTR := q'\
0 BEGIN
9999 \';;
9999 END;;
/
-- '


-- SETによる環境設定
SET SERVEROUTPUT ON SIZE UNLIMITED FORMAT WRAPPED
SET LINESIZE 32767
SET PAGESIZE 50000
SET LONG 2000000000
SET ECHO OFF
SET FEEDBACK OFF
SET VERIFY OFF
SET HEADING ON
SET MARKUP HTML OFF SPOOL OFF
SET TRIMOUT ON TRIMSPOOL ON
SET TAB OFF
SET TERMOUT ON

-- COL-FORMAT文生成
SPOOL &_MY_TMPFILE_PATH_SPOOL
DECLARE
    vCRSR      INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
    vVCHR2     VARCHAR2(4000);
    vANYDATA   ANYDATA;
    vINT       INTEGER;
    vDEC       INTEGER;
    vSTATUS    INTEGER;
    vDESCTBL   DBMS_SQL.DESC_TAB;
    vCOLCNT    INTEGER;
    TYPE       vCHRCNTARRTYPE IS TABLE OF INTEGER INDEX BY VARCHAR2(64);
    vCHRCNTARR vCHRCNTARRTYPE;
    vIDX       INTEGER;
    vFLG       INTEGER;
BEGIN

    -- SQL実行
    DBMS_SQL.PARSE(vCRSR, :MY_BUFFSTR, DBMS_SQL.NATIVE);
    DBMS_SQL.DESCRIBE_COLUMNS(vCRSR, vCOLCNT, vDESCTBL);
    FOR I IN 1 .. vCOLCNT LOOP
        IF vDESCTBL(I).COL_TYPE=109 THEN

            -- ANYDATA型(COL_TYPEは109)の場合、第3引数をVARCHAR2型にするとエラーになる。
            DBMS_SQL.DEFINE_COLUMN( vCRSR, I, vANYDATA);
        ELSE
            DBMS_SQL.DEFINE_COLUMN( vCRSR, I, vVCHR2, 4000);
        END IF;
    END LOOP;
    vSTATUS := DBMS_SQL.EXECUTE(vCRSR);

    -- =================================================================================
    -- LENGTHB(CONVERT(vDESCTBL(I).COL_NAME, 'JA16EUC'))について
    -- CONVERTで変換しているのは、日本語に対応するため。
    --   日本語一文字のサイズを"A2"としたい。しかし、EUC/SJISでは2バイト、UTF8では3バイト
    --   だったりする。
    --   そのため、一度JA16EUCに変換し、そのバイト数を取得するようにしている。
    -- =================================================================================

    -- 配列の初期化
    FOR I IN 1 .. vCOLCNT LOOP
        -- 初期値には列名のサイズを設定する。
        -- (データの最大長が列名より短い場合に、一行目の列名文字列が切れてしまうのを防ぐため)
        IF vDESCTBL(I).COL_TYPE = 2 THEN
            -- 数値対応
            -- 小数に対応
            vCHRCNTARR(vDESCTBL(I).COL_NAME || '_int') := LENGTHB(CONVERT(vDESCTBL(I).COL_NAME, 'JA16EUC'));
            vCHRCNTARR(vDESCTBL(I).COL_NAME || '_dec') := 0;
        ELSE
            -- その他の型
            vCHRCNTARR(vDESCTBL(I).COL_NAME) := LENGTHB(CONVERT(vDESCTBL(I).COL_NAME, 'JA16EUC'));
        END IF;
    END LOOP;

    -- 最大長の抽出
    WHILE ( DBMS_SQL.FETCH_ROWS(vCRSR) > 0 ) LOOP
        FOR I IN 1 .. vCOLCNT LOOP
            IF (vDESCTBL(I).COL_TYPE = 109) THEN
                -- ANYDATA型(COL_TYPEは109)は対処しようが無いので、無視。
                CONTINUE;
            END IF;
            -- 値を取得
            DBMS_SQL.COLUMN_VALUE(vCRSR, I, vVCHR2);
            -- NULLの場合、なにもしない。
            IF vVCHR2 IS NULL THEN
                CONTINUE;
            ELSIF (vDESCTBL(I).COL_TYPE = 2) THEN
                -- 数値対応
                vINT :=0;
                vDEC :=0;
                vFLG :=0;
                FOR vIDX IN 1..length(vVCHR2) LOOP
                    IF substr(vVCHR2, vIDX, 1) = '.' THEN
                        vFLG := 1;
                    ELSIF vFLG = 0 THEN
                        vINT := vINT+1;
                    ELSE
                        vDEC := vDEC+1;
                    END IF;
                END LOOP;
                IF vINT > vCHRCNTARR(vDESCTBL(I).COL_NAME || '_int')  THEN
                    vCHRCNTARR(vDESCTBL(I).COL_NAME || '_int') := vINT;
                END IF;
                IF vDEC > vCHRCNTARR(vDESCTBL(I).COL_NAME || '_dec')  THEN
                    vCHRCNTARR(vDESCTBL(I).COL_NAME || '_dec') := vDEC;
                END IF;
            ELSE
                -- その他の型        
                vINT := LENGTHB(CONVERT(vVCHR2, 'JA16EUC'));
                IF vINT > vCHRCNTARR(vDESCTBL(I).COL_NAME)  THEN
                    vCHRCNTARR(vDESCTBL(I).COL_NAME) := vINT;
                END IF;
            END IF;
        END LOOP;
    END LOOP;

    -- COL-FORMAT文の生成
    FOR I IN 1 .. vCOLCNT LOOP
        IF vDESCTBL(I).COL_TYPE = 109 THEN
            -- ANYDATA型(COL_TYPEは109)は対処しようが無いので、無視。
            CONTINUE;
        END IF;
        DBMS_OUTPUT.PUT('COL ' || RPAD('"' || vDESCTBL(I).COL_NAME || '"', 32) || ' ');
        IF vDESCTBL(I).COL_TYPE = 2 THEN
            -- 数値フォーマット
            vINT := vCHRCNTARR(vDESCTBL(I).COL_NAME || '_int');
            vDEC := vCHRCNTARR(vDESCTBL(I).COL_NAME || '_dec');
            DBMS_OUTPUT.PUT('FOR ' || LPAD('9', vINT, '9'));
            IF vDEC <> 0 THEN
                DBMS_OUTPUT.PUT('.' || LPAD('9', vDEC, '9'));                
            END IF;
            DBMS_OUTPUT.PUT_LINE('');
        ELSE
            -- 文字フォーマット
            vINT := vCHRCNTARR(vDESCTBL(I).COL_NAME);
            -- 100文字以上の場合は"A100"に設定する。
            IF vINT > 100 THEN
              vINT := 100;
            END IF;
            DBMS_OUTPUT.PUT_LINE('FOR A' || vINT);
        END IF;
    END LOOP;

    --- カーソルを閉じる。
    DBMS_SQL.CLOSE_CURSOR(vCRSR);
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('COLFMT_ERROR: ' || SQLCODE || ': ' || SQLERRM);
        DBMS_OUTPUT.PUT_LINE('COLFMT_ERROR: (SELECT文でない可能性があります。)');
END;
/

SPOOL OFF

-- バッファ・環境設定の復元
SET TERMOUT OFF
@&_MY_TMPFILE_PATH_STORE
CL BUFF
GET &_MY_TMPFILE_PATH_SAVE NOLIST

-- 生成したCOL-FORMATの実行
@&_MY_TMPFILE_PATH_SPOOL
set linesize 250
SET TERMOUT ON 

 実行例

最初に普通にselect文を実行します。

SQL> select a.TABLESPACE_NAME
  2  , min(a.BYTES)/1024/1024 "現サイズ(MB)"
  3  , round(min(a.BYTES)/(1024*1024) - sum(b.BYTES)/ (1024*1024),2) "使用量(MB)"
  4  , round((min(a.BYTES)/(1024*1024) - sum(b.BYTES)/(1024*1024))/
  5  (min(a.BYTES)/1024/1024)*100,2) "使用率(%)"
  6  , round(sum(b.BYTES)/(1024*1024),2) "空き容量(MB)"
  7  from dba_data_files a, dba_free_space b
  8  where a.FILE_ID = b.FILE_ID
  9  group by a.TABLESPACE_NAME;

TABLESPACE_NAME                                              現サイズ(MB)
------------------------------------------------------------ ------------
使用量(MB)  使用率(%) 空き容量(MB)
---------- ---------- ------------
SYSTEM                                                               1360
   1358.19      99.87         1.81
SYSAUX                                                                970
    910.31      93.85        59.69
UNDOTBS1                                                              120
     18.63      15.52       101.38

TABLESPACE_NAME                                              現サイズ(MB)
------------------------------------------------------------ ------------
使用量(MB)  使用率(%) 空き容量(MB)
---------- ---------- ------------
USERS                                                                   5
      2.69      53.75         2.31

デフォルトのままだと表示が崩れています。

一度SQLを実行後colfmt.sqlを実行します。

SQL> @colfmt
COL "TABLESPACE_NAME"                FOR A15
COL "現サイズ(MB)"                   FOR 999999999999
COL "使用量(MB)"                     FOR 9999999999.99
COL "使用率(%)"                      FOR 999999999.99
COL "空き容量(MB)"                   FOR 999999999999.99

自動でCOL文が実行されます。

再度select文を実行します。

「/」を入力すると再度SQLを実行できます。

SQL> /
TABLESPACE_NAME  現サイズ(MB)     使用量(MB)     使用率(%)     空き容量(MB)
--------------- ------------- -------------- ------------- ----------------
SYSTEM                   1360        1358.19         99.87             1.81
SYSAUX                    970         910.31         93.85            59.69
UNDOTBS1                  120          18.63         15.52           101.38
USERS                       5           2.69         53.75             2.31

表示がきれいになりました。