Removing duplicate from TStringList - delphi

I am parsing a dataset and assigning values to TStringList i want to avoid the duplicates. I use the following code but still duplicates are inserted.
channelList := TStringList.Create;
channelList.Duplicates := dupIgnore;
try
dataset.First;
while not dataset.EOF do
begin
channelList.Add(dataset.FieldByName('CHANNEL_INT').AsString) ;
dataset.Next;
end;
why does the duplicates added?
http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TStringList.Duplicates

You did read http://docwiki.embarcadero.com/Libraries/XE2/en/System.Classes.TStringList.Duplicates , didn't you ?
Then you missed the most repeated word there - "sorted"
channelList.Sorted := true
var F: TField;
channelList := TStringList.Create;
channelList.Sorted := True;
channelList.Duplicates := dupIgnore;
try
dataset.First;
F := dataset.FieldByName('CHANNEL_INT');
while not dataset.EOF do
begin
channelList.Add(F.AsString);
dataset.Next;
end;

Think out of the box and avoid the duplicates up front?
I don't know what DB you are using but for example on SQL server it is just a matter of querying:
'SELECT DISTINCT CHANNEL_INT FROM MYTABLE';
and then you can add the results to your TStringList without being worried about duplicates.

Related

How to select a specific row from ClipBoard

I have copy text from multiple TEdit using sLineBreak, and I would like to paste the clipboard row by row in other TEdit
Thank you
You can use a TStringList to convert Clipboard.AsText to a list of separate lines. Then get individual lines from the string list using the strings index. For example:
procedure TForm21.Button1Click(Sender: TObject);
var
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Text := clipboard.AsText;
Edit1.Text := sl[2];
finally
sl.Free;
end;
end;

How to get number of records of a table stored in an Access .MDB file?

I need the RcordCount of a TADOTable before opening it. So I run this query:
ADOQuery1.SQL.Text := ‘SELECT Count(*) AS Record_Count FROM Table1’;
ADOQuery1.Open;
But this takes 9.7s (average of 10 runs) on my PC for a table consisting of over 5 million records. This time seems nothing compared to even touching a table with this size, but I think this kind of data should be stored somewhere in the .mdb file. Is there any other way to get RecordCount of a table directly from the file?
I'm using Delphi XE-5 and the file is in Microsoft Access 2003 format.
No, SELECT Count(*) is the only and the correct way to do it.
But: almost 10s for this query is awfully slow, especially if this is on your local PC.
Does the table have a Primary Key? If yes, what data type is it?
I created a simple table with an Autonumber Primary Key and inserted 5 million records.
SELECT COUNT(*) FROM tBig takes less than 1/10 seconds to execute.
Well, I searched a little more and found it. You can read the number of records of a table directly from the schema:
function ReadRecordCountFromSchema(const Connection: TADOConnection;
const TableName: string): Integer;
var
CardinalityField,
NameField: TField;
DataSet: TADODataSet;
begin
DataSet := TADODataSet.Create(nil);
Result := -1;
try
Connection.OpenSchema(siStatistics, EmptyParam, EmptyParam, DataSet);
CardinalityField := DataSet.FieldByName('CARDINALITY'); { do not localize }
NameField := DataSet.FieldByName('TABLE_NAME'); { do not localize }
while not DataSet.EOF do
begin
if CompareText(NameField.AsString, TableName) = 0 then
begin
Result := CardinalityField.AsInteger;
Break;
end;
DataSet.Next;
end;
finally
DataSet.Free;
end;
end;
Actually TADOConnection.GetTableNames gave me the idea.
Edit:
As Andre451 mentioned, it's better to pass TableName to OpenSchema as a restriction:
function ReadRecordCountFromSchema(const Connection: TADOConnection;
const TableName: string): Integer;
var
DataSet: TADODataSet;
begin
DataSet := TADODataSet.Create(nil);
Result := -1;
try
Connection.OpenSchema(siStatistics,
VarArrayOf([Unassigned, Unassigned, TableName]),
EmptyParam, DataSet);
if DataSet.RecordCount > 0 then
Result := DataSet.FieldByName('CARDINALITY').AsInteger;
finally
DataSet.Free;
end;
end;
I agree with Andre451. The only other thing I would say is that you should specify the field in the Count statement instead of using the *.
SELECT COUNT(PrimaryKey) FROM tBig; would do it.

FireDac query not reading large integers correctly

I am trying to query a database using FireDac. Here is my code;
procedure TfSMSViewer.LoadSMSFromDatabase(path: AnsiString);
var
con: TFDConnection;
query: TFDQuery;
LI: TListItem;
B: Int64;
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := False;
con.Open('DriverID=SQLite;Database=' + Path +' ;');
query.Connection := con;
query.SQL.Text := 'SELECT * FROM sms';
query.Open;
query.First;
While Not Query.EOF Do
Begin
LI := ListView1.Items.Add;
LI.Caption := inttostr(query.Fields[4].AsLargeInt); //This line
if query.FieldValues['type'] = 1 then
LI.SubItems.Add('Incoming')
else
LI.SubItems.Add('Outbound');
LI.SubItems.Add(query.FieldValues['address']);
LI.SubItems.Add(query.FieldValues['body']);
Query.Next;
End;
end;
However the line highlighted doesn't work correctly. In the database, an example value set in this column is 1418421520957 (a UNIX timestamp).
When that line of code is executed, the result is 1082313277.
The data type in the SQLite database is set to Integer. The freeware software I'm using to debug this shows the correct value. When debugging my code, the incorrect value is pulled from the database before any assignment is made.
Also some of the values populated in my TListView are negated.
Does TFDQuery not support large integers? How can I fix this?
Thanks
This is how I fixed it, with suggestions from TLama. Maybe this will be useful for someone, or my future self.
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
with query.FormatOptions do begin
OwnMapRules := True;
with MapRules.Add do begin
SourceDataType := dtInt32;
TargetDataType := dtInt64;
end;
end;
'BIGINT' for your Database column can solve this problem

What is the most elegant way to reposition after filtering a TClientDataset

I'm working on a TClientDataset that the user can filter at any time based on some criterias. My problem is, we'd like the dataset's cursor to remain positionned "mostly" at the same place after filtering. ("Mostly" in double quote since, of course, it can't stay at the same place if the record is filtered out).
After doing some research, the best I could come up with is the following :
procedure RefreshFilter;
var
I : Integer;
sFilter : string;
vIndexValue: array of TVarRec;
vIndexValueAsVar : Array of Variant;
begin
sFilter := GenerateNewFilterExpression;
if sFilter <> MyDataset.Filter then
begin
if MyDataset.IndexFieldCount > 0 then
begin
SetLength(vIndexValueAsVar, MyDataset.IndexFieldCount);
SetLength(vIndexValue, MyDataset.IndexFieldCount);
for I := 0 to MyDataset.IndexFieldCount - 1 do
begin
vIndexValueAsVar[I] := MyDataset.IndexFields[I].AsVariant;
vIndexValue[I].VType := vtVariant;
vIndexValue[I].VVariant := #vIndexValueAsVar[I];
end;
end;
MyDataset.Filtered := sFilter <> '';
Mydataset.Filter := sFilter;
if MyDataset.IndexFieldCount > 0 then
begin
MyDataset.FindNearest(vIndexValue);
end;
end;
end;
Even though it works pretty well, I find the solution a bit "bulky". I was wondering if there was a some built-in function or a different approach that might be more elegant and less "heavy".
And please, don't mention bookmarks... Bookmarks don't work properly after changing the active filter, and not at all if your record gets filtered out.

Remove duplicates from combobox

Say i have a combobox with
apples
apples
pears
oranges
oranges
i would like to have it show
apples
pears
oranges
how can i do this?
for iter := combobox.Items.Count - 1 downto 0 do
begin
index := combobox.Items.IndexOf(combobox.Items[iter]);
if index < iter then
combobox.Items.Delete(iter);
end;
I suggest that you simply refill the combo box each time. That makes the logic simpler:
ComboBox.Items.BeginUpdate;
try
ComboBox.Clear;
for Str in Values do
begin
if ComboBox.Items.IndexOf (Str) = -1 then
ComboBox.Items.Add (Str);
end;
finally
ComboBox.Items.EndUpdate;
end;
Just to put methods against eachother: one keeps the order but is increasingly slow with larger number of items. The other stays relatively faster but doesn't keep order:
procedure SortStringlist;
var
i,index,itimer: integer;
sl : TStringlist;
const
numberofitems = 10000;
begin
sl := TStringlist.Create;
for i := 0 to numberofitems-1 do begin
sl.Add(IntToStr(random(2000)));
end;
Showmessage(IntToStr(sl.Count));
itimer := GetTickCount;
sl.Sort;
for I := sl.Count-1 downto 1 do begin
if sl[i]=sl[i-1] then sl.Delete(i);
end;
Showmessage(IntToStr(sl.Count)+' Time taken in ms: '+IntToStr(GetTickCount-itimer));
sl.free;
sl := TStringlist.Create;
for i := 0 to numberofitems-1 do begin
sl.Add(IntToStr(random(2000)));
end;
Showmessage(IntToStr(sl.Count));
itimer := GetTickCount;
for i := sl.Count - 1 downto 0 do
begin
index := sl.IndexOf(sl[i]);
if index < i then
sl.Delete(i);
end;
Showmessage(IntToStr(sl.Count)+' Time taken in ms: '+IntToStr(GetTickCount-itimer));
end;
If you don't care if the items get reordered (or they're sorted already), TStrings can do the work for you - it eliminates all of the looping, deletion, and other work. (Of course, it requires the creation/destruction of a temporary TStringList, so if that's an issue for you it won't work.)
var
SL: TStringList;
begin
ComboBox1.Items.BeginUpdate;
try
SL := TStringList.Create;
try
SL.Sorted := True; // Required for Duplicates to work
SL.Duplicates := dupIgnore;
SL.AddStrings(ComboBox1.Items);
ComboBox1.Items.Assign(SL);
finally
SL.Free;
end;
finally
ComboBox1.Items.EndUpdate;
end;
end;
To properly compare with Igor's answer (which includes no BeginUpdate/EndUpdate), remove those things:
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.Sorted := True; // Required for Duplicates to work
SL.Duplicates := dupIgnore;
SL.AddStrings(ComboBox1.Items);
ComboBox1.Items.Assign(SL);
finally
SL.Free;
end;
end;
You have to remove duplicates from the source data.
In most scenarios, a ComboBox is filled with data in run-time, which means, data is coming from some source. There are basically 2 scenarios here: a dataset from database and a collection of strings from any other source. In both cases you filter out duplicates before inserting anything into the ComboBox.
If source is a dataset from database, simply use the SQL DISTINCT keyword.
If source is any collection of strings, use a peace of code provided in the answer by #Smasher.
I faced this problem several times before, and i used all the previous approaches and I'm still using them, but do you know : i think the best approach , though not mentioned here, is to subclass TComboBox, creating a new method (say AddUnique ) that add the string to the combo ONLY if it does not exist previously , otherwise it will drop it.
This solution may cost some extra time in the beginning , but it will solve the problem once and for all.

Resources