Firebirdで配列


こんにちは。
これは Delphi Advent Calendar 2017 最終日の投稿です。

最終日だというのに、おいらなんかがノコノコ出て来て良いのか?って話もありますが。
よろしくお願いします。

Firebirdで配列を使いたい。と思って挑戦した時の顛末です。
データロガーを作っているのですが、DoubleとBooleanの配列をテーブルに格納したかったというところから始まっています。

環境

Delphi 10.2.2
Firebird 2.5
Windows10
の環境での話です。

取り敢えず、データベースを作成して、テーブルを作成します。

isqlやFrameRobin等を使用して、次のテーブルを作成しました。

CREATE TABLE LOG_DATA(
 LOG_NO    INTEGER   NOT NULL,
 LOG_COUNT INTEGER   NOT NULL,
 GET_TIME  TIMESTAMP NOT NULL,
 SET_ANLG  DOUBLE PRECISION[32],
 SET_DGTL  SMALLINT[32],
 GET_ANLG  DOUBLE PRECISION[512],
 GET_DGTL  SMALLINT[1024],
 CONSTRAINT PK_LOG_DATA_ID PRIMARY KEY( LOG_NO, LOG_COUNT )
);

PRIMARY KEYはLOG_NO, LOG_COUNTとしておきます。

SET_ANLG, SET_DGTL, GET_ANLG, GET_DGTLの4項目。
型定義の後に[]で添字が付けられているのが配列項目になります。

Delphiから、 FireDACを用いてこのテーブルにアクセス

FDConnectionを置いて、Server, Database, UserName, Password等のパラメータをセット。

そしてFDQueryを置いて、FDConnectionを関連付けて、
FDQueryのSQLに

 SELECT * 
 FROM LOG_DATA
 WHERE LOG_NO = :LogNo AND LOG_COUNT = :LogCount

と設定。
実際の読み込みは、

 FDQuery.ParamByName( 'LogNo' ).AsInteger    := _LogNo;
 FDQuery.ParamByName( 'LogCount' ).AsInteger := _LogCount;
 FDQuery.Open;

とすれば、_LogNo, _LogCountで指定した行が返されます。

さてさて、配列部分はどうアクセスすれば良いのでしょうか?

 v := FDQuery.FieldByName( 'GET_ANLG' )[0].AsFloat;

みたいに書けるのかと思ってみたのですが、違うようです。

色々試してみました。

 v := FDQuery.FieldByName( 'GET_ANLG[0]' ).AsFloat;

と書くと動きました。

でも、これだと、for文で回して配列内の全ての項目にアクセスする場合、

 for i := 0 to 512 - 1
  v := FDQuery.FieldByName( 'GET_ANLG[' + i.ToString + ']' ).AsFloat;

と、かなり冗長というか、何のための配列なの?となってしまいます。

もっと違う方法があるはず

もっとスマートな方法が必ずあると思い、Helpを舐め回しました。
ありました!もう少しスマートなやり方が!

TArrayFieldのFieldValuesを使う方法です。
早速書き換えてみます。

for i := 0 to 512 - 1 do
  v := TArrayField( FDQuery.FieldByName( 'GET_ANLG' ) ).FieldValues[i];

TArrayFieldにキャストして、FieldValuesにアクセスするというのが基本みたいです。
FieldValuesはVariantの為、AsFloatとか無くてもそのままDoubleが帰って来ます。(vがDoubleで宣言されている場合)
個人的にはVariantはあまり使わないのですが、背に腹は代えられません。。。

このFieldValuesはTArrayFieldのデフォルトプロパティなので

for i := 0 to 512 - 1 do
  v := TArrayField( FDQuery.FieldByName( 'GET_ANLG' ) )[i];

でアクセスできました。
うん、少しはスマートになりました。

もう一声って感じで。

for文で回す中で、毎回毎回FieldByNameが走るとなると、いくらDictionaryを使っているとはいえ、
パフォーマンスに難がありそうです。

そこで、

var
 fld: TArrayField;

と宣言してfor文の前にfldに該当のTArrayFieldを取り出します。

すると、

 fld := TArrayField( FDQuery.FieldByName( 'GET_ANLG' ) );
 for i := 0 to 512 - 1 do
  v := fld[i];

と、かなりスッキリしました。

あとがき

みなさん、あまりDBで配列使わないみたいで、情報がネットにも殆どありませんでした。
そもそも、DBで配列がサポートされて、それをFireDAC等のミドルウェア(?)がサポートしたのも
最近っぽいのでいたしかたないのかと思いますが。

実際、上記テーブルに、1秒間隔でログデータを落としていますが、今の所、問題なさそうです。
これから、長期稼働テストとかしないといけませんけど。。。

それでは、みなさま、よいお年をお迎えください。

今年もお世話になりました。
来年もよろしくお願いいたします。