I have a adoQuery and would like to fill a listbox with the results, but with no duplicates.
with Fdeptlayout.ADOQuery2 do
begin
sql.Clear;
sql.BeginUpdate;
sql.Add('SELECT');
sql.Add(' *');
sql.Add('FROM');
sql.Add(' `MList`');
sql.Add(' ORDER BY `Basic Name`');
sql.EndUpdate;
open;
end;
while not fdeptlayout.ADOquery2.EOF do
fdeptlayout.ListBox1.Items.Add(fdeptlayout.ADOQuery2['Basic Name']);
end;
Currently this adds 350 items to the listbox, a lot of duplicates. This is too much. How can I alter the query to remove duplicates from the result? :( Any help would be great!
You forgot "Next".
while not fdeptlayout.ADOquery2.EOF do begin
fdeptlayout.ListBox1.Items.Add(fdeptlayout.ADOQuery2['Basic Name']);
fdeptlayout.ADOquery2.Next;
end;
Without next you get an endless loop.
Change the query:
sql.Add('SELECT DISTINCT');
sql.Add(' `Basic Name`');
sql.Add('FROM');
sql.Add(' `MList`');
sql.Add(' ORDER BY `Basic Name`');
DISTINCT filters out duplicates, and by selecting only the field you need, you save fetching possibly a lot of unneeded data. Also, other fields in the records may differ, causing DISTINCT to work sub-optimal.
As a general rule: Don't use * in queries and only select the fields you actually need.
[edit]
And, as agreed in the comments, calling Fdeptlayout.ADOQuery2.Next within the while loop certainly prevents your application locking up. ;-)
Related
with AdoQuery do
begin
Close;
SQL.Clear;
SQL.Add('SELECT (name+' '+surname+' '+father) as initihal, address from user');
Open;
end;
How to add space between name surname and father ?
i want to see result in DbGrid like that
Lionel Andrés Messi
The ADO layer/backend server should be fine with returning a column value which is an expression including a number of column values and string literals.
As you are trying to construct your SQL statement in Delphi source code (rarely a good idea, ime) you need to express what you want in a way which is syntactically correct from the compiler's point of view, as well as producing the desired result from your RDMS. To do this, all of the single quotes inside SQL.Add(...) except the outer two need to be doubled-up, otherwise you will get a syntax error on compilation.
However, even once you've done that, your SQL still may not execute correctly at run-time depending on whether your naming of the columns in your result-set is digestible by your back-end server. It's best to create and test your Select statement using whatever query tool is available for your RDMS, then attempt to set it up in Delphi only after you've got it working correctly from the RDMS's pov.
Btw, you will encounter fewer problems if you get into the habit of using the QuotedStr function in constructing the column values you want - you obviously need the syntax practice so I leave you to look that up in the Online Help.
Also, if you need to constrain the result set by specifying a Where clause, be sure to do it in a way which minimizes the risk of Sql Injection (https://en.wikipedia.org/wiki/SQL_injection) which arises when the SQL includes input text from the user. The bet way to do this is to use a parameterized Where clause.
If using inline SQL you have escape the quote characters or use a function that returns spaces. You will not be able to update the result. Another option is to add a calculated field in your Delphi ADOQuery component and do the combine client side in OnCalcFields.
with AdoQuery do
begin
Close;
SQL.Clear;
SQL.Add('SELECT name+'' ''+surname+'' ''+father as initihal, address from user');
Open;
end;
If using SQL Server for a database you can use the space() function.
with AdoQuery do
begin
Close;
SQL.Clear;
SQL.Add('SELECT name+ space(1) + surname+ space(1) +father as initihal, address from user');
Open;
end;
I have a TFDMemTable filled with thousands of records. Is there a way to limit result records for only the first 50 ?
I've tried to use:
FDMemTable.FetchOptions.RecsSkip := 0;
FDMemTable.FetchOptions.RecsMax := 50;
FDMemTable.Open;
But it did not work, data remained unchanged.
I expect #Victoria will be able to show you a better and more general
way, but there are at least two ways to do this:
Use FD's FDLocalSQL feature to copy the first X rows of the FDMemTable into, say,
an FDQuery and then copy them back into your FDMemTable.
Apply a filter to the FDMemTable to filter out the other records, use an FDBatchMove
to copy the X records into a second FDMemTable and then copy them back into the original
FDMemTable.
To implement the first of these, add the following components to your form/datamodule:
FDLocalSQL1: TFDLocalSQL;
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
and then execute code like this:
procedure TForm3.CopyData1;
begin
FDConnection1.DriverName := 'SQLite';
FDConnection1.Connected := True;
FDLocalSQL1.Connection := FDConnection1;
FDLocalSQL1.DataSets.Add(FDMemTable1); // this is the source dataset
FDLocalSQL1.Active := True;
FDQuery1.SQL.Text := 'select * from FDMemTable1 order by ID limit 5'; // ID being an Integer field of the FDMemTable
FDQuery1.Active := True;
FDMemTable1.Close;
FDMemTable1.Data := FDQuery1.Data; // Re-opens FDMemTable 1, which now contains only the first X records
end;
FD's LocalSQL uses Sqlite to do its stuff. The functional equivalent in Sqlite's SQL
to "Select Top X ..." is its limit clause.
An advantage of using LocalSQL for your task, of course, is that because LocalSQL
supports order by, you can it to determine which (top) X records are retained.
The batchmove method requires a bit less code but requires you to have a way of identifying
the first X records using a filter expression. An example using an ID field might be
procedure TForm3.CopyData2;
begin
FDMemTable1.Filter := 'ID <=50';
FDMemTable1.Filtered := True;
FDBatchMove1.Execute; // move data from FDMemTable1 to FDMemTable2;
FDMemTable1.Close;
FDMemTable1.Data := FDMemTable2.Data; // Re-opens FDMemTable 1, which now contains only the first X records
end;
Btw, you say
I have a TFDMemTable filled with thousands of records. I
I think the problem with the method you've tried is probably that by the time you have the records in the FDMemTable, it's too late to try and limit the number of them in the way you're attempting. *)
I managed to make a "Search" bar through a TEdit that seeks whatever i type from inside
a ListView that gets its information from a DataBase and goes through a filter and updates the ListView's items on the fly after a key is pressed.
Now i am trying to learn how to implement a way of limiting the results i get in my ListView temporarily until i press a Show More button or something like that in order to get some more relevant results.
Since the Database might return over 500 results by the time i press "A" and that would be harsh to a mobile phone's capabilities so i need that feature to make my Search button more efficient.
Could someone give me some pointers on what i can use in order to make something like that?
EDIT.
The current code i am using for searching in the ListView is this...
procedure TContactsForm.Edit1ChangeTracking(Sender: TObject);
var
Lower: string;
i: integer;
begin
Lower:= LowerCase(Edit1.Text.Trim);
if Lower= '' then
begin
if Filtered then
begin
ListView1.Items.Filter := nil;
ListView1.ItemIndex := BindSourceDB1.ComponentIndex;
end;
end
else
begin
ListView1.ItemIndex := -1;
ListView1.Items.Filter :=
function(X: string): Boolean
begin
Result:= (Lower = EmptyStr) or LowerCase(X).Contains(Lower);
end;
end;
end;
function TContactsForm.Filtered: Boolean;
begin
Result := Assigned(ListView1.Items.Filter);
end;
The easiest way is to model your select statement so it only returns a limited about of rows (you can always remove the limitation upon user request).
For SQLite, MySQL and PostgreSQL you'd use a LIMIT clause:
SELECT acolumn FROM atable WHERE afield LIKE :param LIMIT 4;
In SQL Server you'd have to do something like:
SELECT * FROM (
SELECT column, ROW_NUMBER() OVER (ORDER BY name) as row FROM atable
) a WHERE a.row <= 4
This has the added benefit that less data is generated and transmitted by the database.
When doing the full search you simple omit the limit clause.
If you want to keep the results you already have and just add to extra results, use a
LIMIT 20 OFFSET 5 clause (without the offset keyword the operands are reversed LIMIT 5,20).
You always want to limit so as to make the experience snappy.
Every new page, you fetch the next x records.
You can even do this in real time, as the user is scrolling the list down.
Fetch new records as he nears the bottom of the list.
Well, this seems a little tricky (if not imposible). I'm trying to make my DBGrid sort its data by clicking on column's title.
The thing is that I'm (sadly) working with Delphi 3, I'm not using ADO DataSets and the query gets a lot of rows, thus I can't reopen my TQuery changing the order by clause on clicks.
Someone has implemented something like this?
This is actually done by sorting the dataset, and then the grid reflects the change. It can be done easily enough by creating an index on the dataset field for that column. Of course, this can only be done on a dataset that supports index sorting, such as TClientDataset.
On the TDBGrid's OnTitleClick method you can do something like...
procedure TfmForm1.DBGrid1TitleClick(Column: TColumn);
var
i: Integer;
begin
// apply grid formatting changes here e.g. title styling
with DBGrid1 do
for i := 0 to Columns.Count - 1 do
Columns[i].Title.Font.Style := Columns[i].Title.Font.Style - [fsBold];
Column.Title.Font.Style := Column.Title.Font.Style + [fsBold];
with nxQuery1 do // the DBGrid's query component (a [NexusDB] TnxQuery)
begin
DisableControls;
if Active then Close;
for i := 0 to SQL.Count - 1 do
if (Pos('order by', LowerCase(SQL[i])) > 0) then
//NOTE: ' desc' The [space] is important
if (Pos(' desc',LowerCase(SQL[i])) > 0) then
SQL[i] := newOrderBySQL
else
SQL[i] := newOrderBySQL +' desc';
// re-add params here if necessary
if not Active then Open;
EnableControls;
end;
end;
There are plenty of ways in which you could optimise this I'm sure however it depends on the capabilities of the components you use. The example above uses a query component though if you used a table component you'd change the index used instead of the 'order by' clause.
The handling of the SQL here is a very basic version. It does not handle things like SQL batch statements, resulting in possible multiple 'order by..' clauses or commented SQL statements i.e. ignoring bracketed comments "{..}" or single line comments "//"
Regards
Delphi 3 have TClientDataset. And TQuery can use explicitly created indexes on the database to order data on the IndexName property.
Here are some examples of how to do this: Sorting records in Delphi DBGrid by Clicking on Column Title .
As already mentioned, sorting is quite easy if you are using a TClientDataSet (cds.IndexFieldNames := Column.FieldName in the OnTitleClick of the TDBGrid). However if you are not able to do this you can either regenerate your query (which you have stated you don't want to do) or obtain a more advanced data grid such as Express Quantum Grid (which I think allows you to sort).
On the TDBGrid's OnTitleClick method you can write this simple code:
procedure TForm1.DBGrid3TitleClick(Column: TColumn);
var
cFieldName:string;
begin
cFieldName:= DBGrid3.SelectedField.FieldName;
AdoDataset1.Sort:=cFieldName;
end;
example: (https://www.thoughtco.com/sort-records-in-delphi-dbgrid-4077301)
procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
pt: TGridcoord;
begin
pt:= DBGrid1.MouseCoord(x, y);
if pt.y=0 then
DBGrid1.Cursor:=crHandPoint
else
DBGrid1.Cursor:=crDefault;
end;
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
var
cFieldName:string;
begin
Adotable1.Sort := Column.Field.FieldName;
end;
If you are using a combination of TFDQuery, TDataSource and TDBGrid you can order with this easy way!
procedure TFrmGer.DBGridTitleClick(Column: TColumn);
begin
OrderByTitle(MyFDQuery, Column);
end;
Put it in a helper file so you can use it latter again.
procedure OrderByTitle(AQuery: TFDQuery; Column: TColumn);
begin
AQuery.IndexFieldNames := Column.DisplayName;
end;
Ascending and Descending Mode
if Pos('DESC',PChar(Q2.Sort))>0 then
Q2.Sort:=Column.FieldName + ' ASC'
else
Q2.Sort:=Column.FieldName + ' DESC';
I've got an in-memory dataset with several fields, one of which is a primary key that another dataset references as a foreign key. Thing is, the master dataset can have multiple references to the detail dataset. (This is modeling an object that contains a dynamic array of other objects.)
If there was only one of each sub-object, I could make the right association with the KeyFields and LookupKeyFields properties of the reference field in the master dataset, but that's only designed to return one result. I want to load all the records whose primary key matches the right ID key and display them in a listbox.
I thought a TDBListBox would help with this, but it turns out that's not what they do. So how would I populate a listbox or similar control with the result set of a multiple-match check like that for further editing? I want something similar to the result of a SQL query like this:
select field1, field2, field3
from client_dataset
where client_dataset.primary_key = master_dataset.id
Only thing is, this is done entirely with in-memory datasets. No real databases are being used here. Does anyone know how this can be done?
The dataset has a Filter property which can be set with a condition. You also have to set the filtered flag on true. And with he datacontrols you can select which fields are visible.
So:
var
c : TColumn;
begin
clientdataset.Filter := Format('primary_key = %d', [master_dataset.id]);
clientdataset.Filtered := True;
c := DBGrid1.Columns.Add;
c.FieldName := 'field1';
c := DBGrid1.Columns.Add;
c.FieldName := 'field2';
c := DBGrid1.Columns.Add;
c.FieldName := 'field3';
end;
Should do the trick.