データベースプログラミングというと、コントロールをフォーム配置してさぁ簡単、という話が多いのですが、それでは済まないケースの方が多いです。そういうときに、コードをゴリゴリ書いていくにはどうしたらいいか、私の経験から備忘録的にまとめてみました。
ちなみに開発環境は、すっかりユーザも少なくなったDelphi、データベースコンポーネントはFireDACです。Delphiの商品構成の見直しでFireDACがEnterprise版以上に制限されるそうですが、ローカルアクセスについては従来通り使えるようです。
話を簡単にするために、接続先のデータベースはSQLiteとします。また、必要な例外処理などは入れていないので、必要に応じてtry~exceptブロックで挟みます。
uses節にFireDACのユニットを追加
これは、何でもいいので1個、フォームにFireDACのコントロールを配置すれば自動的に追加されます。数が多いですが、適当にコピペして他のユニットにも持って行けます。
uses FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Comp.Client, FireDAC.Comp.DataSet, FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLite, FireDAC.VCLUI.Wait, FireDAC.Comp.UI;
TFDConnectionコントロールの生成と破棄
TFDConnectionは、データベースを使うための最も基本的なコントロールです。これを動的に生成し、接続するSQLiteデータベースを指定し、使い終わったら破棄します。一連の流れは、こんな感じです。
var connection: TFDConnection; …… begin …… connection := TFDConnection.Create(AOwner); connection.DriverName := 'SQLite'; connection.Params.Clear; connection.Params.Add('Database=database.db'); connection.Params.Add('DriverID=SQLite'); connection.Open; ……(ここで各種のデータベース操作を行う) connection.Close; connection.Free; ……
直接SQL文を実行
結果を返さないSQL文は、TFDConnectionオブジェクトから直接発行できます。下記はDROP文ですが、INSERT文、ALTER文、CREATE文なども同様です。
connection.ExecSQL('DROP TABLE IF EXISTS [table];');
結果を1個だけ返すSQL文も、TFDConnectionオブジェクトから直接発行できます。
result := connection.ExecSQLScalar('SELECT max(id) FROM [table]');
クエリセットを使う
SELECT文を発行して結果をもらうなどの場合には、TFDQueryコントロールを使います。安全のために、TFDConnectionオブジェクトのConnectedプロパティを参照し、接続されている場合のみ実行しています。
var query: TFDQuery; begin …… if connection.Connected then begin query := TFDQuery.Create(connection); query.Connection := connection; ……(ここで各種のデータベース操作を行う) qyery.Free; end; ……
レコードを参照する
SELECT文を発行すれば、結果セットを取得できます。
FetchOptions.RecordCountModeプロパティにcmTotalを指定すると、RecordCountプロパティの返す値が常に結果セットの全レコード数になります。SQL.Textプロパティに実行したいSQL文を入れておくのですが、コロン(:)で始めた部分(下記では:id)はパラメータidになり、あとで値を設定できます。値を設定するのは、ParamByNameメソッドです。
Openメソッドでクエリをオープン、FetchAllメソッドで結果セットを全部取得します。
あとは、RecordCountプロパティの数だけループを回します。次のレコードに移るのはNextメソッドです。現在のレコードからの値の取り出しは、FieldByNameメソッドです。
これが基本的な形でしょう。
…… query.FetchOptions.RecordCountMode := cmTotal; query.SQL.Text := 'SELECT * FROM [table] WHERE [id] = :id'; query.ParamByName('id').AsInteger := id; query.Open; query.FetchAll; for i := 1 to query.RecordCount do begin strvalue := query.FieldByName('name').AsString; intvalue := query.FieldByName('birth').AsInteger; …… query.Next; end; query.Close; ……
レコードを挿入、削除する
値を返さないクエリも、TFDQueryオブジェクトに対して実行できます。下記のように、複数のテーブルに登録する場合などは、TFDConnectionオブジェクトのStartTransactionメソッドでトランザクションを開始し、無事終われば(例外に捕捉されなければ)Commitメソッドを実行します。
例外が発生すれば、RollbackメソッドでSQL文の実行をなかったことにして、Exceptionオブジェクトからの情報をデバッグ出力に流します。
…… connection.StartTransaction; try query.ExecSQL('INSERT INTO [table1] VALUES(1, 2, 3);'); query.ExecSQL('INSERT INTO [table2] VALUES(4, 5, 6);'); query.ExecSQL('INSERT INTO [table3] VALUES(7, 8, 9);'); connection.Commit; except on E: Exception do begin connection.Rollback; OutputDebugString(PWideChar(E.ClassName+', '+E.Message)); end; end;
ざっと見てきましたが、すごい基本的な形なので、データ数の多寡などはまったく気にしていません。データ量に応じてFetchOptionsプロパティの値をコントロールしたり、AffectedRowsプロパティの値を参照してINSERT文の実行結果を確かめるなど必要でしょう。
機会があれば、データベースコンポーネントの方もやってみたいと思います。