What is the wildcards format for TEdit search function in FireDAC Delphi - delphi

This is my code for searching data using the TEdit component that triggers TFDQuery with parameter:
qryItems.ParamByName('searches').AsString := Format('%%%s%%',[edtSearch.Text]);
If I remove the wildcards (Format('%%%s%%')) format, it works. The wildcards will help me filter the query.
I like the code, its clean, simple, and straight forward. But, I am still not sure if it is correct — it is not returning anything!
My question is:
Does the code above works for query filtering from TEdit.OnChangeTracking event? Otherwise, what is the correct way of doing this?
UPDATE 1:
Heres the code from TFDQuery Editor:
SELECT category.name AS category, item.name, item.description
FROM item
JOIN category ON item.category_id = category.list_id
WHERE item.description LIKE :searches
ORDER BY item.sellable
LIMIT 100
Now, I am trying to access this from this code during runtime but it is not working:
qryItems.ParamByName('searches').AsString := Format('%%%s%%',[edtSearch.Text]);
I think the culprit here is this code Format('%%%s%%',[edtSearch.Text]), I am not getting this right.

A short answer is that you want to end up with a parameter assignment like this:
FDQuery1.Params[0].AsString := '%a%';
FDQuery1.Open();
assuming the value you want to match in your LIKE expression is simply the letter a. Or, if you want to use Format, you could do something like this:
FDQuery1.Params[0].AsString := Format('%%%s%%', [edFilter.Text]);
The reason for the three hash-signs in a row is that the first one 'escapes' the second one in the expression Format evaluates, and the third one, immediately before the 's' combines with it to act as the placeholder for a string as Format constructs its result.
However, given that you are not completely familiar with working with datasets and filtering,
I think you are making this unnecessarily difficult for yourself in at least two respects:
FMX + LiveBindings is not entirely bug free and has some quirks which may well get in your way.
The syntax for using the LIKE operator, which uses hash-signs (#), clashes with the use
of hash signs for resolving parameters in the Format function. This, in particular, can be
extremely confusing, especially when you are trying to obtain a syntactically valid
LIKE expression, whether it is for inclusion in the Sql your query uses or in a 'local filter,
i.e. one which uses the Filter + Filtered properties of the FDQuery.
So, I am going to make a suggestion which might possibly be unwelcome initially,
which is to do your exploration
of things like filtering in a VCL application such as the one below. It will only take a few minutes to set up,
but will probably save you some time and wear and tear on the nervous system compared with
trying to get it right in an FMX + LiveBinding application which is under development. Here is how:
Create a new VCL application and add these components to it.
FDConnection1: TFDConnection;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDQuery1: TFDQuery;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
DataSource1: TDataSource;
edFilter: TEdit;
btnLocalFilter: TButton;
btnSqlFilter: TButton;
Add the code below to the form's file.
Put a debugger breakpoint on the line
case FilterMode of
and exlore the app's behaviour changing the contents of the edFilter control
and clicking the two buttons, once you've adapted the code to the data you have
available. Mine uses an Author's table, I can't remember where I got it from
but maybe it was from the Pubs sample database for Sql-Server.
The app shows - as I'm sure you've gathered - that you can filter the data displayed
by your app either server-side by changing the Sql used to retrieve the data or client-side by using
the Filter property of the FDQuery. So that you can easily see what's going on, the Sql for server-side
filtering is constructed by concatenating the contents of edFilter.Text with the
rest of the Sql, but in real life, you should never do that because of
its exposure to the Sql Injection exploit.
Code
type
TFilterMode = (fmLocal, fmSql);
type
TForm1 = class(TForm)
[...]
public
{ Public declarations }
FilterMode : TFilterMode;
end;
[...]
const
sOrderBy = ' order by lastname, forename';
sSql = 'select * from authors';
sFilteredSql = sSql + ' where lastname like :lastname%';
sLocalFilter = 'lastname like ''%%s%%''';
procedure TForm1.OpenFDQuery;
var
S : String;
begin
if FDQuery1.Active then FDQuery1.Close;
FDQuery1.Params.Clear;
FDQuery1.Filter := '';
FDQuery1.Filtered := True;
case FilterMode of
fmSql : begin
FDQuery1.Sql.Text := '';
// WARNING - don't do this for real - risk of Sql Injection exploit
// use a parameterised query instead - see http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_Parameters_in_Queries
S := 'select * from authors where lastname like ''%' + edFilter.Text + '%''';
FDQuery1.Sql.Text := S;
end;
fmLocal : begin
FDQuery1.Sql.Text := sSql + sOrderBy;
S := 'lastname like ''%' + edFilter.Text + '%''';
FDQuery1.Filter := S;
FDQuery1.Filtered := True;
end;
end;
FDQuery1.Open;
end;
procedure TForm1.ApplySqlFilter;
begin
FilterMode := fmLocal;
OpenFDQuery;
end;
procedure TForm1.ApplyLocalFilter;
begin
FilterMode := fmLocal;
OpenFDQuery;
end;
procedure TForm1.btnLocalFilterClick(Sender: TObject);
begin
ApplyLocalFilter;
end;
procedure TForm1.btnSqlFilterClick(Sender: TObject);
begin
ApplySqlFilter;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
edFilter.Text := 'a';
end;

Related

Delphi 10.4 - Sort Memtable by clicking Stringgrid Header

I feel like an idiot because my question seams so simple but I don't get it done :D
My Settings is that:
One Dataset (Memtable), One Stringgrid. The Grid is bind via live Bindungs.
I would like to sort my Columns by clicking on the GridHeader. In the OnHeaderClick Event I get an tColumn Object. I only can read the Column.Header String, but I changed the Text from the Header to a more speakable Text. When I put Column.header into Memtable.Indexfieldsname Memtable says that field does not exist, what is right, but I don't know how to get the right Fieldname from the column.
What you want is quite straightforward to do. In the example below, which uses the demo data from
the Biolife demo, I've linked the StringgRid to the FDMemTable entirely by binding objects
created in code so that there is no doubt about any of the binding steps or binding properties,
nor the method used to establish the bindings.
procedure TForm2.FormCreate(Sender: TObject);
var
BindSourceDB1 : TBindSourceDB;
LinkGridToDataSourceBindSourceDB1 : TLinkGridToDataSource;
begin
// Note : You need to load FDMemTable1 at design time from the sample Biolife.Fds datafile
// The following code creates a TBindSourceDB which Live-Binds FDMemTable1
// to StringGrid1
//
// As a result, the column header texts will be the fieldnames of FDMemTable1's fields
// However, the code that determines the column on which to sort the StringGrid does not depend
// on this
BindSourceDB1 := TBindSourceDB.Create(Self);
BindSourceDB1.DataSet := FDMemTable1;
LinkGridToDataSourceBindSourceDB1 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB1.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB1.GridControl := StringGrid1;
end;
procedure TForm2.StringGrid1HeaderClick(Column: TColumn);
// Sorts the STringGrid on the column whose header has been clicked
var
ColIndex,
FieldIndex : Integer;
AFieldName : String;
begin
ColIndex := Column.Index;
FieldIndex := ColIndex;
AFieldName := FDMemTable1.Fields[FieldIndex].FieldName;
Caption := AFieldName;
// Should check here that the the field is a sortable one and not a blob like a graphic field
FDMemTable1.IndexFieldNames := AFieldName;
end;
Note that this answer assumes that there is a one-for-one correspondence between grid columns and fields of the bound dataset, which will usually be the case for bindings created using the default methods in the IDE. However, Live Binding is sophisticated enough to support situations where this correspondence does not exist, and in those circumstances it should not be assumed that the method in this answer will work.

How to get rid of dash error on Delphi MySQL query? [duplicate]

I'm trying to get the price of medication from the table but i just get:
procedure TForm1.BuyButtonClick(Sender: TObject);
var
iAmount : integer;
rRate : real;
sMedication : string;
sRate : string;
begin
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) FROM MedicationPrices WHERE Medication = quaotedstr(sMedication)');
sRate := dmHospital.qryPrices.SQL;
ShowMessage(sRate);
end;
You're not using the query properly. qryPrices.SQL is the SQL statement itself. It's just text. You need to do something to actually run the statement. (See below.)
You've also embedded the variable inside the quotes, which means it's not being evaluated, and neither is the function call to the (misspelled) QuotedStr. There is no function quaotedStr(). If you insist on the poor idea of concatenating SQL, you need to do it properly. If you're going to clear and then add, you can just assign to SQL.Text instead to do it in one step:
dmHospital.qryPrices.SQL.Text := 'SELECT Price(R) FROM MedicationPrices WHERE Medication = ' + Quotedstr(sMedication);
Also, the query won't do anything until you actually execute it. You need to use qryPrices.Open to run a SELECT statement, or qryPrices.ExecSQL to run an INSERT, UPDATE or DELETE statement.
You should get out of the thought of concatenating SQL immediately (before you get the habit) and learn to use parameterized queries. It allows the database driver to handle the formatting and conversion and quoting for you, and it also prevents SQL injection that can give others access to your data. Here's a corrected version that should get you started.
procedure TForm1.BuyButtonClick(Sender: TObject);
var
sMedication : string;
sRate : string;
begin
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Text := 'SELECT Price(R) FROM MedicationPrices WHERE Medication = :Medication';
dmHospital.qryPrices.Parameters.ParamByName('Medication').Value := sMedication;
dmHospital.qryPrices.Open;
sRate := dmHospital.qryPrices.FieldByName('Price(R)').AsString;
dmHospital.qryPrices.Close;
ShowMessage(sRate);
end;
You should modify Your code to actually work:
My advise is to use parameters instead of QuotedStr:
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) AS Rate FROM MedicationPrices WHERE Medication = :pMedication');
dmHospital.qryPrices.Params.ParamByName('pMedication').AsString=sMedication;
(Note that in ADOQuery You'd use .Parameters instead of .Params)
dmHospital.qryPrices.Open;
sRate=dmHospital.qryPrices.FieldByName('Rate').AsString;
ShowMessage(sRate);
Regards
Not tested it (dont have Delphi at hand here) but it should be something like this :
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) as price FROM MedicationPrices WHERE Medication = ' + QuotedStr(sMedication));
dmHospital.qryPrices.Open;
if (dmHospital.qryPrices.RecordCount = 1)
sRate := dmHospital.qryPrices.FieldByName('price').AsString;
ShowMessage(sRate);

Delphi - Can you close all of the database tables?

Can you close all database tables except some? Can you then reopen them? I use an absolute database that is similar to BDE. If this is possible, how can I do so many?
Yes, of course you can. You could iterate the Components property of your form/datamodule, use the is operator to check whether each is an instance of your table type and use a cast to call Open or Close on it.
The following closes all TABSDataSet tables on your form except one called Table1.
procedure TForm1.ProcessTables;
var
ATable : TABSDataSet; // used to access a particular TABSDataSet found on the form
i : Integer;
begin
for i := 0 to ComponentCount - 1 do begin
if Components[i] is TABSDataSet then begin
ATable := TABSDataSet(Components[i]);
// Now that you have a reference to a dataset in ATable, you can
// do whatever you like with it. For example
if ATable.Active and (ATable <> Table1) then
ATable.Close;
end;
end;
end;
I've seen from the code you've posted in comments and your answer that you
are obviously having trouble applying my code example to your situation. You
may find the following code easier to use:
procedure ProcessTables(AContainer : TComponent);
var
ATable : TABSTable;
i : Integer;
begin
for i := 0 to AContainer.ComponentCount - 1 do begin
if AContainer.Components[i] is TABSTable then begin
ATable := TABSTable(AContainer.Components[i]);
// Now that you have a reference to a dataset in ACDS, you can
// do whatever you like with it. For example
if ATable.Active then
ATable.Close;
end;
end;
end;
Note that this is a stand-alone procedure, not a procedure of a particular
form or datamodule. Instead, when you use this procedure, you call it passing
whatever form or datamodule contains the TABSTables you want to work with as the
AContainer parameter, like so
if Assigned(DataModule1) then
ProcessTables(DataModule1);
or
if Assigned(Form1) then
ProcessTables(Form1);
However, the downside of doing it this was is that it is trickier to specify which tables, if any, to leave open, because AContainer, being a TComponent, will not have any member tables.
Btw, your task would probably be easier if you could iterate through the tables in a TABSDatabase. However I've looked at its online documentation but can't see an obvious way to do this; I've asked the publishers, ComponentAce, about this but haven't had a reply yet.

Assigning the selected items in a TxpComboBox to a variable

I'm attempting to assign all the selected items in a TxpListBox to a TStringList.
My initial thought was to do something like
Function AssignListBoxToList(ComponentName : TxpListBox) : Boolean;
var
slComponentValue : TStringList;
begin
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items);
end;
But it throws the following exception Incompatible types: 'String' and 'TString'.
Is there a way to either create a TStringList of TStrings, or is it safe to use String instead of TString in my TxpListBox, and/or am I missing something.
TxpListBox is a TListBox with a modified look to fit in with the Windows XP design aesthetic.
It looks like TxpComboBox.Items might be a TStrings descendent (like the standard TComboBox.Items). If that's the case, something like this should work:
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items[ComponentName.ItemIndex]);
Your function won't work as is, though, because it doesn't return slComponentValue.
It's generally not a good idea (without a specific reason to do so) to return an object from a function, because it's not clear where the responsibility lies to free it. I prefer to make that more clear by having a procedure accept an already-created instance of an object instead:
procedure AssignComboBoxToList(ComponentName : TxpComboBox;
ListToFill: TStrings) : Boolean;
begin
Assert(Assigned(ListToFill));
ListToFill.Add(ComponentName.Items[ComponentName.ItemIndex);
end;
You can then use it like this:
slComponentValue := TStringList.Create;
try
AssignComboBoxToList(YourComboBox, slComponentValue);
if slComponentValue.Count > 0 then
// Do whatever with the slComponentValue list
finally
slComponentValue.Free;
end;
However, as you're only dealing with a single string, it might be easier to just use a single string; there's not really a TStringList neededhere:
strResult := YourComboBox.Items[YourComboBox.ItemIndex];
With that being said, TComboBox doesn't support multiple selections; TListBox does, but TComboBox displays a drop down list and allows selecting of a single item, making your question somewhat unclear.

Need a ComboBox with filtering

I need some type of ComboBox which can load it's items from DB. While I type some text in to it, it should filter it's list, leaving only those items, that have my text somewhere (at the beginning, middle...). Not all my DataSet's have filtering capabilities, so it is not possible to use them. Is there any ready to use components with such abilities? I have tried to search in JVCL, but without luck.
You could try customizing the autocomplete functionality of a regular ComboBox. Loading its items from a DB is easy:
ComboBox1.Items.Clear;
while not Table1.Eof do begin
ComboBox1.Items.AddObject( Table1.FieldByName('Company').AsString,
TObject(Table1.FieldByName('CustNo').AsInteger) );
Table1.Next;
end;
As far as the auto-complete for middle-of-word matching, you might try adapting this code. The functionality that matches at the beginning of the text in the Items is enabled by setting AutoComplete to true, and needs to be turned off before you try writing your own OnChange event handler that does auto-complete. I suggest that you could more safely do the match and selection on the enter key, because attempting to do it on the fly makes things quite hairy, as the code below will show you:
Here's my basic version: Use a regular combobox with onKeyDown, and onChange events, and AutoComplete set to false, use above code to populate it, and these two events
procedure TForm2.ComboBox1Change(Sender: TObject);
var
SearchStr,FullStr: string;
i,retVal,FoundIndex: integer;
ctrl:TComboBox;
begin
if fLastKey=VK_BACK then
exit;
// copy search pattern
ctrl := (Sender as TCombobox);
SearchStr := UpperCase(ctrl.Text);
FoundIndex := -1;
if SearchStr<>'' then
for i := 0 to ctrl.Items.Count-1 do begin
if Pos(SearchStr, UpperCase(ctrl.Items[i]))>0 then
begin
FoundIndex := i;
fsearchkeys := ctrl.Text;
break;
end;
end;
if (FoundIndex>=0) then
begin
retVal := ctrl.Perform(CB_SELECTSTRING, 0, LongInt(PChar(ctrl.Items[FoundIndex]))) ;
if retVal > CB_Err then
begin
ctrl.ItemIndex := retVal;
ctrl.SelStart := Pos(SearchStr,UpperCase(ctrl.Text))+Length(SearchStr)-1;
ctrl.SelLength := (Length(ctrl.Text) - Length(SearchStr));
end; // retVal > CB_Err
end; // lastKey <> VK_BACK
end;
procedure TForm2.ComboBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
fLastKey := Key;
end;
Suppose the contents of the list are "David Smith", and "Mark Smithers". You type S and it matches the first letter of the last name, in David Smith. Now it shows David Smith with the "David S" part not selected, and the "mith" part selected (so that the next characters you type will replace the auto completed portion, a standard auto-complete technique). Note that the above code has had to prefix the S you typed with the "David " part you didn't type. If you are a lot more clever than me, you can find a way to remember that the user typed "s" and then, maybe an "m", followed by some more letters, and eventually having typed "Smithe", match Smithers, instead of always David smith. Also note that you can only set the SelStart and SelLength to select a continuous length of a string.
The code I have provided will only work when the list of items never contains any repeated substrings. There are good reasons why the Windows Common Control combobox "autocomplete" functionality only works with prefix matching, and not mid-string matching.
Since anything that would implement mid-string matching should probably draw the part you typed in not-selected, and since that not-selected part would be in mid-string, you would probably need to write your own control from scratch and not rely on the TComboBox base code, and its underlying MS Common Controls combobox functionality.
DevExpress' "TcxExtLookupCombobox" has this capability - and more. Might be overkill though.

Resources