在运行时向查询添加一个计算字段

13

我正在使用Delphi查询获取数据,并希望在运行查询之前向查询中添加一个计算字段。该计算字段使用代码中的值以及查询中的值,因此我无法仅在SQL中对其进行计算。

我知道我可以附加一个OnCalcFields事件来实际进行计算,但问题是在添加计算字段后,查询中没有其他字段......

我进行了一些调查,发现所有字段定义都已创建,但实际字段仅创建了

if DefaultFields then
    CreateFields

指定了默认字段

procedure TDataSet.DoInternalOpen;
begin
    FDefaultFields := FieldCount = 0;
    ...
end;

这意味着如果您添加字段,则只会获取您添加的字段。

我想在查询中获取所有字段,以及我额外添加的字段。

这是否可能,还是我必须也添加我使用的所有字段?


我不明白为什么你不能在SQL中使用代码中的值......我经常构建动态SQL语句,其中使用代码中的值在SQL中。 - Leslie
查看我的答案,了解一种新的在Delphi Berlin中执行此操作的方法。 - r_j
你尝试过准备查询吗?这可能会创建字段定义(尽管还没有字段对象)。 - Arioch 'The
5个回答

16

你完全可以先在代码中创建所有字段,然后添加计算字段。

你可以使用一个“hack类型”来使用受保护的CreateFields方法:

type
  THackQuery = class(TADOQuery)
  end;
[...]
  MyQuery.FieldDefs.Update;
  THackQuery(MyQuery).CreateFields;

或者从CreateFields中借用一些代码:

  MyQuery.FieldDefs.Update;
  // create all defaults fields
  for I := 0 to MyQuery.FieldDefList.Count - 1 do
    with MyQuery.FieldDefList[I] do
      if (DataType <> ftUnknown) and not (DataType in ObjectFieldTypes) and
        not ((faHiddenCol in Attributes) and not MyQuery.FIeldDefs.HiddenFields) then
        CreateField(Self, nil, MyQuery.FieldDefList.Strings[I]);

然后创建你的计算字段:

  MyQueryMyField := TStringField.Create(MyQuery);
  with MyQueryMyField do
  begin
    Name := 'MyQueryMyField';
    FieldKind := fkCalculated;
    FieldName := 'MyField';
    Size := 10;
    DataSet := MyQuery;
  end;

如果你正在子类化TQuery或另一种TDataset类型,你不需要“hack”来访问受保护的字段。当你(a)在代码中添加一个计算字段,并且(b)整个东西都是在代码中,比如你正在完全用代码编写自定义查询或组件时,这个答案是正确的方法。 - Warren P

4

除了计算字段,您需要添加所有字段。

一旦添加了一个字段,您必须添加所有想要在数据集中的字段。

Delphi将其称为持久字段与动态字段。所有字段都是持久或动态的。不幸的是,你不能混合使用两者。

另一个需要注意的事情是从文档中得知:

持久字段组件列表存储在您的应用程序中,即使底层数据集的结构发生变化,它们也不会改变。

因此,如果您稍后向表中添加其他字段,则需要将新字段添加到组件中。删除字段也是同样的道理。

如果您真的不想使用持久字段,则有另一种解决方案。在任何应该显示计算字段的网格或控件上,您可以自定义绘制它。例如,许多网格控件都有OnCustomDraw事件。您可以在那里进行计算。


4

Delphi现在有一个选项可以结合自动生成的字段和计算字段:Data.DB.TFieldOptions.AutoCreateMode,这是一个TFieldsAutoCreationMode类型的枚举。这样你就可以在运行时添加计算字段了。Francois在他的回答中讲解了如何在运行时添加字段。

TFieldsAutoCreationMode的不同模式:

  • acExclusive

    当没有持久字段时,会创建自动字段。这是默认模式。

  • acCombineComputed

    当数据集没有持久字段或只有计算的持久字段时,将创建自动字段。这是一种方便的方式,在设计时创建持久性计算字段并让数据集创建自动数据字段。

  • acCombineAlways

    当没有持久字段时,将为数据库字段创建自动字段。


看起来这允许组合设计时计算字段和运行时数据字段。你测试/成功了你在答案中所声称的吗? - Sertac Akyuz
这不是数学作业,你不需要证明一切。在运行时创建字段的代码已经在另一个答案中描述了。 - r_j
好的,文档并没有说你所声称的内容。 - Sertac Akyuz
1
在C++ Builder XE8中测试过,一切正常。这比Francois的解决方案更简单,因为您不必向项目添加额外的代码。 - truthseeker

2
如果您希望在运行时知道如何计算字段的名称,可以使用类似以下代码:
var
 initing:boolean;

procedure TSampleForm.dsSampleAfterOpen(
  DataSet: TDataSet);
var
 i:integer;
 dmp:tfield;
begin
if not initing then
 try
  initing:=true;
  dataset.active:=false;
  dataset.FieldDefs.Update;
  for i:=0 to dataset.FieldDefs.Count-1 do
  begin
   dmp:=DataSet.FieldDefs.Items[i].FieldClass.Create(self);
   dmp.FieldName:=DataSet.FieldDefs.Items[i].DisplayName;
   dmp.DataSet:=dataset;
   if (dmp.fieldname='txtState') or (dmp.FieldName='txtOldState') then
   begin
     dmp.Calculated:=true;
     dmp.DisplayWidth:=255;
     dmp.size:=255;
   end;
  end;
  dataset.active:=true;
 finally
  initing:=false;
 end;
end;

procedure TSampleForm.dsSampleAfterClose(
  DataSet: TDataSet);
var
 i:integer;
 dmp:TField;
begin
if not initing then
begin
 for i:=DataSet.FieldCount-1 downto 0 do
 begin
  dmp:=pointer(DataSet.Fields.Fields[i]);
  DataSet.Fields.Fields[i].DataSet:=nil;
  freeandnil(dmp);
 end;
 DataSet.FieldDefs.Clear;
end;
end;

procedure TSampleForm.dsSampleCalcFields(
  DataSet: TDataSet);
var
 tmpdurum,tmpOldDurum:integer;
begin
  if not initing then
    begin
      tmpDurum := dataset.FieldByName( 'state' ).AsInteger;
      tmpOldDurum:= dataset.FieldByName( 'oldstate' ).AsInteger;
      dataset.FieldByName( 'txtState' ).AsString := State2Text(tmpDurum);
      dataset.FieldByName( 'txtOldState' ).AsString := State2Text(tmpOldDurum);
    end;
end;

procedure TSampleForm.btnOpenClick(Sender: TObject);
begin
 if dsSample.Active then
   dsSample.Close;
 dsSample.SQL.text:='select id,state,oldstate,"" as txtState,"" as txtOldState from states where active=1';
 dsSample.Open;
end;

0
    //assume we have an fdquery named  FDQuery1
    //we write this code in AfterOpen section

注意: 这种方法存在一个问题 计算字段被放置在最后一个字段的位置 为了解决这个问题,只需要在查询中添加一个额外的字段,例如将值零视为最终字段 像这样:

select * , 0 as myfield from mytable


procedure Tform1.FDQuery1AfterOpen(DataSet: TDataSet);
var
 i : Integer;
 aField : TField;
begin
    FDQuery1.Close;
    for i := 0 to FDQuery1.FieldDefs.Count - 1 do begin
     aField := FDQuery1.FieldDefs [ i ].CreateField ( fdQuery1 );
    end;
    aField.DataSet := fdQuery1;
    with aField do
    begin
      Name := 'myField';
      FieldKind := fkCalculated;
      FieldName := 'myField';
      Size := 10;
      DataSet := FDQuery1;
    end;
    fdQuery1.FieldDefs.Update;
    fdQuery1.AfterOpen:=nil;
    fdQuery1.Open;
    ShowMessage(FDQuery1.FieldByname('myField').AsString);
end;

现在你可以为myField编写onClacField方法


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接