Delphi query multi Parameters - delphi

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

Related

Filtering ADOTable error - "could not convert type Null to type OleStr"

I am trying to filter an ADOTable for records with a field 'OwnerName' value matches the one in a edit on my GUI component. When entering a name that does not exist, i get the above mentioned error. When there are no names in the database that match the filter, I just want the dbgrid to display nothing, how can this be done? Still a noob, so could anyone enlighten me?
Code used:
procedure TOwners.edt_Name_FilterChange(Sender: TObject);
begin
Filter;
end;
//-----------------------------------------------------------
procedure TOwners.Filter;
begin
if edt_Name_Filter.text = '' then
begin
CarOwners.tbl_Owners.Filtered := false;
exit;
end;
with CarOwners do
begin
tbl_Owners.Filtered := false;
tbl_Owners.Filter := 'OwnerName LIKE ''' + '%' + edt_Name_Filter.text +
'%' + '''';
if tbl_owners['OwnerName'] = null then
begin
tbl_owners.filter := '';
end;
tbl_Owners.Filtered := true;
end;
end;

How to add items from an SQL query to a TListView

I want to add items from a query into a TListView using a for loop. But I get an error saying 'Too many actual parameters' from the ListViewShifts.Items.Add() line. What is the correct way of adding to the list from a query?
Qry := TFDQuery.Create(DBConnection);
Qry.Connection := DBConnection;
Qry.SQL.Text := 'SELECT Count(10) FROM Bookings WHERE NurseNo=:nurseID;';
Qry.Params.ParamByName('nurseID').AsInteger := NurseID;
Qry.Active := True;
//Fill the list view with the shifts that have the nurses ID
for Count := 0 to 10 do
begin
ListViewShifts.Items.Add(Qry.Fields[Count].AsString);
end;
You need to consider the following:
If your ListViewShifts variable is TListView, method ListViewShifts.Items.Add doesn't expect parameters. This is the reason for Too many actual parameters error.
SQL statement SELECT Count(10) FROM Bookings WHERE NurseNo=:nurseID; will return result set with only one column.
If you want to get the first 10 rows, then probably your statement should be: SELECT TOP(10) FROM Bookings WHERE NurseNo=:nurseID;
Use First, Eof and Next dataset methods to fetch records from your result set.
Next basic example shows how to add 10 items in your TListView:
procedure TMainForm.btnGet(Sender: TObject);
var
li: TListItem;
begin
Qry := TFDQuery.Create(DBConnection);
Qry.Connection := DBConnection;
Qry.SQL.Text := 'SELECT TOP(10) FROM Bookings WHERE NurseNo=:nurseID;';
Qry.Params.ParamByName('nurseID').AsInteger := NurseID;
Qry.Active := True;
Qry.First;
for Count := 1 to 10 do
begin
Qry.Next;
li := ListViewShifts.Items.Add;
li.Caption := Qry.Fields[0].AsString;
end;
(*
Qry.First;
while not Qry.Eof do begin
li := ListViewShifts.Items.Add;
li.Caption := Qry.Fields[0].AsString;
Qry.Next;
end;
*)
end;
VAR
SY1:INTEGER;
mydata:string;
begin
mydata:='mydatabasename';
qry_tables.Close;
qry_tables.SQL.Clear;
qry_tables.SQL.text:= 'SELECT TABLE_NAME NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '+QuotedStr(mydata);
qry_tables.Open;
ListViewShifts.Clear;
for SY1 := 1 to qry_tables.RecordCount do
begin
ListViewShifts.Items.Add(qry_tables.FieldByName('NAME').TEXT);
qry_tables.NEXT;
end;
i used mysql

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

Fast Report displaying incorrect data from ADOQuery

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;

how to display to a dbgrid my query in this code?

hello is it possible for this code to display to a tdbgrid the search results in a list like style? (e.g. if i searched for john, all the data conataining john on a certain field will be displayed to the tdbgrid)
procedure Tspcb.dccolbtnClick(Sender: TObject);
begin
zdctable.First;
while not zdctable.EOF do
begin
if (zdctable.FieldByName('Collector').AsString = dcedit.Text)
then begin
cn.Caption := zdctable.FieldByName('Client_Name').AsString;
col.Caption := zdctable.FieldByName('Collector').AsString;
pay.Caption := zdctable.FieldByName('Daily_Payment').AsString;
date.Caption := zdctable.FieldByName('Date').AsString;
ddate.Caption := zdctable.FieldByName('Due_Date').AsString;
id.Caption := zdctable.FieldByName('ID').AsString;
la.Caption := zdctable.FieldByName('Loan').AsString;
tc.Caption := zdctable.FieldByName('Total_Collectibles').AsString;
end;
ShowMessage('click ok for next profile');
zdctable.Next;
end;
end;
Just add a datasource, set property dataset to your dataset zdctable, add a DBgrid to your form and set the property datasource to the datasource.
The only piece of code you will need is in the OnchangeEvent of dcedit
procedure TForm3.dceditChange(Sender: TObject);
begin
zdctable.FilterOptions:=[foCaseInsensitive]; // if wished
zdctable.Filtered := Length(dcEdit.Text) > 0;
if zdctable.Filtered then
// zdctable.Filter := 'Collector like ' + QuotedStr('%' + dcEdit.Text + '%')
zdctable.Filter := 'Collector like ' + QuotedStr('*' + dcEdit.Text + '*') // Zeos- Syntax
else zdctable.Filter := '';
end;

Resources