様々な基本になるサンプルを記録しています。

不定期更新です。

記事のサイドに使用している商品の紹介も掲載しているので、良ければご覧ください。

【Oracle】DBMS APIでCSVを出力してみる

DBMS APIを使って、CSVの出力するようなファンクションを作ってみた。

SQL文を引数にして、テキストが吐き出せるので
何かしら便利に使えそうだとは思っている。

とりあえず、適当な型を用意しよう。

CREATE OR REPLACE TYPE STD_VALUE4 AS OBJECT (
    VALUE01   VARCHAR2(4000),
    VALUE02   VARCHAR2(4000),
    VALUE03   VARCHAR2(4000),
    VALUE04   VARCHAR2(4000)    
);

/

CREATE OR REPLACE TYPE STD_VALUE4_TABLE IS  TABLE OF STD_VALUE4;

まずはプロシージャーを作る。
カーソルをIN OUTで定義するのがミソのようだ。

CREATE OR REPLACE  PROCEDURE SP_TEXT_OUT (

    /*カーソル*/
    REF_CUR IN OUT SYS_REFCURSOR,

    /*区切る文字 デフォルトは無し*/
    DELIM_STR   VARCHAR DEFAULT '',

    /*ヘッダーの有無 0:つけない(デフォルト) 1:つける*/
    HEAD_FLG    NUMBER DEFAULT 0,

    STD_ARRY    OUT STD_VALUE4_TABLE,
    OUT_STS     OUT VARCHAR2,
    OUT_MSG     OUT VARCHAR2
) IS
    /*レコード型*/
    REC_VALUE   STD_VALUE4;

    /*定義表*/
    DESC_TBL    DBMS_SQL.DESC_TAB;

    /*カーソルのカラム数*/
    COL_COUNT   NUMBER(3);

    /*カーソルID*/
    CUR_NUM     NUMBER(38);

    /*一時的に格納する文字列*/
    TEMP_STR    VARCHAR(255);
    TEMP_NUM    NUMBER(38,10);
    TEMP_DATE   DATE;

    /*レコード型に入れる文字列*/
    T_STR       VARCHAR(4000);
    
    /*行位置 ヘッダーは0になる*/
    TEXT_CNT NUMBER(38) := 0;
BEGIN




    /*初期化*/
    OUT_STS     := 'OK';
    OUT_MSG     := '';
    T_STR       := '';
    TEMP_STR    := '';
    REC_VALUE   := STD_VALUE4(' ',' ',' ',' ');
    STD_ARRY    := STD_VALUE4_TABLE ();

    CUR_NUM     := DBMS_SQL.TO_CURSOR_NUMBER(REF_CUR);

    /*カーソルで取得した時の定義情報を取得する*/
    DBMS_SQL.DESCRIBE_COLUMNS(CUR_NUM,COL_COUNT,DESC_TBL);

    /*定義情報を展開する*/
    FOR I IN 1..COL_COUNT LOOP

           /*VARCHAR2*/
        IF
            ( DESC_TBL(I).COL_TYPE = 1 )
        THEN
            DBMS_SQL.DEFINE_COLUMN(CUR_NUM,I,TEMP_STR,4000);

            /*NUMBER*/
        ELSIF ( DESC_TBL(I).COL_TYPE = 2 ) THEN
            DBMS_SQL.DEFINE_COLUMN(CUR_NUM,I,TEMP_NUM);

            /*DATE*/
        ELSIF ( DESC_TBL(I).COL_TYPE = 12 ) THEN
            DBMS_SQL.DEFINE_COLUMN(CUR_NUM,I,TEMP_DATE);

            /*その他*/
        ELSE
            DBMS_SQL.DEFINE_COLUMN(CUR_NUM,I,TEMP_STR,4000);
        END IF;


        /*ヘッダー情報を無関係に取得しておく*/
        TEMP_STR   := DESC_TBL(I).COL_NAME;

        /*NULLだったら、項目は連結させない。*/
        IF
            TRIM(TEMP_STR) IS NOT NULL
        THEN
            T_STR   := T_STR || TEMP_STR;
        END IF;

        /*最後の要素以外はデルミタ文字をつける*/
        IF
            I < COL_COUNT
        THEN
            /* NULLであればつけない*/
            IF
                DELIM_STR IS NOT NULL
            THEN
                T_STR   := T_STR || DELIM_STR;
            END IF;
        END IF;
    END LOOP;

    /*1の場合、要素を格納する*/
    IF
        HEAD_FLG = 1
    THEN
    /*要素追加*/
        REC_VALUE.VALUE01              := TO_CHAR(TEXT_CNT);
        REC_VALUE.VALUE02              := T_STR;
        REC_VALUE.VALUE03              := LENGTH(T_STR);
        STD_ARRY.EXTEND;
        STD_ARRY(STD_ARRY.COUNT)   := REC_VALUE;
    END IF;

/*フェッチでループ*/
    WHILE DBMS_SQL.FETCH_ROWS(CUR_NUM) > 0 LOOP
 
    /* 再初期化 */
        T_STR                      := '';
        TEMP_STR                   := '';
        TEMP_NUM                   := 0;
        TEMP_DATE                  := NULL;
        REC_VALUE                  := STD_VALUE4(' ',' ',' ',' ');
        TEXT_CNT                   := TEXT_CNT + 1;
        FOR I IN 1..COL_COUNT LOOP

        /*定義情報を利用してカーソル内のフィールドの値を取得する*/
        /*VARCHAR2*/
            IF
                ( DESC_TBL(I).COL_TYPE = 1 )
            THEN
                DBMS_SQL.COLUMN_VALUE(CUR_NUM,I,TEMP_STR);

            /*NUMBER 文字列に変換する*/
            ELSIF ( DESC_TBL(I).COL_TYPE = 2 ) THEN
                DBMS_SQL.COLUMN_VALUE(CUR_NUM,I,TEMP_NUM);
                TEMP_STR   := TO_CHAR(TEMP_NUM);

            /*DATE  YYYY/MM/DDの形式にする*/
            ELSIF ( DESC_TBL(I).COL_TYPE = 12 ) THEN
                DBMS_SQL.COLUMN_VALUE(CUR_NUM,I,TEMP_DATE);
                TEMP_STR   := TO_CHAR(TEMP_DATE,'YYYY/MM/DD');

            /*その他*/
            ELSE
                DBMS_SQL.COLUMN_VALUE(CUR_NUM,I,TEMP_STR);
            END IF;

        /*TRIMかけてNULLだった場合、項目は連結させない。*/
            IF
                TRIM(TEMP_STR) IS NOT NULL
            THEN
                T_STR   := T_STR || TEMP_STR;
            END IF;

        /*最後の要素以外は区切り文字を付加する*/
            IF
                I < COL_COUNT
            THEN

            /* 区切り文字はNULLだったら付けない。*/
                IF
                    DELIM_STR IS NOT NULL
                THEN
                    T_STR   := T_STR || DELIM_STR;
                END IF;
            END IF;
        END LOOP;

        /*要素追加*/
        REC_VALUE.VALUE01              := TEXT_CNT;
        REC_VALUE.VALUE02              := T_STR;
        REC_VALUE.VALUE03              := LENGTH(T_STR);
        STD_ARRY.EXTEND;
        STD_ARRY(STD_ARRY.COUNT)   := REC_VALUE;
    END LOOP;

/*カーソルクローズ*/
    DBMS_SQL.CLOSE_CURSOR(CUR_NUM);
    OUT_STS     := 'OK';
    OUT_MSG     := ''; 
/*終了*/
    RETURN;

/*エラー*/
EXCEPTION
    WHEN OTHERS THEN

    /*念のためカーソルをオープンしてた場合は閉じる*/
        IF
            REF_CUR%ISOPEN
        THEN
            DBMS_SQL.CLOSE_CURSOR(CUR_NUM);
        END IF;

    /*エラー内容追加*/
        OUT_STS   := 'NG';
        OUT_MSG   := SQLERRM;
        RETURN;
END;

上記のプロシージャーを呼び出してパイプラインのファンクションにする。

CREATE OR REPLACE  FUNCTION FUNC_TEXT_OUT (

    /*SQL文*/
    STRSQL      VARCHAR2,


    /*区切る文字 デフォルトは無し*/
    DELIM_STR   VARCHAR2 DEFAULT '',


    /*ヘッダーの有無 0:つけない(デフォルト) 1:つける*/
    HEAD_FLG    NUMBER DEFAULT 0

) RETURN STD_VALUE4_TABLE
    PIPELINED
IS
    /*レコード型*/
    REC_VALUE   STD_VALUE4;

    /*戻り値のコレクション*/
    STD_ARRY    STD_VALUE4_TABLE;

    /*カーソル*/
    REF_CUR     SYS_REFCURSOR;

    /*レコード型に入れる文字列*/
    W_STR       VARCHAR2(4000);


    /*プロシージャー結果*/
    OUT_STS     VARCHAR2(4000);
    OUT_MSG     VARCHAR2(4000);
BEGIN

    /*初期化*/
    REC_VALUE   := STD_VALUE4(' ',' ',' ',' ');


    /*カーソルオープン*/
    OPEN REF_CUR FOR STRSQL;
    SP_TEXT_OUT(REF_CUR,DELIM_STR,HEAD_FLG,STD_ARRY,OUT_STS,OUT_MSG);
    IF
        OUT_STS = 'OK'
    THEN
        FOR I IN 1..STD_ARRY.COUNT LOOP
            PIPE ROW ( STD_ARRY(I) );
        END LOOP;
    ELSE

        /*エラー内容追加*/
        REC_VALUE.VALUE04   := OUT_MSG;
        PIPE ROW ( REC_VALUE );
        RETURN;
    END IF;



/*終了*/
    RETURN;

/*エラー*/
EXCEPTION
    WHEN OTHERS THEN

    /*念のためカーソルをオープンしてた場合は閉じる*/
        IF
            REF_CUR%ISOPEN
        THEN
            CLOSE REF_CUR;
        END IF;

    /*エラー内容追加*/
        REC_VALUE.VALUE04   := SQLERRM;
        PIPE ROW ( REC_VALUE );
        RETURN;
END;

f:id:karinto441:20181009234842p:plain

CSV出力よりも、もっと他の何かに役に立ちそうな気がするな。