#:g1: データの検索に組み込みPrologを使ってみる(4): RDBと組み合わせる

Posted 2021-10-14 03:53:50 GMT

Common Prolog + Common SQL 篇

前回は、Allegro Prolog + AllegroCache の組み合わせを試しましたが、今回は、同じく商用の処理系であるLispWorksのCommon Prolog + Common SQLの組み合わせで試してみたいと思います。

Common Prologは、LispWorksの組み込みProlog、Common SQLは、各種SQLとの接続パッケージでSQLのテーブルとオブジェクトとのマッピングが可能な所謂ORM機能もあります。
これらは元々KnowledgeWorksとして1990年からあるようですが、Common SQLは1992年にDataWorksの後継として開発されたようなので若干新しいようです(といっても古い)

パッケージ定義/ユーティリティ等は前回のものを引き続き利用しています。

(require "sql")
(require "sqlite")
(require "kw")

(defpackage covid19.mhlw.go.jp (:use cl fare-csv sql kw))

(in-package covid19.mhlw.go.jp)

Allegro CLのAllegroCacheに対応するものとしてCommon SQLとSQLite3を試してみます。
Common SQLではSQLをLisp的に記述することが可能ですが、そのためにはリーダーマクロを有効する必要があります。
便利なようなそうでもないような。
SQLのクエリを文字列で与えるqueryという仕組みもありSQLに慣れた人はそちらの方が便利かもしれません。

(locally-enable-sql-reader-syntax)

;; SQLite3のDBへ接続(DBファイルが作成される)
(connect "covid19.sqlite3")

;; テーブル作成 (create-table [newly_confirmed_cases_daily] '(([date] text) ([prefecture] text) ([newly_confirmed_cases] integer)))

;;query版 (query "create table newly_confirmed_cases_daily(date text, prefecture text, newly_confirmed_cases integer);")

;; データベースへ登録 (with-transaction (dolist (line (subseq (cdr *newly_confirmed_cases_daily.csv*) 0 nil)) (destructuring-bind ($date $prefecture $newly-confirmed-cases) line (insert-records :into [newly_confirmed_cases_daily] :attributes '([date] [prefecture] [newly_confirmed_cases]) :values (list (parse-date $date) $prefecture $newly-confirmed-cases)))))

;;query版 (with-transaction (dolist (line (subseq (cdr *newly_confirmed_cases_daily.csv*) 0 nil)) (destructuring-bind ($date $prefecture $newly-confirmed-cases) line (query (format nil "insert into newly_confirmed_cases_daily(date, prefecture, newly_confirmed_cases) values ('~A', '~A', ~A);~%" (parse-date $date) $prefecture $newly-confirmed-cases)))))

;;;愛知の2021年9月の合計
(select [sum [newly_confirmed_cases]] :from [newly_confirmed_cases_daily]
        :where [and [= [prefecture] "Aichi"]
                    [between [date] "2021-09-01" "2021-09-30"]]
        :flatp T)(21209) 
  ("SUM(NEWLY_CONFIRMED_CASES)") 

;;query版 (query "select sum(newly_confirmed_cases) from newly_confirmed_cases_daily where prefecture='Aichi' and date between '2021-09-01' and '2021-09-30'" :flatp T)

;;;都道府県のリストを取得する (select [distinct [prefecture]] :from [newly_confirmed_cases_daily] :where [<> [prefecture] "ALL"])

;;query版 (query "select distinct prefecture from newly_confirmed_cases_daily where prefecture != 'ALL'" :flatp T)

;;;都道府県ごとの合計 (select [prefecture] [sum [newly_confirmed_cases]] :from [newly_confirmed_cases_daily] :where [<> [prefecture] "ALL"] :group-by [prefecture] :limit 2)(("Shizuoka" 26308) ("Shiga" 12388))

;;query版 (query " select prefecture, sum(newly_confirmed_cases) from newly_confirmed_cases_daily where prefecture != 'ALL' group by prefecture limit 2")

Common SQLのORM機能とCommon Prologの組み合わせ

さて課題自体は、Prologを使わずにSQLで解決してしまいましたが、Prolog連携も試してみます。
SQLのテーブルとのマッピングにはdef-view-classという、standard-db-classをメタクラスとしたdefclassの派生を利用します。

SQL=Objectと、Prologとの連携ですが、KnowledgeWorksではOPS5互換の前向き推論とProlog互換の後ろ向き推論をワーキングメモリ上のオブジェクトと連携させる仕組みがあり、オブジェクトが生成されると自動でワーキングメモリに登録されます。
自動登録のためにはstandard-kb-objectのサブクラスにする必要があるためクラス定義でmixinしておきます。

ワーキングメモリに処理対象を全件載せるためには、SQLでクエリしオブジェクトを生成→ワーキングメモリに載る、の手順を踏みます。

(def-view-class newly_confirmed_cases_daily (standard-db-object standard-kb-object)
  ((id :type integer :db-kind :key :column rowid) ;sqlite3では自動でrowidというのができる
   (date :type T)
   (prefecture :type T)
   (newly_confirmed_cases :type integer))
  (:base-table |newly_confirmed_cases_daily|))

;; ワーキングメモリに処理対象を全件載せる (select 'newly_confirmed_cases_daily)

;;愛知の2021年9月の合計 (let ((query '(and (newly_confirmed_cases_daily ? date ?date prefecture "Aichi" newly_confirmed_cases ?cases) ((search "2021-09-" ?date) 0)))) (reduce #'+ (findall '?cases query))) → 21255

;;都道府県ごとの合計 (let ((tab (make-hash-table :test #'equal))) (findall nil `(and (newly_confirmed_cases_daily ?x prefecture ?pref newly_confirmed_cases ?cases) ((incf (gethash ?pref ,tab 0) ?cases) ?))) (loop :for pref :being :the :hash-keys :of tab :using (:hash-value cases) :repeat 2 :collect (list pref cases)))(("Shizuoka" 26308) ("Shiga" 12388))

まとめ

KnowledgeWorksでは、未だに開発版扱いのAllegro Prologよりは、オブジェクトシステムとPrologの融合がより進んでいます。
ワーキングメモリに載せて処理するというのがちょっと特殊ですがPrologだけでなく、前向き推論部とも連携できますし多分強力なのでしょう。

知識ベース+前向き推論+後ろ向き推論をRDB+OPS5+Prologで実現したようなシステムは、1980年代後半のエキスパートシステムの割と標準的な構成だったようです。
1990年に登場したKnowledgeWorksもそういう流れの一つなのかなと思いますが、現在でも、AllegroGraphのようなグラフDB(知識ベース)の流れに脈々と受け継がれているかと思います。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus