ラベル Basic の投稿を表示しています。 すべての投稿を表示
ラベル Basic の投稿を表示しています。 すべての投稿を表示

2022年8月30日火曜日

APEXアプリケーションの変更点を調べる

 APEXアプリケーションの変更(コンポーネントの作成、変更、削除)は、管理メニューのアクティビティのモニターを開いた画面にある、開発者アクティビティのアプリケーション変更(詳細)から確認することができます。

デフォルトではいくつかの列が非表示になっています。すべて表示させると以下のレポートになります。一覧される履歴を制限するために、期間やアプリケーションによる絞り込みを行うと良いでしょう。日付の降順で一覧すると見やすくなると思います。

このレポートに列として、APEX表名、SCNおよびコンポーネント・キーが含まれています。

Autonomous Databaseの場合、APEXがインストールされているスキーマが保護されているため、APEX表に直接アクセスすることはできません。すべて標準ビューを介してのアクセスになるため、これから説明する作業はできません。

誰が何をいつ変更したか、といったことはAutonomous Databaseでも、上記のレポートより確認できます。いつ、についてはSCNより正確な時刻を割り出すことも可能です。

select scn_to_timestamp(<SCN>) at time zone 'Asia/Tokyo' from dual;

オンプレミス環境の場合、APEX表に直接問い合わせを発行できるため、実施された変更を確認できます。

アクションが作成および削除であれば、コンポーネント(の種類)とコンポーネント名から概ね実行された作業は分かります。そのため、詳細まで調べる必要性はあまりないかと思います。

以下は、アクションが変更のときの確認手順です。

例としてアプリケーションIDが110、ページ番号が3のページにあるファセットP3_MGRのラベルを、マネージャーから上司に変更し保存します。


アプリケーションの変更のレポートを確認すると、変更履歴が見つかります。変更されたAPEX表名として、WWV_FLOWS_STEPSとWWV_FLOW_STEP_ITEMSがあります。


コマンドライン・ツールでデータベースに接続します。

APEXがインストールされているスキーマをカレント・スキーマに変更します。APEX 22.1の場合はAPEX_220100がAPEXがインストールされているスキーマになります。

SQL> alter session set current_schema = apex_220100;


Session altered.


SQL> 


現在のデータと変更前のデータを、別の表に保存します。APEX表名、SCN、コンポーネント・キーの値を使い、以下のCREATE TABLE文を実行します。

CREATE TABLE <変更後の表> AS SELECT * FROM <APEX表名> where ID = <コンポーネント・キー>;
CREATE TABLE <変更前の表> AS SELECT * FROM <APEX表名> as of scn <列SCNの値> where ID = <コンポーネント・キー>;

APEXのワークスペース・スキーマとして、APEXDEVが作成済みであるとします。

上記のDDLを実行して、表を作成します。変更後のアイテムのデータを表STEP_ITEMS_AC、変更前をSTEP_ITEMS_BC、変更後のページのデータを表STEPS_AC、変更前をSTEPS_BCに保存しています。

SQL> create table apexdev.step_items_ac as select * from wwv_flow_step_items where id = 4025742564048869;


Table created.


SQL> create table apexdev.step_items_bc as select * from wwv_flow_step_items as of scn 3874676 where id = 4025742564048869;


Table created.


SQL> create table apexdev.steps_ac as select * from wwv_flow_steps where flow_id = 110 and id = 3;


Table created.


SQL> create table apexdev.steps_bc as select * from wwv_flow_steps as of scn 3874678 where flow_id = 110 and id = 3;


Table created.


SQL> 


APEX表は大抵列ID が主キーで、コンポーネント・キーで検索すると1行だけが返されます。ただし、表WWV_FLOW_STEPS(これはページのメタデータ)は例外で、アプリケーションIDであるFLOW_IDとページIDであるIDの複合主キーなので、FLOW_IDとIDを検索条件にします。

変更前の情報の検索には、フラッシュバック問い合わせ(AS OF SCN)を使っています。そのため、初期化パラメータのundo_retentionの期間内に検索を実行する必要があります。

APEXのアプリケーションを作って、変更後と変更前の表の違いを確認します。

アプリケーションのページにクラシック・レポートのリージョンを2つ作成します。ひとつはソースの表名に変更後の表STEP_ITEMS_ACを指定します。もうひとつはソースの表名に変更前の表STEP_ITEMS_BCを指定します。リージョンの配置を横並びにするため、変更前のリージョンのレイアウトの新規行の開始をOFFにします。


クラシック・レポートの属性を開き、外観のテンプレートとしてValue Attribute Pairs - Columnを選択します。列と値を縦方向に一覧表示します。


以上の設定を行い、アプリケーションを実行します。


Promptがマネージャーから上司に変更されていることが確認できます。


変更後の列Last Updated ByとLast Updated Onより、変更した人と時刻を確認できます。


変更履歴には表WWV_FLOW_STEPSへの変更がレポートされています。しかし、表WWV_FLOW_STEP_ITEMSと同様の手順で変更内容を確認すると、メタデータには変更は見つかりませんでした。変更したのはファセットのラベルだけなので、これは想定通りです。列Last Updated ByとLast Updated Onのみが変更されています。


APEXアプリケーションの変更点を調べる方法の紹介は以上になります。

ちなみにアプリケーションの変更履歴はAPEX表WWV_FLOW_BUILDER_AUDIT_TRAILに保存されています。この表にはビューやシノニムは登録されていないため、ユーザーSYSやSYSTEMのみがアクセスできます。Autonomous Databaseの場合は管理者ユーザーのADMINであってもアクセスできません。必ずアクティビティのモニターを開いて確認する必要があります。

WWVで始まるAPEX表を直接問い合わせることは、サポート対象外です。そのため取得した情報の扱いは、参考程度にとどめておくべきです。APEXアプリケーションのメタデータを参照する場合は、APEX_で始まる標準ビューを使用します。

完

2022年8月23日火曜日

アプリケーションのインストール時にパラメータを設定する

 前回の記事でStripeで支払いを行うアプリケーションを作成しました。その際、公開可能キーやAPEXが稼働しているホスト名、メール・アドレスを直接記述しています。

var stripe = Stripe('pk_公開可能キーの貼り付け');
var apex_path = 'https://ホスト名/ords/r/ワークスペース名/';
var successUrl = apex_path + '/stripe-payment/success?session=' + apex.env.APP_SESSION;
var cancelUrl =  apex_path + '/stripe-payment/error?session=' + apex.env.APP_SESSION;
var customerEmail = '電子メール・アドレス';

これらの値はAPEXアプリケーションのインストール先で変更する必要があります。

アプリケーション置換文字列とサポートするオブジェクトの設定を行なった上でアプリケーションをエクスポートすると、そのアプリケーションをインポートする際に置換文字列に値を設定できます。

以下より、設定の手順を紹介します。

アプリケーション置換文字列を設定すると、前出のJavaScriptのコードは以下のように書き換えることができます。

var stripe = Stripe('&G_STRIPE_KEY.');
var apex_path = '&G_APEX_PATH.';
var successUrl = apex_path + '/stripe-payment/success?session=' + apex.env.APP_SESSION;
var cancelUrl =  apex_path + '/stripe-payment/error?session=' + apex.env.APP_SESSION;
var customerEmail = '&G_CUSTOMER_EMAIL.';

置換文字列として、G_STRIPE_KEY、G_APEX_PATH、G_CUSTOMER_EMAILを設定しています。

アプリケーション定義の置換を開いて、置換文字列と置換値を設定します。


JavaScriptでは置換文字列、例えば&G_STRIPE_KEY.、PL/SQLやSQLではバインド変数 :G_STRIPE_KEYといった形式で参照することができます。

置換文字列を設定した後にサポートするオブジェクトのアプリケーション置換文字列の設定を行います。

サポートするオブジェクトを開きます。


インストールのアプリケーション置換文字列を開きます。


アプリケーションのインポート時に、値の入力を要求する置換文字列のプロンプトにチェックを入れます。また、プロンプト・テキストを設定します。


現在の値は、インストール時に元の値として表示されます。センシティブな値の場合、エクスポートする前に変更しておくか、エクスポート・ファイルを直接編集して、元の値を変更しておく必要があります。

あとは通常のエクスポートを行い、アプリケーションをファイルに出力します。

そのファイルをインポートすると、インストールの手順の中で以下の画面が表示され、置換文字列の値の入力を求められます。


このような設定を行うことで、アプリケーションのインポート時に置換文字列の値を設定することができます。

完

影響を受ける要素に設定された数値の合計を計算する

 動的アクションのアクションには、影響を受ける要素というプロパティがあります。影響を受ける要素に複数のページ・アイテムを設定し、そのページ・アイテムが保持している値の合計を計算してみます。

ページの構成は以下になります。

ページ・アイテムP1_ARG_1、P1_ARG_2、P1_ARG_3、P1_SUMすべて、タイプは数値フィールドです。合計を計算するボタンB_CALCを作成し、そのボタンをクリックしたときに動作するアクションとして、以下のJavaScriptを実行します。

// 影響を受ける要素として設定されているページ・アイテムの値を数値として、すべて配列に取り出す。
let args = this.affectedElements.toArray().map( elem => Number(elem.value) );
// 合計を計算する。
const sum = args.reduce(
    ( previousValue, currentValue ) => previousValue + currentValue,
    0
);
// 合計をページ・アイテムに設定する。
apex.items.P1_SUM.setValue(sum);

このとき、TRUEアクションであるJavaScriptコードの実行の影響を受ける要素として、選択タイプをアイテム、アイテムとしてP1_ARG_1、P1_ARG_2、P1_ARG_3を指定します。


TRUEアクションの影響を受ける要素は、アクションが値の設定の場合は、値が設定されるページ・アイテムを指定します。また、リフレッシュの場合は、リフレッシュされるリージョンを指定します。英語ではAffected Elementsなので、影響を受ける要素と翻訳されていますが、実際はthis.affectedElementsに渡される要素というだけで、どのように影響を受けるかはコードに依存します。JavaScriptでコードを書く場合、this.affectedElementsをどのように扱うかは、コード中で決めることができます。

今回のように、必ずしもページ・アイテムP1_SUMを影響を受ける要素として指定する必要はなく、影響を受ける要素はJavaScriptのコードが書きやすくなるように指定できます。

以上です。

簡単ですが今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-affected-elements.zip

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

完

2022年8月22日月曜日

パラメータを受け取るレポートのソース設定

 レポートのソースとなる表/ビューまたはSQL問合せに、ページ・アイテムの値をパラメータとして適用する実装方法はいくつかあります。最近では、Oracle Database 19cにバックポートされた新機能SQLマクロも活用できます。

従業員名を選択するページ・アイテムを作成し、選択した従業員と同じ部門に所属する従業員をレポートに一覧します。

以下の実装方法を試してみます。

  1. SQL問合せにバインド変数を使う
  2. V関数を使ったビューにする
  3. SQL問合せを返すファンクション本体を使う
  4. SQLマクロを使う
  5. アプリケーション・アイテムを使う
  6. アプリケーション・コンテキストを使う
どの実装方法でも、以下の動作をするページになります。



準備


テスト用のデータとして、サンプル・データセットのEMP/DEPTに含まれる表EMPを使用します。

SQLワークショップのユーティリティのサンプル・データセットより、EMP/DEPTのデータセットをインストールします。言語は英語でも日本語でも構いません。今回の例では英語を選択しています。アプリケーションの作成は行いません。


アプリケーション作成ウィザードを起動します。

アプリケーションの名前をパラメータ付きレポートとします。ホーム・ページの編集を開いて、削除します。代わりにページを作成するため、ページの追加をクリックします。


追加するページはクラシック・レポートですが、対話モード・レポートを選択します(追加ページを開いて、クラシック・レポートを選ぶこともできます)。


ページ名はバインド変数とします。アプリケーションの作成後に、バインド変数を使ったレポートのソースを実装します。表またはビュー、クラシック・レポートを選択します。表またはビューとしてEMPを選択します。データの編集は行わないため、フォームを含めるにチェックは入れません。

ルックアップ列を開き、ルックアップ・キー1としてMGR、表示列1としてEMP.ENAMEを指定します。次にルックアップ・キー2としてDEPTNO、表示列2としてDEPT.DNAMEを指定します。作成されたレポートの列MGRに従業員名であるENAMEの値、列DEPTNOに部門名である列DNAMEの値が数値の代わりに表示されます。

以上の設定を行い、ページの追加をクリックします。


アプリケーションの作成を実行します。


アプリケーションが作成されました。


最初に最も一般的な方法である、バインド変数を使った実装をページ番号1のホーム・ページに行います。その他の実装は、作成したページ番号1をコピーして新たなページを作成し、そのページを改変します。


SQL問合せにバインド変数を使う



リージョンEmployeesに従業員を選択するページ・アイテムP1_ENAMEを作成します。

識別の名前としてP1_ENAME、タイプとして選択リストを選びます。ラベルはEnameとします。LOVのタイプとしてSQL問合せを選択し、SQL問合せとして以下を記述します。

select ename d, ename r from emp

追加値の表示はOFF、NULL値の表示はONとし、NULL表示値として-- 従業員を選択--と記述します。


ページ・アイテムP1_ENAMEの値を変更した際にレポートをリフレッシュするため、P1_ENAMEに動的アクションを作成します。

作成した動的アクションの識別の名前はonChange Refresh EMPとします。ページ・アイテムに動的アクションを作成すると、タイミングはデフォルトで、イベントは変更、選択タイプはアイテム、アイテムはP1_ENAMEになります。


TRUEアクションはリフレッシュに変更し、影響を受ける要素の選択タイプとしてリージョンを選び、リージョンとしてEmployeesを選択します。


クラシック・レポートのリージョンEmployeesを選択し、ソースのタイプを表/ビューからSQL問合せに変更します。SQL問合せとして、選択した従業員と同じ部門の従業員が一覧されるよう、以下のSELECT文を記述します。
select
    empno
    , ename
    , job
    , mgr
    , hiredate
    , sal
    , comm
    , deptno
from emp
where deptno in (
    select deptno from emp where ename = :P1_ENAME
)
送信するページ・アイテムとしてP1_ENAMEを指定します。


以上で、バインド変数を使った検索の絞り込みは完成です。

これが最も一般的な実装でしょう。


ページのコピーを作成する



ページのコピーを実行します。作成メニューからコピーとしてのページを実行します。


次のコピーとしてのページを作成として、このアプリケーションのページを選択します。

次へ進みます。


コピー元ページとして、1.バインド変数を選択します。新規ページ番号は2、新規ページ名はV関数を使ったビューとします。

次へ進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。新規ナビゲーション・メニュー・エントリは、ページ名がデフォルトとして設定されます。

次へ進みます。


ラベルなどの値は変更せず、コピーを実行します。


ページのコピーが作成されます。コピーされたページを改変して、それぞれの実装を確認します。


V関数を使ったビューにする



ページのコピーをページ番号2、ページ名をV関数を使ったビューとして作成します。

以下の定義にて、ビューemp_in_same_dept_vを作成します。
create or replace view emp_in_same_dept_v(empno, ename, job, mgr, hiredate, sal, comm, deptno)
as
select
    empno
    , ename
    , job
    , mgr
    , hiredate
    , sal
    , comm
    , deptno
from emp
where deptno in 
(
    select deptno from emp where ename = V('P2_ENAME')
);
ビューを定義しているSELECT文にバインド変数は使えないため、代わりにV関数を使用しています。

レポートのソースのタイプを表/ビューに変更し、表名としてビューEMP_IN_SAME_DEPT_Vを指定しています。送信するページ・アイテムはP2_ENAMEです。


以上で、V関数を使ったビューによる実装は完成です。バインド変数を使った実装と、動作に違いはありません。

ただし、このようなビューの実装はほとんど使い道がありません。
  1. ページ・アイテム名としてP2_ENAMEが埋め込まれていて、ビューとして再利用ができない。
  2. V関数が値を返すには、APEXセッションが開始している必要がある。(APEX_SESSION.CREATE_SESSIONまたはATTACHが呼び出されている必要がある)。
  3. 2、3より、このビューを使った単体テストの実施が難しい。
  4. V関数による値の取得は遅い。
V関数のパフォーマンスについては、Srihari Ravvaさん(Oracle APEXの開発チームの方)が彼のブログ記事 - Oracle APEX SYS_CONTEXT vs V Function - で検証しています。

V関数はつねにセッション・ステートとして保存されている値を返すと思っていたのですが、実際は、送信されたページ・アイテムの値を返すようです。それが無い場合、セッション・ステートの値を返します。また、ページ・アイテムP2_ENAMEのソースのセッション・ステートの保持がリクエストごと(メモリーのみ) - つまりセッション・ステートとして保存されない - であってもV関数は送信されたページ・アイテムが設定されていると、その値を返します。

とはいえ、他に実装の選択肢がある場合、V関数の使用は避けるべきです。


SQL問合せを返すファンクション本体を使う



ページのコピーをページ番号3、ページ名をファンクション本体として作成します。

従業員名を引数として、同じ部門に所属する従業員を一覧するSELECT文を生成するファンクションgen_select_emp_in_same_deptを作成します。SQLインジェクションを抑止するため、DBMS_ASSERT.ENQUOTE_LITERALを使用します。

create or replace function gen_select_emp_in_same_dept(
    p_ename in varchar2
)
return varchar2
as
    l_stmt varchar2(32767);
begin
    l_stmt := 'select empno, ename, job, mgr, hiredate, sal, comm, deptno from emp';
    l_stmt := l_stmt || ' where  deptno in (select deptno from emp where ename = ' || dbms_assert.enquote_literal(p_ename) || ')';
    return l_stmt;
end gen_select_emp_in_same_dept;

ソースのタイプをSQL問合せを返すファンクション本体に変更し、ソースとして以下を記述します。

return gen_select_emp_in_same_dept(:P3_ENAME);

送信するページ・アイテムとしてP3_ENAMEを指定します。


以上で実装は完了です。

この実装では、リテラルの異なるSQLが毎回生成されているため、SELECT文のパース処理もその度に実行されています。そのため、パフォーマンス面に悪い影響があります。

また、gen_select_emp_in_same_deptはOracle APEXのアプリケーションの中からでなくても実行できるため単体テストが可能ですが、返されるのがSELECT文であるため、動的SQLとして実行させる必要があります。


SQLマクロを使う



ページのコピーをページ番号4、ページ名をSQLマクロとして作成します。

従業員名を引数として、同じ部門に所属する従業員を一覧するSELECT文を生成するSQLマクロmacro_select_emp_in_same_deptを作成します。
create or replace function macro_select_emp_in_same_dept(
    p_ename in varchar2
)
return clob sql_macro
as
    l_stmt clob;
begin
    l_stmt := 'select empno, ename, job, mgr, hiredate, sal, comm, deptno from emp';
    l_stmt := l_stmt || ' where  deptno in (select deptno from emp where ename = p_ename)';
    return l_stmt;
end macro_select_emp_in_same_dept;

ソースのSQL問合せを、SQLマクロを使ったSELECT文に変更します。
select
    empno
    , ename
    , job
    , mgr
    , hiredate
    , sal
    , comm
    , deptno
from macro_select_emp_in_same_dept(:P4_ENAME)
送信するページ・アイテムとしてP4_ENAMEを指定します。


SQLマクロを使ったSQLは、単体での実行も可能です。

引数に従業員名としてALLENを指定し、SQLコマンドで上記のソースであるSELECT文を実行した結果になります。



アプリケーション・アイテムを使う



ページのコピーをページ番号5、ページ名をアプリケーション・アイテムとして作成します。

V関数を使ったビューで作成したビューEMP_IN_SAME_DEPT_Vでは、ページ・アイテムP2_ENAMEを内部で参照しています。そのため、ページが変わると再利用ができません。この部分をアプリケーション・アイテムに置き換えます。

アプリケーション・アイテムG_ENAMEを作成します。

共有コンポーネントのアプリケーション・アイテムを開きます。


作成済みのアプリケーション・アイテムが一覧されます。作成をクリックします。


アプリケーション・アイテムの名前をG_ENAMEとします。有効範囲はアプリケーション、セキュリティのセッション・ステート保護として、一番保護が厳しい、制限付き - ブラウザから設定不可を選択します。

アプリケーション・アイテムの作成をクリックします。


アプリケーション・アイテムG_ENAMEが作成されます。


アプリケーション・アイテムG_ENAMEの値で、一覧する従業員を制限するビューEMP_IN_DEPT_Vを作成します。ビューEMP_IN_SAME_DEPT_VのP2_ENAMEをG_ENAMEに置き換えています。
create or replace view emp_in_dept_v(empno, ename, job, mgr, hiredate, sal, comm, deptno)
as
select
    empno
    , ename
    , job
    , mgr
    , hiredate
    , sal
    , comm
    , deptno
from emp
where deptno in 
(
    select deptno from emp where ename = V('G_ENAME')
);

ページ・アイテムP5_ENAMEの値が変更されたときに実行される動的アクションの、リフレッシュの前にTRUEアクションを作成します。

識別のアクションとしてサーバー側のコードを実行を選択します。設定の言語としてPL/SQL、PL/SQLコードとして、以下を記述します。ページ・アイテムP5_ENAMEの値をアプリケーション・アイテムG_ENAMEに設定しています。

:G_ENAME := :P5_ENAME;

送信するアイテムにP5_ENAMEを設定します。戻すアイテムの指定は、画面に戻す値です。G_ENAMEは画面に戻す値では無いので、戻すアイテムとして指定する必要はありません。


レポートのソースの表/ビューとして、EMP_IN_DEPT_Vを指定します。送信するページ・アイテムの指定は不要です。ページ・アイテムP5_ENAMEの値は、onChange Refresh EMPとして作成した動的アクションでサーバーに送信済みです。


以上で実装は完了です。

ビューのコードに埋め込んだアイテムがアプリケーション・アイテムであるため、ビューEMP_IN_DEPT_Vの再利用ができるようになっています。ただし、APEXセッションが開始されている必要があること、V関数のパフォーマンスが良く無い点については変わりありません。


アプリケーション・コンテキストを使う



ページのコピーをページ番号6、ページ名をアプリケーション・コンテキストとして作成します。

アプリケーション・コンテキストを扱う権限をAPEXのワークスペース・スキーマに割り当てます。

grant create any context to <APEXワークスペース・スキーマ>;
grant execute on dbms_session to <APEXワークスペース・スキーマ>;

今回の作業ではAutonomous Databaseを使用しているため、データベース・アクションのSQLより以下のコマンドを実行しました。APEXのワークスペース・スキーマの名前はwksp_apexdevです。

grant create any context to wksp_apexdev;
grant execute on dbms_session to wksp_apexdev;



APEXにて、以下のSQLスクリプトを実行します。アプリケーション・コンテキストEMP_DEPT_CTX、アプリケーション・コンテキストを操作するパッケージEMP_DEPT_CTX_PKG、アプリケーション・コンテキストを使ったビューEMP_IN_DEPT_APPCTX_Vを作成しています。

アプリケーション・コンテキストを使用する場合、動的アクションによるリフレッシュは利用できません。アプリケーション・コンテキストに値を設定したデータベース・セッションと、レポートのリフレッシュの際に発行されたAjaxコールを処理するデータベース・セッションが異なることがあるためです。アプリケーション・コンテキストに設定した値は、同一のデータベース・セッションからのみ参照することができます。

そのため、ページ・アイテムP6_ENAMEの設定の選択時のページ・アクションをRedirect and Set Valueに変更します。作成済みの動的アクションは削除します。


レンダリング前にプロセスを作成し、アプリケーション・コンテキストに値を設定します。

作成したプロセスの識別の名前はコンテキストの設定とします。タイプはコードの実行です。ソースのPL/SQLコードとして、以下を記述します。

emp_dept_ctx_pkg.set_ename(:P6_ENAME);

アプリケーション・コンテキストEMP_DEPT_CTXにENAMEとして、ページ・アイテムP6_ENAMEの値を設定します。


ソースの表名にEMP_IN_DEPT_APPCTX_Vを指定します。


ページ・アイテムP6_ENAMEを切り替えるたびに、変更が保存されていない旨の警告が表示されます。その警告を抑止するため、ページ・プロパティのナビゲーションの保存されていない変更の警告をOFFに変更します。


以上で、アプリケーション・コンテキストを使った実装は完了です。

アプリケーション・コンテキストを使用すると、V関数のような速度の低下は発生しないようです。そのため、ビューを作る必要がある場合は、V関数ではなくアプリケーション・コンテキストを使用するのが望ましいです。

また、以下のマニュアルに記載があるAPP_ID、APP_USER、APP_SESSIONといったSYS_CONTEXT変数として参照可能な組み込み置換文字列は、パフォーマンス上の利点よりV関数ではなくSYS_CONTEXT変数として参照することが推奨されています。

Oracle APEXアプリケーション・ビルダー・ユーザーズ・ガイド


おおむね実装方法としての推奨は、バインド変数、SQLマクロ、アプリケーション・コンテキストの順になりますが、要件が変わればこの順番も変わります。特に複雑なSQLで単体テストを行う必要がある場合は、SQLマクロやアプリケーション・コンテキストを使ったビューを使った実装は、検討する価値があるでしょう。

以上になります。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。grant create contextやexecute on dbms_sessionは、アプリケーションのインポート前に実施しておきます。
https://github.com/ujnak/apexapps/blob/master/exports/sample-report-with-parameter.zip

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

完

2022年8月10日水曜日

セッション・ステートの保持 - メモリーとディスクの違い

 ページ・アイテムのソースのセッション・ステートの保持の設定で、リクエストごと(メモリーのみ)とセッションごと(ディスク)のどちらかを選んだ時の違いを、クラシック・レポートとポップアップLOVを使って説明してみます。



準備


セッション・ステートの保持の設定による動作の違いを確認するために、APEXアプリケーションを作成します。

サンプル・データセットのEMP/DEPTに含まれる表EMPを検証に使用します。

SQLワークショップのユーティリティのサンプル・データセットを開き、EMP/DEPTのインストールを実行します。言語は日本語と英語のどちらを選んでも作業は可能です。また、アプリケーションの作成は行いません。

アプリケーション作成ウィザードを実行します。

アプリケーションの名前をセッション・ステートの保持とし、ページの作成をクリックします。

作成するのはクラシック・レポートですが、とりあえず対話モード・レポートを選択します。

ページ名はEMPとします。表またはビュー、クラシック・レポートを選択します。表またはビューとしてEMPを選択します。使用するのはクラシック・レポートのみなので、フォームを含めるのチェックは入れません。

ページの追加をクリックします。

アプリケーションの作成を実行します。

アプリケーションが作成されます。

ページ・デザイナにてクラシック・レポートのページ(ページ番号2)を開きます。

クラシック・レポートのリージョンEmployeesに、ページ・アイテムP2_HIREDATEを作成します。識別のタイプとして日付ピッカーを選択し、ラベルは採用日とします。

最初はソースのセッション・ステートの保持はリクエストごと(メモリーのみ)とします。

リージョンEmployeesのソースのWHERE句として hiredate > :P2_HIREDATE を記述します。送信するページ・アイテムは指定しません。


リージョンEmployeesに、ポップアップLOVのページ・アイテムを作成します。

識別の名前をP2_ENAME、タイプをポップアップLOVとします。ラベルは従業員とします。LOVのタイプにSQL問合せを選択し、SQL問合せとして、クラシック・レポートのソースと同等のSELECT文を記載します。

select ename d, empno r from emp where hiredate > :P2_HIREDATE

追加値の表示はOFFとします。

ソースのセッション・ステートの保持はリクエストごと(メモリーのみ)を選択します。今回のアプリケーションでは、ページ・アイテムP2_ENAMEの値が参照されることはないため、セッション・ステートの保持はセッションごと(ディスク)でもかまいません。どちらを設定しても良い場合は、サーバーへの負荷の少ないリクエストごと(メモリーのみ)を選びます。

このレポートを呼び出すボタンを、ホーム・ページに作成します。

ページ・デザイナでホーム・ページを開きます。

ページ・アイテムP1_HIREDATEを作成します。識別のタイプは日付ピッカー、ラベルは採用日とします。ソースのセッション・ステートの保持はリクエストごと(メモリーのみ)を選択します。


クラシック・レポートのページに移動するボタンを作成します。

識別のボタン名をB_SUBMIT、ラベルを送信とします。動作のアクションはページの送信とします。


ページが送信された後にクラシック・レポートのページに遷移するために、ブランチを作成します。

左ペインでプロセス・ビューを開きます。

ブランチを作成し、識別の名前をレポートを開くとします。動作のタイプとしてページまたはURL(リダイレクト)を選択します。サーバー側の条件のボタン押下時としてB_SUBMITを選択します。


リンク・ビルダー・ダーゲットの設定です。

ターゲットのタイプはこのアプリケーションのページ、ページは2になります。アイテムの設定の名前にP2_HIREDATEを選び、その値として&P1_HIREDATE.を指定します。


以上で検証に使用するアプリケーションは完成しました。

ここまでのアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/maintain-session-state.zip

このアプリケーションを使って、検証を行います。


クラシック・レポートへの遷移にブランチを使う点について


検証に使用するアプリケーションでは、ブランチを作成してクラシック・レポートのページに遷移しています。このときボタンの動作のアクションにこのアプリケーションのページにリダイレクトを選択して、ページ番号2に遷移させると期待した動作にはなりません。

ボタンの動作のアクションにこのアプリケーションのページにリダイレクトを設定すると、ボタンはapex.navigation.redirectを呼び出します。設定されたページ・アイテムはGETリクエストの引数として、サーバーに送信されます。GETリクエストの宛先となるURLはページの生成時に引数を含めて決定され、ページが表示された後のページ・アイテムの変更がURLに反映されることはありません。

簡略化すると、以下のHTML要素が生成されます。

<button onclick='apex.navigation.redirect("/ords/r/apexdev/maintain-session-state/emp?p2_hiredate=レンダリング時のP1_HIREDATEの値&session=セッションID
");'>

AタグのHREFの指定が、HTTPのGETで呼び出される動作と同じです。

動作のアクションがページの送信の場合は、apex.submitを呼び出します。画面上のページ・アイテムの値はPOSTリクエストの内容として、すべてサーバーに送信されます。

<button onclick='apex.submit({request: "B_SUBMIT"; validate:true});'>

formタグの内容が、HTTPのPOSTで送信される動作と同じです。

今回のボタンB_SUBMITの動作のアクションを、このアプリケーションのページにリダイレクトに変更してページ番号2に遷移させてみます。ページ・アイテムP1_HIREDATEはホーム・ページが表示された時点では値が無いため、採用日に何を設定してもクラシック・レポートには従業員が表示されません。

ホーム・ページが表示されたときの状況です。


採用日に値を設定し、送信ボタンを押します。


送信ボタンのURLの引数P2_HIREDATEの値は空白なので、ターゲットのページに存在するクラシック・レポートには何も表示されません。


ボタンの動作のアクションとしてページの送信が選択されていると、このような結果にはなりません。


リクエストごと(メモリーのみ)の場合



ページ・アイテムP2_HIREDATEのソースのセッション・ステートの保持が、リクエストごと(メモリーのみ)のときの動作について確認してみます。

採用日に1960/01/01を入力し、送信をクリックします。


ページ・アイテムP1_HIREDATEの値1960/01/01が、P2_HIREDATEに渡されます。

ページ・アイテムP2_HIREDATEには1960/01/01が表示されます。

ポップアップLOVP2_ENAMEのSQL問合せに含まれるP2_HIREDATEはNULLとなり、従業員は表示されません。

クラシック・レポートはP2_HIREDATEに1960/01/01が割り当てられ、すべての従業員が一覧されます。


クラシック・レポートにはP2_HIREDATEに1960/01/01といった値が割り当てられるのに、ポップアップLOVはなぜNULLになるのか、その違いですが、コンポーネント自体がAjaxコールを発行してデータを取得している場合、バインド変数にページ・アイテムの値を割り当てるには、送信するページ・アイテムにそのページ・アイテムを設定する必要があります。

動的アクションのリフレッシュに対応しているコンポーネントは、概ねコンポーネントの表示(HTMLの生成)とデータの取得は独立しているので、データ・ソースにバインド変数が含まれる場合は送信するページ・アイテムの設定が必要になります。

上記の設定ではクラシック・レポートで従業員の一覧が表示されています。クラシック・レポートの属性のパフォーマンスの遅延ロードをONにすると、レポートの表示(HTMLの生成)とデータの取得が非同期で行われるようになります。


この場合、送信するページ・アイテムを設定していないと、P2_HIREDATEがNULLになり従業員が表示されません。


ソースの送信するページ・アイテムにP2_HIREDATEを設定すると、ソースのSELECT文を実行する際にはページ・アイテムP2_HIREDATEの画面上の値を取得し、サーバーに送信します。そのため、取得される従業員はつねにページ・アイテムP2_HIREDATEが評価された結果になります。


ポップアップLOVではカスケードLOVの設定があり、ここで設定したページ・アイテムは、LOVのSQL問合せが実行されるときにサーバーに送信されます。

ページ・アイテムP2_ENAMEのカスケードLOVの親アイテムとしてP2_HIREDATEを設定することにより、ポップアップLOVでも従業員の一覧が表示されます。


これらの設定を行うことにより、クラシック・レポートおよびポップアップLOVの双方で、データ取得時にページ・アイテムP2_HIREDATEが評価されます。


多くのコンポーネントは送信するページ・アイテムの設定を含んでいます。送信するページ・アイテムによる設定では、その時点で画面に表示されている値がサーバーに渡されます。

ページ・アイテムP2_HIREDATEが変更されたときにクラシック・レポートがリフレッシュされるよう、動的アクションを作成します。

ページ・アイテムP2_HIREDATEに動的アクションを作成します。名前をonChange P2_HIREDATEとします。タイミングはデフォルトがそのまま使えます。TRUEアクションにリフレッシュ、影響を受ける要素の選択タイプをリージョン、リージョンとしてEmployeesを選択します。


採用日の値が変更されると、クラシック・レポートのリフレッシュが呼び出されます。データ・ソースとなるSELECT文が実行されるときはつねに送信するページ・アイテムとして指定されているページ・アイテムの値もサーバーに送信されるため、変更された採用日がP2_HIREDATEに割り当てられた結果が、従業員の一覧として表示されます。

ポップアップLOVはカスケードLOVの親アイテムとしてP2_HIREDATEが設定されているため、クラシック・レポートと同様に変更された採用日でLOVの一覧に置き換えられます。


セッションごと(ディスク)の場合



ページ・アイテムP2_HIREDATEのソースのセッション・ステートの保持をセッションごと(ディスク)に変更して、動作を確認します。

今までの設定を残したままだと、ほとんど動作に違いはありません。送信するページ・アイテムが設定されていると、データ・ソースを評価する際にセッション・ステートの保持がどちらであっても、画面上の値をサーバーに送信するためです。

そのため、クラシック・レポートから送信するページ・アイテムの設定を外します。遅延ロードはONのまま変更しません。


また、カスケードLOVからは親アイテムの設定を外します。


採用日に1960/01/01を入力し、送信します。


セッション・ステートの保持
がリクエストごと(メモリーのみ)のときとは異なり、クラシック・レポート、ポップアップLOVともに、ページ・アイテムP2_HIREDATEが1960/01/01を割り当てた検索結果が、従業員として一覧されます。セッション・ステートの保持がセッションごと(ディスク)となっているため、ページ・アイテムP2_HIREDATEに1960/01/01が設定された時点で、セッション・ステートとしてデータベースに保持されています。データ・ソースにバインド変数としてP2_HIREDATEが使われている場合、セッション・ステートに保持されているP2_HIREDATEの値が割り当てられます。


セッション・ステートに保持されているアイテムの値は、開発者ツール・バーのセッションのセッション・ステートの表示から参照することができます。この画面から参照できる値が、バインド変数P2_HIREDATEに割り当てられています。


この状態で、採用日を2022/08/01に変更します。採用日が2022/08/01以降の従業員は存在しません。動的アクションは有効なので、リフレッシュされたクラシック・レポートには従業員は表示されないことが期待されます。しかし、実際はそうならず、従業員の一覧は変わりません。

採用日の変更は画面上だけで、サーバー側に送信されていないためです。

変更されたページ・アイテムの値をサーバーに送信するため、リフレッシュの直前にTRUEアクションを作成します。

識別のアクションとしてサーバー側のコードを実行を選択し、設定のPL/SQLコードとしてnull;を記述します。送信するアイテムとしてP2_HIREDATEを指定します。コードには何も書いていないので、単にP2_HIREDATEの値をサーバーに送信するアクションです。


ページ・アイテムの値が送信されると、セッション・ステートにその値が保存されます。クラシック・レポートやポップアップLOVで、リフレッシュが行われる場合は更新されたセッション・ステートがバインド変数に割り当てられます。

コンポーネントが送信するページ・アイテムの設定を持っているのであれば、わざわざ動的アクションを作成する必要は無いでしょう。

ホーム・ページより採用日に1982/01/23を指定して、クラシック・レポートのページを開きます。

クラシック・レポートでは2名の従業員が選択されています。


ポップアップLOVもSELECT文は同等なので、従業員は2名になります。


採用日を1960/01/01に変更します。動的アクションにより、ページ・アイテムP2_HIREDATEの値1960/01/01がサーバーに送信され、クラシック・レポートがリフレッシュされます。

結果としてクラシック・レポートにはすべての従業員が一覧されます。


ポップアップLOVには変更がなく、従業員は2名です。ポップアップLOVはリフレッシュされていないためです。ポップアップLOVをリフレッシュするためには、カスケードLOVの親アイテムの設定が必要です。



まとめ


  1. 画面上の値をSQLのバインド変数に割り当てるには、送信するページ・アイテムの設定を使用する。もしくは、それと同等の設定を使用する。(ポップアップLOVの場合はカスケードLOV)。
  2. 送信するページ・アイテムにてバインド変数にページ・アイテムの値を割り当てている場合、セッション・ステートに値を保持する必要性はほぼない。
  3. 送信するページ・アイテムの設定を持たないがリフレッシュが可能なコンポーネントであれば、セッション・ステートの保持をセッションごと(ディスク)にし、動的アクションでページ・アイテムの値をサーバーに送信することで対応できる。
  4. 送信するページ・アイテムの設定がなく、リフレッシュにも対応していないコンポーネントを更新するには、ページの送信を実施する必要がある。

以上です。

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

完