2020年7月22日水曜日

Oracle APEXでのデータ・ロード(6) - データのアンロード

Oracle APEXでのデータ・ロードについて紹介してきましたが、最後に、ロードではなく、アンロード、つまり保存されているデータをCSVやJSONで出力する方法について紹介します。

データ・ワークショップ


データ・ワークショップのデータ・ロードの機能については、こちらの記事で紹介しています。データ・ワークショップには、データのロードの他にデータのアンロードも行うことができます。


クリックすると、テキスト形式とXML形式の選択が求めらられます。


テキスト形式とありますが、実際はCSV形式です。CSV形式のダウンロードは、アプリケーションに組み込まれたレポートから実施することができるため、データ・ワークショップを積極的に利用する必要はあまりないでしょう。簡単にファイルに落としたいというのであれば、SQLコマンドの結果もCSV形式でダウンロードすることが可能です。


XML形式の出力はデータ・ワークショップでは出来ますが、レポートからはできません。ですので、この場合はデータ・ワークショップを使用します。

XML形式をクリックします。


を最初に選択すると、指定可能なが一覧されます。その中からアンロードの対象列を(複数)選択し、必要であればWHERE句を指定します。ここではprefecture_name = '東京都'を指定し、アンロードの対象を東京都に絞っています。すべて設定した後、データのアンロードをクリックすると、アンロード対象のデータがファイルとしてダウンロードされます。ファイル名は指定した表名.xmlです。

ファイルの内容は以下のようなります。アンロードする列に指定されていてもデータが存在しなければ、XMLタグを含めて出力されません。
<?xml version="1.0"?>
<ROWSET>
 <ROW>
  <No>5854</No>
  <MUNICIPALITY_CODE>130001</MUNICIPALITY_CODE>
  <PREFECTURE_NAME>東京都</PREFECTURE_NAME>
  <PUBLISHED_DATE>2020-06-26T00:00:00.000</PUBLISHED_DATE>
  <PATIENT_LOCATION>都内</PATIENT_LOCATION>
  <PATIENT_AGE>20代</PATIENT_AGE>
  <PATIENT_SEX>男性</PATIENT_SEX>
  <PATIENT_LEFT_HOSPITAL>1</PATIENT_LEFT_HOSPITAL>
 </ROW>
 <ROW>
  <No>5858</No>
  <MUNICIPALITY_CODE>130001</MUNICIPALITY_CODE>
  <PREFECTURE_NAME>東京都</PREFECTURE_NAME>
  <PUBLISHED_DATE>2020-06-26T00:00:00.000</PUBLISHED_DATE>
  <PATIENT_LOCATION>都内</PATIENT_LOCATION>
  <PATIENT_AGE>20代</PATIENT_AGE>
  <PATIENT_SEX>男性</PATIENT_SEX>
  <PATIENT_LEFT_HOSPITAL>1</PATIENT_LEFT_HOSPITAL>
 </ROW>
 <ROW>
 
テキスト形式(CSV形式)では、セパレータ囲み文字(オプション)列名を含めるのチェック、ファイル形式ファイルの文字セットを指定できます。


残念なことに、なぜかapex.oracle.com上では、今回作成した表COVID19_PATIENTSをテキスト形式でアンロードしようとすると、エラーが発生します。原因は今のところ不明です。

レポートのダウンロード機能


Oracle APEXが提供しているコンポーネントのうち、レポートを扱うものが3つあります。クラシック・レポート、対話モード・レポート、それと対話グリッドです。これらすべてのレポートに、データのダウンロード機能が提供されています。

対話モード・レポートを例にとって説明します。対話モード・レポートではアクション・メニューにダウンロードが含まれます。


ダウンロードを実行すると、ダウンロード形式の選択を要求されます。形式を選ぶとダウンロードが開始します。レポートで選択している列、および、行がダウンロードの対象になります。


ダウンロードの機能は、レポートの属性にて構成することができます。


出力可能な形式CSVセパレータCSV囲み文字ファイル名などはここで指定します。PDF 形式などは、あらかじめ外部のサーバーと連携するように構成されている必要があります。されていない場合は、形式として選んでダウンロードを実行するとエラーが発生します。そのため、ここのオプションを外しておきます。

データのロードの場合は、データ・ロード・ウィザードを構成するページを新規に作成し、アプリケーションに組み込む必要がありましたが、アンロード(ダウンロード)は、レポートに組み込みの機能であるため、レポートさえあれば追加の開発をする必要はありません。


RESTデータ・サービス


今まではOracle APEXが提供している機能を使い、まったくコードを書かずにデータのアンロードを行っています。最後にコードを記述することで、柔軟なデータ交換を実現する方法を紹介します。Oracle RESTデータ・サービスにて実装します。

データ・ワークショップであれ、アプリケーションのレポート機能であれ、これらを使ってデータのアンロードを行うには、ユーザー認証が必要です。また、外部からのAPI呼び出しの結果、データを返すという実装はできません。RESTデータ・サービスでは、REST APIとしてデータのアンロードを実装します。

JSON形式でのアンロードを行うには、RESTデータ・サービスで実装する必要があります。

RESTデータ・サービスの実装には、SQLワークショップRESTfulサービスを使用します。


RESTデータ・サービスの利用が許可されていないと、ORDSにスキーマを登録というボタンが表示されます。


登録時のスキーマ属性の設定を要求されます。スキーマ別名はワークスペース名と同じ名前にします。なので、表示された値は変更しません。サンプル・サービスのインストールはON/OFFどちらでもかまいませんが、サンプルを参考にしたい場合はONにします。メタデータ・アクセスに必要な認可は、とりあえずOFFにします。これは後で変更できます。これらを設定して、スキーマ属性の保存をクリックします。


以上で、RESTデータ・サービスを実装する準備が整いました。

モジュールの作成


これから作成するREST APIをまとめる単位である、モジュールの作成を実行します。


モジュール名は任意です。ここではCOVID-19 Japanとしています。日本語の入力は不可です。ベース・パスとしては/covid19jp/を設定しています。ベース・パスはREST APIのURLに含まれることになります。公開ONにしています。本来であれば、APIが出来上がってから公開した方がよいでしょう。これでモジュールの作成を実行します。


ページ区切りサイズは、この後に出てくるハンドラの設定のソース・タイプをCollection Queryを選択した際に、一回のリクエストで返却する行数になります。Collection Queryにはページネーションの仕組みが実装されています。

テンプレートの作成


モジュールが作成されたら、それに含まれるテンプレートを作成します。CSV形式のデータをダウンロードするためのテンプレートを作成します。テンプレートの作成をクリックします。


URIテンプレートとして、csv/:prefecture_nameを設定し、テンプレートの作成を実行します。


最後尾が/covid19jp/csv/都道府県名となるURLがREST APIの呼び出し先になります。このURLにたいして、HTTPのGETメソッドを発行してCSVデータのダウンロードを行います。

ハンドラの作成


テンプレートを作成した後、ハンドラの作成を実行します。


メソッドGETを選択します。CSVを出力するには、ソース・タイプPL/SQLを選択します。以前はQueryを選んで、書式としてCSVを選択することができましたが、最近のバージョンではDeprecatedとなっています。つまり、CSV形式での出力は標準機能からは外されています。JSONでの出力が標準機能となっています。

ソース・タイプとしてPL/SQLを選んで、出力形式も含めてコーディングします。ですので、書式がJSONとなっていて変更できませんが、こちらの設定を気にする必要はありません。ページ区切りサイズもPL/SQLの出力に影響を与えません。

CSV形式でデータを出力するコードとして、ソースに以下を入力します。
declare
  type cur_t is ref cursor;
  c cur_t;
  r_patient covid19_patients%rowtype;
begin
  -- 都道府県の指定を参照して、出力対象を絞り込む。無ければ全件出力する。
  if :prefecture_name is not null then
    open c for select * from covid19_patients 
        where prefecture_name = :prefecture_name order by "No";
  else
    open c for select * from covid19_patients order by municipality_code, "No";
  end if;
  -- 出力がCSV形式であること、UTF-8であることをHTTPヘッダーで宣言する。
  owa_util.mime_header('text/csv', FALSE, 'utf-8');
  htp.p('Content-disposition: attachment; filename="patients' || 
        utl_url.escape(:prefecture_name, false, 'utf-8') || '.csv"');
  owa_util.http_header_close;
  -- BOM(バイトオーダーマーク)を最初に出力する。
  htp.prn(unistr('\feff'));
  -- カラムヘッダーを出力する。
  htp.prn('"No",');
  htp.prn('"全国地方公共団体コード",');
  htp.prn('"都道府県名",');
  htp.prn('"市区町村名",');
  htp.prn('"公表_年月日",');
  htp.prn('"発症_年月日",');
  htp.prn('"患者_居住地",');
  htp.prn('"患者_年代",');
  htp.prn('"患者_性別",');
  htp.prn('"患者_職業",');
  htp.prn('"患者_状態",');
  htp.prn('"患者_症状",');
  htp.prn('"患者_渡航歴の有無フラグ",');
  htp.prn('"患者_退院済フラグ",');
  htp.p('"備考"');
  -- データを一行づつ出力する。
  loop
    fetch c into r_patient;
    exit when c%notfound;
    htp.prn(r_patient."No"); htp.prn(',');
    htp.prn(to_char(r_patient.municipality_code,'000000')); htp.prn(',');
    htp.prn(r_patient.prefecture_name); htp.prn(',');
    htp.prn(r_patient.city_name); htp.prn(',');
    htp.prn(to_char(r_patient.published_date,'YYYY-MM-DD')); htp.prn(',');
    htp.prn(to_char(r_patient.onset_date,'YYYY-MM-DD')); htp.prn(',');
    htp.prn(r_patient.patient_location); htp.prn(',');
    htp.prn(r_patient.patient_age); htp.prn(',');
    htp.prn(r_patient.patient_sex); htp.prn(',');
    htp.prn(r_patient.patient_occupation); htp.prn(',');
    htp.prn(r_patient.patient_status); htp.prn(',');
    htp.prn(r_patient.patient_symptom); htp.prn(',');
    htp.prn(r_patient.patient_travel_history); htp.prn(',');
    htp.prn(r_patient.patient_left_hospital); htp.prn(',');
    htp.p(r_patient.remark);          
  end loop;
  close c;
end;
作成したREST APIをGETで呼び出すと、以下のような結果が得られます。
"No","全国地方公共団体コード","都道府県名","市区町村名","公表_年月日","発症_年月日","患者_居住地","患者_年代","患者_性別","患者_職業","患者_状態","患者_症状","患者_渡航歴の有無フラグ","患者_退院済フラグ","備考"
1, 130001,東京都,,2020-01-24,,湖北省武漢市,40代,男性,,,,,1,
2, 130001,東京都,,2020-01-25,,湖北省武漢市,30代,女性,,,,,1,
3, 130001,東京都,,2020-01-30,,湖南省長沙市,30代,女性,,,,,1,
4, 130001,東京都,,2020-02-13,,都内,70代,男性,,,,,1,
5, 130001,東京都,,2020-02-14,,都内,50代,女性,,,,,1,
6, 130001,東京都,,2020-02-14,,都内,70代,男性,,,,,1,
7, 130001,東京都,,2020-02-15,,都内,80代,男性,,,,,1,


JSONの出力


JSONを出力する場合は、ソース・タイプのCollection Queryが使えるので、ソースの記述はより簡単です。

新たにテンプレートを作成し、URIテンプレートjson/:prefecture_nameとします。作成したテンプレートにハンドラを作成します。

メソッドGETを選択し、ソース・タイプCollection Queryとします。ソースとして以下のSQLを入力し、ハンドラの作成を行います。JSON形式へのフォーマットはOracle APEX側(正確にはOracle REST Data Services)が行うため、単にSELECT文をソースへ記述します。
select
  "No",
  municipality_code "全国地方公共団体コード",
  prefecture_name "都道府県名",
  city_name "市区町村名",
  published_date "公表_年月日",
  onset_date "発症_年月日",
  patient_location "患者_居住地",
  patient_age "患者_年代",
  patient_sex "患者_性別",
  patient_occupation "患者_職業",
  patient_status "患者_状態",
  patient_symptom "患者_症状",
  patient_travel_history "患者_渡航歴の有無フラグ",
  patient_left_hospital "患者_退院済フラグ",            
  remark "備考"
from covid19_patients 
where
  :prefecture_name is null
  or
  prefecture_name = :prefecture_name
order by municipality_code, "No"


Collection Queryで設定したREST APIを呼び出すと、以下のような結果がJSONとして返されます。

一回のリクエストで返される行数(itemsの配列に含まれるオブジェクトの数)は、ページ区切りサイズになります。出力の最後にページネーションを行うためのURLが追加出力されます。
"hasMore": true,
"limit": 25,
"offset": 0,
"count": 25,
"links": [
  {
"rel": "self",
"href": "https://apex.oracle.com/pls/apex/dbcloud_demo/covid19jp/json/%E6%9D%B1%E4%BA%AC%E9%83%BD"
},
  {
"rel": "describedby",
"href": "https://apex.oracle.com/pls/apex/dbcloud_demo/metadata-catalog/covid19jp/json/item"
},
  {
"rel": "first",
"href": "https://apex.oracle.com/pls/apex/dbcloud_demo/covid19jp/json/%E6%9D%B1%E4%BA%AC%E9%83%BD"
},
  {
"rel": "next",
"href": "https://apex.oracle.com/pls/apex/dbcloud_demo/covid19jp/json/%E6%9D%B1%E4%BA%AC%E9%83%BD?offset=25"
}
],
URLに引数としてoffsetが追加されています。すでに読み出した数をoffsetとして渡すことで、データ全体を複数回のAPI呼び出しに分けて取得することが可能になっています。

以上でOracle APEXが提供しているデータ・ロードの機能およびアンロードの機能紹介を終了します。

2020年7月21日火曜日

Oracle APEXでのデータ・ロード(5) - XMLパーサー

CSV形式およびExcel形式のファイルをデータベースにロードするために、APEX_DATA_PARSERパッケージを使用しました。現在は多くの自治体が、これらの形式で新型コロナウイルス感染症の陽性患者属性のデータを提供しています。とはいえ、すべての自治体ではありません。HTMLの表としてデータが公開されているケースも多いです。

これからHTMLの表をOracle APEXで扱えるよう、データベースの表に取り込むコードを紹介します。HTML表のパースにはOracle Databaseが提供しているXMLパーサーの機能を利用します。APEX_DATA_PARSERパッケージのPARSEファンクションはXMLファイルも扱うことができます。しかし、HTMLページ自体はXMLではなく、また、一連の文章の中に表が含まれていたり、1ページにいくつものHTML表があったりします。そのため、取り込むHTML表を含んだページから、読み込みの対象部分を切り出したり、不要なタグなどを取り除くなど、前処理が必要です。結果として、APEX_DATA_PARSERパッケージのPARSEファンクションでは扱いにくくなっています。

HTMLページのロード


CSVおよびExcelと同様に、バイナリ・データとしてデータベースに保存します。これらはキャッシュの役割になります。

秋田県を例にとって説明します。秋田県のデータは以下のURLで公開されています(2020年7月21日現在)。

このページをそのままの形でデータベースに保存するためのコードは、CSVやExcel形式の場合とまったく同じです。
declare
  l_url covid19_municipalities.content_url%type;
  l_file_name covid19_municipalities.file_name%type;
begin
  l_file_name := 'akita.html';
  l_url := 'https://www.pref.akita.lg.jp/pages/archive/47957';
  update covid19_municipalities
  set content_blob = apex_web_service.make_rest_request_b(l_url, 'GET'),
      content_url = l_url,
      file_name = l_file_name,
      last_update_date = systimestamp
  where name = '秋田県';
end;
ファイル名として与えることのできるデータが無いため、ここではファイル名をakita.htmlとしています。

HTML表を読み込んで表データを返す、APEX_DATA_PARSER.PARSEファンクションと等価な表関数を作成します。使用する関数はXMLTABLEです。

マニュアルの説明はこちらになります。

まず最初に、BLOBで保存したHTMLページをCLOBで取り出す必要があります。その処理を行うために、以下のファンクションを作成しました。
create or replace function get_content_as_clob
(
    p_prefecture_name varchar2,
    p_charset         varchar2
)
return clob
is
    l_blob blob;
    l_clob clob;
    ls integer := 1;
    le integer := 1;
    l_lang_ctx integer := DBMS_LOB.DEFAULT_LANG_CTX;
    l_warning  integer;
begin
    select content_blob into l_blob from covid19_municipalities where name = p_prefecture_name;
    if dbms_lob.getlength(l_blob) = 0 then
        return null;
    end if;
    dbms_lob.createtemporary(l_clob, true, dbms_lob.call);
    dbms_lob.converttoclob(
      l_clob,
      l_blob,
      dbms_lob.lobmaxsize,
      ls,
      le,
      NLS_CHARSET_ID(p_charset),
      l_lang_ctx,
      l_warning);
    return l_clob;
end get_content_as_clob;
APEX_WEB_SERVICE.MAKE_REST_REQUESTファンクションをAPEX_WEB_SERVICE.MAKE_REST_REQUEST_Bの代わりに使用すると、HTMLページのデータをBLOB(バイナリ・データ)ではなく、CLOB(文字列)のデータとして受け取れます。今回は、CSVやExcelと同じ保存先を使うため、また、こちらの方が重要ですが、文字コードを指定するためにBLOBからCLOBへ変換するコードを書いています。

都道府県別にCONTENT_BLOBとして保存されているデータをl_blobに取り込み、DBMS_LOB.CONVERTTOCLOBファンクションを使用してCLOBへ変換しています。

HTML表に含まれる、取り出したいデータは概ねHTMLのタグ、TBODY要素(<tbody>...</tbody>)として存在しています。ですので、CLOBに変換したHTMLページの、<tbody></tbody>までを取り出し、その内容をXMLTABLEファンクションを使用して表形式のデータへ変換します。

表関数で使用するタイプを2つ定義します。T_TABLE_ROWは一行に対応します。
CREATE OR REPLACE TYPE  "T_TABLE_ROW" as object (
    col001 varchar2(200),
    col002 varchar2(200),
    col003 varchar2(200),
    col004 varchar2(200),
    col005 varchar2(200),
    col006 varchar2(200),
    col007 varchar2(200),
    col008 varchar2(200),
    col009 varchar2(200),
    col010 varchar2(200),
    col011 varchar2(200),
    col012 varchar2(200),
    col013 varchar2(200),
    col014 varchar2(200),
    col015 varchar2(200),
    col016 varchar2(200),
    col017 varchar2(200),
    col018 varchar2(200),
    col019 varchar2(200),
    col020 varchar2(200)
);
T_TABLEはT_TABLE_ROWで定義された表に対応します。
CREATE OR REPLACE TYPE  "T_TABLE" is table of t_table_row;
T_TABLEを戻り値とする表関数は、その列がCOL001、COL002、...となるようにし、APEX_DATA_PARSER.PARSEファンクションとの入れ替えを容易に行えるようにします。

秋田県が提供するHTMLページを受け取って、表形式でデータを返す関数は以下になります。
create or replace function parse_html_table_akita
return t_table pipelined 
as  
  l_html   clob; 
  l_start  number; 
  l_end    number; 
  l_row    t_table_row; 
  c_record sys_refcursor; 
  l_data   varchar2(200);
begin 
  -- 取り込むデータの部分を切り出す。 
  l_html := get_content_as_clob('秋田県','AL32UTF8');
  l_start := instr(l_html, '<tbody'); 
  l_end   := instr(l_html, '</tbody>'); 
  l_html  := substr(l_html, l_start, l_end - l_start + 8); 
  l_html  := replace(l_html,'&nbsp;');  
  for r in 
  ( 
     select rownum rn, td from xmltable('tbody/tr' passing xmltype(l_html) 
                               columns td xmltype path './td') 
  ) 
  loop
    l_row := t_table_row(
        null, null, null, null, null, null, null, null, null, null,
        null, null, null, null, null, null, null, null, null, null
    );
    open c_record for  
        select cell from xmltable('./td' passing r.td 
                               columns cell varchar2(200) path '.'); 
        fetch c_record into l_row.col001;
        fetch c_record into l_row.col002;
        fetch c_record into l_row.col003;
        fetch c_record into l_row.col004;
        fetch c_record into l_row.col005;
        fetch c_record into l_row.col006;
        fetch c_record into l_row.col007;
        fetch c_record into l_row.col008;
    close c_record; 
    pipe row(l_row); 
  end loop; 
end parse_html_table_akita;
最初のXMLTABLEを使用したSELECT文は、TBODY要素の部分を取り出したHTML表を受け取り、TR要素(<tr>...</tr>)を一行として、それに含まれるすべてのTD要素(<td>...</td>)をXMLドキュメントとして返します。つまり、以下のSQLの結果は、
     select td from xmltable('tbody/tr' passing xmltype(l_html) 
                               columns td xmltype path './td') 
以下のような、TD要素の(TR要素毎の)集まりとなります。
<td style="text-align: center;">16例目</td><td>4月14日</td><td>50歳代</td><td>男性</td><td>湯沢保健所管内</td><td>会社員</td><td>調査終了</td><td> </td>
<td style="text-align: center;">15例目</td><td>4月11日</td><td>10歳代</td><td>男性</td><td>秋田市</td><td>中学生</td><td>調査終了</td><td>秋田市6例目、県内12例目の濃厚接触者</td>
<td style="text-align: center;">14例目</td><td>4月11日</td><td>10歳代</td><td>女性</td><td>秋田市</td><td>高校生</td><td>調査終了</td><td>秋田市5例目、県内12例目の濃厚接触者</td>
<td style="text-align: center;">13例目</td><td>4月10日</td><td>50歳代</td><td>男性</td><td>大仙保健所管内</td><td>自営業</td><td>調査終了</td><td> </td>
続くSELECT文で、一行分のの集まりより、このセルのデータを取り出します。
    open c_record for  
        select cell from xmltable('./td' passing r.td 
                               columns cell varchar2(200) path '.'); 
SQLコマンドにて動作確認を行います。
select * from table(parse_html_table_akita);

SELECT文で表形式のデータが取得できたので、後はCSVやExcel形式と同様にMERGE文(もしくはDELETE/INSERT文)によって表COVID19_PATIENTS表に読み込みます。秋田県の例では以下になります。
merge into covid19_patients p
using
(
    select
        to_number(regexp_replace(col001, '^\s*(\d+)例目','\1')) "No",
        50008 municipality_code,
        '秋田県' prefecture_name,
        null city_name,
        to_date(col002, 'MM"月"DD"日"') published_date,
        null onset_date,
        col005 patient_location,
        case
        when regexp_like(col003, '^\d+歳代') then
            replace(col003, '歳')
        else
            col003
        end patient_age,
        col004 patient_sex,
        col006 patient_occupation,
        null patient_status,
        null patient_symptom,
        null patient_travel_history,
        null patient_left_hospital,
        col008 remark
    from table(parse_html_table_akita)
    minus
    select
        "No", municipality_code, prefecture_name, city_name, 
        published_date, onset_date,
        patient_location, patient_age, patient_sex, patient_occupation,
        patient_status, patient_symptom, 
        patient_travel_history, patient_left_hospital, remark
    from covid19_patients
) n
on (p."No" = n."No" and p.prefecture_name = n.prefecture_name)
when matched then
    update set
        p.city_name              = n.city_name,
        p.published_date         = n.published_date,
        p.onset_date             = n.onset_date,
        p.patient_location       = n.patient_location,
        p.patient_age            = n.patient_age,
        p.patient_sex            = n.patient_sex,
        p.patient_occupation     = n.patient_occupation,
        p.patient_status         = n.patient_status,
        p.patient_symptom        = n.patient_symptom,
        p.patient_travel_history = n.patient_travel_history,
        p.patient_left_hospital  = n.patient_left_hospital,
        p.remark                 = n.remark
when not matched then
    insert(
        "No", municipality_code, prefecture_name, city_name, 
        published_date, onset_date, 
        patient_location, patient_age, patient_sex, patient_occupation,
        patient_status, patient_symptom,
        patient_travel_history, patient_left_hospital, remark
    )
    values
    (
        n."No", n.municipality_code, n.prefecture_name, n.city_name, 
        n.published_date, n.onset_date, 
        n.patient_location, n.patient_age, n.patient_sex, n.patient_occupation,
        n.patient_status, n.patient_symptom,
        n.patient_travel_history, n.patient_left_hospital, n.remark
    );
長いSQLになっていますが、以下のSELECT文だけがHTML表、および、秋田県に対応した部分で、それ以外はCSVやExcel形式の読み込みと同じです。他の自治体のHTML表についても、parse_html_table_akita表関数と同等の表関数を作成することで、データの取り込みが可能になります。
    select
        to_number(regexp_replace(col001, '^\s*(\d+)例目','\1')) "No",
        50008 municipality_code,
        '秋田県' prefecture_name,
        null city_name,
        to_date(col002, 'MM"月"DD"日"') published_date,
        null onset_date,
        col005 patient_location,
        case
        when regexp_like(col003, '^\d+歳代') then
            replace(col003, '歳')
        else
            col003
        end patient_age,
        col004 patient_sex,
        col006 patient_occupation,
        null patient_status,
        null patient_symptom,
        null patient_travel_history,
        null patient_left_hospital,
        col008 remark
    from table(parse_html_table_akita)
データの取り込みについての解説は以上で終了です。次は取り込んだデータをCSV形式、または、JSON形式で出力する方法について紹介します。

参考情報


HTML表としてデータを公開している自治体


秋田県、茨城県、京都府、鳥取県、広島県、徳島県、香川県、佐賀県、宮崎県、鹿児島県。ただし、広島県は居住地、年代、性別などの属性をHTML表に含まないケースが多いです。

CSV、Excel、HTML表以外の形式の自治体


PDFで公開している自治体は、群馬県、千葉県、愛知県、沖縄県。
滋賀県、和歌山県、島根県については、陽性患者属性の情報は症例ごとに提供。

2020年7月20日月曜日

Oracle APEXでのデータ・ロード(4) - APEX_DATA_PARSERパッケージ

これまでの記事で、Oracle APEXに組み込まれているツールであるデータ・ワークショップ、および、Oracle APEXのアプリケーションに組み込まれるデータ・ロード・ウィザードについて説明しました。どちらのツールも、エンド・ユーザーが操作を行いデータのロードを行います。

今回の一連の記事は、新型コロナウイルス感染症の陽性患者属性をデータベースに取り込むことを題材にしています。陽性患者属性のデータは頻繁に更新されているため、最新の情報を取り込むためには、高い頻度でデータの再アップロードを行う必要があります。これを都道府県毎に行うのは大変な作業です。また、取り込むファイルのフォーマットは、基本的には都道府県単位では変わらないため、列のマッピングの指定をその都度行うのも非効率です。

Oracle APEXでは、PL/SQL APIとしてAPEX_DATA_PARSERパッケージを公開しています。マニュアルでの記載は以下です。日本語の記述はバージョンが古いので注意してください。


APEX_DATA_PARSERパッケージはデータ・ワークショップおよびデータ・ロード・ウィザードの内部で使用されているパッケージであり、これらのツールでロード可能なファイルは、すべてAPEX_DATA_PARSERパッケージで扱うことができます。

それでは、APEX_DATA_PARSERパッケージを使ってデータをロードするコードを書いてみます。

CSVファイルのロード


東京都が提供しているCSVファイルをAPEX_DATA_PARSERパッケージに含まれるPARSEファンクションを使ってパースし、表COVID19_PATIENTSに取り込むコードを書いてみます。


新型コロナウイルス陽性患者発表詳細として、東京都・オープンデータ・カタログサイトに掲載されています。

CSVファイルは以下のURLで提供されています。
https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv

Oracle APEXでは、APEX_WEB_SERVICEパッケージとして、アクセスしたURLのデータをCLOBまたはBLOBとして返すファンクションを提供しています。

APEX_WEB_SERVICE.MAKE_REST_REQUEST(CLOB)およびAPEX_WEB_SERVICE.MAKE_REST_REQUEST_B(BLOB)ファンクションですが、その名前のせいか、RESTサービスの呼び出しにしか使えないと思われてることがあります。そのようなことはなく、一般的なHTTPのリクエストの発行に使用することができます。

最初にAPEX_WEB_SERVICEパッケージを使って、東京都が提供しているデータを取得できることを確認します。SQLワークショップのSQLコマンドから、PL/SQLスクリプトを実行します。

実行するPLSQLスクリプトは以下になります。
declare
    l_data     blob;
    l_data_len number;
begin
    l_data := apex_web_service.make_rest_request_b(
        p_url => 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv',
        p_http_method => 'GET'
    );
    dbms_output.put_line('Content Length is ' || dbms_lob.getLength(l_data));
end;
以下の実行画面になります。


正常にファイルが取得されていれば、Content Length is ...結果に表示されます。CSVファイルの取得は、APEX_WEB_SERVICE.MAKE_REST_REQUEST_Bの呼び出しで完了します。

次に取得したCSVファイルのデータをパースします。APEX_DATA_PARSER.PARSEファンクションを使用します。APEX_DATA_PARSER.PARSEファンクションは表関数なので、受け取ったデータをパースして、結果を表形式で返します。

以下のSQLで取得したCSVファイルを表形式にパースします。一行のSELECT文で完了します。
select * from 
    apex_data_parser.parse(
        p_content => apex_web_service.make_rest_request_b(
            p_url => 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv',
            p_http_method => 'GET'
        ),
        p_file_name => 'file_is.csv',
        p_skip_rows => 1
    ) where col001 is not null
p_contentとして、取得したCSVの内容を渡しています。p_file_nameはfile_is.csvとしていますが、これはファイル名の拡張子よりファイルタイプを判別させるためで、ファイル名はそれ以外の用途では使われていません(p_file_typeという引数でCSVファイルとして2を指定する方法もあります)。最初の行は項目名なので、1行読み飛ばします。

SQLコマンドで実行すると結果は以下になります。


パースされた結果を使って、表COVID19_PATIENTSの内容を入れ替えるには、以下のSQLを実行します。DELETE/INSERTの2行です。
delete from covid19_patients where prefecture_name = '東京都';
insert into covid19_patients(
    "No", 
    municipality_code,
    prefecture_name,
    published_date,
    patient_location,
    patient_age,
    patient_sex,
    patient_left_hospital
)
select
    to_number(col001),
    to_number(col002),
    col003,
    to_date(col005, 'YYYY-MM-DD'),
    col008,
    col009,
    col010,
    to_number(col016)
from 
    apex_data_parser.parse(
        p_content => apex_web_service.make_rest_request_b(
            p_url => 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv',
            p_http_method => 'GET'
        ),
        p_file_name => 'file_is.csv',
        p_skip_rows => 1
    ) where col001 is not null
完全に入れ替えるのではなく、差分を更新する場合は、MERGE文を使います。こちらは1行です。(とは言っても、長いですけど。。。)
merge into covid19_patients p
using
(
    select
        to_number(col001) "No",
        to_number(col002) municipality_code,
        col003 prefecture_name,
        to_date(col005, 'YYYY-MM-DD') published_date,
        col008 patient_location,
        col009 patient_age,
        col010 patient_sex,
        to_number(col016) patient_left_hospital
    from 
        apex_data_parser.parse(
            p_content => apex_web_service.make_rest_request_b(
                p_url => 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv',
                p_http_method => 'GET'
            ),
            p_file_name => 'file_is.csv',
            p_skip_rows => 1
        ) where col001 is not null
    minus
    select
        "No", municipality_code, prefecture_name, published_date, 
        patient_location, patient_age, patient_sex, patient_left_hospital
    from covid19_patients
) n
on (p."No" = n."No" and p.prefecture_name = n.prefecture_name)
when matched then
    update set
        p.published_date   = n.published_date,
        p.patient_location = n.patient_location,
        p.patient_age      = n.patient_age,
        p.patient_sex      = n.patient_sex,
        p.patient_left_hospital = n.patient_left_hospital
when not matched then
    insert(
        "No", municipality_code, prefecture_name, published_date, 
        patient_location, patient_age, patient_sex, patient_left_hospital
    )
    values
    (
        n."No", n.municipality_code, n.prefecture_name, n.published_date, 
        n.patient_location, n.patient_age, n.patient_sex, n.patient_left_hospital    
    );
INSERT文とMERGE文の両方で、以下のSELECT文が使われています。
select
    to_number(col001) "No",
    to_number(col002) municipality_code,
    col003 prefecture_name,
    to_date(col005, 'YYYY-MM-DD') published_date,
    col008 patient_location,
    col009 patient_age,
    col010 patient_sex,
    to_number(col016) patient_left_hospital
from 
    apex_data_parser.parse(
        p_content => apex_web_service.make_rest_request_b(
            p_url => 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv',
            p_http_method => 'GET'
        ),
        p_file_name => 'file_is.csv',
        p_skip_rows => 1
    ) where col001 is not null
都道府県より新型コロナウイルス感染症の陽性患者属性のデータがCSVやExcel形式で提供されている場合は、上記のSELECT文の記述を変更することで、データを標準化して表COVID19_PATIENTSへ投入することができます。

集合演算子のMINUSを使っている以下の部分は、SQLから削除しても結果は変わりません。
    minus
    select
        "No", municipality_code, prefecture_name, published_date, 
        patient_location, patient_age, patient_sex, patient_left_hospital
    from covid19_patients
この部分で、変更のないデータは評価の対象から外しているため、アップデートから除外されます。MINUSをしていない場合は、同じデータによるアップデートが実行されます。

取得したデータのキャッシュ


今までの方法だと、SQL文を実行するたびにオープンデータへのアクセスが発生します。特に開発時にはSQLの実行を何回も行うことになるため、あまり行儀がよくありません。取得したデータは一旦表のBLOB列に保存して、SQLからはそちらを参照するようにします。

すでに作成済みの表COVID19_MUNICIPALITIES表にBLOBデータを保持する列(CONTENT_BLOB)と保存を実行した日時(LAST_UPDATE_DATE)、ファイル名(FILE_NAME)、取得したデータのURL(CONTENT_URL)を追加します。
alter table covid19_municipalities add content_blob blob;
alter table covid19_municipalities add last_update_date timestamp with local time zone;
alter table covid19_municipalities add file_name varchar2(200);
alter table covid19_municipalities add content_url varchar2(400);
これらを追加した上で、データの取得処理とパース処理を分けて実行します。

東京都のデータ取得処理は以下になります。
declare
  l_url covid19_municipalities.content_url%type;
  l_file_name covid19_municipalities.file_name%type;
begin
  l_url := 'https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv';
  l_file_name := '130001_tokyo_covid19_patients.csv';
  update covid19_municipalities
  set content_blob = apex_web_service.make_rest_request_b(l_url, 'GET'),
      content_url = l_url,
      file_name = l_file_name,
      last_update_date = systimestamp
  where name = '東京都';
end;
設定されたURLが変更されなければ、データの更新は以下のSQLで実行できます。
update covid19_municipalities m
set m.content_blob = apex_web_service.make_rest_request_b(m.content_url, 'GET'),
    m.last_update_date = systimestamp
where m.name = '東京都';
保存したデータを使ったパース処理は以下になります。
select
    to_number(col001)  "No",
    to_number(col002) municipality_code,
    col003 prefecture_name,
    to_date(col005, 'YYYY-MM-DD') published_date,
    col008 patient_location,
    col009 patient_age,
    col010 patient_sex,
    to_number(col016) patient_left_hospital
from 
    apex_data_parser.parse(
        p_content => 
          (select content_blob from covid19_municipalities where name = '東京都'),
        p_file_name => 'file_is.csv',
        p_skip_rows => 1
    ) where col001 is not null

東京都として与えている条件を変えることで、他の都道府県のデータを処理の対象にすることができます。

参考情報


CSV形式でデータを提供している都道府県


北海道、青森県、山形県、福島県、埼玉県、東京都、神奈川県、石川県、福井県、長野県、岐阜県、静岡県、三重県、岡山県、山口県、愛媛県、高知県、福岡県、長崎県、熊本県、大分県
(21自治体 - 7月20日現在)

Excel形式でデータを提供している都道府県


宮城県、栃木県、新潟県、富山県、山梨県、大阪府、兵庫県、奈良県(8自治体 - 7月20日現在)

WEBAPIを提供している都道府県


北海道、青森県、静岡県、愛媛県ではWEBAPIを呼び出すことで、提供しているCSVファイルのURLを取得できるようになっています。いくつかの都道府県では、更新がある度にファイル名を変更しているため、WEBAPIがあると便利です。上記の道と県が提供しているWEBAPIは、同じ形式のJSONドキュメントを返します。


返されるJSONドキュメント(対象部分のみ抜粋)です。
      {
        "id": 3827,
        "uuid": "f2805dd5-02cd-4e78-8aae-b81bb7a8a6b3",
        "revision_id": "bc508f0b-c8f4-4b21-b657-7c771df595f6",
        "name": "010006_hokkaido_covid19_patients.csv",
        "filename": "010006_hokkaido_covid19_patients.csv",
        "text": "「新型コロナウイルス感染症対策に関するオープンデータ項目定義書(Code for Japan)」に沿った形で情報をまとめた陽性患者属性データです。   \r\nhttps://www.code4japan.org/activity/stopcovid19\r\n「患者_退院済フラグ」については公表がないため記入していません。\r\n「患者_再陽性フラグ」を追加しています。",
        "license": {
          "id": 1,
          "name": "表示(CC BY)",
          "uid": null,
          "state": "public",
          "created": "2018-03-07T18:35:30.492+09:00",
          "updated": "2018-03-07T18:35:30.500+09:00"
        },
        "rdf_iri": null,
        "rdf_error": null,
        "created": "2020-06-19T21:56:42.175+09:00",
        "updated": "2020-07-20T10:31:06.376+09:00",
        "download_url": "https://www.harp.lg.jp/opendata/dataset/1369/resource/3132/010006_hokkaido_covid19_patients.csv",
        "url": "https://www.harp.lg.jp/fs/3/8/2/7/_/010006_hokkaido_covid19_patients.csv",
        "format": "CSV"
      }
idが3827のリソースに含まれる、download_urlfilenameを取り出す必要があります。

APEX_WEB_SERVICEおよびAPEX_DATA_PARSERパッケージを使用することで、WEBAPIを呼び出してdownload_urlとfilenameを取得することができます。北海道では以下のコードで、データの取り込みを行うことができます。
declare
  l_url covid19_municipalities.content_url%type;
  l_file_name covid19_municipalities.file_name%type;
begin
  select col016, col019 into l_file_name, l_url from 
    apex_data_parser.parse(
      p_content => apex_web_service.make_rest_request_b(
        p_url => 'https://www.harp.lg.jp/opendata/api/package_show?id=752c577e-0cbe-46e0-bebd-eb47b71b38bf',
        p_http_method => 'GET'
      ),
      p_file_type => 4
    )
  where col001 = '3827';
  update covid19_municipalities
  set content_blob = apex_web_service.make_rest_request_b(l_url, 'GET'),
      content_url = l_url,
      file_name = l_file_name,
      last_update_date = systimestamp
  where name = '北海道';
end;
APEX_WEB_SERVICEを使って取得したJSONドキュメントを、APEX_DATA_PARSERを使用してパースします。idはCOL001、filenameはCOL016、download_urlはCOL019として返されます。JSONドキュメントのパースでは、p_file_typeに4を指定します。


Excel形式の日付


宮城県など、日付データが1900日付システムによる数値として取得される場合があります。

1900日付システムの説明(Microsoftのサイト)

以下の式を使って、Oracleの日付型に変換できます。
date'1900-01-01'  + Excel日付 - 2
Excelの日付は1900年1月1日を1とするので、date'1900-01-01' + Excel日付 - 1 ではと思ったのですが、-2します。-2をする理由は、Oracleでは1900年を平年として扱っていますが、Excelでは閏年として扱っているためです。厳密にいうと、1900年2月末日までは -1、それ以降は -2 となりますが、実用上はつねに -2 で問題ないでしょう。

2020年7月17日金曜日

Oracle APEXでのデータ・ロード(1) - はじめに

Oracle APEXで実施できるデータ・ロードのいくつか代表的な方法を紹介します。トピックとしては以下を予定しています。
  • はじめに - 表とレポート・アプリケーションの作成
  • データ・ワークショップ
  • データ・ロード・ウィザード
  • APEX_DATA_PARSERパッケージ
  • XMLパーサー
  • CSVによる出力
  • JSONによる出力
各都道府県が公開している新型コロナウイルス感染症の陽性患者データを題材に、それぞれのデータ取り込み方法について解説していきます。また、追加で取り込んだデータを出力する方法についても若干説明を行う予定です。


表の作成


一般社団法人コード・フォー・ジャパンの方々によって、以下の活動が行われています。

新型コロナウイルス感染症対策のためのデータ公開支援

この活動の一環として、「新型コロナウイルス感染症対策に関するオープンデータ項目定義書」が作成されています。今回はその中の陽性患者属性を取り込みの対象とし、この定義書に準拠してデータを保持する表を定義します。本記事はOracle APEXの機能の紹介を目的としており、自治体向けのサイトを構築することは目的としてはいません。公開サイトを構築する場合は、上記URLに記載されている内容に従ってください。

では、「新型コロナウイルス感染症対策に関するオープンデータ項目定義書」(2020年5月25日更新版)に含まれるデータセット名、陽性患者属性の定義に従って表を作成します。表の定義にはクイックSQLを使用します。

SQLワークショップよりユーティリティを開き、クイックSQLを実行します。


クイックSQLによる表定義を左側に記述します。定義は以下になります。
# prefix: covid19
# semantics: default
# genpk: false
PATIENTS
   "No" number
   MUNICIPALITY_CODE   number
   PREFECTURE_NAME     vc16 /nn
   CITY_NAME           vc40
   PUBLISHED_DATE      date
   ONSET_DATE          date
   PATIENT_LOCATION    vc200
   PATIENT_AGE         vc40
   PATIENT_SEX         vc40
   PATIENT_OCCUPATION  vc200
   PATIENT_STATUS      vc200
   PATIENT_SYMPTOM     vc200
   PATIENT_TRAVEL_HISTORY number /check 1,0
   PATIENT_LEFT_HOSPITAL  number /check 1,0
   REMARK              vc200
陽性患者属性とクイックSQLで定義した列名の関係は以下になります。属性定義の詳細は元の定義書を参照してください。

列名

定義書の属性名

No

No

MUNICIPALITY_NAME

全国地方公共団体コード

PREFECTURE_NAME

都道府県名

CITY_NAME

市区町村名

PUBLISHED_DATE

公表_年月日

ONSET_DATE

発症_年月日

PATIENT_LOCATION

患者_居住地

PATIENT_AGE

患者_年代

PATIENT_SEX

患者_性別

PATIENT_OCCUPATION

患者_職業

PATIENT_STATUS

患者_状態

PATIENT_SYMPTOM

患者_症状

PATIENT_TRAVEL_HISTORY

患者_渡航歴の有無フラグ

PATIENT_LEFT_HOSPITAL

患者_退院済フラグ

REMARK

備考


クイックSQLによる定義から、表を作成するSQLが生成されます。生成されるSQLに条件を加えるため、設定をクリックします。


オブジェクト接頭辞としてCOVID19を指定し、主キーは12c identityデータ型を選択します。実際は主キー定義のない表を作りますが、これ以外を選択すると追加のトリガーが生成されます。トリガーの生成を避けるために12c identityデータ型を選択します。


画面下へスクロールし、自動主キーOFFに設定した後、変更の保存を実行します。


設定に準じた形で画面右側に表COVID19_PATIENTSを作成するSQLが表示されます。このSQLを実行して、表を新規作成します。レビューおよび実行の前に、SQLスクリプトを保存を実行します。SQLスクリプトを保存するまでは、レビューおよび実行のボタンはグレーアウトしていて、実行することができません。


スクリプト名は任意ですが、ここではcovid19patientsを入力しています。"_"などの記号はスクリプト名に含められません。スクリプト名を入力したのち、スクリプトを保存をクリックします。


レビューおよび実行のボタンがクリック可能になります。クリックして実行します。


カラム名として"No"としたいところが"no"となっているので、変更します。その後、実行をクリックします。


確認画面が表示されるので、即時実行を行います。


スクリプトの実行結果が表示されます。フィードバックとして「表が作成されました。」、結果として行が成功となっていれば、表が作成されています。アプリケーションの作成をクリックし、アプリケーション作成ウィザードを起動します。


アプリケーションの作成


確認画面が表示されるので、アプリケーションの作成をクリックします。


アプリケーション作成ウィザードでは、名前新型コロナウイルス感染症陽性患者属性とします。


下にスクロールして、ユーザー・インターフェースのデフォルト値をクリックします。


生成されるページのラベルを指定することができます。デフォルトではラベルは表名や列名から導出されるため、英語になります。ここで鉛筆アイコンをクリックしてひとつひとつラベルを日本語に変更することもできますが、今回はJSONを編集することで、一括でラベルを変更します。JSONの表示をクリックします。


JSONが表示されたら、以下の内容で置き換えます。元々の定義に従って、各種ラベルの部分だけを日本語に置き換えています。
{
"applicationDefaults": {
  "name": "Application Defaults",
  "tablePrefixes": "",
  "description": "",
  "tables": [
    {
      "table": "COVID19_PATIENTS",
      "labelSingular": "陽性患者属性",
      "labelPlural": "陽性患者一覧",
      "primaryDisplayColumn": "%",
      "secondaryDisplayColumn": "%",
      "primaryKeyColumn": "%",
      "primaryParentTable": "%",
      "description": ""
    }
  ],
  "tableColumns": [
    {
      "table": "COVID19_PATIENTS",
      "column": "CITY_NAME",
      "label": "市区町村名",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "MUNICIPALITY_CODE",
      "label": "全国地方公共団体コード",
      "formControl": "numberField",
      "includeOnForms": true,
      "includeOnReports": true,
      "formatMask": "999G999G999G999G999G999G999G999G999G990",
      "required": false,
      "help": "NUMBER"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "NO",
      "label": "No",
      "formControl": "numberField",
      "includeOnForms": true,
      "includeOnReports": true,
      "formatMask": "999G999G999G999G999G999G999G999G999G990",
      "required": false,
      "help": "NUMBER"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "ONSET_DATE",
      "label": "発症_年月日",
      "formControl": "datePicker",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "DATE"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_AGE",
      "label": "患者_年代",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_LEFT_HOSPITAL",
      "label": "患者_退院済フラグ",
      "formControl": "numberField",
      "includeOnForms": true,
      "includeOnReports": true,
      "formatMask": "999G999G999G999G999G999G999G999G999G990",
      "required": false,
      "help": "NUMBER"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_LOCATION",
      "label": "患者_居住地",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_OCCUPATION",
      "label": "患者_職業",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_SEX",
      "label": "患者_性別",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_STATUS",
      "label": "患者_状態",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_SYMPTOM",
      "label": "患者_症状",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PATIENT_TRAVEL_HISTORY",
      "label": "患者_渡航歴の有無フラグ",
      "formControl": "numberField",
      "includeOnForms": true,
      "includeOnReports": true,
      "formatMask": "999G999G999G999G999G999G999G999G999G990",
      "required": false,
      "help": "NUMBER"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PREFECTURE_NAME",
      "label": "都道府県名",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": true,
      "help": "VARCHAR2"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "PUBLISHED_DATE",
      "label": "公表_年月日",
      "formControl": "datePicker",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "DATE"
    }
    ,
    {
      "table": "COVID19_PATIENTS",
      "column": "REMARK",
      "label": "備考",
      "formControl": "text",
      "includeOnForms": true,
      "includeOnReports": true,
      "required": false,
      "help": "VARCHAR2"
    }
  ],
  "components": {
    "reports": {
      "rowsPerPage": "50",
      "paginationMethod": "nextPrevious",
      "numericFormatMask": "99G999G999G999G999G999G999G999G999G999G990",
      "dateFormatMask": "SINCE"
    }
  }
}
}
内容を置き換えた後、属性デフォルトの適用を実行します。


JSONでの指定が属性デフォルトに反映されます。変更されたラベルを確認した後、アプリケーション作成ウィザードに戻るをクリックします。


表名から導出されたレポート名がPatientsとなっているので変更します。編集をクリックします。


ページ名陽性患者一覧へ変更し、変更の保存をクリックします。


レポート名が変更されていることを確認し、アプリケーションの作成を実行します。


アプリケーションの作成が完了すると、以下の画面が表示されます。


アプリケーションを実行して確認しましょう。