データベースプログラミングというと、コントロールをフォーム配置してさぁ簡単、という話が多いのですが、それでは済まないケースの方が多いです。そういうときに、コードをゴリゴリ書いていくにはどうしたらいいか、私の経験から備忘録的にまとめてみました。
ちなみに開発環境は、すっかりユーザも少なくなった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文の実行結果を確かめるなど必要でしょう。
機会があれば、データベースコンポーネントの方もやってみたいと思います。