2024年10月25日金曜日

マウス・エンターのイベントを拾ってレポートをリフレッシュする

マスター・ディテール関係のある表で、マスター表の行にポインタが乗ったときにディテール表を更新する、という実装について考えてみます。サンプル・データセットのEMP/DEPTを使って実装してみます。

表DEPTがマスター表で、部門の列にポインタが乗ったときに、表EMPのレポートを、その部門に限定して更新します。動作としては、以下のようになります。


DEPTソースとしたクラシック・レポートを左側に配置しています。クラシック・レポートのリージョンをJavaScriptのコードより参照するため、詳細静的IDとしてREPORT_DEPTを設定しています。


DEPTのクラシック・レポートでは、列DEPTNO非表示になっています。特にサロゲート・キーの場合、主キーの値をレポートに表示しないように設定すると思いますが、レポート上から列DEPTNOの値を取得できないと、表EMPの検索条件のDEPTNOに値を指定できません。

そのため、列DNAMEHTML式に以下を記述し、HTMLの出力にDEPTNOを含めます。カスタム属性としてdata-deptnoを追加しています。

<span data-deptno="#DEPTNO#">#DNAME#</span>


クラシック・レポートに含まれる列の上にポインタが乗った時に実行される動的アクションを作成します。

タイミングイベントマウス・エンターです。クラシック・レポート上の列をイベントのソースとして設定するために、選択タイプとしてjQueryセレクタを選び、jQueryセレクタとして#REPORT_DEPT tbody trを指定します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードに以下を記述します。
const tre = this.triggeringElement;
const tde = tre.querySelector("span[data-deptno]");
const deptno = tde ? tde.dataset.deptno : null;
if ( deptno ) {
    apex.item("P1_DEPTNO").setValue(deptno);
}

ポインタが乗ったTR要素がthis.triggeringElementになります。そのTR要素よりカスタム属性data-deptnoが設定されている要素(これはDNAMEを持つSPAN要素)を取り出し、DEPTNOを取得します。取得したDEPTNOをページ・アイテムP1_DEPTNOに設定します。


ページ・アイテムP1_DEPTNOにはDEPTNO、つまり番号を設定します。

ページ・アイテムには部門名を表示させるため、タイプとして選択リストを選び、LOVSQL問合せとして以下を記述します。

select dname d, deptno r from dept

セッション・ステートストレージリクエストごと(メモリーのみ)を選択します。


表EMPのクラシック・レポートのWHERE句として deptno = :P1_DEPTNO を設定します。レポートがバインド変数P1_DEPTNOを参照していますが、送信するページ・アイテムP1_DEPTNOが含まれているため、P1_DEPTNOの値をセッション・ステートに保存する必要はありません。


JavaScriptのコードよりページ・アイテムP1_DEPTNOに値を設定する際に(item#setValueを呼び出す)変更イベントを抑止していないため、ページ・アイテムP1_DEPTNO変更イベントを拾うことができます。

ページ・アイテムP1_DEPTNOの値が変更された時に、動的アクションを呼び出し表EMPのクラシック・レポートをリフレッシュします。

実行タイプデバウンスを選び、時間として1000(つまり1秒)を設定します。


TRUEアクションとしてリフレッシュを選び、影響を受ける要素として表EMPのクラシック・レポートを設定します。


主要な設定は以上になります。

今回の実装のポイントは、動的アクションに設定したデバウンスです。

デバウンスを設定することにより、レポートのリフレッシュ回数(つまり、サーバーでのSELECT文に実行回数)を大幅に削減できます。

デバウンスの設定を変更し、ビューV$SQLのEXECUTIONSを確認してみます。

WHERE句にコメントとして/* CHECK_FOR_EXECUTIONS */を含めているので、これを条件にして、レポートから実行されているSELECT文のSQL_IDを見つけます。

select sql_id, sql_text from v$sql where sql_text like '%CHECK_FOR_EXECUTIONS%';

クラシック・レポートが発行するSELECT文は、ページ送りなどを可能にするために、実際のソースであるSELECT文をインライン・ビューにしています。

SQL> select sql_id, sql_text from v$sql where sql_text like '%CHECK_FOR_EXECUTIONS%';


SQL_ID           SQL_TEXT                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

________________ __________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ 

fykf2n6mxnw57    select /*+qb_name(apex$112_1)*/* from(select a.*,row_number()over(order by null)apx$rownum from(select i.*  from (select "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" from(select /*+ qb_name(apex$inner) */d."EMPNO",d."ENAME",d."JOB",d."MGR",d."HIREDATE",d."SAL",d."COMM",d."DEPTNO" from(select x.* from "EMP" x  where(deptno = :P1_DEPTNO /* CHECK_FOR_EXECUTIONS */ ) )d  )i   )i where 1=1  order by "EMPNO" asc nulls last )a )where apx$rownum<=:p$_max_rows    

fykf2n6mxnw57    select /*+qb_name(apex$112_1)*/* from(select a.*,row_number()over(order by null)apx$rownum from(select i.*  from (select "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" from(select /*+ qb_name(apex$inner) */d."EMPNO",d."ENAME",d."JOB",d."MGR",d."HIREDATE",d."SAL",d."COMM",d."DEPTNO" from(select x.* from "EMP" x  where(deptno = :P1_DEPTNO /* CHECK_FOR_EXECUTIONS */ ) )d  )i   )i where 1=1  order by "EMPNO" asc nulls last )a )where apx$rownum<=:p$_max_rows    

fykf2n6mxnw57    select /*+qb_name(apex$112_1)*/* from(select a.*,row_number()over(order by null)apx$rownum from(select i.*  from (select "EMPNO","ENAME","JOB","MGR","HIREDATE","SAL","COMM","DEPTNO" from(select /*+ qb_name(apex$inner) */d."EMPNO",d."ENAME",d."JOB",d."MGR",d."HIREDATE",d."SAL",d."COMM",d."DEPTNO" from(select x.* from "EMP" x  where(deptno = :P1_DEPTNO /* CHECK_FOR_EXECUTIONS */ ) )d  )i   )i where 1=1  order by "EMPNO" asc nulls last )a )where apx$rownum<=:p$_max_rows    

g8b4vm8rxk618    select sql_id, sql_text from v$sql where sql_text like '%CHECK_FOR_EXECUTIONS%' and sql_text like '%P1_DEPTNO%'                                                                                                                                                                                                                                                                                                                                                                            

2q370ccg7afva    select --+qb_name(apex$4000_4500)  no_merge(@apex$inner d) * from (select * from(select /*+ qb_name(apex$inner) */* from(select x.* from "EMP" x  where(deptno = :P1_DEPTNO /* CHECK_FOR_EXECUTIONS */ ) )d  )i  )                                                                                                                                                                                                                                                                         

d5yrrbtwtyquy    select sql_id, sql_text from v$sql where sql_text like '%CHECK_FOR_EXECUTIONS%'                                                                                                                                                                                                                                                                                                                                                                                                            


6行が選択されました。 


SQL>


SQL_IDより、現時点でのSQLの実行回数を確認します。

select executions, sql_id from v$sql where sql_id = 'fykf2n6mxnw57' and executions > 0;

SQL> select executions, sql_id from v$sql where sql_id = 'fykf2n6mxnw57' and executions > 0;


   EXECUTIONS SQL_ID           

_____________ ________________ 

          222 fykf2n6mxnw57    


SQL> 


Departmentsの列の上を3往復くらいしてみます。


デバウンスを設定し時間を1秒としているため、ポインタが移動して最低でも1秒、移動した列の上に止まっていないと表EMPのレポートはリフレッシュされません。

V$SQLのEXECUTIONSは228なので、サーバーでは6回SELECT文が実行されました。

SQL> /


   EXECUTIONS SQL_ID           

_____________ ________________ 

          228 fykf2n6mxnw57    


SQL> 


動的アクションタイプ即時に変更し、同様の操作を行います。


開始時点でのSELECT文の実行回数は228回です。

SQL> /


   EXECUTIONS SQL_ID           

_____________ ________________ 

          228 fykf2n6mxnw57    


SQL> 


以下の操作を実行します。


終了時点のSELECT文の実行回数を確認すると、249回でした。

SQL> /


   EXECUTIONS SQL_ID           

_____________ ________________ 

          249 fykf2n6mxnw57    


SQL> 


実行しているSELECT文は select * from emp where deptno = :P1_DEPTNO なので、ほぼ瞬時に結果が返されます。動的アクションは非同期で実行される(つまり先に発行されたSELECT文の終了を待たない)ため、もっと検索に時間がかかるSELECT文であれば、サーバーに過大な負荷をかけることになります。

今回の記事で使用したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/mouse-enter-and-refresh-report.zip

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