2024年10月31日木曜日

Graph Visualization Plug-inのサンプル・アプリケーションをOracle Database 23aiのAPEXにインストールする

Oracle Database 23aiからProperty GraphおよびSQL/PGQがサポートされています。Graphの開発チームより、Oracle APEX向けのGraph Visualization Plug-inが提供されています。

このプラグインの使い方について、以下のドキュメントに記載されています。

Graph Developer's Guide for Property Graph

記載が非常に分かりにくいです。

以下より、実際にサンプル・アプリケーションの導入を行い、確認できた手順を紹介します。導入する対象は、Oracle APEX 24.1とOracle Database 23ai Freeです。データベースのバージョンやOracle APEXのバージョンが異なると、アプリケーションとしてインポートするファイルや実行するファイルが異なるので注意が必要です。

Graphのドキュメントの手順にはAPEXのワークスペース・スキーマにCREATE PROPERTY GRAPH権限を与える必要があると記載されていますが、APEX 24.1ではワークスペースの作成時にCREATE PROPERTY GRAPHの権限を与えています。そのため、管理サービスからAPEXのワークスペースを作成していれば、この作業は不要です。

Oracle APEXのサンプル・アプリを保持しているGitHubのリポジトリより、sample-graph-visualizations_23ai.sqlをダウンロードします。



パッケージDBMS_GVTを作成するスクリプトgvt_sqlgraph_to_json.sqlをダウンロードします。optional-23ai-onlyの下にあるファイルです。



パッケージDBMS_GVTを作成します。

Oracle Database 23ai FreeにはパッケージDBMS_GVTが含まれていますが、試したところファイルから作成するパッケージの代わりには使用できなかったです。

ダウンロードしたファイルgvt_sqlgraph_to_json.sqlを実行して、パッケージDBMS_GVTを作成します。

SQLワークショップSQLスクリプトを開きます。


アップロードをクリックします。


ファイルとしてgvt_sqlgraph_to_json.sqlを選択し、アップロードをクリックします。


ファイルがアップロードされたら、gvt_sqlgraph_to_json.sql実行します。


即時実行をクリックします。


パッケージDBMS_GVTとそのパッケージ本体およびファンクションORA_SQLGRAPH_TO_JSONが作成されます。


続いて、サンプル・アプリケーションをワークスペースにインポートします。手順自体は、通常のアプリケーションのインポートと変わりありません。

アプリケーション・ビルダーからインポートを開きます。


インポートするファイルとしてsample-graph-visualizations_23ai.sqlを選択します。

へ進みます。


アプリケーションのインストールをクリックします。


アプリケーションがインストールされます。

サポートするオブジェクトのインストールをクリックします。サンプル・アプリケーションが使用する表の作成やデータのロードなどが行われます。


サポートするオブジェクトのインストールが完了したら、インストール・サマリーをクリックして確認します。


スクリプトの実行が成功していることを確認し、インストール・サマリー左横にある矢印をクリックして、アプリケーション・ビルダーに移動します。


これからサンプル・アプリケーションSample Graph Visualizations - Do not edit -24.4.1を実行します。

その前に共有コンポーネント認証スキームにあるカレントの認証スキームApplication Express Accountsを開き、設定を少し変更します。


セッション共有タイプをカスタムからアプリケーション(非共有)に変更します。


必ずしも必要な作業ではありませんが、テーマのリフレッシュの実施は推奨します。


以上の作業を行なった後、アプリケーションを実行します。

サインインに、以下のようなホーム・ページが表示されます。


アプリケーションに実装されている、各種のサンプルを確認できます。


以上でGraph Visualization Pluginのサンプル・アプリケーションのインストールは完了です。

2024年10月29日火曜日

OpenAIのChat Completions APIでStructured Outputsを指定する

以前の記事「Oracle APEXのアプリからいろいろなローカルLLMを呼び出してみる」にて、OpenAI Chat Completions APIを呼び出すAPEXアプリケーションを作成しています。

上記の記事で作成したAPEXアプリケーションを、Structured Outputsの指定ができるように改変しました。API呼び出しを実装しているパッケージUTL_OPENAI_CHAT_APIも、Structured Outputsに対応するために変更しています。ただしFunction Callingを実装していたので、改変部分は非常に少ないです。

パッケージ定義
https://gist.github.com/ujnak/93c9d427ccef53e30dd89e623f08c880

パッケージ本体
https://gist.github.com/ujnak/a6126f7e9ee264d0e384103cea0379b5

以下は、Structured Outputsを指定してOpenAIのChat Completions APIを呼び出した結果です。

与えているシステム・プロンプトとユーザーのメッセージは、OpenAIのStructured outputsのドキュメントにExampleとして掲載されていたものを与えています。

システム・プロンプト
「You are a helpful math tutor. Guide the user through the solution step by step.」
ユーザー・メッセージ
「how can I solve 8x + 7 = -23」


モデルgpt-4o-miniは、以下の結果をJSON形式で返してきました。
{
  "steps" :
  [
    {
      "explanation" : "Start with the original equation: 8x + 7 = -23",
      "output" : "8x + 7 = -23"
    },
    {
      "explanation" : "To isolate the term with x, subtract 7 from both sides of the equation.",
      "output" : "8x + 7 - 7 = -23 - 7"
    },
    {
      "explanation" : "Simplifying both sides gives us: 8x = -30",
      "output" : "8x = -30"
    },
    {
      "explanation" : "Next, divide both sides by 8 to solve for x.",
      "output" : "x = -30 / 8"
    },
    {
      "explanation" : "Simplifying -30/8 gives us -15/4 or -3.75.",
      "output" : "x = -3.75 or x = -15/4"
    }
  ],
  "final_answer" : "x = -15/4 or x = -3.75"
}
Structured Outputsに対応したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-so.zip

以下より、アプリケーションの変更点を紹介します。

最初に、Structured Outputsの指定に含めるJSONスキーマを保存する表を作成します。クイックSQLの以下のモデルより、表OPENAI_RESPONSE_FORMATSを作成しています。
# prefix: openai
response_formats
    format_name vc80 /nn
    format_type vc20 /check text,json_object,json_schema
    json_schema json
    description vc4000

クイックSQLで列のタイプにjsonを指定すると、DDLでの列定義はclob check (json_schema is json)になります。本当はCLOB型ではなくBLOB型にformat osonの指定を加えるか、23aiであればJSON型とする方が、容量面でも性能面でも有利です。今回はAPEXのアプリケーションの作りやすさを優先して、CLOB型のままとします。Oracle APEXのウィザードは、CLOB型についてはテキスト領域を自動生成し、BLOB型についてはファイル選択を生成します。

作成した表OPENAI_RESPONSE_FORMATSのデータを操作するため、フォーム付き対話モード・レポートのページを作成します。


対話モード・レポート、フォームともに、ソースとしてOPENAI_RESPONSE_FORMATSが指定されている、ごく一般的なページになります。


OPENAI_RESPONSE_FORMATSには、設定の名前である列FORMAT_NAME、形式の指定である列FORMAT_TYPE、出力形式を指定するJSONスキーマの列JSON_SCHEMAと、補足説明を記述する列DESCRIPTIONがあります。

FORMAT_TYPEとして指定できる値はtextjson_objectと、Structured Outputsの指定であるjson_schemaがあります。これらの値は、ページ・アイテムのタイプ選択リストとし、静的値として設定しています。


JSON_SCHEMAには、正確に言うとOpenAIのChat Completions APIのメッセージの属性reponse_formatに与えるJSONデータを設定します。今回の実装例で参照しているOpenAIのExampleでは、以下の値がJSON Schemaとして設定する値になります。

2024年11月29日修正: 列JSON_SCHEMAにはJSON Schemaのみを与え、nameとstrict属性は引数として与えるようにプロシージャを変更しました。

schema属性のJSONデータが本来はJSONスキーマになりますが、namestrictschema属性を含むJSONオブジェクトをJSONスキーマとして設定しています。
{
  "name" : "math_reasoning",
  "schema" :
  {
    "type" : "object",
    "properties" :
    {
      "steps" :
      {
        "type" : "array",
        "items" :
        {
          "type" : "object",
          "properties" :
          {
            "explanation" :
            {
              "type" : "string"
            },
            "output" :
            {
              "type" : "string"
            }
          },
          "required" :
          [
            "explanation",
            "output"
          ],
          "additionalProperties" : false
        }
      },
      "final_answer" :
      {
        "type" : "string"
      }
    },
    "required" :
    [
      "steps",
      "final_answer"
    ],
    "additionalProperties" : false
  },
  "strict" : true
}
JSONデータを入力するページ・アイテムに計算を設定し、保存するデータをプリティ・プリントしています。

select json_serialize(:P5_JSON_SCHEMA pretty) from dual


以上で、Structured Outputsのresponse_formatを設定するページが作成できました。

ホーム・ページのTool Setの選択を行う選択リストの下に、Response Formatを選択するページ・アイテムを作成します。


ページ・アイテムのタイプ選択リストLOVSQL問合せとして、以下のSELECT文を記述します。

select format_name d, id r from openai_response_formats


Chat Completions APIの応答なるJSONデータが読みやすくなるように、本体拡張フォーマットオンにし、HTML式に以下を記述します。

<pre>&CLOB001.</pre>


ボタンSend Messageをクリックしたときに呼び出されるプロセスSend Messageは、パッケージUTL_OPENAI_CHAT_APIに含まれるプロシージャCHATを呼び出しています。

Structured Outputsに対応するため、引数p_response_formatの扱いを変えています。また、引数p_json_schemaが追加されています。

引数p_response_formatタイプSQL問合せ(単一の値を返す)に変更し、SQL問合せとして、以下を記述しています。ページ・アイテムP1_RESPONSE_FORMATで選択したフォーマット・タイプ(つまりtext、json_object、json_schemaのどれか)が、引数p_response_formatに渡されます。

select format_type from openai_response_formats where id = :P1_RESPONSE_FORMAT


新しく追加された引数p_json_schemaSQL問合せに、以下を記述しています。

select json_schema from openai_response_formats where id = :P1_RESPONSE_FORMAT

ページ・アイテムP1_RESPONSE_FORMATで選択したJSONスキーマが、引数p_json_schemaに渡されます。


APEXアプリケーションをStructured Outputsに対応させるために行なった変更は以上になります。

Ollamaにllama3.2をロードして、Structured Outputsに対応しているかどうか確認してみました。

指示通りに正解を返しますが、Strucured Outputsは無視されました。OllamaでサポートしているのはあくまでJSON mode(typeがjson_objectで、schemaまでは指定できない)までのようです。


llama3.2の回答です。
To solve for x, we need to isolate the variable x on one side of the equation.

Here's our starting point:

8x + 7 = -23

Step 1: Subtract 7 from both sides of the equation to get rid of the constant term on the left-hand side.

This will give us a new equation with no constant terms on the left-hand side:
8x = -23 - 7
= -30

Now we have:

8x = -30

Step 2: Divide both sides of the equation by 8 to isolate x. This is called "inverse operations".

Dividing by 8 will cancel out the coefficient (the number multiplied by the variable) on the left-hand side.

(-30) ÷ 8 = ?

= -3.75

And that's our solution for x!

Now you know: x = -3.75

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

2024年10月28日月曜日

Microsoft AI Chat Protocolのクライアントを使ってブラウザよりローカルLLMを呼び出す

Oracle APEXのページにMicrosoft AI Chat Protocolのクライアントを読み込んで、ローカルLLMをブラウザから直接呼び出してみます。

作成したAPEXアプリケーションは以下のように動作します。

macOSのLM Studioに、モデルとしてmmnga/Llama-3-ELYZA-JP-8B-gguf/Llama-3-ELYZA-JP-8B-Q8_0.ggufをロードしてローカルLLM(ローカル・サーバー)を実行しています。

Oracle APEXのアプリケーションは別のホストよりロードしているため、localhostで動作しているLLMを呼び出そうとするとCORSのエラーが発生します。そのため、LM Studioの設定のCross-Origin-Resource-Sharing (CORS)ONにしています。


LM Studioは上記の設定によりAPEXのアプリケーションからローカルLLMを呼び出せたのですが、OllamaではCORSを許可する方法が分かりませんでした。

せっかくブラウザから直接LLMを呼び出すのでストリーミングを実装するつもりだったのですが(Microsoft AI Chat ProtocolはgetStreamedCompletion APIでストリーミングをサポートしています)、コードに以下のように記載があり、エンドポイントURLの末尾が/streamであることが必須のようでした。そのため、ストリーミングは断念してgetCompletion APIを呼び出しています。
    const response = await asStream(
      this.client.path(`${this.basePath}/stream`).post(request),
    );
以下より実装について紹介します。

空のAPEXアプリケーションを作成し、チャットの呼び出しはデフォルトで作成されるホーム・ページに実装しています。

ローカルLLMとやり取りするダイアログは、インライン・ダイアログとして実装しています。

タイプ静的コンテンツレイアウトスロットDialogs, Drawers and Popupsを選択し、外観テンプレートInline Dialogを選択します。これで、動的アクションでリージョンを開くまでは、画面にダイアログは表示されません。

テンプレート・オプションAuto Heightをチェックし、SizeとしてMedium (600x400)を選択しています。


ページ・プロパティJavaScriptCSSに、Microsoft AI Chat Protocolのクライアントを呼び出すために必要な設定や、メッセージの見かけを良くするためのCSSクラスを設定します。

JavaScriptファイルURLに以下を指定します。Microsoft AI Chat Protocolを扱うクライアント・ライブラリを読み込みます。

https://cdn.jsdelivr.net/npm/@microsoft/ai-chat-protocol/dist/iife/index.min.js

ファンクションおよびグローバル変数の宣言に以下を記述します。
var client;      /* Microsoft AI Chat Protocolのクライアント */
var messages;    /* 送信するメッセージの配列 */
const contentDiv = document.getElementById("content"); /* チャット履歴を表示する要素 */

/* 表示されたチャット履歴を初期化 */
function removeAllChildren(element) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
}
CSSインラインに以下を記述します。
.bubble-left {
  max-width: 70%;              /* 吹き出しの幅 */
  padding: 10px 15px;          /* 内側の余白 */
  margin: 10px 0;              /* 吹き出しの上下の間隔 */
  background-color: #e1f5fe; /* 吹き出しの背景色 */
  text-align: left;            /* 文字を左寄せ */
  position: relative;          /* 吹き出しの位置を相対的に */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

.bubble-right {
  max-width: 70%;               /* 吹き出しの幅 */
  padding: 10px 15px;           /* 内側の余白 */
  margin: 10px 0;               /* 吹き出しの上下間隔 */
  background-color: #c8e6c9;  /* 吹き出しの背景色(薄い緑色) */
  text-align: left;             /* 文字は左寄せ */
  position: relative;           /* 吹き出しの位置を相対的に */
  margin-left: auto;            /* 吹き出しを右寄せ */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

インライン・ダイアログのリージョンを開くボタンOPENを作成し、ボタンをクリックしたときに実行される動的アクションを作成します。


ボタンOPENを押した時に、最初に以下のJavaScriptコードを実行します。メッセージの履歴を消去し、ローカルLLMとのチャットを初期化します。

以下のURLよりLM StudioのローカルLLMを呼び出します。

http://localhost:8080/v1/chat/completions
/* AI Chat Protocolのクライアントを作成する */
client = new ChatProtocol.AIChatProtocolClient(
    "http://localhost:8080/v1/chat/completions",
    {
        "allowInsecureConnection": true
    }
);

/* メッセージとチャット履歴を表示するDIV要素も初期化。 */
messages = [];
removeAllChildren(contentDiv);

初期化を行なった後に、リージョンChatを開きます


リージョンChatには、チャットのやり取りを表示するリージョンとしてContent、ユーザーのメッセージを入力するページ・アイテムとしてP1_MESSAGE、メッセージをローカルLLMに送信するボタンSUBMIT、および、ダイアログを閉じるボタンCLOSEを作成します。

リージョンContent静的コンテンツとして作成し、ソースHTMLコードとして以下を記述します。

<div id="content"></div>

修飾を最低限にするため、外観テンプレートとしてBlank with Attributesを選択します。CSSクラスh400を設定し、リージョンの高さを400ピクセルに固定します。

詳細カスタム属性としてstyle="overflow-y: scroll;"を設定し、400ピクセルで表示できないメッセージはスクロールさせて表示するようにします。


メッセージを入力するページ・アイテムP1_MESSAGEは、テキスト領域として作成しています。


ボタンSUBMITをクリックしたときに、以下のJavaScriptコードを実行します。



ボタンCLOSEをクリックしたしたときは、TRUEアクションとしてリージョンを閉じるを呼び出すようにします。


以上でアプリケーションは完成です。実行すると記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-microsoft-ai-chat-protocol-call-local-llm.zip

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

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のアプリケーション作成の参考になれば幸いです。