Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2015年09月21日] [更新 2015年09月21日] Delphi 一般・その他
   
Delphi 6 Personal 版で ADO によるデータベース接続
動作確認等 Windows 8.1 Pro64 + Delphi 6 Personal(UP2)
Microsoft ActiveX Data Object 6.0 Library (Version6.0)
[整理番号 - 0550]







Delphi 6 Personal 版で ADO によるデータベース接続


Delphi 6 Personal 版にはデータベースの機能が実装されていませんが,タイプライブラリの取り込みで作成した ADODB_TLB ユニットを使用すると ADO によるデータベースの接続が可能になります.データベース接続用の文字列は,当サイトの ADO 関係のサンプルと同じです.

テキストファイルも読み出し専用として扱えます.テキストファイルは,データの追加や編集は自由ですから,見かけ上,読み書き可能なデータベースとすることも可能です.
エクセルのブックもデータベースとして接続できますが,エクセルのブックやテキストファイルは,世間一般で言うところの「データベース」としての機能を持たせることはできません.データ数が少ない個人用のテータベースとしての使用に限定されるでしょう.
それでも,SQL 文でクエリを実行できるという利点はあります.



[備考 1]
本ページにも当サイトにも,データベース,クエリ, SQL 等に関する記事,説明はありません.それらの情報は参考書やネット上にいくらでもあります.


[備考 2]
データベースの表 (テーブル) は,よくエクセルシートのような表にたとえられますが,データベースには行番号という概念はありません.ある条件に一致するデータを取り出すのがデータベースの主な目的です.特定の行のデータを取り出という操作はありません.そのため,SQL (クエリ) に関する知識は必須となります.


[備考 3]
当サイトの Personal 版に関係する記事は,本ページだけです.以前は Personal 版の場合の記事や文章も書いていましたが,2015年09月21日現在,それらは全て削除しています.





01_タイプライブラリの取り込み


今回は,Microsoft ActiveX Data Object 6.0 Library (Version6.0) からタイプライブラリの取り込みを行ってテストしています.
下図の操作手順で,コンポーネントを登録しました.
マイクロソフトが提供している COM オブジェクトの名前は,他のオブジェクトと名前が重複する場合があります.その場合は,インストールしないで (どのパッケージにも登録しないで) ユニットだけを作成します.そして,uses に ADODB_TLB を追加して,実行時に TConnection と TRecordset のインスタンスを生成して使用します.

Microsoft ActiveX Data Object XX Library は,バージョンによって微妙な違いがあります.ネット上のコードを参考にする際には注意が必要です.



図1
タイプライブラリの取り込み
  • [プロジェクト] メニューで [タイプライブラリの取り込み] を選択
  • [コンポーネントラッパーの作成] をチェックして [インストール]
  • [インストール] ダイアログの [既存のパッケージに追加] タブを表示
  • dclusr.dpk に追加




02_Access 2007 以前 の .mdb と Access 2010 の .accdb


本ページで扱っている .mdb ファイルは,Microsoft Access 97 〜 2003 用の JET エンジンデータベースファイルが対象です.Microsoft Access 2007 の拡張子は同じ .mdb ですが,これは本ページのコードでは操作できません.
また,Microsoft Access 2010 の,拡張子が .accdb の JET エンジンデータベースファイルも,本ページのコードでは操作できません.Access 2007 の .mdb ファイル,.acdb ファイルを扱う方法は,当サイトの関連記事を参考にしてください.

Access 2010, Access 2007 の JET エンジンデータベースファイルは,Access 2000 用の JET エンジンデータベースファイルに変換できます.Microsoft Access のヘルプで確認してください.






03_特定の項目 ( Field ) の値だけを取得


ADO で JET エンジンデータベースに接続する例です.
テストには,以下のサイトから address03.lzh をダウンロードし,その中の「住所録Data03.mdb」を使用しています.このデータベースの「ta住所録」という表にある [名前] の項目の値だけを取得して表示します.

拡張子が .mdb のファイルは,Microsoft Access で作成したデータベースのファイルです.



図2
設計時画面
  • [ActiveX] タブから TConnection をフォームに配置
  • 結果の表示用に TListBox コンポーネントも配置
図3
実行結果
  • [名前] の項目のデータのみ取得

リスト1
指定項目のデータのみ取得する例 (項目へのアクセスの参考用)
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleServer, ADODB_TLB, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Connection1: TConnection;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  LRecordSet  : ADODB_TLB.Recordset;
  LRcdAffectd : OleVariant;
  LStrConnect : WideString;
  LDBFile     : WideString;
  LUserID     : WideString;
  LPassword   : WideString;
  LStrSQL     : WideString;
  LDataText   : WideString;
begin
  ListBox1.Items.Clear;


  //データベースのファイル名
  LDBFile   := ExtractFilePath(Application.ExeName) + '住所録Data03.mdb';
  LUserID   := '';
  LPassword := '';

  //接続文字列を作成してデータベース接続
  LStrConnect := 'Provider=Microsoft.Jet.OLEDB.4.0'
               + ';Data Source=' + LDBFile;
  Connection1.Open(LStrConnect, LUserID, LPassword, Integer(adConnectUnspecified));

  try
    //描画の更新を停止
    ListBox1.Items.BeginUpdate;

    //SQL文を指定して実行
    LStrSQL    := 'SELECT * FROM ta住所録';
    LRecordSet := Connection1.Execute(LStrSQL, LRcdAffectd, 0);

    //指定の項目の値だけ取得して表示
    while not LRecordSet.EOF do begin
      LDataText := LRecordSet.Fields['名前'].Value;
      ListBox1.Items.Add(LDataText);
      LRecordSet.MoveNext;

      Application.ProcessMessages;
      Sleep(1);
    end;
  finally
    //描画の更新を再開
    ListBox1.Items.EndUpdate;
    //レコードセットとデータベースを閉じる
    LRecordSet.Close;
    Connection1.Close;
  end;
end;

end.


上のテストで使用したデータベースのファイルには,複数の表があります.
以下は,これを取得した結果の図と,取得に使用したコードです.



図4
表のリストを取得
  • JET エンジンデータベースのシステム関係の表もリストされている

リスト2
データベースの表のリストを取得して表示
procedure TForm1.Button1Click(Sender: TObject);
var
  LRecordSet  : Recordset;
  LStrConnect : WideString;
  LDBFile     : WideString;
  LUserID     : WideString;
  LPassword   : WideString;
begin
  ListBox1.Items.Clear;

  //データベースのファイル名
  LDBFile   := ExtractFilePath(Application.ExeName) + '住所録Data03.mdb';
  LUserID   := '';
  LPassword := '';

  //接続文字列を作成してデータベース接続
  LStrConnect := 'Provider=Microsoft.Jet.OLEDB.4.0'
               + ';Data Source=' + LDBFile;
  Connection1.Open(LStrConnect, LUserID, LPassword, Integer(adConnectUnspecified));

  try
    //描画の更新を停止
    ListBox1.Items.BeginUpdate;

    //OpenSchemaでファイル情報のレコードセットを取得
    LRecordSet := Connection1.OpenSchema(adSchemaTables);
    while not LRecordSet.EOF do begin
      ListBox1.Items.Add(LRecordSet.Fields['TABLE_NAME'].Value);
      LRecordSet.MoveNext;
    end;
  finally
    //描画の更新を再開
    ListBox1.Items.EndUpdate;
    //レコードセットとデータベースを閉じる
    LRecordSet.Close;
    Connection1.Close;
  end;
end;




04_読み出し専用と更新可能なクエリ


ADO で JET エンジンデータベースに接続する例です.
これは,Microsoft Access で作成した,拡張子が .mdb のファイルをデータベースとして利用することを意味しています.

上のサンプルでは,クエリの実行に TConnection.Execute メソッドを使用し,そのメソッドの戻り値であるデータセットから項目のデータを取得しています.この TConnection.Execute によるクエリの実行結果のレコードセットは,読み出し専用です.または更新クエリの場合に使用します.

本サンプルでは,クエリの実行に TRecordset コンポーネントの Open メソッドを使用しています.
TRecordset.Open では,実行結果のレコードセットに対して細かい設定ができます.以下のサンプルでは読み出し専用と,読み書き可能なレコードセットの両方をテストしています.

レコードセットからのデータ取得には,TRecordSet.GetRows メソッドを使用しています.GetRows メソッドも,取り出すレコード数等が指定できます.
テストには,以下のリンクのサンプルに添付している .mdb ファイルを使用しました.

TConnection.Open メソッドの第 1 引数の接続文字列を変更すれば,他のデータベースや,エクセルのブックにも接続できます.



図5
設計時画面
  • TConnection, TRecordset コンポーネントを配置 ([ActiveX] タブ)
  • データ表示用に TStringGrid を配置
図6
Button1 クリック
  • 全てのデータを取得して表示
図7
Button2 クリック
  • [食品群] 項目の値の先頭が '野菜' という文字である場合,その値を '野菜類等' に変更
図8
もう 1 度 Button2 をクリック
  • [食品群] 項目の値の先頭が '野菜' という文字である場合,その値を '野菜' に変更

リスト3
TRecordset.ooen によるクエリの実行と,TRecordSet.GetRows によるデータの取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleServer, ADODB_TLB, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    Connection1: TConnection;
    Recordset1: TRecordset;
    Button1: TButton;
    Button2: TButton;
    StringGrid1: TStringGrid;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FStrConnect : WideString;
    FUserID     : WideString;
    FPassword   : WideString;
    procedure DisplayToStringGrid(AGrid: TStringGrid; ARecordSet: TRecordset);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LDBFile : WideString;
begin
  //データベースのファイル名
  LDBFile   := ExtractFilePath(Application.ExeName) + 'ForExport.mdb';
  FUserID   := '';
  FPassword := '';

  //接続文字列を作成
  FStrConnect := 'Provider=Microsoft.Jet.OLEDB.4.0'
               + ';Data Source=' + LDBFile;
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Connection1.State = adStateOpen then begin
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
    Connection1.Close;
  end;
end;

//=============================================================================
//  読み出し専用クエリの実行例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrSQL : OleVariant;
begin
  //データベース接続
  if Connection1.State = adStateClosed then begin
    Connection1.Open(FStrConnect, FUserID, FPassword, Integer(adConnectUnspecified));
  end else begin
    //レコードセットを閉じる
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
  end;

  //SQL文を指定してレコードセットを取得
  LStrSQL := 'SELECT 番号,食品群,食品名,キロカロリ FROM 食品カロリ表';
  Recordset1.Open(LStrSQL,
                  Connection1.DefaultInterface,
                  adOpenForwardOnly,
                  adLockReadOnly,
                  adCmdText);

  //取得したデータの表示
  DisplayToStringGrid(StringGrid1, RecordSet1);
end;

//=============================================================================
//  更新可能なクエリの実行例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrSQL    : OleVariant;
  LTempValue : OleVariant;
begin
  //データベース接続
  if Connection1.State = adStateClosed then begin
    Connection1.Open(FStrConnect, FUserID, FPassword, Integer(adConnectUnspecified));
  end else begin
    //レコードセットを閉じる
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
  end;

  //SQL文を指定してレコードセットを取得
  //項目の[食品群]の先頭に'野菜'があるデータを取り出す
  LStrSQL := 'SELECT * FROM 食品カロリ表 WHERE 食品群 LIKE "野菜%"';
  Recordset1.Open(LStrSQL,
                  Connection1.DefaultInterface,
                  adOpenKeyset,
                  adLockOptimistic,
                  adCmdText);

  //項目のデータを変更してデータベースも更新
  if Recordset1.RecordCount > 0 then begin
    Recordset1.MoveFirst;
    while not Recordset1.EOF do begin
      LTempValue := Recordset1.Fields['食品群'].Value;
      if LTempValue = '野菜類等' then begin
        Recordset1.Fields['食品群'].Value := '野菜';
      end else begin
        Recordset1.Fields['食品群'].Value := '野菜類等';
      end;
      //TRecordset.MoveNextで自動的にUpdateするので実際には不要
      //Recordset1.Update;
      RecordSet1.MoveNext;
    end;
  end;

  //取得したデータの表示
  DisplayToStringGrid(StringGrid1, Recordset1);
end;

//=============================================================================
//  引数のARecordSetのデータをTStringGridに表示するメソッド
//=============================================================================
procedure TForm1.DisplayToStringGrid(AGrid: TStringGrid; ARecordSet: TRecordset);
var
  LDataArray : OleVariant;
  LColsLow   : Integer;
  LColsHigh  : Integer;
  LRowsLow   : Integer;
  LRowsHigh  : Integer;
  LVarValue  : OleVariant;
  iCol       : Integer;
  iRow       : Integer;
  i          : Integer;
  j          : Integer;
begin
  //配列の初期化
  LDataArray := Unassigned;

  //描画の更新を停止
  AGrid.Rows[0].BeginUpdate;

  //データを見やすくするために列幅の変更を可能にしておく
  AGrid.Options := AGrid.Options + [goColSizing];
  //TStringGridの初期化
  AGrid.FixedCols := 1;
  AGrid.FixedRows := 1;
  AGrid.ColCount  := AGrid.FixedCols + 1;
  AGrid.RowCount  := AGrid.FixedRows + 1;

  for i := 0 to AGrid.RowCount - 1 do begin
    AGrid.Rows[i].Clear;
  end;


  try
    if Recordset1.State = adStateClosed then exit;

    //データベースの項目名だけを取得してTStringGridの固定行に表示
    AGrid.ColCount := ARecordSet.Fields.Count + 1;
    for i := 0 to ARecordSet.Fields.Count - 1 do begin
      AGrid.Cells[i + AGrid.FixedCols, 0] := ARecordSet.Fields[i].Name;
    end;
    if ARecordSet.RecordCount = 0 then exit;

    //先頭のレコードに移動しておく
    Recordset1.MoveFirst;

    //クエリーの結果のデータを取得
    //adGetRowsRestは全てのデータ
    LDataArray := ARecordSet.GetRows(Integer(adGetRowsRest));

    //取得した2次元配列の要素番号の下限と上限値を取得
    LColsLow  := VarArrayLowBound(LDataArray, 1);
    LColsHigh := VarArrayHighBound(LDataArray, 1);
    LRowsLow  := VarArrayLowBound(LDataArray, 2);
    LRowsHigh := VarArrayHighBound(LDataArray, 2);

    //TStringGridの行数を設定
    AGrid.RowCount := (LRowsHigh - LRowsLow) + 1 + AGrid.FixedRows;

    //セルに値を代入していく
    for i := LRowsLow to LRowsHigh do begin
      for j := LColsLow to LColsHigh do begin
        iCol := j - LColsLow + AGrid.FixedCols;
        iRow := i - LRowsLow + AGrid.FixedRows;
        LVarValue := LDataArray[j, i];
        if VarType(LVarValue) = VarNull then begin
          AGrid.Cells[iCol, iRow] := '';
        end else begin
          AGrid.Cells[iCol, iRow] := VarToStr(LVarValue);
        end;
      end;
    end;
  finally
    //描画の更新を再開
    AGrid.Rows[0].EndUpdate;
  end;
end;

end.




05_TConnection を使用しないデータベース接続


TConnection を使用すればトランザクション処理もできて便利ですが,TConnection を使用しなくてもデータベースに接続できます.
TRecordset.Open メソッドの第 2 引数に接続文字列を指定します.



リスト4
TConnectiion を使用しないデータベースの接続例
DisplayToStringGrid は上のサンプルで作成したメソッド
//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LDBFile : WideString;
begin
  //データベースのファイル名
  LDBFile   := ExtractFilePath(Application.ExeName) + 'ForExport.mdb';
  FUserID   := '';
  FPassword := '';

  //接続文字列を作成
  FStrConnect := 'Provider=Microsoft.Jet.OLEDB.4.0'
               + ';Data Source=' + LDBFile
               + ';User ID=' + FUserID
               + ';Password=' + FPassword;
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Recordset1.State = adStateOpen then begin
    Recordset1.Close;
  end;
end;

//=============================================================================
//  読み出し専用クエリの実行例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrSQL : OleVariant;
begin
  //レコードセットを閉じる
  if Recordset1.State = adStateOpen then begin
    RecordSet1.Close;
  end;

  //SQL文を指定してレコードセットを取得
  LStrSQL := 'SELECT 番号,食品群,食品名,キロカロリ FROM 食品カロリ表';
  Recordset1.Open(LStrSQL, FStrConnect, adOpenForwardOnly, adLockReadOnly, adCmdText);

  //取得したデータの表示
  DisplayToStringGrid(StringGrid1, RecordSet1);
end;




06_エクセルのブックにデータベース接続


ADO でエクセルのブックをデータベースとして扱う例です.
エクセルの場合,ブックのファイル名 (フルパス) がデータベース名です.シートがデータベースの表に相当します.シートを表名として認識させるには,[Sheet2$] のように,シート名の末尾に $ を付け,全体を [] で括ります.
JET エンジンデータベースへの接続との違いは,接続文字列です.
テストには,以下のリンクのサンプルに添付しているブックを使用しました.

ADO によるエクセルへの接続の場合,データの編集はできますが,レコードの削除はできません.



図9
設計時画面
  • TConnection, TRecordset コンポーネントを配置 ([ActiveX] タブ)
  • データ表示用に TStringGrid を配置
図10
実行結果
  • [食品群] の先頭に '野菜' という文字が含まれているデータだけを取得
  • [キロカロリ] の値でソートして表示

リスト5
エクセルのブックをデータベースとして扱う例
DisplayToStringGrid は上のサンプルで作成したメソッド
//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LDBFile : WideString;
begin
  //データベースのファイル名
  LDBFile   := ExtractFilePath(Application.ExeName) + 'ADOTestBook.xls';
  FUserID   := '';
  FPassword := '';

  //接続文字列を作成
  FStrConnect := 'Provider=Microsoft.Jet.OLEDB.4.0'
               + ';Data Source=' + LDBFile
               + ';Extended Properties="Excel 8.0"';
end;

//=============================================================================
//  フォームを閉じる時の処理
//=============================================================================
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Connection1.State = adStateOpen then begin
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
    Connection1.Close;
  end;
end;

//=============================================================================
//  更新可能なクエリの実行例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrSQL : OleVariant;
  LText   : String;
begin
  //データベース接続
  if Connection1.State = adStateClosed then begin
    try
      Connection1.Open(FStrConnect, FUserID, FPassword, Integer(adConnectUnspecified));
    except
      LText := '他のユーザが編集中です' + sLineBreak + '開くことは出来ません';
      MessageBox(Handle, PChar(LText), '情報', MB_ICONINFORMATION);
      exit;
    end;
  end else begin
    //レコードセットを閉じる
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
  end;


  //SQL文を指定してレコードセットを取得
  //項目の[食品群]の先頭に'野菜'があるデータを取り出す
  //'SELECT * FROM [Sheet2$]'とすれば全てのデータの取得
  LStrSQL := 'SELECT * FROM [Sheet2$] WHERE 食品群 LIKE "野菜%" ORDER BY キロカロリ';
  Recordset1.Open(LStrSQL,
                  Connection1.DefaultInterface,
                  adOpenKeyset,
                  adLockOptimistic,
                  adCmdText);

  //取得したデータの表示
  DisplayToStringGrid(StringGrid1, RecordSet1);
end;


エクセルのブックをデータベースとして使用する場合,編集可能なモードで開くと,他のユーザからは開けなくなります.以下のように,TConnection.Mode := adModeRead; にして読み出し専用にすれば,他のユーザも開くことができます.TConnection.Mode の値を adModeRead にすると,TRecordset.Open の引数の値に関係なく読み出し専用となります.
この動作仕様は,エクセルのブックの「共有設定」には関係ありません.



  if Connection1.State = adStateClosed then begin
    Connection1.Mode := adModeRead;
    Connection1.Open(FStrConnect, FUserID, FPassword, Integer(adConnectUnspecified));
  end else begin
    //レコードセットを閉じる
    if Recordset1.State = adStateOpen then begin
      RecordSet1.Close;
    end;
  end;




07_TStringGrid の行番号とカレントレコード位置


TStringGrid の行番号に相当するレコード位置や,カレントレコード位置に相当する TStringGrid の行番号は,例えば TRecordset.AbsolutePosition プロパティを利用して,以下のコードで取得,設定できます.



図11
実行例
  • TStringGrid のアクティブな行にカレントレコード位置を移動
  • そのレコードの項目の値を取得して表示

リスト6
カレントレコード位置の変更と取得
//=============================================================================
//  指定レコード位置に相当するTStringGridの行番号を取得する
//  TRecordset.AbsolutePositionプロパティによるレコード位置の移動
//  TRecordset.AbsolutePositionの最小値は1
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Recordset1.State = adStateClosed then exit;
  if Recordset1.RecordCount = 0 then exit;

  //テスト用にカレントレコードを移動
  Recordset1.AbsolutePosition := 5;

  StringGrid1.Row := Recordset1.AbsolutePosition + StringGrid1.FixedRows - 1;
end;

//=============================================================================
//  TStringGridの行番号に相当する位置にカレントレコードを変更する
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LRecPos : Integer;
begin
  if Recordset1.State = adStateClosed then exit;
  if Recordset1.RecordCount = 0 then exit;

  LRecPos := StringGrid1.Row + StringGrid1.FixedRows - 1;
  if Recordset1.RecordCount >= LRecPos then begin
    Recordset1.AbsolutePosition := LRecPos;
    //確認用の表示
    //項目番号でなく項目名でFields['食品名']でも可
    ShowMessage(Recordset1.Fields[2].Value);
  end;
end;