2021年5月15日土曜日

データをロードするページにユーザー表を使用する

 こちらの記事の続きになります。すでにアプリケーションが作成済み、ページ作成ウィザードにより、ページ番号6としてデータのロードを行うページが存在していることを前提とします。

以下より、変更作業について記述します。

表FLD_FILESの列CONTENTにファイルをアップロードするよう、リージョンファイルのアップロードを静的コンテンツからフォームへ変更し、BLOBへの保存を行うプロセスを追加します。

タイプフォームに変更し、ソース表名FDL_FILESを選択します。サーバー側の条件アイテムはF6_FILEからF6_IDヘ変更します。


新規に認識されたページ・アイテムP6_IDを選択し、タイプ非表示に変更します。主キーONセッション・ステートの保持セッションごと(ディスク)に切り替えます。


データのロードを行うページは、いくつかの処理でページの送信(フォームのPOST処理)を行います。ページの送信後ページが再表示される際に、セッション・ステートの保持リクエストごと(メモリーのみ)であると、変更されたページ・アイテムが保持されず初期状態に戻ります。モーダル・ダイアログなど、ページの送信後に別ページに遷移する場合は、送信時のページ・アイテムの値を保持する必要はありませんが、今回は送信時のページを再表示するため、ページ・アイテムの値を保持するように設定します。

ページ・アイテムP6_CONTENTのソース定義を確認した後、削除します。すでに存在するP6_FILEをP6_CONTENTに変更します。


ページ・アイテムP6_FILEを選択し、名前P6_CONTENTに変更します。設定記憶域タイプBLOB column specified in Item Source attributeに変更し、ソースに定義されているBLOBカラムに保存するよう指定します。MIMEタイプ列CONTENT_MIMETYPEファイル名列CONTENT_FILENAME文字セット列CONTENT_CHARSETBLOB最終更新列CONTENT_LASTUPDを指定し、ダウンロード・リンクの表示OFFにします。

ソースの定義として、フォーム・リージョンファイルのアップロードを選択します。CONTENTを選択し、データ型BLOBセッション・ステートの保持セッションごと(ディスク)を選択します。


ページ・アイテム名は内部的にはIDにて保存されており(選択リストでページ・アイテムを設定するプロパティの場合)、P6_FILEとして指定されていたプロパティは改めて設定を変更しなくてもP6_CONTENTとして認識されるようになります。

ページ・アイテムP6_CONTNT_FILENAMEのソース定義を確認した後、削除します。すでに存在するP6_FILE_NAMEをP6_CONTENT_FILENAMEに変更します。


ページ・アイテムP6_FILE_NAMEを選択し、名前P6_CONTENT_FILENAMEに変更します。ソースの定義として、フォーム・リージョンファイルのアップロードを選択します。CONTENT_FILENAMEを選択し、データ型VARCHAR2セッション・ステートの保持セッションごと(ディスク)を選択します。


ページ・アイテムP6_CONTENT_MIMETYPEP6_CONTENT_CHARSETP6_CONTENT_LASTUPDを非表示とするため、これらのページ・アイテムをすべて選択し、サーバー側の条件タイプなしとします。


リージョンの表示条件としてP6_FILE(名称をP6_FILEからP6_CONTENTに変更済みなので、P6_CONTENTとして表示される)がNULLかどうかで判断している部分をページ・アイテムP6_IDの有無で判定するように変更します。

タイプファイル参照...のページ・アイテムで記憶域タイプとしてBLOB column specified in Item Source attributeが指定されていると、ページの送信後にセッション・ステートの保持の設定に関わらず、値が初期化される模様です。ユーザー表に保存する場合は、主キーによってデータの有無を判断する方が適切です。

リージョンのボタン・バーを選択し、サーバー側の条件アイテムをP6_CONTENTからP6_IDヘ変更します。


サブ・リージョンのロード済みファイルに、同様の変更を行います。


リージョンのプレビューにも、同様の変更を行います。


ファイル・タイプの検証に使用されるコードを更新します。以下のコードでファイルがXLSXであることを検証します。

begin
if apex_data_parser.assert_file_type(
p_file_name => :P6_CONTENT,
p_file_type => apex_data_parser.c_file_type_xlsx )
then
return true;
else
apex_util.clear_page_cache;
return false;
end if;
end;

ページ・アイテムP6_CONTENTに紐づく検証Is valid file typeを選択し、PL/SQLファンクション本体を上記のコードに置き換えます。またサーバー側の条件として、タイプアイテムはNULLではないアイテムとしてP6_CONTENTを指定し、P6_CONTENTが指定されているときのみ検証を実施します。


動的アクションファイルのアップロードのTrueアクションページの送信を選択し、リクエスト/ボタン名CREATEを設定します。プロセスフォームの行の自動処理(DML)は、このリクエスト/ボタン名を認識し行の挿入処理(つまりBLOBへの保存)を行います。


ページ・アイテムP6_CONTENT_FILENAMEに紐づく計算は不要なので削除します。(これから追加する)プロセスによって、ファイル名は設定されます。


アップロードされたExcelのシートを選択するためのページ・アイテムP6_XLSX_WORKSHEETの、LOVを表示するためのSQL問合せを以下に変更します。

select p.sheet_display_name,
p.sheet_file_name
from fdl_files f,
table( apex_data_parser.get_xlsx_worksheets( p_content => f.content ) ) p
where f.id = :P6_ID


サーバー側の条件として設定されているPL/SQLファンクション本体も以下のコードに変更します。Excelに含まれているシート数が1より多いときのみ、シートが選択を行います。

declare
l_sheet_count number;
begin
select count(*)
into l_sheet_count
from fdl_files f,
table( apex_data_parser.get_xlsx_worksheets( p_content => f.content ) ) p
where f.id = :P6_ID;
-- display if the XSLX file contains multiple worksheets
return ( l_sheet_count > 1 );
exception
when others then
return false;
end;


選択されたシートが変更される度にページの送信が行われるよう、動的アクションが設定されています。ファイル自体はすでにアップロード済みなので、プレビューのクラシック・レポートをリフレッシュするように変更します。

動的アクションSubmit worksheet on changeを選択し、名前Refresh preview on changeに変更します。


Trueアクションのページの送信を選択します。


識別アクションリフレッシュに変更し、影響を受ける要素選択タイプとしてリージョンを選択します。リージョンとしてプレビューを指定します。


プレビューのソースであるSQL問合せを以下に変更します。

select p.line_number,
p.col001, p.col002, p.col003, p.col004, p.col005, p.col006, p.col007, p.col008, p.col009, p.col010
-- add more columns (col011 to col300) here.
from fdl_files f,
table( apex_data_parser.parse(
p_content => f.content,
p_file_name => f.content_filename,
p_xlsx_sheet_name => case when :P6_XLSX_WORKSHEET is not null then :P6_XLSX_WORKSHEET end,
p_add_headers_row => 'Y',
p_file_charset => 'AL32UTF8',
p_max_rows => 10 ) ) p
where f.id = :P6_ID

リージョンのプレビューを選択し、ソースSQL問合せを上記のコードに更新します。リフレッシュの際にSELECT文で参照しているページ・アイテムの値が送信されるよう、送信するページ・アイテムとしてP6_IDおよびP6_XLSX_WORKSHEETを設定します。


左ペインでプロセス・ビューを開き、新規にファイルを表FDL_FILESに保存するプロセスを作成します。

プロセスの作成を行い、名前ファイルのアップロードとします。タイプフォーム - 行の自動処理(DML)を選択し、フォーム・リージョンとしてファイルのアップロードを指定します。他の設定はデフォルトのままとします。


ユーザー表からのデータのロードについては、2021年5月15日の時点で不具合のためソース・データ型としてSQL Queryを使えません。そのため以下のPL/SQLコードにてデータのロードを行います。

declare
l_file blob;
l_res apex_data_loading.t_data_load_result;
begin
select content into l_file from fdl_files where id = :P6_ID;
l_res := apex_data_loading.load_data
(
p_static_id => 'CITYLIST',
p_data_to_load => l_file,
p_xlsx_sheet_name => case when :P6_XLSX_WORKSHEET is not null then :P6_XLSX_WORKSHEET end
);
:P6_ERROR_ROW_COUNT := l_res.error_rows;
end;

プロセスのデータのロードを選択し、タイプコードを実行に変更し、上記のPL/SQLコードを設定します。


以上でアプリケーションの変更は完了です。

複数のシートを含むテスト用のExcelシートcitylist2.xlsxを以下に配置しました。

https://github.com/ujnak/apexapps/blob/master/exports/citylist2.xlsx

こちらを使用して、アプリケーションの動作を確認してみます。成功時のメッセージ出力などは調整の余地がありますが、概ねページ作成ウィザードによって作成されたページと同様な動作を行なっています。


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

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