2021年6月21日月曜日

2021年6月10日のOracle APEX Office Hours - What's new in APEX 21.1でのアナウンスされたこと

 2021年6月10日にOracle APEX Office Hoursとして、Oracle APEX 21.1の新機能について紹介がありました。録画はこちらから参照できます。

最初の15分ほどは、製品に関する報告です。その後、新機能のデモに移ります。最後の10分ほどは、Oracle APEXのDirectorであり、Oracle APEXを最初に作った人であるJoel Kallmanを追悼するメッセージになっています。

最初から試聴するのがお勧めですが、アナウンスの部分だけを以下に紹介します。

サンプル・アプリケーションとスターター・アプリケーション(以前の生産性アプリケーション)は、oracle.github.io/apexからダウンロードできます。

New Feature Request App(こういう新機能が欲しい、という要望をあげるアプリケーション)が近々、リリースされる予定です。

あまり認知されていないのですが、以前よりOracle Application Express Feature Requestsというサイトはありました。しかし、残念ながら機能していませんでした。

apex.oracle.com/roadmapとして、今後に予定している機能拡張が記載されています。以前はapex.oracle.com/sodでした。こちらでも同じページを参照できます。

Oracle Support以外では、apex.oracle.com/forumでも不具合の報告は受け付けています、とのことです。ただし、こちらはコミュニティによる支援ですので、回答が得られるという保証はありません。

Oracle APEX 21.1より、サポートするデータベースが12.1.0.2以降になります。11.2.0.4はサポート対象外です。

サポート対象のブラウザにIEは含まれません。また、Microsoft Edge Legacyも含まれません。

PSE(Patch Set Exception)が、APEXのバージョン番号に含まれるようになります。パッチバージョンは今まで日付であったのが、数字になります。例えば、Oracle APEX 21.1のPatch Setは32598392ですが、このパッチの最初のバージョンは1です。このパッチを適用する前のAPEXのバージョンは21.1.0で、パッチを適用したあとは21.1.1になります。

静的リソースのCDNのパスは変更されず、パッチに含まれる静的リソースも含みます。

https://static.oracle.com/cdn/apex/21.1.0/

Websheets、Oracle Formsの移行ユーティリティ、インスタンス・メトリックを取得するためのRESTインターフェース、Microsoft Edge Legacyはサポート対象外になります。


AnyChart, AnyMap, AnyGanttがデサポートになるので、対応方法について説明しています。使っているアプリがある場合は、バージョンアップ前に実施すべき内容もあるため、手順を確認しておく必要があります。

Oracle Database 21cより、オンプレのデータベースのインストールから、Oracle APEXが除かれます。ライセンスやサポートなどについては、まったく変更はありません。

常にOracle APEXの最新版を使用していただくための措置になります。詳しくはこちらの記事に記載されています。データベースのメディアからAPEXが無くなりますが、誤解しないように、ということを説明しています。

APEX 21.1よりプレミア・サポートの期間が最長3年に短縮されます。


以上です。

新機能紹介のデモは、機能を開発した人が実施しています。そのためか、内容が詳細でわかりやすいです。

2021年6月15日火曜日

自動化のアクションを途中で停止する

 以前の記事でOracle APEXの自動化の機能を使って、外部のExcelをロードする方法を紹介しています。

作成した自動化では、データの取得というアクションによって、オープンデータのExcelファイルを取得し一旦データベースに保存し、その後、データのロードというアクションによって、ファイルの内容をパースし表に保存しています。

取得したデータが以前と変更がなければ、後続のアクションであるデータのロードは実行する必要はありません。そのように動作するよう、実装してみました。


アクションデータの取得PL/SQLコードを以下に変更しています。

declare
l_blob blob;
l_last_blob blob;
e_already_loaded exception;
begin
l_blob := apex_web_service.make_rest_request_b
(
p_url => :IRYO_RESULT_URL,
p_http_method => 'GET'
);
select content into l_last_blob from covid19_iryo_data
where id = (select max(id) from covid19_iryo_data);
if dbms_lob.compare(l_blob, l_last_blob) = 0 then
apex_automation.log_info('Skip data loading because the data is not updated.');
raise e_already_loaded;
else
apex_automation.log_info('The data is updated.');
insert into covid19_iryo_data(store_date, content) values(sysdate, l_blob);
end if;
end;

直近に取得したExcelファイル(l_last_blobに保存)とHTTP経由で取得したExcelファイルのデータ(l_blobに保存)を比較し、両方が一致している場合(dbms_lob.compreの結果が0)、ユーザー定義の例外e_already_loadedを発生させています。

アクションのエラー処理自動化の中断に変更すると、例外が発生したアクションで自動化は終了し、後続のアクションは実行されません。自動化の無効化とは異なり、スケジュール・ステータスは無効にならないので、自動化は次回の時間になると再度実行されます。

プロシージャAPEX_AUTOMATION.LOG_INFOを呼び出し、処理の経過を自動化メッセージに書き込んでいます。書き込んだメッセージは自動化実行ログより確認できます。


APEX_AUTOMATION.LOG_INFOで書き込んだ文字列を確認することができます。


自動化の実行状況やログは、ビューAPEX_AUTOMATION_LOGまたはAPEX_AUTOMATION_MSG_LOGからも確認することができます。

以上です。

追記

apex_automation.exitを使うことができます。アクションの開始問合せでない場合、以降のアクションがスキップされ、自動化処理としては成功で終了します。skip_current_rowもアクションが問合せでなければ、同様の動作になります。

declare
l_blob blob;
l_last_blob blob;
e_already_loaded exception;
begin
l_blob := apex_web_service.make_rest_request_b
(
p_url => :IRYO_RESULT_URL,
p_http_method => 'GET'
);
select content into l_last_blob from covid19_iryo_data
where id = (select max(id) from covid19_iryo_data);
if dbms_lob.compare(l_blob, l_last_blob) = 0 then
apex_automation.exit('Skip data loading because the data is not updated.');
-- apex_automation.log_info('Skip data loading because the data is not updated.');
-- raise e_already_loaded;
else
apex_automation.log_info('The data is updated.');
insert into covid19_iryo_data(store_date, content) values(sysdate, l_blob);
end if;
end;


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

2021年6月8日火曜日

動的にSQLを生成しているレポートでSQLの構文エラーが出るのだけど、どうしたらいい?

 ORA-00907: missing right parenthesisが発生するのだけど、どうすればいい?と聞かれたので、SQLの完全トレースをとって確認したら、と回答しました。

デバッグの完全トレースについては、実際にOracle APEXが実行しているSQLを確認する方法として何度か紹介しています。この記事では、発生しているエラーを確認する方法として、完全トレースを取得する手順を紹介してみます。

例として、以下のPL/SQLコードによって生成したSELECT文でレポートを表示する、クラシック・レポートを作ります。

begin
if :P1_D = 'R' then
return q'~select 1 as a, 2 as b from (select 1 as a, 2 as b from dual)~';
end if;
return q'~select 1 as a, 2 as b from (select 1 as a, 2 as b from dual)~';
end;


このページを実行すると、以下の表示になります。


ここでPL/SQLコードを以下のように変更し、ページ・アイテムP1_DがRのときには、右カッコが足りないSELECT文にします。ページ・デザイナがPL/SQLコードを保存するときにはP1_DをnullとしてPL/SQLコードを評価するため、エラーは発生しません。エラーが発生するのは、アプリケーションの実行時のみです。

begin
if :P1_D = 'R' then
return q'~select 1 as a, 2 as b from (select 1 as a, 2 as b from dual~';
end if;
return q'~select 1 as a, 2 as b from (select 1 as a, 2 as b from dual)~';
end;

アプリケーションを実行すると、構文エラーが発生します。


PL/SQLコードがそれほど長くなくても、実際に生成されるであろうSELECT文を想像するのは大変です。そこで、アプリケーションのデバッグを有効にして、実際に実行されているSELECT文を確認します。

開発者ツール・バーの右端にある歯車アイコンより、デフォルト・デバッグ・レベルを開き、完全トレースを選択します。


開発者ツール・バーのデバッグ(完全トレース)をクリックし、デバッグを有効にします。デバッグが有効になると、開発者ツール・バーの表示はデバッグなしに変わります。


アプリケーションより、エラーが発生する処理を実行します。今回の例で言うと、レポートの表示なので、再表示を行うためにページをリロードします。

デバッグ、特に完全トレースを有効にすると操作が極端に遅くなります。ログを取得したら開発者ツール・バーのデバッグなしをクリックし、デバッグを解除します。

取得したログを参照するため、デバッグの表示をクリックします。


デバッグ画面より、対象の処理のログを見つけてビュー識別子をクリックします。

ページ番号の条件には注意が必要です。レポートとフォームの組み合わせの場合、データの更新処理はフォームのページに登録されたプロセスが実行します。フォームの処理が終わるとレポートのページに戻ります。レポートのページ上でデバッグの表示を実行すると、ページの条件は、現在表示されているレポートのページ番号になります。そのため、別のページであるフォームのページに紐づくビュー識別子は一覧に表示されません。ページ番号の検索条件をレポートではなく、フォームのページ番号に変更する必要があります。

今回の対象はレポートのページなので、ページの検索条件の変更は不要です。


記録されたメッセージの一覧が表示されます。


エラーが発生した箇所を探します。対話モード・レポートの検索ボックスで検索すると、検索に一致したメッセージが取り出されますが、その前後の行は表示されません。そのため、私はブラウザによる検索を主に使用しています。


実際に実行しているSELECT文を確認することで、なぜ構文エラーが発生したのか調査を進められます。

以上になります。

非常に簡単なものですが、今回の作業の確認に使用したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/generate-error.sql

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

2021年6月7日月曜日

チャートの追加 - チャートにy2軸を加える、シリーズをSQLで生成する

ワクチン接種状況ダッシュボードに組み込んだ(もしくは組み込もうとした)チャートについて、2つほど紹介します。


チャートにy2軸を追加する


以下のように接種数と接種率の2つのチャートを作成しています。これを左のy軸に接種量、右のy軸(y2軸)に接種率を割り当て、1つのチャートにまとめてみます。

時系列のチャートで複数のシリーズがある場合、それぞれのシリーズの時刻を合わせる必要があります。この場合、通常は設定時間軸タイプ混合頻度にします。

例えば以下のチャートがあるとします。

折れ線チャートには、6月1,2,3,5,8日にデータがあります。

棒グラフには、6月2,3,6,7日にデータがあります。

これを1つのチャートに含まれる、2つのシリーズにまとめます。時間軸タイプ有効の場合の表示は以下になり、シリーズの間で時刻が一致しません。


シリーズ間の時刻を一致させるには、時間軸タイプ混合頻度にします。混合頻度にすると、シリーズ間で時刻が一致します。


ただし、時間軸タイプを混合頻度にすると、積上げチャートにすることができません。

接種数は積上げチャートなので、今回は時間軸タイプを有効にした上で、接種率をシリーズとしてチャートに加えます。

以前の記事で作成したアプリケーションを元にします。

接種数日次推移のチャートに接種率のシリーズを追加します。

シリーズ名前接種率1回目とします。タイプ折れ線です。ソースタイプSQL問合せSQL問合せとして、接種率日次推移のチャートの1回目に記述したSQLをそのまま貼り付けます。送信するページ・アイテムとして、P1_GENDER,P1_AGE,P1_PREFECTUREを指定します。列のマッピングラベルCOUNT_DATERATEです。

ここまでは接種率日次推移のチャートの、1回目のシリーズと同じ設定になります。


外観Y2軸に割当てONにします。他のシリーズとの積み上げにならないよう、積上げカテゴリ接種率1回目を設定します。他のシリーズとの違いが分かりやすくなるよう、折れ線スタイル点線にします。


作成したシリーズを重複させ、識別名称接種率2回目に変更します。SQL問合せのwhere status = 1の部分をstatus = 2に変更し、積上げカテゴリ接種率2回目に変更します。


y2軸の書式割合にし、小数点2とします。


チャートの識別タイトルを、接種数日次推移から日次推移に変更します。


Y2軸に割合が表示され、接種率も点線でチャートに加わっていることが確認できます。


ツールチップの表示はつねにy軸の書式が適用されます。そのため、例えば5月31日の接種率1回目ですが、値が0.0467になっています。割合では表示されていません。


このような場合には、カスタム・ツールチップを設定します。接種率1回目のシリーズのSQL問合せを、以下に変更します。ツールチップとして表示される内容を、列CUSTOM_TOOLTIPとして生成しています。

select
count_date,
rate,
'系列 接種率1回目' || CHR(10)
|| '日付 ' || to_char(count_date,'DL') || CHR(10)
|| '値 ' || round(rate * 100, 2) || '%' custom_tooltip
from
(
select
count_date,
(
sum(count)
over (order by count_date asc)
/
(
select sum(count) total
from covid19_vaccination_targets
where 1=1
and gender in (select column_value from apex_string.split(:P1_GENDER, ':'))
and age in (select column_value from apex_string.split(:P1_AGE, ':'))
and prefecture in (select column_value from apex_string.split(:P1_PREFECTURE, ':'))
)
)
rate
from
(
select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 1
and gender in (select column_value from apex_string.split(:P1_GENDER, ':'))
and age in (select column_value from apex_string.split(:P1_AGE, ':'))
and prefecture in (select column_value from apex_string.split(:P1_PREFECTURE, ':'))
group by count_date
)
)


同様の変更を、接種率2回目のシリーズにも実施します。

最後にy軸とy2軸にタイトルを設定します。

y軸のタイトルとして、接種数を設定します。


y2軸のタイトルとして、接種率(%)を設定します。


以上で作業は完了です。ページを実行し、チャートを表示してみます。


左のチャートの凡例として表示されているシリーズをクリックし、今回追加した接種率だけを表示しています。表示されているチャートが同一であることを確認し、接種率日次推移のチャートを削除します。

こちらの作業を実施したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/chart-merge.sql

都道府県をシリーズとして比較する


シリーズは、それぞれ定義する以外に、SELECT文の検索結果に含まれる列から生成することもできます。

接種数の移動平均のチャートを作成し、選択した都道府県をシリーズとして表示させます。また同様に、接種率のチャートを作成し、選択した都道府県をシリーズとして表示させます。

事前に都道府県のコードと都道府県名のペアをリストするためのビューPREFECTURES_Vを作成します。DDLは以下になります。

create or replace view prefectures_v
as
select prefecture_name, prefecture
from covid19_vaccination_targets
group by prefecture_name, prefecture;

SQLワークショップSQLコマンドなどから実行します。


1回目の接種数の7日間移動平均について、以下のチャートを作成します。北海道、山口県、秋田県が選択されています。


1回目の接種率のチャートは以下になります。選択している都道府県は同様に、北海道、山口県、秋田県です。


チャートの作成手順を紹介します。最初は接種数1回目 - 7日間移動平均のチャートを作成します。

リージョンの作成を行い、識別タイトル接種数1回目 - 7日間移動平均タイプチャートとします。ソース位置ローカル・データベースタイプSQL問合せを選択します。

SQL問合せとして、以下を記載します。

select
prefecture_name,
count_date,
avg(count) over (partition by prefecture_name order by count_date asc
range between interval '6' day preceding and current row
) count
from (
select
p.prefecture_name,
r.count_date,
sum(r.count) count
from covid19_vaccination_results r join prefectures_v p on r.prefecture = p.prefecture
where r.status = 1
and r.gender in (select column_value from apex_string.split(:P1_GENDER, ':'))
and r.age in (select column_value from apex_string.split(:P1_AGE, ':'))
and r.prefecture in (select column_value from apex_string.split(:P1_PREFECTURE, ':'))
group by p.prefecture_name, r.count_date
)

列PREFECTURE_NAMEが検索結果に含まれるようにしています。接種数は接種日(COUNT_DATE)だけでなく都道府県名(PREFECTURE_NAME)ごとに集計し、また、移動平均はpartition by prefecture_nameの指定を追加することで、都道府県ごとに計算しています。

送信するページ・アイテムP1_GENDER,P1_AGE,P1_PREFECTUREを指定しています。


チャートのAttributesとして、以下の設定を行いました。

  • タイプ:折れ線
  • マルチシリーズ・チャート・データ
    • チャート・データのギャップを埋める:ON
    • ソート順序:ラベル - 昇順
    • ギャップをゼロとしてレンダリング:OFF
  • 設定
    • 時間軸タイプ:混合頻度
    • ズームとスクロール:いいえ
  • 凡例
    • 表示:ON
    • 位置:下

シリーズ識別名前は、接種数とします。ソース位置リージョン・ソースとし、列のマッピングシリーズ名としてPREFECTURE_NAMEを選択します。この指定により、列PREFECTURE_NAMEとして検索された都道府県名がそれぞれシリーズになります。

ラベルにはCOUNT_DATEにはCOUNTを指定します。マーカー表示はいにします。


以上で最初に掲載した1回目の接種数の7日間移動平均のチャートが表示されます。

続いて、1回目の接種率のチャートを作成します。今、作成したチャートを重複させ、タイトル接種率1回目とします。以下のSQL問合せに変更します。

select
prefecture_name,
count_date,
(
sum(count)
over (partition by prefecture_name order by count_date asc)
/
total
)
rate
from
(
select
p.prefecture_name,
p.prefecture,
r.count_date,
sum(r.count) count
from covid19_vaccination_results r
join prefectures_v p on r.prefecture = p.prefecture
where r.status = 1
and r.gender in (select column_value from apex_string.split(:P1_GENDER, ':'))
and r.age in (select column_value from apex_string.split(:P1_AGE, ':'))
and r.prefecture in (select column_value from apex_string.split(:P1_PREFECTURE, ':'))
group by p.prefecture_name, p.prefecture, r.count_date
) s
join
(
select
prefecture,
sum(count) total
from covid19_vaccination_targets
where 1=1
and gender in (select column_value from apex_string.split(:P1_GENDER, ':'))
and age in (select column_value from apex_string.split(:P1_AGE, ':'))
and prefecture in (select column_value from apex_string.split(:P1_PREFECTURE, ':'))
group by prefecture
) t on s.prefecture = t.prefecture


接種数(表COVID19_VACCINATION_RESULTSの集計)および対象人口(表COVID19_VACCINATION_TARGETSの集計)の集計を行います。接種数の累積値を求める際にはpartition by prefecture_nameを含めることにより、都道府県ごとに累積値を計算しています。累積値を都道府県ごとの対象人口で割ることにより、接種率を計算しています。


シリーズ識別名前接種率に変更し、列のマッピングはCOUNTからRATEへ変更します。


y軸を%で表示するため書式割合小数点2と設定します。


以上で1回目の接種率のチャートも完成です。アプリケーションを保存して実行すると、最初に示したチャートが表示されます。

この他に2回目の移動平均、合計の移動平均、2回目の接種率のチャートを加えたアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/select-prefectures.sql

このアプリケーションでは、値の適用は動的アクションを使わず、ページの送信で行なっています。そのため、本来はリージョンのソースの設定に、送信するページ・アイテムの指定は不要です。今までに作成したダッシュボードの設定と整合させるために、送信するページ・アイテムの設定を行なっています。

以上になります。

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

2021年6月5日土曜日

医療従事者の日別実績を読み込む

 政府のダッシュボードに含まれていない、医療従事者等のワクチン接種状況の日別の実績をデータベースに取り込んでみました。以下、その作業ログです。


表とアプリケーションを作成する


元となるデータは首相官邸のサイトにあるExcelファイルです。

それより過去のデータは厚生労働省のこちらのサイトに掲載されているデータです。

厚生労働省のサイトに掲載されている4月9日までのデータと、Excelに含まれる4月20日以前のデータは、実質的には手作業の入力になります。

最初にデータの入れ物になる表を作成します。SQLワークショップのクイックSQLで以下のモデルを定義します。

# prefix: covid19_iryo
# semantics: default
# ondelete: restrict
data
store_date
content file
results
count_date
first_pfizer num
first_moderna num
second_pfizer num
second_moderna num
source_data /reference data
results_old
count_date
first num
second num

COVID19_IRYO_RESULTSに投入されたデータが、元々どのExcelファイルに含まれていたかを示すために列SOURCE_DATAを定義しています。/reference dataの列ディレクティブを付加することにより、表COVID19_IRYO_DATAへの外部キー参照を定義しています。設定として# ondelete: restrictを追加し、保存されているExcelファイルから入力されたデータが表COVID19_IRYO_RESULTSに存在している限り、Excelファイルの削除を禁止しています。NOT NULL制約は付けず、NULLの場合は手入力と見做します。

クイックSQLの画面で、SQLの生成SQLスクリプトの保存レビューおよび実行を行います。

レビューの画面で表COVID19_IRYO_RESULTS_OLD主キー制約名をcovid19_iryo_resul_id_pkからcovid19_iryo_resold_id_pkに変更します。表名の最初から18文字を元に制約名が生成されているため、表COVID19_IRYO_RESULTSの制約名と競合しています。

実行をクリックし、確認画面で即時実行をクリックします。

3つの表、COVID19_IRYO_DATACOVID19_IRYO_RESULTS_OLDCOVID19_IRYO_RESULTSが作成されます。アプリケーションの作成を実行します。確認画面が開いたら、アプリケーションの作成をクリックします。

アプリケーションの名前医療従事者ワクチン接種状況とします。アプリケーションの作成を実行します。


アプリケーションが作成されるので、実行して確認します。

首相官邸のサイトに掲載されているデータの入力画面です。


厚生労働省のサイトに掲載されているデータの入力画面です。


大量のデータ入力には向いていないですが、基本的なデータの一覧、登録、編集、削除は行うことができます。

今回は自動で行う処理を対象にしているため、この画面の改良は行いません。また、初期のデータ入力のためにExcelファイルの作成および修正を行ない、SQLワークショップデータ・ワークショップを使ってデータをロードするため、この入力画面は使用しません。

Excelファイルを取得する


Excelファイルを取得するボタンを、データファイルの一覧画面に追加します。画面は以下になります。


ページ・デザイナで表COVID19_IRYO_DATAを一覧する画面を開き、リージョン・ボタンの位置で、ボタンの作成を実行します。識別ボタン名B_GETラベルデータの取得とします。ボタン位置対話モード・レポートの検索バーの右を選んでいます。動作アクションはデフォルトで、ページの送信です。


ボタンを押した時に実行されるプロセスを作成します。左ペインでプロセス・ビューを開き、プロセスの作成を実行します。識別名前データの取得とします。タイプコードの実行ソースタイプPL/SQLで、PL/SQLコードとして以下を記述します。サーバー側の条件ボタン押下時に、ボタン名のB_GETを指定します。

declare
l_blob blob;
begin
l_blob := apex_web_service.make_rest_request_b
(
p_url => :IRYO_RESULT_URL,
p_http_method => 'GET'
);
insert into covid19_iryo_data(store_date, content) values(sysdate, l_blob);
end;


コード内でExcelファイルを入手するURLとして、置換文字列IRYO_RESULT_URLを使用しています。アプリケーション定義置換IRYO_RESULT_URLを定義します。

https://www.kantei.go.jp/jp/content/IRYO-vaccination_data2.xlsx


以上でデータの取得については実装完了です。ページを実行しデータの取得をクリックしてみます。レポートに1行追記されることが確認できます。



Excelファイルからデータをロードする


取得したExcelファイルから接種数のデータを取り出し、表COVID19_IRYO_RESULTSにロードするプロセスを作成します。表COVID19_IRYO_RESULTSの一覧ページにデータをロードするボタンを作成します。


ボタンの作成自体は、先程のB_GETと同じです。識別ボタン名B_LOADラベルデータのロードとしています。


ボタンを押した時に実行されるプロセスを作成します。左ペインでプロセス・ビューを開き、プロセスの作成を実行します。識別名前データのロードとします。タイプコードの実行ソースタイプPL/SQLで、PL/SQLコードとして以下を記述します。サーバー側の条件ボタン押下時に、ボタン名のB_LOADを指定します。

declare
l_id number;
l_blob blob;
begin
-- 最後の取得したExcelファイルをロード対象とする
select id, content into l_id, l_blob from covid19_iryo_data
where id = (select max(id) from covid19_iryo_data);
--
merge into covid19_iryo_results r
using
(
select
to_date(col001,'YYYY-MM-DD') count_date,
to_number(col004) first_pfizer,
to_number(col005) first_moderna,
to_number(col006) second_pfizer,
to_number(col007) second_moderna
from
apex_data_parser.parse(
p_content => l_blob,
p_file_name => 'dummy.xlsx'
-- 読み込める範囲に限定する
, p_skip_rows => 5
, p_max_rows => 34
)
) l
on ( r.count_date = l.count_date )
when matched then
update set
r.first_pfizer = l.first_pfizer,
r.first_moderna = l.first_moderna,
r.second_pfizer = l.second_pfizer,
r.second_moderna = l.second_moderna,
r.source_data = l_id
when not matched then
insert
(
count_date,
first_pfizer, first_moderna,
second_pfizer, second_moderna,
source_data
)
values
(
l.count_date,
l.first_pfizer, l.first_moderna,
l.second_pfizer, l.second_moderna,
l_id
)
;
end;


プロシージャAPEX_DATA_PARSER.PARSEの引数として、p_skip_rowsp_max_rows指定することにより、ロード対象の行の範囲を限定しています。

Excelファイルの先頭行は以下の様になっています。実際のデータが始まるのは6行目からなので、p_skip_rowsに5を指定しています。


p_skip_rowsの設定をしないと、ORA-1841が発生します。


次にp_max_rowsですが、Excel内のデータで4月19日以前の曜日に式が設定されています。Oracle APEXのデータ・パーサーはこれを読むことができませんでした。そのためp_max_rowsを指定し、2021年4月20日の行までをロード対象にしています。


p_max_rowsを指定しないと、0RA-6502が発生します。


Excelファイルに含まれているすべてのデータを置き換えるのではなく、先頭から34行(5行スキップしているので読み込まれるデータは29行)のみが表COVID19_IRYO_RESULTSに投入されます。

最近のファイルで過去のデータが変更されている場合(記録には土日が含まれていないので、大体1ヶ月前のデータ)、その変更はロード対象外なので表に反映されないことになります。政府CIOポータルからダウンロードできる高齢者などのワクチン接種回数のデータ(NDJSON形式)では、過去に遡ってデータが更新されているため、医療従事者のデータについても過去に遡って更新されることが無い、とはいえないと思います。変更があったときに確認できるように、表COVID19_IRYO_RESULTSには列SOURCE_DATAを追加し、ロード時に使用したファイルを特定できるようにしています。

データの更新を1ヶ月以上行わないと取り込まれない行が発生しますが、定期的な更新をOracle APEXの自動化の機能を使って実装することにより回避できます。

以上で、データをロードするプロセスが完成です。データのロードのボタンをクリックし、動作を確認します。



手作業でデータを入力する


厚生労働省のデータを取り込みます。Web上の実績データをExcelにコピペします。


Excelブック形式(.xlsx)で保存し、SQLワークショップデータ・ワークショップよりロードします。

データを変換しないと表COVID19_IRYO_RESULTS_OLDには投入できないため、一旦、新規表を作成して内容をロードします。表名OLD_DATAとしました。列見出し最初の行にヘッダーが含まれるチェックを外します。データのロードを実行します。


37行のデータがロードされます。


表OLD_DATAの内容を表COVID19_IRYO_RESULTS_OLDへ、データ変換を行ないつつ投入します。SQLコマンドを開き、以下のSQLを実行します。

insert into covid19_iryo_results_old(count_date, first, second)
select
to_date(to_single_byte(replace(col001, chr(49824))),'YYYY/MM/DD') count_date
, to_number(to_single_byte(replace(col003, chr(49824))), '999G999G999') first
, to_number(to_single_byte(replace(col004, chr(49824))), '999G999G999') second
from old_data;


使用するデータはCOUNT_DATEとしてCOL001、列FIRSTとしてCOL003、列SECONDとしてCOL004を投入しています。それぞれto_single_byteで全角/半角変換、replaceを使ってUnicodeのC2A0(ノーブレークスペース)を文字列から削除しています。

最近のデータを含むExcelファイルの方は、先頭と末尾のコメントの削除と、曜日列の削除を行います。



変更したファイルを保存します。


SQLワークショップデータ・ワークショップを使って、表COVID19_IRYO_RESULTSヘロードします。

ロード先として既存の表を選択します。COVID19_IRYO_RESULTSです。Updateメソッドには置換を選びます。これから投入するデータは、過去のデータをすべて含んでいるため、今までに投入したデータは不要です。列見出し最初の行にヘッダーが含まれるチェックは外します。データのロードを実行する前に構成をクリックします。


列のマッピングとして、COL001COUNT_DATEをマップします。COL002は合計なので未マップのままとします。COL003FIRST_PFIZERCOL004FIRST_MODERNACOL005SECOND_PFIZERCOL006SECOND_MODERNAをそれぞれマップします。変更の保存をクリックしダイアログを閉じ、戻った画面でデータのロードを実行します。


データのロードが成功すれば、医療従事者に関するワクチン接種状況のデータのロードは完了です。



チャートを作成する


せっかくデータをデータベースに取り込んだので、いくつかチャートを作成してみます。ページ・デザイナホーム・ページを開き、リージョンの作成を行います。

識別タイトル接種数日次推移とし、タイプチャートを選びます。ソース位置ローカル・データベースとし、タイプSQL問合せを選択します。SQL問合せとして以下を記述します。

select
count_date,
(nvl(first_pfizer,0) + nvl(first_moderna,0)) as first,
(nvl(second_pfizer,0) + nvl(second_moderna,0)) as second
from covid19_iryo_results
union all
select
count_date,
first,
second
from covid19_iryo_results_old

厚生労働省から取得した過去のデータと首相官邸のサイトから取得したExcelのデータをUNION ALLで連結して、チャートに表示します。


Attributesを開き、チャートの属性を調整します。チャートタイプ外観向き積上げONマルチシリーズ・チャート・データチャート・データのギャップを埋めるONソート順序ラベル - 昇順ギャップをゼロとしてレンダリングOFF設定時間軸タイプ有効凡例表示ON位置、としました。時系列での表示で必須の設定もありますが、色々変えてみてチャートを調整してもよいでしょう。


シリーズの設定を行います。最初のシリーズの識別名前医療従事者2回目とします。ソース位置リージョン・ソースとし、列のマッピングラベルCOUNT_DATE、値にSECONDを選択します。


医療従事者2回目のシリーズを重複させ、識別名前医療従事者1回目に変更します。列のマッピングをSECONDからFIRSTへ変更します。


ページを実行し、作成したチャートを確認してみます。


以前の記事の作業を行なっていると、表COVID19_VACCINATION_RESULTSとして高齢者などの接種データがデータベースに取り込まれています。こちらのデータもシリーズとして追加してみます。

シリーズの作成を行い、識別名前2回目とします。ソース位置ローカル・データベースタイプをSQL問合せとし、SQL問合せとして以下を記述します。

select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 2
group by count_date

年代、性別、都道府県による絞り込みはしないので、SQLは単純です。列のマッピングのラベルCOUNT_DATECOUNTを選択します。


作成したシリーズを重複させ、1回目のシリーズを作成します。識別名前1回目に変更します。SQL問合せのwhere status = 2の部分をstatus = 1へ変更します。

select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 1
group by count_date


ページを実行して結果を確認します。


もうひとつ接種数の累積を表示するチャートを作ってみます。接種数日次推移のチャートを重複させ、識別タイトル接種数累積に変更します。ソースのSQL問合せは、元のSQLにWINDOW関数を使用して累積を計算する様に変更します。

select
count_date,
sum(first) over (order by count_date asc) first,
sum(second) over (order by count_date asc) second
from
(
select
count_date,
(nvl(first_pfizer,0) + nvl(first_moderna,0)) as first,
(nvl(second_pfizer,0) + nvl(second_moderna,0)) as second
from covid19_iryo_results
union all
select
count_date,
first,
second
from covid19_iryo_results_old
)

チャートのAttributesを開き、チャートタイプ面付き折れ線に変更します。


高齢者などの接収数のSQLも、累積を計算する様に変更します。

シリーズ2回目のSQLを以下に変更します。

select
count_date,
sum(count) over (order by count_date asc) count
from
(
select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 2
group by count_date
)


1回目は以下のSQLに変更します。where status = 2の条件がstatus = 1になっています。

select
count_date,
sum(count) over (order by count_date asc) count
from
(
select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 1
group by count_date
)


以上で一旦ページを実行し、作成したチャートを確認してみます。


累接種数の累積であるにもかかわらず、値が減少している部分があります。これは主に医療従事者の接種データに土日が含まれていないため、前日と同じ値で累積を計算すべきところを0と見做されているためです。チャートとして問題があります。

SQLを修正して対応します。

リージョン・ソースのSQLは以下の様に記述します。

select
a.count_date as count_date
,sum(nvl(d.first,0)) over (order by a.count_date asc) as first
,sum(nvl(d.second,0)) over (order by a.count_date asc) as second
from
(
select (select min(count_date) from covid19_iryo_results_old) + level - 1 as count_date
from dual
connect by
(select min(count_date) from covid19_iryo_results_old) + level -1
<=
(select max(count_date) from covid19_iryo_results)
) a
left outer join
(
select
count_date,
(nvl(first_pfizer,0) + nvl(first_moderna,0)) as first,
(nvl(second_pfizer,0) + nvl(second_moderna,0)) as second
from covid19_iryo_results
union all
select
count_date,
first,
second
from covid19_iryo_results_old
) d on a.count_date = d.count_date

以下のSQLにて、医療従事者のワクチン接種が開始した日から最近のデータが存在する日までのそれぞれの日付となる行を生成します。

select (select min(count_date) from covid19_iryo_results_old) + level - 1 as count_date
from dual
connect by
(select min(count_date) from covid19_iryo_results_old) + level -1
<=
(select max(count_date) from covid19_iryo_results)

----
追記: 医療従事者のデータの方が更新が遅い様です。高齢者等のデータと重ね合わせる場合は、生成する日付の最大値はcovid19_iryo_resultsの最大ではなくcovid19_vaccination_resultsの最大値にする方が適切です。つまり、以下です。
select (select min(count_date) from covid19_iryo_results_old) + level - 1 as count_date
from dual
connect by
(select min(count_date) from covid19_iryo_results_old) + level -1
<=
(select max(count_date) from covid19_vaccination_results)
---

元々の接種数のデータを、この日付だけのビューとCOUNT_DATEをキーにして外部結合するすることにより、COUNT_DATEの抜けを無くしています。対応する日付の行が接種数の表に無い場合はNVL関数を適用して、0に変換しています。

シリーズの2回目のSQLも同様に、以下に変更します。

select
a.count_date as count_date
,sum(nvl(d.count,0)) over (order by a.count_date asc) as count
from
(
select (select min(count_date) from covid19_vaccination_results) + level - 1 as count_date
from dual
connect by
(select min(count_date) from covid19_vaccination_results) + level -1
<=
(select max(count_date) from covid19_vaccination_results)
) a
left outer join
(
select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 2
group by count_date
) d on a.count_date = d.count_date

シリーズの1回目はwhere status = 2の部分がstatus = 1に変えただけですが、以下に変更します。

select
a.count_date as count_date
,sum(nvl(d.count,0)) over (order by a.count_date asc) as count
from
(
select (select min(count_date) from covid19_vaccination_results) + level - 1 as count_date
from dual
connect by
(select min(count_date) from covid19_vaccination_results) + level -1
<=
(select max(count_date) from covid19_vaccination_results)
) a
left outer join
(
select
count_date,
sum(count) count
from covid19_vaccination_results
where status = 1
group by count_date
) d on a.count_date = d.count_date

以上で対策は完了です。ページを実行して結果を確認してみます。


きれいに累積値が表示されるようになりました。

データの更新を自動化する


Excelファイルの取得とデータのロードは、PL/SQLコードを実行するプロセスとして作成しています。コードをそのまま流用し、自動的にデータを取得し、ロードを行うようにします。

共有コンポーネント自動化を開きます。


作成済みの自動化が一覧されます。作成をクリックします。


自動化の作成のダイアログで、処理が実行されるタイミングを設定します。名前データのロードとします。

タイプスケジュール済を選択します。定期的に実行させる設定です。オンデマンドは定期的ではなく、プロシージャAPEX_AUTOMATION.EXECUTEを都度呼び出す使い方になります。

アクションの開始常時です。指定した時刻になると無条件で処理が開始します。問合せを選ぶと、開始の条件を設定することができます。

実行スケジュールカスタムを選びました。正時というのは毎時0分に開始の意味です。

頻度としてを選び、間隔1にしているので毎日実行されます。実行時刻12:00を指定していますが、これはサーバーの時刻です。サーバーがUTCで動いているときはUTCの12:00になります。サーバーのSYSDATEが返す時刻を確認しましょう。

作成をクリックします。


概ね設定が完了した状態で自動化が作成されます。アクションを設定します。


名前データの取得とし、コードはデータを取得するプロセスに記述したコードをそのまま記載します。変更の適用をクリックします。


データの取得に続いて、データのロードを行うアクションを追加します。アクションの追加をクリックします。


名前データのロードとし、コードはデータをロードするプロセスに記述したコードをそのまま記載します。作成をクリックします。


最低限必要な設定が完了したので、スケジュール・ステータスアクティブに変更し、保存して実行をクリックします。スケジュールの設定にかかわらず、定義したアクションが即時で実行されます。


メッセージに自動化実行が開始されました。と表示されます。また、最終実行次の実行期限も確認しましょう。


自動化の実行ログは、自動化のページの実行ログのタブから確認できます。


今回は画面で使用しているプロセスをそのままアクションにしましたが、自動化の場合はAPEX_AUTOMATION.LOG_INFOといったプロシージャをコードに組み込むことで、処理の進捗などを記録することができます。

表COVID19_IRYO_RESULTSを確認することでも、データの更新が行われたかどうか、確認することができます。今回作成したアプリケーションであれば、以下のページになります。


以上で今回の作業は終了です。

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

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