« MyBaseを試してみる。(Index、FilterでAggregateが変!?) | トップページ | MyBaseを試してみる。(PreviewHandlerで表示) »

MyBaseを試してみる。(インデックスを使って、行の追加、挿入、削除編)

レコードの順番にインデックスを使って、行の編集をするバージョンです。言うまでもなく、インデックスは検索や目的に合ったレコードの並び替えをするために、取り扱うデータを分析し、その内容によっては、新規にカテゴリを作ってそのIDを割り当てたり、個別にコードを割り当てたりして作成されるべきものです。
しかしここでは、各レコードの順番を登録するフィールド(ここではORD)を用意し、そのインデックス(ここではORD_IDX)を利用してレコードの順番を保持します。そのため、場合によっては、一度に全レコードのORDフィールドを書き換えるという禁断の処理です(^-^)プロの方々から「データベースの使い方を知ってるのか?」と怒られそうですが、「レコードの並びは、ユーザーが決める」「レコード数はせいぜい数千から1万件程度」を前提条件としています。もちろん、このような場合、リスト+仮想ListView(or StringGrid)が最適なのは、知っていますが、UIとしてDBGridを使うための処理です(^-^)

Usingindex




// COMMON

// サンプルデータを追加します。
procedure MakeSample(CDS: TClientDataSet);
var
  No: Integer;
  F: Boolean;
begin
  F := CDS.Active;
  if not F then
    CDS.Open;
  try
    // サンプルデータの追加
    No := 0;
    with CDS do
    begin
      Inc(No);
      Appendrecord([No, 'Delphi XE2 Starter ESD',10,'本',18000]);
      Inc(No);
      Appendrecord([No, 'Delphi XE2 Professional ESD',5,'本',94000]);
      Inc(No);
      Appendrecord([No, 'Delphi XE2 Enterprise ESD',10,'本',236000]);
      Inc(No);
      Appendrecord([No, 'Delphi XE2 Ultimate ESD',3,'本',356000]);
      Inc(No);
      Appendrecord([No, 'Delphi XE2 Architect ESD',1,'本',416000]);
      CheckBrowseMode;
    end;
  finally
    if not F then
      CDS.Close;
  end;
end;

// データベースの作成
procedure CreateDB(CDS: TClientDataSet);
var
  I: Integer;
begin
  // データベースの作成
  CDS.Close;
  CDS.FieldDefs.Add('ORD',ftInteger);
  CDS.FieldDefs.Add('ITEM',ftWideString,30);
  CDS.FieldDefs.Add('QTY',ftFloat);
  CDS.FieldDefs.Add('UNIT',ftWideString,4);
  CDS.FieldDefs.Add('PRICE',ftCurrency);
  CDS.IndexDefs.Add('ORD_IDX','ORD', [ixPrimary]);
  CDS.CreateDataSet;
  CDS.Close;

  for I := 0 to CDS.FieldDefs.Count - 1 do
    CDS.FieldDefs[I].CreateField(CDS);

  // 計算フィールド
  with TCurrencyField.Create(CDS) do
  begin
    FieldName := 'AMOUNT';
    Visible:=True;
    FieldKind := fkInternalCalc;
    DataSet := CDS;
  end;

  // 集合フィールド
  with TAggregateField.Create(CDS) do
  begin
    DisplayLabel := '合計';
    DisplayWidth := 10;
    DisplayFormat := '#,###,###,###';
    AlignMent := taRightJustify;
    FieldKind := fkAggregate;
    FieldName := 'TOTAL_F';
    ReadOnly := True;
    Expression := 'SUM(AMOUNT)';
    Active := True;
    DataSet := CDS;
  end;
  CDS.AggregatesActive := True;

  // インデックスの設定
  CDS.IndexName := 'ORD_IDX';

  // 表示用にフィールド幅を設定
  CDS.FieldByName('ORD').DisplayWidth := 3;
  CDS.FieldByName('ITEM').DisplayWidth := 25;
  CDS.FieldByName('QTY').DisplayWidth := 3;
  CDS.FieldByName('UNIT').DisplayWidth := 4;
  CDS.FieldByName('PRICE').DisplayWidth := 8;
  CDS.FieldByName('AMOUNT').DisplayWidth := 10;

  // 計算フィールドの設定
  CDS.OnCalcFields := Form1.CalcFields;

  // DBEditの設定
  Form1.DBEdit1.DataField := 'TOTAL_F';
  CDS.Open;

  // サンプルデータの作成
  MakeSample(CDS);
end;

// DataSetの有効/無効
procedure DataSetEnabled(DataSet: TDataSet; Value: Boolean);
begin
  if Value then
    DataSet.EnableControls
  else
    DataSet.DisableControls;
end;

// Filteredの設定
procedure cdsSetFiltered(CDS: TClientDataSet; F: Boolean);
begin
  if F and (not CDS.Filtered) then
    CDS.Filtered := F
  else if (not F) and CDS.Filtered then
    CDS.Filtered := F;
end;

// Indexの設定
procedure cdsSetIndexFieldNames(CDS: TClientDataSet; S: String);
begin
  if (S <> '') then
    CDS.IndexFieldNames := S
  else
    CDS.IndexName := 'ORD_IDX';
end;

// 追加/挿入項目用の通し番号を返します。
var
  SN: Integer = 0;

function GetSN: String;
begin
  Inc(SN);
  Result := IntToStr(SN);
end;

// UNDO

var
  SP: Integer = -1;

// 現在の状態を保存
procedure SaveUndoPoint(CDS: TClientDataSet);
begin
  // 変更があれば更新させます。
  if CDS.ChangeCount > 0 then
    CDS.MergeChangeLog;
  SP := CDS.SavePoint;
end;

// Undo
procedure Undo(CDS: TClientDataSet);
begin
  if (SP > -1) then
    CDS.SavePoint := SP;
  SP := -1;
end;

// 追加/挿入/削除

// 追加
procedure cdsAppendUsingIndex(CDS: TClientDataSet; Count: Integer = 1);
var
  I, No: Integer;
begin
  //フィルター中と他のインデックスの場合は処理させません。
  if CDS.Filtered or (CDS.IndexName <> 'ORD_IDX') then Exit;

  SaveUndoPoint(CDS);
  DataSetEnabled(CDS, False);
  try
    No := CDS.RecordCount;
    for I := 0 to Count - 1 do
    begin
      Inc(No);

      // 追加します。
      CDS.Insert; // 10000回以下だとAppendより速かったから(^-^)

      // 空白のままだとレコードが追加されません。
      // CDS.FieldByName('ITEM').AsString := '' ←これはOK
      CDS.FieldByName('ITEM').AsString := '追加'+GetSN;
      CDS.FieldByName('ORD').AsInteger := No;
    end;
    CDS.CheckBrowseMode;

    // 今回追加した空白行の先頭レコードに移動させます。
    CDS.MoveBy(1-Count);
  finally
    DataSetEnabled(CDS, True);
  end;
end;

// 挿入
procedure cdsInsertUsingIndex(CDS: TClientDataSet; Count: Integer = 1);
var
  I: Integer;
  NowRec: Integer;
begin
  //フィルター中と他のインデックスの場合は処理させません。
  if CDS.Filtered or (CDS.IndexName <> 'ORD_IDX') then Exit;

  SaveUndoPoint(CDS);
  DataSetEnabled(CDS, False);
  try
    // 挿入位置以後のレコードのORDをCount分増やして更新させます。
    NowRec := CDS.RecNo;
    CDS.RecNo := NowRec;
    CDS.Last;
    while (not CDS.BOF) and (CDS.RecNo >= NowRec) do
    begin
      CDS.Edit;
      CDS.FieldByName('ORD').AsInteger :=
        CDS.FieldByName('ORD').AsInteger + Count;
      CDS.Prior;
    end;
    CDS.CheckBrowseMode;

    // 挿入
    CDS.RecNo := NowRec;
    for I := 0 to Count -1 do
    begin
      CDS.Insert;
      CDS.FieldByName('ITEM').AsString := '挿入'+GetSN;
      CDS.FieldByName('ORD').AsInteger := NowRec;
      Inc(NowRec);
    end;
    CDS.CheckBrowseMode;

    // カーソルを挿入位置に戻します。
    CDS.MoveBy(1-Count);
    //Form1.DBGrid1.SelectedRows.Clear;
  finally
    DataSetEnabled(CDS, True);
  end;
end;

// 削除
procedure cdsDeleteUsingIndex(CDS: TClientDataSet; BMList: TBookmarkList = nil);
var
  I: Integer;
  F: Boolean;
  S: String;
  J: Integer;
begin
  if CDS.RecordCount = 0 then Exit;

  SaveUndoPoint(CDS);
  DataSetEnabled(CDS, False);

  // 取り扱うデータ量によっては、高速化のため計算部分の処理をさせない。
  CDS.AggregatesActive := False;
  CDS.AutoCalcFields := False;
  try
    // 削除対象を削除します。
    // フィルターやインデックスに関係なく選択されたものを削除します。
    if (BMList = nil) or (BMList.Count = 0) then
    begin
      CDS.Delete;
      // レコードの位置
      if CDS.Eof then
        J := CDS.RecordCount
      else
        J := CDS.RecNo;
    end
    else if (BMList.Count = 1) and (not CDS.BookmarkValid(BMList[0])) then
    begin
      // 挿入した後、そのまま削除ボタンを押した時、
      //「レコードが見つかりません」というエラーが表示される時が
      // あるため、その対応です。
      // cdsInsertUsingIndex中でForm1.DBGrid1.SelectedRows.Clear;
      // をすれば、このエラーは発生しない。
      Exit;
    end
    else
    begin
      J := 0;
      for I := BMList.Count -1 downto 0 do
      begin

        CDS.GotoBookmark(BMList[I]);
        if I = BMList.Count -1 then
          J := CDS.FieldByName('ORD').AsInteger;
        CDS.Delete;
      end;
      // レコードの位置
      J := J - BMList.Count+1;
    end;

    // 選択されたままになるのでクリアしておきます。
    BMList.Clear;

    // ORDを通し番号に修正します。

    // フィルターは解除しておきます。
    F := CDS.Filtered;
    cdsSetFiltered(CDS, False);

    // ORD以外のインデックスの場合は、一度ORDに設定します。
    // 基本的に順番用インデックスの入れ替えはしないつもりだけど・・・
    S := CDS.IndexFieldNames;
    cdsSetIndexFieldNames(CDS, '');

    // 番号を揃えます。
    // 本当はBMList[0]のレコードまでを処理するのがいいかな。
    try
      CDS.First;
      while (not CDS.Eof) do
      begin
        if CDS.FieldByName('ORD').AsInteger <> CDS.RecNo then
        begin
          CDS.Edit;
          CDS.FieldByName('ORD').AsInteger := CDS.RecNo;
        end;
        CDS.Next;
      end;
      CDS.CheckBrowseMode;

      // レコード位置を移動させます。
      if J < CDS.RecordCount then
        CDS.RecNo := J
      else
        CDS.Last;
    finally
      cdsSetFiltered(CDS, F);
      cdsSetIndexFieldNames(CDS, S);
    end;
  finally
    CDS.AutoCalcFields := True;
    CDS.AggregatesActive := True;
    DataSetEnabled(CDS, True);
  end;
end;

// TForm1

procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateDB(ClientDataSet1);
  SaveUndoPoint(ClientDataSet1);
end;

// 計算フィールド
procedure TForm1.CalcFields(DataSet: TDataSet);
begin
  DataSet.FieldByName('AMOUNT').AsCurrency :=
    DataSet.FieldByName('QTY').AsFloat *
    DataSet.FieldByName('Price').AsCurrency;
end;

// 追加
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  cdsAppendUsingIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1));
end;

// 挿入
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  cdsInsertUsingIndex(ClientDataSet1, StrToIntDef(Edit1.Text,1));
end;

// 削除
procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
  cdsDeleteUsingIndex(ClientDataSet1, DBGrid1.SelectedRows);
  ClientDataSet1.AggregatesActive := True;;
end;

// Undo
procedure TForm1.SpeedButton4Click(Sender: TObject);
begin
 if (SP > -1)  then
    Undo(ClientDataSet1)
  else if ClientDataSet1.ChangeCount > 0 then
    ClientDataSet1.UndoLastChange(True);
end;
レコードの順番を登録するフィールド(ここではORD)の番号を、10000毎にするとか、浮動小数点フィールドとかにして、挿入時には、その間の番号を設定すると、番号の打ち直しが減って、処理がより早くなるかも知れないですね。

|

« MyBaseを試してみる。(Index、FilterでAggregateが変!?) | トップページ | MyBaseを試してみる。(PreviewHandlerで表示) »