2024年12月10日火曜日

ボタンのクリックで表のデータをJSONドキュメントとしてダウンロードする

対話モード・レポートなどは表示しているデータをファイルとしてダウンロードする機能を持っています。ダウンロード可能な形式はCSV、Excel、HTML、PDFでJSONは含まれていません。

ボタンをクリックして表のデータをJSONドキュメントとしてダウンロードする機能を、動的アクションのダウンロードを使って実装してみました。


空のAPEXアプリケーションを作成し、すべての機能をホーム・ページに実装しています。

ダウンロードするJSONドキュメントは、対話モード・レポートに表示されているデータを元に生成します。対話モード・レポートのソースとしてビューALL_OBJECTSを設定しています。また、リージョンIDを取得するために、対話モード・レポートに静的IDとしてALL_OBJECTSを割り当てています。


ページ・アイテムP1_FILENAMEに設定したファイル名を、ダウンロードするファイル名にします。


ボタンDOWNLOAD_ALLはレポートの表示とは関係なく、ビューALL_OBJECTSの内容をすべてJSONドキュメントとしてダウンロードします。

ボタンをクリックしたときに実行される動的アクションの実行タイプとしてデバウンスを選択し、時間2000ミリ秒、即時オンにします。


TRUEアクションとしてダウンロードを作成します。設定複数のファイルオフファイルの表示形式として添付を選択します。

ソースSQL問合せとして以下を記述します。OracleのSQLで使えるJSONファンクションはデフォルトでVARCHAR2(4000)を返します。そのため、4000バイトを超えるデータを扱う場合はreturning clobを明示する必要があります
select 
    json_serialize(
        json_object(
            'objects' value
                json_arrayagg(
                    json_object(*)
                returning clob)
        returning clob)
    returning clob pretty)    
    ,:P1_FILENAME
    ,'application/json'
from all_objects

動的アクションによるダウンロードが開始した時点で、アクションの処理は終了します。大抵の場合、JSONドキュメントの準備はすぐに終了し、そのデータをサーバーからブラウザにダウンロードする際に時間がかかります。その間にボタンをクリックすると、クリックした回数分同じJSONドキュメントがダウンロードされます

JSONドキュメントのダウンロードが完了するまで新たなボタン・クリックを許可しないように実装できればよいのですが、かなり難しい実装になります。次善の策として、動的アクションにデバウンスを設定しています。即時オンにしているため、最初のクリック時はすぐにダウンロードが呼び出されます。時間2000ミリ秒(2秒)としているので、その後2秒間はボタン・クリックを無視します。ボタンを何回クリックするかにかかわらず、最大で2秒に一回しかダウンロードは実行されません。ユースケースに応じて秒数を延長することで、サーバーへの負荷を軽減できます。

ボタンDOWNLOAD_SELECTEDをクリックすると、対話モード・レポートに一覧されているデータをJSONドキュメントとしてダウンロードします。

ダウンロードを開始する前に、対話モード・レポートからダウンロード対象のデータを取り出し、JSONに変換します。

TRUEアクションとしてサーバー側のコードを実行を作成し、設定PL/SQLコードとして以下を記述します。取り出したJSON形式のデータは、一旦APEXコレクションJSON_CONTENTに保存して、後続のアクションにデータを渡します。
declare
    l_region_id apex_application_page_regions.region_id%type;
    l_context apex_exec.t_context;
    l_clob clob;
begin
    select region_id into l_region_id from apex_application_page_regions
    where static_id = 'ALL_OBJECTS';
    l_context := apex_region.open_query_context(
        p_page_id => :APP_PAGE_ID
        ,p_region_id => l_region_id
    );
    apex_json.initialize_clob_output;
    apex_json.open_object;
    apex_json.write_context( 'objects', l_context );
    apex_json.close_object;
    --
    l_clob := apex_json.get_clob_output;
    apex_collection.create_or_truncate_collection('JSON_CONTENT');
    apex_collection.add_member(
        p_collection_name => 'JSON_CONTENT'
        ,p_n001 => 1
        ,p_clob001 => l_clob
    );
    --
    apex_json.free_output;
end;
後続の処理でAPEXコレクションのデータを参照するため、実行結果の待機オンにします。


ダウンロードソースSQL問合せでは、APEXコレクションよりCLOBを取り出し、ダウンロード対象とします。
select
    clob001
    ,:P1_FILENAME
    ,'application/json'
from apex_collections
where collection_name = 'JSON_CONTENT' and N001 = 1;

以上で今回の実装は完了です。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-json-download-for-report.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。