Добавление вычисляемого поля в запрос во время выполнения

Я получаю данные, используя запрос в Delphi, и хотел бы добавить вычисляемое поле к запросу до его запуска. В вычисляемом поле используются значения как в коде, так и в запросе, поэтому я не могу просто вычислить его в SQL.

Я знаю, что могу прикрепить OnCalcFields Событие для фактического расчета, но проблема в том, что после добавления вычисляемого поля в запросе нет других полей...

Я немного покопался и обнаружил, что все определения полей созданы, но фактические поля созданы только

if DefaultFields then
    CreateFields

По умолчанию поля указаны

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

Что означало бы, что если вы добавите поля, вы получите только те поля, которые вы добавили.

Я хотел бы, чтобы все поля в запросе, как хорошо, как те, которые я добавляю.

Это возможно или мне нужно добавить все поля, которые я использую?

5 ответов

Решение

Delphi теперь имеет возможность комбинировать автоматически сгенерированные поля и вычисляемые поля: Data.DB.TFieldOptions.AutoCreateMode перечисление типа TFieldsAutoCreationMode. Таким образом, вы можете добавить свои вычисляемые поля во время выполнения. Франсуа написал в своем ответе, как добавить поле во время выполнения.

Различные режимы TFieldsAutoCreationMode:

  • acExclusive

    Если нет постоянных полей вообще, автоматически создаются поля. Это режим "по умолчанию".

  • acCombineComputed

    Автоматические поля создаются, когда в наборе данных нет постоянных полей или имеются только вычисленные постоянные поля. Это удобный способ создания постоянных вычисляемых полей во время разработки и возможность набора данных автоматически создавать поля данных.

  • acCombineAlways

    Автоматические поля для полей базы данных будут созданы, когда нет постоянных полей.

Ничто не мешает вам сначала создать все поля в вашем коде,
затем добавьте свои рассчитанные поля.

Вы можете использовать "взломанный тип", чтобы использовать защищенные 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;

Вам необходимо добавить все поля в дополнение к вычисляемому полю.

Как только вы добавите поле, вы должны добавить все поля, которые вы хотите в наборе данных.

Delphi называет это постоянными полями по сравнению с динамическими полями. Все поля являются постоянными или динамическими. К сожалению, вы не можете иметь смесь обоих.

Еще одна вещь, которую стоит отметить, из документации

Списки компонентов постоянных полей хранятся в вашем приложении и не изменяются даже при изменении структуры базы данных, лежащей в основе набора данных.

Поэтому будьте осторожны, если позже вы добавите дополнительные поля в таблицу, вам нужно будет добавить новые поля в компонент. То же самое с удалением полей.

Если вы действительно не хотите постоянных полей, есть другое решение. На любой сетке или элементе управления, которые должны показывать вычисляемое поле, вы можете сделать это на заказ Например, многие элементы управления сеткой имеют событие OnCustomDraw. Вы можете сделать свой расчет там.

Если вы знаете, какие имена полей должны быть вычислены во время выполнения, вы можете использовать что-то подобное.

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;
          //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;

теперь вы можете написать метод onClacField для myField

Другие вопросы по тегам