0

I have a TClientDataSet in Delphi 7 and I'd like to apply a filter which I type into a simple TEdit, so it looks like this:

CDS.Filter:=Edit1.Text;
CDS.Filtered:=True;

Now I looked at the Helpfile for filtering records and according to it I should be able to Filter DateTime-Fields as well. But whenever I write something like this into my Edit:

DAY(EDATUM)=17  

and apply the filter I get a "Type Mismatch in Expression"-Exception.

I have tried numerous different formats of the example above.

DATE(DAY(EDATUM))=DATE(DAY(17))     //Doesn't work
DAY(EDATUM)='17'                    //Doesn't work
DAY(EDATUM)=DAY(17)                 //Doesn't work   
DAY(EDATUM)=DAY(DATE('17.09.2016'))
...
...

the only one that works is

EDATUM='17.09.2016'                 //Works

But I want to filter on Days months and years seperately and not have them together in a string.

Nothing I found online elsewhere worked either.

Any Idea what I'm doing wrong?

Edatum is a TimeStamp in a Firebird 1.5 Database.

Ken White
  • 123,280
  • 14
  • 225
  • 444
Viktor Pagels
  • 271
  • 4
  • 15
  • Why don't you add three hidden columns to your query : ...... extract(day of edatum) as edatum_day, extract(month of edatum) as edatum_month, extract(year of edatum) as edatum_year, so you can now filter those fields very easily ?. – Marc Guillot Feb 10 '17 at 19:53
  • It's not necessary to repeat the tag information in the title. – Ken White Feb 11 '17 at 02:21

1 Answers1

7

If you want to use a Filter expression instead of an OnFilterRecord handler, it is worthwhile taking a look at the source of the TExprParser class, which is what TClientDataSet uses for textual filters. It is contained in the DBCommon.Pas unit file in your Delphi source. The D7 TExprParser supports the following functions:

function TExprParser.TokenSymbolIsFunc(const S: string) : Boolean;
begin
  Result := (CompareText(S, 'UPPER') = 0) or
            (CompareText(S, 'LOWER') = 0) or
            [...]
            (CompareText(S, 'YEAR') = 0) or
            (CompareText(S, 'MONTH') = 0) or
            (CompareText(S, 'DAY') = 0) or
            [...]
end;

Btw, it is worthwhile looking through the rest of TExprParser's source because it reveals things like support for the IN construct found in SQL.

On my (UK) system, dates display in a DBGrid as dd/mm/yyyy. Given that, all of the filter expressions shown below work in D7 without producing an exception and return the expected results:

procedure TForm1.Button1Click(Sender: TObject);
begin

  //  ADate field of CDS is initialised by
  //  CDS1.FieldByName('ADate').AsDateTime := Now - random(365);

  edFilter.Text := 'ADate = ''10/2/2017''';  //  works, date format = dd/mm/yyyy
  edFilter.Text := 'Month(ADate) = 2';       //  works
  edFilter.Text := 'Year(ADate) = 2017';     //  works
  edFilter.Text := '(Day(ADate) = 10) and (Year(ADate) = 2017)';        //  works

  CDS1.Filtered := False;
  CDS1.Filter := edFilter.Text;
  CDS1.Filtered := True;
end;

If you don't get similar results, I'd suggest you start by looking at your regional settings and how dates are displayed in a TDBGrid.

Filter expressions are not particularly efficient compared to the alternative method of filtering, namely to use the OnFilterRecord event.

In the event handler, you can use e.g. DecodeDateTime to decode it into its Year, Month, Day, etc components and apply whatever tests you like to their values. Then set Accept to True or False.

Update I gather from your comment to an answer here Delphi: check if Record of DataSet is visible or filtered that the problem you had with this was that the date functions supported by TExprParser.TokenSymbolIsFunc() are not in your user's language.

You can use the code below to translate the date function names in the filter expression. See the embedded comments for explanation of how it works

type
  TForm1 = class(TForm)
    [...]
  public
    NameLookUp : TStringList;
    [...]
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  NameLookUp := TStringList.Create;
  //  Assume Y, M & C are the local-language names
  NameLookUp.Add('Y=Year');
  NameLookUp.Add('M=Month');
  NameLookUp.Add('D=Day');
  [...]
end;

procedure TForm1.Log(const Title, Msg : String);
begin
  Memo1.Lines.Add(Title + ' : ' + Msg);
end;

function TForm1.TranslateExpression(const Input : String; ADataSet : TDataSet) : String;
var
  SS : TStringStream;
  TokenText : String;
  LookUpText : String;
  Parser : TParser;
  CH : Char;
begin
  SS := TStringStream.Create(Input);
  Parser := TParser.Create(SS);
  Result := '';
  try
    CH := Parser.Token;
    //  following translates Input by parsing it using TParser from Classes.Pas
    while Parser.Token <> #0 do begin
      TokenText :=  Parser.TokenString;
      case CH of
        toSymbol : begin
          //  The following will translate TokenText for symbols
          //  but only if TokenText is not a FieldName of ADataSet
          if ADataSet.FindField(TokenText) = Nil then begin
            LookUpText := NameLookUp.Values[TokenText];
            if LookUpText <> '' then
              Result := Result + LookUpText
            else
              Result := Result + TokenText;
          end
          else
            Result := Result + TokenText;
        end;
        toString :
          //  SingleQuotes surrounding TokenText in Input and ones embedded in it
          //  will have been stripped, so reinstate the surrounding ones and
          //  double-up the embedded ones
        Result := Result + '''' + StringReplace(TokenText, '''', '''''', [rfReplaceAll]) + '''';
        else
          Result := Result + TokenText;
      end; { case }
      if Result <> '' then
        Result := Result + ' ';
      CH := Parser.NextToken;
    end;
  finally
    Parser.Free;
    SS.Free;
  end;
  Log('TransResult', Result);
end;

procedure TForm1.btnSetFilterExprClick(Sender: TObject);
begin
  //  Following tested with e.g edFilter.Text =
  //  LastName = 'aaa' and Y(BirthDate)  = 2000
  UpdateFilter2;
end;

procedure TForm1.UpdateFilter2;
var
  T1 : Integer;
begin
  CDS1.OnFilterRecord := Nil;
  T1 := GetTickCount;
  CDS1.DisableControls;
  try
    CDS1.Filtered := False;
    CDS1.Filter := TranslateExpression(edFilter.Text, CDS1);
    if CDS1.Filter <> '' then begin
      CDS1.Filtered := True;
    end;
    Log('Filter update time', IntToStr(GetTickCount - T1) + 'ms');
  finally
    CDS1.EnableControls;
  end;
end;
Community
  • 1
  • 1
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • I really hoped that I could avoid the OnFilterRecord method for this one – Viktor Pagels Feb 10 '17 at 16:24
  • Why would you worry about that? It avoids the overhead of the CDS constructing an expression parser and then applying it, inefficiently, to each row. – MartynA Feb 10 '17 at 17:05
  • for transportability. – Viktor Pagels Feb 10 '17 at 17:10
  • Those sophisticated filters as seen on embarcadero docs might not work on delphi 7, try one of those and check if they work: http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/DBClient_TClientDataSet_Filter.html – Vancalar Feb 10 '17 at 17:12
  • What is "transportable" about something which depends on dealing with string representations of dates and expressions? – MartynA Feb 10 '17 at 17:12
  • "Those sophisticated filters" Well, I've never come across a filter that couldn't be implemented in D7 (or any other Delphi version) in OnFilterRecord - you never have to worry about whether the Delphi filter expression-parser, which **is** limited, can do what you want. – MartynA Feb 10 '17 at 17:14
  • MartynA: Agree :) – Vancalar Feb 10 '17 at 17:17
  • And from the problem as presented in your q, what is the point in wasting time and effort using trial and error to see whether you can express what you want in terms that the parser can handle, and **then** put yourself at the mercy of its opaque application of the filter? With OnFilterRecord, you only have to rely on your own ability to code, and it's trivially easy to use the debugger to investigate any time you don't get the results you expect. – MartynA Feb 10 '17 at 17:19
  • I'd like to make a function that goes through each column of a grid and creates the necessary filter expression based on the FieldType and an entered value so I type let's say 22 into my edit, now every row that has no 22 in it anywhere should be filtered out. I can't be sure that a Dataset in a different unit will have OnFilterRecord not already assigned and catching all those things and assigning events seems more of a hassle than necessary. Writing a function that returns a string seems like the better more general option for me. – Viktor Pagels Feb 10 '17 at 17:24
  • " I can't be sure that a Dataset in a different unit ..." Well, it may equally already have an OnFilterRecord assigned, but it might already have a Filter expression assigned. In terms of housekeeping effort (saving what was there before and reinstating it afterwards), there isn't any difference. – MartynA Feb 10 '17 at 18:09
  • saw your update, I'll try to fiddle with my dateformat settings in the database and the program. – Viktor Pagels Feb 12 '17 at 11:16
  • @ViktorPagels: It's go to be something like that, because the filter expressions I've included (and every other that I've tried) all work fine for me. Btw, I'm using D7 on Win10 64-bit (with UK regional settings as I mentioned) – MartynA Feb 12 '17 at 11:59
  • I wasn't able to try it so far, my delphi 7 at work kept on crashing while linking with an error at ntdll.dll, throwing an out of memory exception afterwards. – Viktor Pagels Feb 14 '17 at 07:17
  • Today Delphi was more willing to cooperate. the timestamp-format in the database, accoding to flamerobin, is: D.N.Y, H:M:S.T Dateformat is before the "," and timeformat after the ",". my DBGrid displays the date according to the entered format default is: "02.08.2013 15:14:44" or "dd.MM.yyyy hh:mm:ss" with "." being the dateseperator and ":" the timeseperator. I tried setting shortdateformat and longdateformat equal to my Database-format, that didn't work. I tried setting them to the format you posted, that didn't work, I tried more but comment is too long now – Viktor Pagels Feb 14 '17 at 10:49
  • 1
    This answer demonstrates one and explains another method to achieve what is asked in the question. The "update", AFAICS, tries to resolve a problem that is mentioned entirely in another post. I don't know what is being discussed yet. I admire the effort that's been put forth in order to help, and downvoting the question which evidently fails to ask what's meant to ask. – Sertac Akyuz Mar 21 '17 at 22:35
  • 1
    @ViktorPagels: Has this answered your question or, if not, what further information do you expect an answer to provide? – MartynA Mar 23 '17 at 08:56
  • @ViktorPagels: Considering the effort that has obviously gone into this answer (see Sertac's comment), I'm a bit surprised you haven't acceptred it. Or are you expecting you might get a better one? – Alex James Mar 30 '17 at 15:51
  • My understanding of accepting an answer is that the answer has to work. I could not get a single answer here to function on my Setups, and while I really appreciate the effort I didn't think it a good idea to Accept just anything simply for the sake of accepting something. – Viktor Pagels Apr 04 '17 at 12:02
  • Are you saying you could not get the TranslateExpression function in the update to my answer to work? If not, why not? How hard did you try? – MartynA Apr 04 '17 at 12:26