2025年2月20日木曜日

APEX 24.2のJSONソースを使って東京都のバス停の位置を表示する

これまでのOracle APEXは、レポート、チャートおよびフォームといったデータを扱うコンポーネントのデータ・ソースとして、ローカル・データベース(表、ビューまたはSQL問合せ)、REST対応SQLRESTソースの3種類を提供していました。

Oracle APEX 24.2では、データ・ソースの新しいタイプとしてJSONソース二面性ビュー(Duality View)が加わっています。JSONソースは、JSON列のある表およびJSONコレクション表から作成します。作成したJSONソースをAPEXのコンポーネントにデータ・ソースとして設定することにより、JSON列やJSONコレクション表を対象としたデータの挿入、参照、更新、削除を行ないます。

今回は東京都交通局がJSON形式で公開している東京都のバス停情報を元にJSONソースを作成して、対話モード・レポートとマップのページを作成します。

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


東京都交通局のバス停情報は以下のページよりダウンロードします。

公共交通オープンデータセンター
東京都交通局 バス停情報


以下より、APEXアプリケーションの作成手順を説明します。

東京都バス停情報はJSONコレクション表に保存します。そのため、使用するデータベースはOracle Database 23aiが対象です。

最初にJSONコレクション表としてTOKYO_BUS_STOPSを作成します。以下のDDLを実行します。

create json collection table tokyo_bus_stops with etag;


空のAPEXアプリケーションを作成します。名前東京都バス停とします。


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


ダウンロードした東京都のバス停情報ですが、バス停ひとつひとつをJSONオブジェクトとした、JSON配列になっています。JSON配列からオブジェクトを取り出して、JSONコレクション表に投入できる出来合いのツールは見つかりませんでした。

そのため、最初にBusstopPoles.jsonをJSONコレクション表TOKYO_BUS_STOPSに投入する機能を実装します。

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

アップロードするファイルを選択するページ・アイテムを、P1_FILEとして作成します。タイプファイルのアップロードです。JSONファイルをアップロードして表に投入するだけなので、APEXの標準機能を使います。ストレージタイプ表APEX_APPLICATION_TEMP_FILESを選択し、ファイルをパージするタイミングリクエストの終わりとします。念の為、ファイル・タイプapplication/jsonを設定します。

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


ファイルをアップロードするボタンUPLOADを作成します。動作アクションはデフォルトのページの送信です。


ボタンUPLOADをクリックしたときに実行されるプロセスを作成します。

識別名前バス停情報のロードタイプコードの実行です。ソースPL/SQLコードとして以下を記述します。

サーバー側の条件ボタン押下時UPLOADを指定します。


以上で、バス停情報をJSONコレクション表に投入する機能が実装できました。

APEX 24.2でJSONソースを作成する場合、JSONソースデータ・プロファイル(いわゆるデータ・ソースの列情報)は、JSONスキーマから作成するか、または、表に保存されているJSONデータを元に自動生成します。JSONスキーマが無い場合は、あらかじめ表にJSONデータを投入しておく必要があります。手作業でもデータ・プロファイルを作成できないことはないですが、あまり現実的ではありません。

APEXアプリケーションを実行して、BusstopPoles.jsonを表TOKYO_BUS_STOPSにロードします。

FileBusstopPoles.jsonを選択し、ボタンUploadをクリックします。


エラーが表示されなければロードは完了です。

JSONソースを作成します。

共有コンポーネントJSONソースを開きます。


作成をクリックします。


名前東京都バス停とします。JSONソース・タイプJSONコレクション表を選択し、JSONコレクション表としてTOKYO_BUS_STOPSを選択します。

へ進みます。


表に保存されているデータからデータ・プロファイルが自動生成されます。

JSONコレクション表の場合は、セレクタが_idである列が主キーになります。JSONコレクション表は、Oracle REST Data Servicesが提供するMongoDB互換APIからコレクションとして扱うことができます。

作成をクリックします。


JSONソースとして東京都バス停が作成されます。


作成したJSONソース、東京都バス停をデータ・ソースとしたフォーム付き対話モード・レポートのページを作成します。

ページの作成をクリックします。


対話モード・レポートを選択します。


レポートの名前バス停一覧とします。フォーム・ページを含めるオンフォーム・ページ名バス停詳細とします。

データ・ソースJSONソースを選び、JSONソースとして先ほど作成した東京都バス停を選択します。ネストした行2. ODPT_BUSROUTEPATTERNを選択します。

バス停オブジェクトは、属性odpt:operator(データ・プロファイルの行はODPT_OPERATOR)、odpt.BusroutePattern(同ODPT_BUSROUTEPATTERN)およびodpt:busstopPoleTimetable(同ODPT_BUSSTOPPOLETIMETABLE)の3つがJSON配列を持ちます。ただしodpt:operatorToei(都営)のみ、odpt:busstopPoleTimetable(つまり時刻表)は4000行を超えるため、ネストを展開するのは現実的ではありません。ネストした行2. ODPT_BUSROUTEPATTERN(属性はodpt:busroutePattern、つまり運行経路)を指定すると、同じバス停でも運行経路が異なれば別の行になります。

へ進みます。


JSONコレクション表TOKYO_BUS_STOPSが元なので、主キー列1はセレクタが_idの列C_ID2(Varchar2)です。また、ネストした行2. ODPT_BUSROUTEPATTERNを選択しているため、同じバス停で運行経路が異なるケースを特定するために、主キー列2ODPT_BUSROUTEPATTERN_POS(Number)を設定します。

ページの作成をクリックします。


東京都のバス停を一覧する対話モード・レポートと、編集するフォームのページが作成されました。


ページを実行して、バス停の情報を参照します。


人が見て意味がわかる列は、Title En、Title Ja、Title Ja Hrkt、Geo Lat、Geo Long、ODpt Busroutepattern2くらいです。列の表示を制限します。


同じバス停でも運行経路が異なると、別の行になっていることが確認できます。


編集アイコンをクリックすると、ドロワーにフォームが開きます。

JSONソースはフォームによる編集ができます。今回は運行経路をネストした行として指定しているため、バス停に紐づいた運行経路だけが編集できます。

取消をクリックし、ドロワーを閉じます。


マップのページを作成し、バス停の位置を表示します。

ページの作成を実行しマップを選択します。


ページ名前バス停の位置とします。

データ・ソースJSONソースを選択し、JSONソースとして東京都バス停を選択します。ネストした行なしとします。対話モード・レポートで指定した2. ODPT_BUSROUTEPATTERNは、マップではファセット検索に使用します。

へ進みます。


マップ・スタイルポイントを選択します。

マップ属性ジオメトリ列タイプとして2つの数値列を選択し、経度列GEO_LONG緯度列GEO_LATを指定します。ツールチップ列TITLE_JAを指定します。

ファセット検索ページの作成オンにします。

以上でページの作成をクリックします。


ファセット検索がついたマップのページが作成されます。


ページを実行するとエラーが発生します。JSONソースからマップのページを作成する際に、適切にデータ・ソースが設定されていません。


リージョンのマップを選択します。ソースが未設定になっているので(おそらく不具合です)、ソース位置JSONソースを選び、JSONソースとして東京都バス停を指定します。


以上の変更を保存し、ページを実行します。

マップ上に東京都のバス停が表示されます。


運行経路をファセットとして追加します。

ファセットの同期化を実行します。マップのソースに含まれる列がすべてファセットとして作成されます。この中で運行経路にあたるP4_ODPT_BUSROUTEPATTERNを除いた、すべてのファセットをコメント・アウトします。


ファセットP4_ODPT_BUSROUTEPATTERNを選択し、プロパティを変更します。

識別タイプをチェック・ボックス・グループにします。ラベル運行経路とします。LOVタイプ個別値を選び、複数の値タイプJSON配列フィルタの結合OR(和集合)とします。


この設定でページを実行すると、以下のエラーが発生します。

  • Ajaxコールで検索のサーバー・エラーORA-40556: 2つのJSON_TABLE式のチェーンはサポートされていませんが返されました。


JSONソースはソースに定義されている表から、JSON_TABLEファンクションを使って属性を列として取り出します。ファセット検索の複数の値JSON配列を選択すると、JSON_TABLEファンクションを使って配列の要素を取り出します。結果として、1つのSELECT文に2つのJSON_TABLEファンクションが含まれます。その結果としてORA-40556が発生します。

JSONソースのときは、ファセットの複数の値JSON配列を選択することは、データベースの制限によりできません。

以下よりワークアラウンドを設定します。

マップローカル後処理タイプSQL問合せを選択し、SQL問合せとして以下を記述します。JSON配列区切りリストに変更しています。
select C_ID2,
       C_ID,
       C_TYPE,
       TITLE_EN,
       TITLE_JA,
       TITLE_JA_HRKT,
       DC_DATE,
       GEO_LAT,
       C_CONTEXT,
       DC_TITLE,
       GEO_LONG,
       C_METADATA_ETAG,
       ODPT_KANA,
       ODPT_NOTE,
       OWL_SAMEAS,
       replace(replace(replace(replace(json_serialize(ODPT_OPERATOR returning clob),'['),']'),'odpt.Operator:'),'"') ODPT_OPERATOR,
       ODPT_PLATFORMNUMBER,
       replace(replace(replace(replace(json_serialize(ODPT_BUSROUTEPATTERN returning clob),'['),']'),'odpt.BusroutePattern:'),'"') ODPT_BUSROUTEPATTERN,
       ODPT_BUSSTOPPOLENUMBER,
       replace(replace(replace(replace(json_serialize(ODPT_BUSSTOPPOLETIMETABLE returning clob),'['),']'),'odpt.BusstopPoleTimetable:'),'"') ODPT_BUSSTOPPOLETIMETABLE
  from #APEX$SOURCE_DATA#

ファセットP4_ODPT_BUSROUTEPATTERN複数の値タイプ区切りリストに変更します。セパレータ,(カンマ)です。


ページを実行します。運行経路のファセットがきちんと動作します。


検索ファセットP4_SEARCHも適切に動作するように、ソースデータベース列TITLE_EN,TITLE_JA,TITLE_JA_HRKTのみに変更します。


以上でJSONソースを使用したAPEXアプリケーションは完成です。

今回はこちらの記事で構築手順を紹介している、podmanで実行しているコンテナ環境で作業しています。

東京都のバス停のデータをJSONコレクション表TOKYO_BUS_STOPSに投入しています。JSONコレクション表は、MongoDB互換APIからはコレクションとして扱うことができます。Oracle REST Data ServicesのMongoDB互換APIを有効にして、mongoshから確認してみます。

Oracle REST Data ServicesのMongoDB互換APIを有効にします。

ORDSが実行されているコンテナに接続します。

podman exec -it apex-ords bash

% podman exec -it apex-ords bash

[oracle@apex ords]$ 


MongoDB互換APIを有効にします。

ords --config /etc/ords/config config set mongo.enabled true

[oracle@apex ords]$ ords --config /etc/ords/config config set mongo.enabled true


ORDS: Release 24.4 Production on Thu Feb 20 08:41:10 2025


Copyright (c) 2010, 2025, Oracle.


Configuration:

  /etc/ords/config


The global setting named: mongo.enabled was set to: true

[oracle@apex ords]$ 


今回は必要ないので、TLS接続を無効にします。

ords --config /etc/ords/config config set mongo.tls false

[oracle@apex ords]$ ords --config /etc/ords/config config set mongo.tls false


ORDS: Release 24.4 Production on Thu Feb 20 08:42:44 2025


Copyright (c) 2010, 2025, Oracle.


Configuration:

  /etc/ords/config


The global setting named: mongo.tls was set to: false

[oracle@apex ords]$ 


MongoDB互換APIは、ポート番号27017で接続を待ち受けます。コンテナのポート27017をホストのポート27017にマップする必要があります。

apex.yamlportsに、containerPort: 27017hostPort: 27017を加えます。

spec:

  containers:

  - env:

    image: container-registry.oracle.com/database/free:latest

    name: db

    ports:

    - containerPort: 1521

      hostPort: 1521

    - containerPort: 8181

      hostPort: 8181

    - containerPort: 8443

      hostPort: 8443

    - containerPort: 27017

      hostPort: 27017

    securityContext: {}

    volumeMounts:

    - mountPath: /opt/oracle/oradata

      name: oradata-pvc


ポッドapexを作り直します。データベースのデータやORDSの設定は、それぞれボリュームoradataおよびords_configに含まれているため、ポッドを作り直しても以前の状態は維持されます。

podman pod stop apex
podman pod rm apex
podman play kube apex.yaml

apex-podman-setup% podman pod stop apex

apex

apex-podman-setup% podman pod rm apex

5a5ecf14277eabbfc88a420b8b7b2661baf249dc33065ee65328dbd722677d2b

apex-podman-setup % podman play kube apex.yaml

Pod:

ca2a3b6cf7fe45a3888cc9b6e062847e047a8c20d00f3d240f390b6da2c7a022

Containers:

abd925c7d9b30709b296841f761d3c6c086a728efe352c39d0dfab60229bac3f

c3f18d46b14ba32f3088cc5a5ed433d68b4c5d4d1ff01a88fb8514d5b835129e


apex-podman-setup % podman pod ps

POD ID        NAME        STATUS      CREATED             INFRA ID      # OF CONTAINERS

ca2a3b6cf7fe  apex        Running     About a minute ago  e726c524be3c  3

apex-podman-setup %


データベースにSYSで接続し、APEXのワークスペース・スキーマにロールSODA_APPをグラントします。

grant soda_app to <APEXワークスペース・スキーマ>;

QL> grant soda_app to wksp_apexdev;


Grantが正常に実行されました。


SQL> 


APEXのワークスペース・スキーマがORDSに登録済みであることを確認します。


mongoshで接続します。

mongosh 'mongodb://[APEXワークスペース・スキーマ]:[パスワード]@localhost:27017/[APEXワークスペース・スキーマ]?authMechanism=PLAIN&authSource=$external&tls=false&retryWrites=false&loadBalanced=true'

% mongosh 'mongodb://wksp_apexdev:*******@localhost:27017/wksp_apexdev?authMechanism=PLAIN&authSource=$external&tls=false&retryWrites=false&loadBalanced=true'

Current Mongosh Log ID: 67b6ee2a57ecb55b5c2d949b

Connecting to: mongodb://<credentials>@localhost:27017/wksp_apexdev?authMechanism=PLAIN&authSource=%24external&tls=false&retryWrites=false&loadBalanced=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.3.9

Using MongoDB: 4.2.14

Using Mongosh: 2.3.9


For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/


wksp_apexdev> 


コレクションtokyo_bus_stopsに含まれるドキュメントの件数を確認します。

db.tokyo_bus_stops.countDocuments()

3721件です。

wksp_apexdev> db.tokyo_bus_stops.countDocuments()

3721

wksp_apexdev> 


先頭行を表示してみます。

db.tokyo_bus_stops_findOne()

wksp_apexdev> db.tokyo_bus_stops.findOne()

{

  _id: ObjectId('67b6cb450eb8ab046f777c62'),

  title: { en: 'JA Nishi-Tokyo', ja: 'JA西東京前', 'ja-Hrkt': 'じぇいえいにしとうきょうまえ' },

  'odpt:busroutePattern': [

    'odpt.BusroutePattern:Toei.Ume77Kou.28601.2',

    'odpt.BusroutePattern:Toei.Ume77Kou.28602.2',

    'odpt.BusroutePattern:Toei.Ume77Kou.28603.2',

    'odpt.BusroutePattern:Toei.Ume77Kou.28606.4'

  ],

  'odpt:busstopPoleNumber': '2',

  'odpt:note': 'JA西東京前',

  'owl:sameAs': 'odpt.BusstopPole:Toei.JANishiTokyo.323.2',

  '@id': 'urn:ucode:_00001C0000000000000100000330C26C',

  'odpt:operator': [ 'odpt.Operator:Toei' ],

  '@context': 'http://vocab.odpt.org/context_odpt_BusstopPole.jsonld',

  '@type': 'odpt:BusstopPole',

  'dc:date': '2025-02-07T03:01:09+09:00',

  'odpt:kana': 'じぇいえいにしとうきょうまえ',

  'odpt:busstopPoleTimetable': [

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.KabeStationKitaguchi.25-100',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.KabeStationKitaguchi.25-160',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.KabeStationKitaguchi.25-170',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeShako.25-100',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeShako.25-160',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeShako.25-170',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeStation.25-100',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeStation.25-160',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.OmeStation.25-170',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.Urajukucho.25-100',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.Urajukucho.25-160',

    'odpt.BusstopPoleTimetable:Toei.Ume77Kou.JANishiTokyo.323.2.Urajukucho.25-170'

  ],

  'dc:title': 'JA西東京前',

  'geo:lat': 35.794155,

  _metadata: { etag: Binary.createFromBase64('LoyZd3xjBG/gYwQAWQq48g==', 0) },

  'geo:long': 139.291522

}

wksp_apexdev> 


JSONコレクション表TOKYO_BUS_STOPSを、APEXからはJSONソースとして、そして、MongoDB互換APIからはコレクションtokyo_bus_stopsとしてアクセスできることを確認しました。

今回の記事は以上になります。

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

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