JVLinkオペレーション、レコードのパース、データベース追加のフロー

JVLinkを使うには、はじめにJVInitメソッドをコールして初期化します。初期化は、JVLinkにソフトウェアIDやレジストリに記憶してあるプロパティをセットする作業です。JRA-VANで開発したソフトウェアを公開する場合、JRA-VANからソフトウェアIDが与えられます。ソフトウェアIDがない場合は sid = "UNKNOWN"とする約束です。

JVLinkを初期化するフローは次のようにしました。

JVLink初期化フロー

JVInitをコールして、デバッグのためにステートメントと戻り値を記録して、戻り値によってユーザーへの通知を変えます。

次に、JVOpenメソッドでJVLinkをオープンします。オープンは、取得したいレコードがいつ、どの種類のレースかを指定して、JVLinkがjvdファイル(暗号化・圧縮してあるレコードの集合)をダウンロードするためにサーバーとの通信経路を開く作業です。

JVLinkをオープンするフローは次のようにしました。

JVLinkオープンフロー

初期化と同様で、JVOpenのコール、記録、戻り値による通知の場合分けをしてます。

JVOpenのコールが成功したら、ダウンロードしていないjvdファイルがある場合、JVLinkはダウンロードを開始します。ダウンロードが完了したjvdファイルからレコードの読み込みができます。

JVStatusメソッドでダウンロードしたjvdファイルの数を得られるので、全てのjvdファイルのダウンロードが完了するまで待つフローにしました。

jvdファイルのダウンロードを待つフロー

数秒おきに繰り返しJVStatusをコールして、全jvdファイルダウンロードが完了したらループを抜けます。

jvdファイルのダウンロードが完了したら、JVGetsメソッド[1]でレコードを1行ずつ取得して蓄積します。JVGetsはByte型配列のレコードを取得するメソッドです。文字コードShift-JISのバイナリデータが1 Byteずつ代入してあります。

レコードを取得するフロー

JVGetsはコールするたびにjvdファイル内のレコードの番号を表すポインタを自動で更新します。そして、jvdファイル内の全てのレコードを取得し終わると、-1を返します。全jvdファイルのレコードを取得し終わると0を返します。

1つのjvdファイルは数MBから数10MBで、レコード数は数百から数千くらいです。1986年からの全てのjvdファイルは4000くらいです。

レコードはjvdファイル単位で蓄積することにしました。まだ取得したことがないjvdファイルならレコードを蓄積して、蓄積し終えるとループを抜けます。

取得したレコードを管理するために、jvdファイル情報のリストを用意することにします。jvdファイルリストに、jvdファイル名、タイムスタンプ、取得成功可否などの情報を記録して、このリストと照合して、レコードを取得すべきか判断します。

このjvdファイルリストを作ることで、取得するレコードの重複を避けることができるはずです。あるレコードが既にデータベースに存在するかをデータベースで検索をして確認すると、レコード1つの検索が10msecかかるなら、レコードが百万個あれば3時間ほどかかります。時間の節約のためにjvdファイル情報を管理することにします。

また、Excel VBAではJVGetsは1レコードの取得に15msec~50msecかかります。Thinkpad X230(2012年のIntel Core i7 / 16GB RAM)、Windows 10、Excel 2010 32bitで試しました。1レコードの取得に平均30msecほどかかっていて、10万レコードの取得に1時間くらいかかります。全部で100万レコードくらいあるので、JVGetsのコールだけで10時間くらいかかりそうです。データベース操作も合わせると完全なデータベースを構築するのに丸1日はかかる感じです。

一度、過去のレースのデータベースを構築してしまえば、毎週のレースデータの更新には時間がそうなにかからないので問題ないのですが、もっと速くならないかなぁと思っています。

レコードを蓄積したら、レコードを1つずつパースします。

JVDataは37種類のレコードがあります。レコードはTK、RAなどの2文字のレコード種別IDで区別でき、1つのレコード種別には1つのレコードフォーマットが対応しています。JVData仕様書にレコードフォーマット表があります。ですので、レコード種別毎にパースするプロシージャを作ることにしました。

レコードをパースするフロー

レコード種別IDはレコードの先頭2 Bytesと決まっていますので、始めにレコード種別IDを読み取って、パースするプロシージャを場合分けしてコールします。

レコードをパースしてテーブルを作ったら、テーブルをデータベースに新規追加または更新または削除します。

パースしたデータをデータベースに追加・削除・更新する

レコードの3 Bytes目がデータ区分と決まっていて、データ区分により、そのレコードが新規なのか、既存レコードの更新なのか、削除なのかわかるようになっています。データ区分で場合分けをしてSQL文を作り、実行します。

SQL文を実行するプロシージャのエラー処理を設計していないなど、まだ、ロジックを考え決めていないのですが、だいたい、こんな感じのフローでよいはずです。

このフローを作った後に、クラスとオブジェクト設計をしていて考えが進展したので、このフローは少し変更しそうです。


  1. JVLinkにはJVGetsの他にJVReadというString型のレコードを取得できるメソッドがあり、便利なので最初はJVReadを使っていたのですが、レコード長が仕様書の値と変わってしまうようで使うのをやめました。 ↩︎

Kosuke Maeda / まえだこうすけ

「機械学習で競馬予想して勝てるのか?」をテーマに活動中! QiitaにはR、VBAなどのTipsを投稿しています。