2024年4月11日木曜日

日本の法令データをデータベースにロードする

e-Gov法令検索のサイトからダウンロードできるXMLの法令データを、データベースにロードするAPEXアプリケーションを作ってみます。


最初から全ての法令データをロードしようとすると時間がかかるので、憲法のデータを使ってアプリケーションを作成します。アプリケーションが完成したら全ての法令データをデータベースにロードしてみます。

法令分類データの憲法をダウンロードします。1_xml.zipがダウンロードされます。


ダウンロードした1_xml.zipには、法令の一覧が記載されている1.csvというCSVファイルと、法令本文であるXMLファイルがそれぞれ個別のディレクトリの下に含まれています。

ディレクトリ名と法令本文のファイル名は同じで、XMLファイルには.xmlの拡張子が付けられています。例えば法令番号昭和二十一年憲法にあたるファイルは以下です。

321CONSTITUTION_19470503_000000000000000/321CONSTITUTION_19470503_000000000000000.xml


CSVに記載されている一覧の情報と法令本文であるXMLファイルを、以下の表JLAW_DATAに保存します。列BODYBODY_HASHはXMLファイルの情報なので、CSVファイルには含まれません。列IDは列BODY_URL(CSVでは本文URL)の一部を取り出して割り当てます。

create table jlaw_data (
id varchar2(40 char) not null
constraint jlaw_data_id_pk primary key,
law_type varchar2(16 char), -- 法令種別
law_number varchar2(100 char), -- 法令番号
law_title varchar2(400 char), -- 法令名
law_title_kana varchar2(400 char), -- 法令名読み
law_title_former varchar2(600 char), -- 旧法令名
promulgation_date varchar2(16 char), -- 公布日
law_title_ammended varchar2(400 char), -- 改正法令名
law_number_ammended varchar2(100 char), -- 改正法令番号
promulgation_date_ammended varchar2(16 char), -- 改正法令公布日
enforcement_date varchar2(16 char), -- 施行日
enforcement_date_remark varchar2(200 char), -- 施行日備考
law_id varchar2(16 char), -- 法令ID
body_url varchar2(100 char), -- 本文URL
not_in_force varchar2(1 char), -- 未施行
body xmltype, -- 本文
body_hash raw(32) -- 本文ハッシュ(SHA-256)
)
xmltype body store as binary xml
;
view raw jlaw_data.sql hosted with ❤ by GitHub
上記の表を作成した後、この表に法令データをロードするAPEXアプリケーションを作成します。

アプリケーション作成ウィザードを起動します。作成するアプリケーションの名前e-Gov法令とします。デフォルトで追加されているホーム・ページ削除し、代わりに表JLAW_DATAをソースとした対話モード・レポートのページを追加します。


対話モード・レポートのページ名法令表またはビューとしてJLAW_DATAを選択します。フォームは不要なので、フォームを含めるチェックしません


以上でアプリケーションの作成を実行します。

アプリケーションが作成されたら、最初にデータ・ロード定義を作成します。

共有コンポーネントデータ・ロード定義を開きます。


作成をクリックします。


データ・ロードの作成最初から行います。

へ進みます。


データ・ロード定義の名前LOAD_CSVとします。ターゲット・タイプ表名JLAW_DATAを選択します。

へ進みます。


ソース・タイプファイルのアップロードとし、サンプル・ファイルとしてダウンロードした法令データに含まれる1.csvを選択します。

へ進みます。


列見出し最初の行にヘッダーが含まれるにチェックを入れます。列のマッピングについては、表JLAW_DATAのDDLのコメントにマップ先とソース列の対応が記載されています。


主キー列は後で本文URLからSQL式を用いて生成します。そのため、ここでは主キーにチェックを入れる列はありません。

データ・ロードの作成をクリックします。


データ・ロード定義としてLOAD_CSVが作成されます。これを編集します。


静的IDは大文字および小文字のどちらでもよいのですが、大文字のLOAD_CSVに変更します。ケース・センシティブなので、大文字小文字で異なっていると、別の定義と見做されます。設定ロード・メソッドマージを選択します。

データ・プロファイルの編集を開きます。


列の追加をクリックします。


主キー列IDに保存する値を設定します。

列タイプSQL式を選択します。名前IDです。データ型VARCHAR2長さ40とします。設定主キーオンにします。

ソースSQL式として以下を記述します。本文URLの引数にlawidとして指定されている値を主キー列IDに設定します。

replace(BODY_URL, 'https://elaws.e-gov.go.jp/document?lawid=')

e-Gov法令検索リニューアル後
replace(replace(BODY_URL, 'https://laws.e-gov.go.jp/law/'),'/','_')

作成をクリックします。


データ・プロファイルに列IDが追加されました。

変更の適用をクリックします。


以上でデータ・ロード定義LOAD_CSVは完成です。

変更の適用をクリックします。


CSVファイルをロードするページを作成します。

ページ作成ウィザードを開きます。

データのロードを選択します。


ページの名前CSVのロードとします。データ・ロード属性データ・ロードに先ほど作成したLOAD_CSVを選択します。データのアップロード元ファイル最大ファイル・サイズ(MB)10を入力します。すべての法令データに含まれるCSVファイルのサイズはほぼ6MBなので、それをロードできるサイズにしています。

ナビゲーションはデフォルトから変更せず、ブレッドクラムとナビゲーションの双方を作成します。

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


作成されたページを実行し、1.csvを表JLAW_DATAにロードします。


ファイルの選択をクリックし、1.csvを選択します。


プレビューが表示されます。データのロードをクリックします。


92行がロードされます。他の法令分類とは異なり、憲法は改正されない限り92行は変わらないでしょう。(実際には1_xml.zipには憲法以外も含まれるため、ロードされる行は変わります。)


ホーム・ページの対話モード・レポートにも、ロードされたデータが一覧されます。


XMLファイルをロードする機能を追加します。

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

その都度、e-Gov法令のページから法令データをダウンロードするのは手間なので、URLを入力して、ZIPファイルのダウンロードからデータのロードまで一気に行うようにします。

URLを入力するページ・アイテムとしてP1_URLを作成します。識別タイプテキスト・フィールドラベルURL設定サブタイプとしてURLを選択します。


法令データのロードを実行するボタンLOADを作成します。

ラベルLoad外観ホットオンにします。テンプレート・オプションを開き、WidthStretchに変更します。

動作アクションはデフォルトのページの送信とし、データのロードはプロセスとして実装します。比較的重い処理が実行されるため、確認の要求オンにし、確認メッセージとして法令データをロードしますか?を設定します。


長時間の処理になることがあるため、データのロードはバックグラウンドで実行します。

バックグラウンド実行の進捗を確認するため、ビューAPEX_APPL_PAGE_BG_PROC_STATUSを一覧する対話モード・レポートを作成します。

識別タイトル進捗とし、ソースタイプSQL問合せSQL問合せとして以下を記述します。

select * from APEX_APPL_PAGE_BG_PROC_STATUS


プロセス・ビューを開き、法令データをロードするプロセスを作成します。

最初にバックグラウンドで実行するため、実行チェーンを作成します。

プロセスを作成します。識別名前法令データのロードタイプは実行チェーンとします。

設定バックグラウンドで実行オンにします。サーバー側の条件ボタン押下時LOADを指定します。


実行チェーンに小プロセスを作成します。

識別名前Load e-Gov CSV and XMLタイプコードの実行とし、ソースPL/SQLコードに以下を記述します。

declare
l_blob_zip blob;
l_blob_csv blob;
l_blob_xml blob;
l_clob_xml clob;
l_new_hash raw(32);
l_old_hash raw(32);
e_web_access_failed exception;
l_files apex_zip.t_files;
l_load_result apex_data_loading.t_data_load_result;
l_total_laws number;
l_id varchar2(4000);
l_xml xmltype;
l_skipped number := 0;
l_ignored number := 0;
l_updated number := 0;
l_message varchar2(100);
begin
/*
* URLで指定された法令種別データセットを取得する。ZIP形式のバイナリ。
*/
apex_background_process.set_status('Start: downloading zip');
apex_web_service.set_request_headers('Content-Type', 'application/zip');
l_blob_zip := apex_web_service.make_rest_request_b(
p_url => :P1_URL
,p_http_method => 'GET'
);
if apex_web_service.g_status_code <> 200 then
raise e_web_access_failed;
end if;
apex_debug.info('Download Completed.');
apex_background_process.set_status('Completed: downloading zip');
/*
* ZIPアーカイブに含まれるファイル一覧を取得する。
*/
l_files := apex_zip.get_files(
p_zipped_blob => l_blob_zip
);
/*
* ZIPアーカイブに含まれるCSVファイルを見つけ、表JLAW_DATAにロードする。
*/
apex_background_process.set_status('Start: load CSV in zip');
for i in 1..l_files.count
loop
/*
  * 含まれているCSVファイルは1つだけなはず。
*/
if l_files(i) like '%.csv' then
apex_debug.info('file %s found.', l_files(i));
l_blob_csv := apex_zip.get_file_content(
p_zipped_blob => l_blob_zip
,p_file_name => l_files(i)
);
l_load_result := apex_data_loading.load_data(
p_static_id => 'LOAD_CSV'
,p_data_to_load => l_blob_csv
);
l_total_laws := l_load_result.processed_rows;
apex_debug.info( 'Processed %s rows.', l_total_laws);
exit; -- CSVがロードできれば以降はスキップしてXMLのロードに移る。
end if;
end loop;
apex_debug.info('CSV Load Complated.');
apex_background_process.set_status('Completed: load CSV in zip');
/*
* ZIPに含まれているXMLを表JLAW_DATAに更新する。
*/
apex_background_process.set_status('Start: load XML files in zip');
for i in 1..l_files.count
loop
if l_files(i) like '%.xml' then
l_blob_xml := apex_zip.get_file_content(
p_zipped_blob => l_blob_zip
,p_file_name => l_files(i)
);
/* 法令のXMLファイルはディレクトリ名とファイル名が同じ。 */
l_id := replace(l_files(i),'.xml');
l_id := substr(l_id, instr(l_id,'/')+1);
/* BLOBをXMLtypeに変換 */
l_xml := xmltype(
xmlData => l_blob_xml
,csid => NLS_CHARSET_ID('AL32UTF8')
);
/* ハッシュ値の計算 */
l_clob_xml := l_xml.getClobVal();
l_new_hash := dbms_crypto.hash(l_clob_xml, dbms_crypto.HASH_SH256);
begin
select body_hash into l_old_hash from jlaw_data where id = l_id;
/* ハッシュ値が一致していれば更新しない。 */
if l_old_hash = l_new_hash then
l_skipped := l_skipped + 1;
else
l_updated := l_updated + 1;
update jlaw_data set body = l_xml, body_hash = l_new_hash where id = l_id;
if mod(l_updated, 200) = 0 then
commit; -- 200行アップデートごとにコミットする。
apex_debug.info('XML Load Commited.');
end if;
end if;
exception
when no_data_found then
/* CSVにエントリがないのにXMLはあるのは異常。 */
l_ignored := l_ignored + 1;
apex_debug.info('id %s not in CSV.', l_id);
end;
apex_background_process.set_progress(p_sofar => i ,p_totalwork => l_total_laws);
end if;
end loop;
commit;
l_message := apex_string.format('Completed: updated %s, skipped %s, ignored %s', l_updated, l_skipped, l_ignored);
apex_background_process.set_status(l_message);
apex_debug.info(l_message);
end;


以上でアプリケーションは完成です。

データベースにロードする法令データのリンクのアドレスをコピーします。


コピーしたURLを入力し、ボタンLoadをクリックしてデータベースへの取り込みを開始します。確認のためのポップアップが表示されるので、OKをクリックします。


ビューAPEX_APPL_PAGE_BG_PROC_STATUSの列STATUS_MESSAGESO_FARに処理の進捗が表示されるようにコードを記述しています。ページをリロードして対話モード・レポートを再表示すると、処理の進捗を確認できます。


すべての法令データをデータベースにロードするには、大体1時間弱ほどの時間がかかりました。


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

今の所、取り込んだXML形式の法令データの使い道は思いつかないのですが、少々複雑な形式のデータをデータベースに取り込む手順の参考になるかと思います。

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