Fast Report displaying incorrect data from ADOQuery - delphi

I have a problem with Fast Report displaying incorrect data from an ADOquery. I use the following sql.text
SELECT * FROM JOB_DATA
INNER JOIN CUSTOMER ON JOB_DATA.CUST_CODE = CUSTOMER.CUST_CODE
WHERE JOB_DATA.SHIP_DATE Between [Date1] And [Date2]
ORDER by SHIP_DATE
Fast Report only shows the data where SHIP_DATE = null.
If I throw up a TDBgrid and attach it to a data source attached to the same ADOquery, then the dbgrid shows exactly the correct information.
I'm out of ideas, any suggestions?
To answer questions about where the dates come from:
var
date1:string;
date2:string;
sql_str:string;
begin
date1:=inputbox('Date Range','Enter Beginning Date','');
Try
StrToDate(date1);
Except
On EConvertError Do
Begin
MessageDlg('Please enter a valid date. Format xx/xx/xx',
mtError, [mbOK], 0);
//ShowMessage('Please enter a valid date. Format `enter code here`xx/xx/xx');
Exit;
End;
End;
date2:=inputbox('Date Range','Enter Ending Date','');
Try
StrToDate(date2);
Except
On EConvertError Do
Begin
MessageDlg('Please enter a valid date. Format xx/xx/xx',
mtError, [mbOK], 0);
//ShowMessage('Please enter a valid date. Format `enter code here`xx/xx/xx');
Exit;
End;
End;
sql_str:= 'SELECT * FROM JOB_DATA INNER JOIN CUSTOMER ON ' +
'JOB_DATA.CUST_CODE = CUSTOMER.CUST_CODE ' +
'WHERE JOB_DATA.SHIP_DATE Between ';
sql_str:= sql_str+ ''' ';
sql_st:=sql_str + date1;
sql_str:= sql_str+ '''';
sql_str:= sql_str+ ' AND ';
sql_str:= sql_str+ ''' ';
sql_str:= sql_str+ date2;
sql_str:= sql_str+ ' ''';
with ADOQuery5 do
begin
Close;
SQL.Clear;
SQL.text:= sql_str;
Open;
end;
frxreport2.ShowReport();
end;
The ADOquery is attached to frxDBDataset2 which is attached to frxReport2. I am doing nothing to alter the results in the query.
No, I have no code in the report, it was all generated from the wizard.

FastReport cannot display records only where SHIP_DATE is NULL, because your query shouldn't be returning them based on your WHERE clause if Date1 and Date2 are properly assigned. This means that either your dataset and the FastReport aren't connected properly or that something in your code assigning the date values for the BETWEEN clause is wrong, and the dates aren't being provided to the query correctly.
The first place to start looking is to make sure that all of the report columns are correctly assigned the proper TfrxDataSet and the proper database column. (Click on the report item (text object or whatever it might be), and check its DataSet and DataField properties to ensure they are correct.)
If that's not the problem, it may be the way you're building your query, which probably isn't correctly formatting the dates for ADO. (You're just using whatever format happens to pass the StrToDate calls without raising an exception.)
The way you're setting up your SQL is really unadviseable. It's unreadable and unmaintainable when you try to manage quoting yourself in code.
You should use parameters, which first and foremost protects you against SQL injection, but also allows the database driver to properly format quoted values and dates for you and keeps things readable. (You can also use readable names for the parameters, so that when you see them six months from now you'll know what they mean.)
var
// Your other variable declarations here
StartDate, EndDate: TDateTime;
begin
Date1 := InputBox(Whatever);
try
StartDate := StrToDate(Date1);
except
// Handle EConvertError
end;
Date2 := InputBox(Whatever);
try
EndDate := StrToDate(Date2);
except
// Handle EConvertError
end;
sql_str := 'SELECT * FROM JOB_DATA J'#13 +
'INNER JOIN CUSTOMER C'#13 +
'ON J.CUST_CODE = C.CUST_CODE'#13 +
'WHERE J.SHIP_DATE BETWEEN :StartDate AND :EndDate';
with ADOQuery5 do
begin
Close;
// No need to clear. If you're using the same query more than once,
// move the SQL assignment and the Parameter.DataType somewhere
// else, and don't set them here.
// The query can be reused just by closing, changing parameter values,
// and reopening.
SQL.Text := sql_str;
with Parameters.ParamByName('StartDate') do
begin
DataType := ftDate;
Value := StartDate;
end;
with Parameters.ParamByName('EndDate') do
begin
DataType := ftDate;
Value := EndDate;
end;
Open;
end;
frxReport2.ShowReport;
end;

When I start having problems with ADO, I log the information.
You'll need to create your own logger...but here's the jest of it...Note it will log the parameter values that are being passed to the query, including the SQL.
procedure TLogger.SetUpConnectionLogging(aParent: TComponent);
var
a_Index: integer;
begin
for a_Index := 0 to aParent.ComponentCount - 1 do
if aParent.Components[a_Index] is TAdoConnection then
begin
TAdoConnection(aParent.Components[a_Index]).OnWillExecute := WillExecute;
TAdoConnection(aParent.Components[a_Index]).OnExecuteComplete := ExecuteComplete;
end;
end;
procedure TLogger.ExecuteComplete(Connection: TADOConnection;
RecordsAffected: Integer; const Error: Error; var EventStatus: TEventStatus;
const Command: _Command; const Recordset: _Recordset);
var
a_Index: integer;
begin
AddLog('AdoConnection ExecuteComplete', True);
AddLog('Execution In MilliSeconds', IntToStr(MilliSecondsBetween(Time, FDif)));
AddLog('Execution In Seconds', IntToStr(SecondsBetween (Time, FDif)));
AddLog('Execution In Minutes', IntToStr(MinutesBetween (Time, FDif)));
AddLog('CommandText', Command.CommandText);
if Assigned(Command) then
begin
AddLog('Param Count', IntToStr(Command.Parameters.Count));
for a_Index := 0 to Command.Parameters.Count - 1 do
begin
AddLog(Command.Parameters.Item[a_Index].Name, VarToWideStr(Command.Parameters.Item[a_Index].Value));
end;
AddLog('CommandType', GetEnumName(TypeInfo(TCommandType),Integer(Command.CommandType)));
end;
AddLog('EventStatus', GetEnumName(TypeInfo(TEventStatus),Integer(EventStatus)));
if Assigned(RecordSet) then
begin
AddLog('CursorType', GetEnumName(TypeInfo(TCursorType),Integer(Recordset.CursorType)));
AddLog('LockType', GetEnumName(TypeInfo(TADOLockType),Integer(Recordset.LockType)));
end;
AddLog('RecordsAffected', IntToStr(RecordsAffected));
AddLog('AdoConnection ExecuteComplete', False);
end;
procedure TLogger.WillExecute(Connection: TADOConnection;
var CommandText: WideString; var CursorType: TCursorType;
var LockType: TADOLockType; var CommandType: TCommandType;
var ExecuteOptions: TExecuteOptions; var EventStatus: TEventStatus;
const Command: _Command; const Recordset: _Recordset);
begin
AddLog('Connection WillExecute', True);
AddLog('Connection Name', Connection.Name);
AddLog('CommandText', CommandText);
AddLog('CommandType', GetEnumName(TypeInfo(TCommandType),Integer(CommandType)));
AddLog('EventStatus', GetEnumName(TypeInfo(TEventStatus),Integer(EventStatus)));
AddLog('CursorType', GetEnumName(TypeInfo(TCursorType),Integer(CursorType)));
AddLog('Connection WillExecute', False);
FDif := Time;
end;

Related

Why, if adding a value to a database through a variable, then “ADOQuery1” is added, and if directly, then a correct value?

There is such a code.
procedure TForm1.FramePositionsAdd1ButtonAddClick(Sender: TObject);
var
Name: String;
begin
Name := FramePositionsAdd1.EditName.Text;
with FramePositionsAdd1.ADOQuery1 do
begin;
SQL.Clear;
SQL.Text := 'INSERT INTO Должности ' +
'(Наименование) ' +
'VALUES ' +
'(:title)';
Parameters.ParamByName('title').Value := Name;
ExecSQL;
end;
end;
The value is added to the database, but instead of FramePositionsAdd1.EditName.Text the string ADOQuery1 is added.
I tried to remove the Name variable. One line changes:
Parameters.ParamByName('title').Value := FramePositionsAdd1.EditName.Text;
And everything work correctly. What is the problem?
Your use of with is causing the reference to Name to resolve to AdoQuery1.Name instead of your local variable Name.
with FramePOstitionsAdd1.AdoQuery1 do
begin
// Name used here, because of the with, refers to the object in the
// with statement and not the local variable declared outside the
// with block.
end;
Either change the name of the variable to something else, or (better) stop using with because of the side effects it can have that cause issues like this.
procedure TForm1.FramePositionsAdd1ButtonAddClick(Sender: TObject);
var
Title: String;
begin
Title := FramePositionsAdd1.EditName.Text;
with FramePositionsAdd1.ADOQuery1 do
begin
SQL.Clear;
SQL.Text := 'INSERT INTO Должности ' +
'(Наименование) ' +
'VALUES ' +
'(:title)';
Parameters.ParamByName('title').Value := Title;
ExecSQL;
end;
end;
Better solution that prevents future problems of the same nature due to edits:
procedure TForm1.FramePositionsAdd1ButtonAddClick(Sender: TObject);
var
Name: String
Qry: TAdoQuery;
begin
Name := FramePositionsAdd1.EditName.Text;
// Get a local reference to your query
Qry := FramePositionsAdd1.ADOQuery1;
// Use that local reference
Qry.SQL.Clear;
Qry.SQL.Text := 'INSERT INTO Должности ' +
'(Наименование) ' +
'VALUES ' +
'(:title)';
Qry.Parameters.ParamByName('title').Value := Name;
Qry.ExecSQL;
// DO NOT free or nil Qry here. It is just a pointer to the original,
// not a new object, and freeing it will free the original query instead.
end;

Exception class EConvertError with message ''' is not a valid floating point value'.

Hey can anybody help me with this error please, I can't seem to find a solution.
Any help would be appreciated.
I am working with Windows 8 and Delphi RAD Studio 2010.
If there are more errors then what I'm referring to please feel free to comment on them.
procedure TfrmStats.FormShow(Sender: TObject);
begin
// // Code that connects the TADOConnection to the database
// //conDatabase.Close;
// conDatabase.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\[Phase 2]\db_DatabaseStock.mdb;Persist Security Info=False' ;
// conDatabase.Open;
// Code sets radiobutton.checked and checkbox.checked to true to avoid errors and
// simplify GUI
rb2D.Checked := True;
chkShowLegend.Checked := True;
// Code hides ShowGrid checkbox
chkShowItemGrid.Visible := False;
//Code hides Stringgrid
SGStats.Visible := False;
DrawPie;
**end;** // The breakpoint is here (Where delphi says the error is)
I will also show the code for the procedure being called:
procedure TfrmStats.DrawPie;
var
sSQL :string;
iRow, iCol, iA : Integer;
dblGT, dblLST, dblValue, PiePercentage : Double;
begin
// Procedure used to draw the chart of data
// Here call Subtotal
SGstats.Cells[0,1] := 'Sub Total';
SGstats.Cells[1,1] := IntToStr(GetSub);
with qryItems do
begin
// Select itemname an populate the stringgrid
SQL.Clear;
sSQL := 'SELECT DISTINCT ItemName FROM tblItems ORDER BY ItemName';
SQL.Add(sSQL);
Open;
Active := False;
Active := True;
if (RecordCount <> 0) then
begin
SGstats.RowCount := SGstats.RowCount + RecordCount;
for iRow := 0 to RecordCount -1 do
begin
SGstats.Cells[0,iRow+2] := FieldByName('ItemName').AsString;
Next;
end;
end;
end;
qryItems.Close;
with qryItems do
begin
// Select itembookquantity and populate the stringgrid
SQL.Clear;
sSQL := 'SELECT DISTINCT ItemName, ItemBookQuantity FROM tblItems ORDER BY ItemName';
SQL.Add(sSQL);
Open;
Active := False;
Active := True;
if (RecordCount <> 0) then
begin
SGstats.RowCount := SGstats.RowCount + RecordCount;
for iRow := 0 to RecordCount -1 do
begin
SGstats.Cells[1,iRow+2] := FieldValues['ItemBookQuantity'];
Next;
end;
end;
end;
// Code that actually draws piechart
with chtStats do
begin
//Clear the charts series
while (SeriesCount> 0) do
Series[0].Free;
//Change title
Title.Text.Clear;
Title.Text.Add('Items');
// Add series to piechart
AddSeries(TPieSeries.Create(Self));
Series[0].Name := 'PieItems';
for iRow := 2 to SGstats.RowCount -2 do
begin
PiePercentage := (StrToFloat(SGstats.Cells[1,iRow])/StrToFloat(SGstats.Cells[1,1]))*100;
Series[0].Add(StrToFloat(SGstats.Cells[1, iRow]), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
end;
end;
The subtotal is supposed to be an integer. I'm also experiencing an 'Authentication Failed' error when running the program, any assistance would be appreciated. I'm still only a beginner so I may overlook small things or make simple mistakes :D
If I need to add more information to help, please let me know!
The error message is very clear: you are trying to convert to float an empty string. Delphi raises an exception because an empty string doesn't represent any valid float value.
You need to first check that the strings that you are using are not empty, and decide what to do in that case : inform the user, draw an empty pie, ...
By the way, if you want to consider your empty strings as zeros, then you can code your own customized conversion function.
function CustomStrToFloat(string: variant): double;
begin
if (string = null) or (Trim(string) = '') then Result := 0
else Result := StrToFloat(string);
end;
Please notice that this function will still raise an exception if your input is not an empty string (or a null variant), so the user will know that you are receiving inconsistent inputs.
Now you just have to change your code in order to use your customized conversion function
...
PiePercentage := (CustomStrToFloat(SGstats.Cells[1,iRow])/CustomStrToFloat(SGstats.Cells[1,1]))*100;
Series[0].Add(CustomStrToFloat(SGstats.Cells[1, iRow]), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...
About the 'Authentication Failed' error, can you debug your code and check what line raises that error ?. Looks like that it's going to be when you execute your SQL query, in that case the credentials that you have defined on the connection object of your SQLQuery are not correct.
EDIT: As Remy Lebeau has suggested, Delphi already includes two functions to deal with conversions from strings not containing valid representations of floating values. The first one is StrToFloatDef (string to float with a default value for non-valid strings).
You will only need to change your code to :
...
PiePercentage := (StrToFloatDef(SGstats.Cells[1,iRow],0)/StrToFloatDef(SGstats.Cells[1,1],0))*100;
Series[0].Add(StrToFloatDef(SGstats.Cells[1, iRow],0), SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...
I don't use it because it will not only consider as zeros all the empty strings, but also every other string with inconsistent contents, and in those cases I prefer to let the program raise an exception, so the user is going to be notified that the input values are not valid.
The other function that you could use is TryStrToFloat, that is going to try to do the conversion and return true or false if the conversion has been successful.
If you use this, you will need to change those two lines to :
var FirstCell, SecondCell: extended;
...
...
FirstCell := 0;
SecondCell := 0;
if not TryStrToFloat(SGstats.Cells[0,iRow], FirstCell) then
ShowMessage('Input Values not valid');
if not TryStrToFloat(SGstats.Cells[1,iRow], SecondCell) then
ShowMessage('Input Values not valid');
PiePercentage := (SecondCell/FirstCell)*100;
Series[0].Add(SecondCell, SGStats.Cells[0,iRow] + ', ' + FormatCurr('0.####',PiePercentage) + ' %', clteecolor);
...

Delphi query multi Parameters

I'm trying to make a query on dataBase Table with ADOQuery by this code
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add(SELECT * FROM Table WHERE name=:name and tel=:tel);
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
adoquery1.open;
end;
Now, if I set edtName and edtTel the query is return a result
But what if edtName or edtTel is empty
the query will return null
what should I do, to make the query returning the result if one of them or both has a value??
Thanks.
You should check if textboxes are empty;
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add(SELECT * FROM Table WHERE 1=1 );
if edtName.text <> '' then
Begin
adoquery1.sql.add(' And name=:name ');
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
End;
if edtTel.text <> '' then
Begin
adoquery1.sql.add(' And tel=:tel ');
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
End;
adoquery1.open;
end;
note: if both of them are empty result will show all records.
Build the SQL dynamically based on whether the Edit boxes are empty or not, eg:
var
hasName, hasTel: Boolean;
whereClause: string;
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM Table');
hasName := edtName.GetTextLen > 0;
hasTel := edtTel.GetTextLen > 0;
if hasName or hasTel then
begin
whereClause := 'WHERE ';
if hasName then
whereClause := whereClause + 'name=:name';
if hasTel then
begin
if hasName then
whereClause := whereClause + ' and ';
whereClause := whereClause + 'tel=:tel';
end;
ADOQuery1.SQL.Add(whereClause);
if hasName then
ADOQuery1.Parameters.ParamByName('name').Value := edtName.Text;
if hasTel then
ADOQuery1.Parameters.ParamByName('tel').Value := edtTel.Text;
end;
ADOQuery1.Open;
end;
Or, more generically:
var
params: TStringList;
I: Integer;
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
params := TStringList.Create;
try
if edtName.GetTextLen > 0 then
params.Add('name=' + edtName.Text);
if edtTel.GetTextLen > 0 then
params.Add('tel=' + edtTel.Text);
// other parameters as needed ...
ADOQuery1.SQL.Add('SELECT * FROM Table');
if params.Count > 0 then
begin
ADOQuery1.SQL.Add('WHERE ' + params.Names[0] + '=:' + params.Names[0]);
for I := 1 to params.Count-1 do
ADOQuery1.SQL.Add('AND ' + params.Names[I] + '=:' + params.Names[I]);
for I := 0 to params.Count-1 do
ADOQuery1.Parameters.ParamByName(params.Names[I]).Value := params.ValueFromIndex[I];
end;
finally
params.Free;
end;
ADOQuery1.Open;
end;
You could do what sddk was suggesting (building your SQL in response of the TEdits value) or
you could assign NULL value to your parameters like that
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add('SELECT * FROM Table WHERE name=:name and tel=:tel');
if edtName.text <> '' then begin
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
end else begin
adoquery1.Parameters.ParamByName('name').Value:= Null;
end;
if edtTel.text <> '' then begin
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
end else begin
adoquery1.Parameters.ParamByName('tel').Value:= Null;
end;
adoquery1.open;
end;
Don't forget to add System.Variants in your uses list (for the Null use)
You need to beware of testing for Null, as others have warned, but there is a bit more to it
than merely setting the parameter value to Null.
Suppose you have a Sql Server table with a name column and a number of rows have null
entries for that column. Then consider this code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if cbUseNullParam.Checked then begin
AdoQuery1.SQL.Text := 'select * from MATable1 where name = :name';
AdoQuery1.Parameters.ParamByName('name').Value := Null;
end
else
AdoQuery1.SQL.Text := 'select * from MATable1 where name is Null';
AdoQuery1.Open;
end;
In other words, if the cbUseNullParam checkbox is checked, set the name param
to Nll, otherwise use SQL which explicitly specifies that the name column is Null.
Open Sql Server Management Studio's Profiler and observe what happens, both in what is sent to the server, and what a DBGrid connected to AdoQuery1 displays, which is this.
When cbUseNullParam is checked, the query fails to return the rows having
a Null name. When it is not checked, the correct rows are returned.
In other words, using ADO against MS Sql Server at any rate, if you want to find rows that have a Null state for a given column, you need to use Sql that explicitly states that the column is Null, rather than relying of setting an AdoQuery parameter for that column to Null. So, if your case, you actually need four versions of your SQL (one each for the two columns being Null, one for them both being Null and one for neither being Null).

How to use the Filter functions of TClientdatasets for dates?

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

On a very slow query, how do I indicate percentage of progress

I'm using ZEOS components to connect to an (ancient) MDB database.
I'm doing a query that reads in lots of data to bridge into a different database.
Is there a way to indicate progress as a percentage?
procedure TForm13.ActionReadInMemoryExecute(Sender: TObject);
var
QueryLine: string;
FullQuery: string;
Tablename: string;
i: integer;
begin
i:= 0;
TableMeter.DisableControls;
try
TableMeter.First;
FullQuery:= '';
while not TableMeter.eof do begin
Tablename:= TableMeter.FieldByName('tabelnaam').AsString;
QueryLine:= ReplaceStr(ImportQuerySjabloon, cTabelname, Tablename);
FullQuery:= FullQuery + QueryLine;
if (TableMeter.RecNo < (TableMeter.RecordCount -1)) then begin
FullQuery:= FullQuery + ' UNION ALL ';
end;
TableMeter.Next;
end; {while}
QueryImportMeterreadings.Close;
QueryImportMeterreadings.SQL.Text:= FullQuery;
QueryImportMeterreadings.Open; <<-- takes a long time
finally
TableMeter.EnableControls;
end;
end;
Is there a way to indicate progress of the query, or can I only do this if I split up the individual queries and eliminate the UNION's.
It takes about 1 minute to run, involving 8 unions.
I don't see any event that I can use for this purpose:
Or should I fake an OnCalcField on a field in the Query to do this (not sure if that will even work in principle).
Or attach a sequence? nope, gives unsupported operation on a Access DB
I say split up the individual queries and eliminate the union, make a timer around each query, depending on the avg time taken * number of queries remaining you should give an estimate / update a text field to say x out of y queries completed (time remaining: -time-)
I would split the huge query into individual queries; in code, you iterate over each query's result set and manually insert the values into a clientdataset (cds). The cds can be connected to a dbgrid. Then you can show when each query completes - you could also show progress after each tuple is handled, but you won't know how many tuples in total there are, unless you perform a separate query which returns a count of tuples. The problem with using such an unconnected cds is that you have to define the fields in code. Here is an example of something similar which I wrote last night - the queries all update one field in the cds.
const
field1 = 'id';
field2 = 'customer name';
field3 = 'total debt';
procedure TTotalCustDebt.FormCreate(Sender: TObject);
var
strings: tstrings;
begin
with qTotalDebt do // this is the clientdataset
begin
fielddefs.add (field1, ftInteger, 0, false);
fielddefs.add (field2, ftString, 32, false);
fielddefs.add (field3, ftInteger, 0, false);
createdataset;
fieldbyname (field1).visible:= false;
open;
addindex ('idx0', field2, [], '', '', 0);
addindex ('idx1', field2, [ixDescending], '', '', 0);
addindex ('idx2', field3, [], '', '', 0);
addindex ('idx3', field3, [ixDescending], '', '', 0);
strings:= tstringlist.create;
getindexnames (strings);
strings.free;
end;
end;
procedure TTotalCustDebt.PopulateCDS;
begin
dsTotalDebt.dataset:= nil;
with qTotalDebt do
begin
emptydataset;
indexfieldnames:= field1; // initially sort by customer.id
end;
with qDBills do
begin
params[0].asdate:= dt;
open;
while not eof do
begin
qTotalDebt.append;
qTotalDebt.fieldbyname (field1).asinteger:= qDBillsID.asinteger;
qTotalDebt.fieldbyname (field2).asstring:= qDBillsName.asstring;
qTotalDebt.fieldbyname (field3).asinteger:= qDBillsTot.asinteger;
qTotalDebt.post;
next
end;
close
end;
// show progress indicator
with qDReceipts do
begin
params[0].asdate:= dt;
open;
while not eof do
begin
if qTotalDebt.findkey ([qDReceiptsID.asinteger]) then
begin // customer already exists
qTotalDebt.edit;
qTotalDebt.fieldbyname (field3).asinteger:= - qDReceiptsTot.asinteger
+ qTotalDebt.fieldbyname (field3).asinteger;
end
else
begin // add new record
qTotalDebt.append;
qTotalDebt.fieldbyname (field1).asinteger:= qDReceiptsID.asinteger;
qTotalDebt.fieldbyname (field2).asstring:= qDReceiptsName.asstring;
qTotalDebt.fieldbyname (field3).asinteger:= - qDReceiptsTot.asinteger;
end;
qTotalDebt.post;
next
end;
close
end;
// show progress indicator
// more queries
// at end, attach the clientdataset to the TDataSource
dsTotalDebt.dataset:= qTotalDebt;
end;

Resources