How do I sort a CSV file in a TTable? - delphi

I have a TTable, and I am loading CSV files to this TTable. Three fields are there: Id, Hits & Path.
I made some lookup fields to this TTable with another query.
I want to sort the table. I am getting the message "Capability not supported." when I try to call AddIndex('ndxHits','HITS',[]);
Here is my code:
with DM.TblCVResults do
begin
try
Active := False;
TableName := 'C:\CSV\123.txt';
Active := True;
AddIndex('ndxHits','HITS',[]);
AddIndex('ndxCandidate','LkCandidate',[]);
AddIndex('ndxLastCV','LkLastCV',[]);
AddIndex('ndxPostCode','LkPostCode',[]);
IndexDefs.Update;
Active := True;
DM.TblCVResults.IndexName := 'ndxHits';
except
on E: Exception do
MsgError(E.Message);
end;
end

Your previous question mentioned you were using ttASCII as the TableType. ttASCII tables, AFAIK, don't support indexes.
Your best bet is to load the ttASCII TTable content into a TClientDataset` (CDS), which does support indexes. I haven't tested with a ttASCII table as the source, but it should be as simple as:
Add a TDatasetProvider component to your application. Set it's DataSet property to your TTable.
Add a TClientDataSet component to your application. Set it's ProviderName to the DataSetProvider you added above. (I've named it CDS in the steps below.)
Open both the Table and the ClientDataSet (CDS), in that order.
Table1.Active := True;
CDS.Active := True;
Turn off updating of the TTable if you don't need it. (It's much faster.)
CDS.LogChanges := False;
Run the following code to create the indexes:
// Repeat for each additional index
with CDS.IndexDefs.AddIndexDef do
begin
Name := 'ndxHits';
Fields := 'Hits';
Options := [];
end;
Set the ClientDataSet's IndexName property to the index you want active:
CDS.IndexName := 'ndxHits';
Use the ClientDataSet like you would any other dataset. Search it using Locate or FindKey, add to it using Insert or Append, filter it, and so forth.

Related

Can one create an MS Access table in Delphi (e.g., FireDAC) from the field defs of an existing table without using SQL?

I wanted to create a new mdb file containing tables based on the structure of previously existing tables. I knew that I can use newTable.FieldDefs.Add() to recreate the fields one by one in a loop. But since there is already an oldTable fully stocked with the correct FieldDefs, that seemed terribly inelegant. I was looking for a single-statement solution!
I had found that newTable.FieldDefs.Assign(oldTable.FieldDefs) would compile (and run) without error but it left newTable with zero defined fields. This caused me to erroneously conclude that I didn't understand that statement's function. (Later I found that it failed only when oldTable.open had not occurred, which could not happen when the database was not available, even though the FieldDefs had been made Persistent and were clearly visible in the Object Inspector)
Here is my original code after some sleuthing:
procedure TForm2.Button1Click(Sender: TObject);
var
fname: string;
Table: TFDTable;
FDConn: TFDConnection;
begin
fname := 'C:\ProgramData\mymdb.mdb';
if FileExists(fname) then DeleteFile(fname);
{ Make new file for Table }
FDMSAccessService1.Database := fname;
FDMSAccessService1.DBVersion := avAccess2000;
FDMSAccessService1.CreateDB;
{ Connect to new file }
FDConn := TFDConnection.Create(nil);
FDConn.Params.Database := fname;
FDConn.Params.DriverID := 'MSAcc';
FDConn.Connected := true;
{ Set up new Table using old table's structure }
Table := TFDTable.Create(nil);
try
{ ADOTable1 has been linked to an existing table in a prior
database with Field Defs made Persistent using the Fields
Editor in the Object Inspector. That database will not be
available in my actual use scenario }
try
ADOTable1.open; // Throws exception when database file not found
except
end;
Table.Connection := FDConn;
{ specify table name }
Table.TableName := ADOTable1.TableName;
Table.FieldDefs.Assign(ADOTable1.FieldDefs); // No errors reported
ShowMessageFmt('New Table %s has %d fields',[Table.TableName,
Table.FieldDefs.Count]);
{ Reports correct TableName but "0 fields" with table not open
(i.e. file not found). Reports "23 fields" with table open }
{ Set Table definition into new mdb file }
Table.CreateTable(False); // Throws exception when 0 fields found
finally
Table.Free;
end;
end;
It turned out that the solution was to use a ClientDataSet originally linked to the same old database instead of the ADOTable. See the working solution below in my answer.
Edit: A final note. I had hoped to use this FireDAC approach, as indicated here, to get around the lack of a TADOTable.CreateTable method. Alas, although the "solutions" above and below do work to create a new TADOTable, that table's field definitions are not faithful replicas of the original table. There may be a combination of the myriad TFDTable options that would get around this, but I was not able to discover it so I reverted to creating my ADO tables with SQL.
Thanks to #Ken White's (unfortunately deleted) pointer, I now think that I have a solution to my original question about cloning the field defs from an old table into a newly created database. My original problem stemmed from the fact that the FieldDefs function for a table evidently does not return the actual stored field data if the table is not "open" (i.e., connected to the relevant database). Since my use scenario would not have a valid database available I could not "open" the table. However, ClientDataSets have an additional option to "StoreDefs" along with editor options to "Fetch Params" and "Assign Local Data". With those settings saved, the ClientDataSet renders its FieldDefs properties without being "open". Using that approach it seems that I can clone the stored field defs to a new table without needing a currently valid database to read them from. Thanks again, Ken, you saved a lot of my remaining hair! I sure wish that Embarcadero would do a better job of rationalizing their help files. They removed BDE from the default installation of Rio while still pointing in their help file discussion on creating Access tables to its TTable type as the way to create new tables and then never point to the equivalent capabilities in FireDAC (or elsewhere) which they continue to support. I wasted a lot of time because of this "oversight"!
Here is my working code after Ken's tip:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
fname: string;
Table: TFDTable;
FDConn: TFDConnection;
begin
fname := 'C:\ProgramData\mymdb.mdb';
if FileExists(fname) then DeleteFile(fname);
FDMSAccessService1.Database := fname;
FDMSAccessService1.DBVersion := avAccess2000;
FDMSAccessService1.CreateDB;
FDConn := TFDConnection.Create(nil);
FDConn.Params.Database := fname;
FDConn.Params.DriverID := 'MSAcc';
FDConn.Connected := true;
Table := TFDTable.Create(nil);
try
Table.Connection := FDConn;
{ specify table name }
Table.TableName := 'ATable';
{ The existingClientDataSet has been linked to a table in the
prior, no longer valid, database using StoreDefs, Fetch Params,
and Assign Local Data in the Object Inspector }
Table.FieldDefs.Assign(existingClientDataSet.FieldDefs);
ShowMessageFmt('New Table has %d fields', [Table.FieldDefs.Count]);
Table.CreateTable(False);
finally
Table.Free;
end;

TStringGrid show (bcd) in delphi live binding

I connect TFDQuery with TStringGrid in live binding in delphi firemonkey apps.
I tried to use filter in TFDQuery based on Editbox for searching purpose, and it's work just fine.
but, whenever I clear the Editbox, one of my row in TStringGrid would show "(bcd)" as it's value like the pict below.
what am I doing wrong ? how can I fix it ?
Edit :
Im using mySql database with firedac tfdconnection + tfdquery
the datatype of the column is AnsiString & FmtBCS(32,0)
Im using live binding feature in delphi.
my filter code
with MainQuery do begin
Filtered := False;
OnFilterRecord := nil;
Filter := FilterValue;
Filtered := True;
end;
I Insert to the table with TFDConnection.execSQL
the "(BCD)" part always change on the selected Row as the pict below.
EDIT 2:
To Reproduce my error, you can :
add TStringGrid.
Add Editbox.
add tfdconnection
add tfdquery
use live binding from tfdquery to tstringgrid.
add query to tfdquery.sql.text that using SUM() in mysql. Example : "select id, sum(stock) as total_stock from stocks"
activate that tfdquery
add onkeyup event on editbox.
add this code :
FilterValue:= 'garmines_id LIKE ''/' +Edit1.Text+'%'' ESCAPE ''/'' ';
with FDQuery1 do begin
Filtered:= false;
OnFilterRecord := nil;
Filter := FilterValue;
Filtered := True;
end;
run
try to type something on editbox to make sure filter works fine.
clear editbox, then the "(BCD)" is show on the selected row.
I reproduce this error. this is the SS :
Well, I still don't know what exactly causing this problem but I found the work around solution that avoid this problem appears.
you need to set TStringGrid.selected value to -1 before refreshing the TFDQuery. so the code become :
FilterValue:= 'garmines_id LIKE ''/' +Edit1.Text+'%'' ESCAPE ''/'' ';
StringGrid1.selected := -1;
with FDQuery1 do begin
Filtered:= false;
OnFilterRecord := nil;
Filter := FilterValue;
Filtered := True;
end;
I suspect that the cause of this problem is data type that come from mysql sum() method namely FmtBCD(32)
Go to DataMapping Rules (firedac connection)
Mark ignore inherited rules
create 2 new rules
rule1: source: dtBCD / target datatype: dtDouble / all min: 0 / all max: 100
rule2: source: dtfmtbcd / target datatype: dtDouble / all min: 0 / all max: 100
click ok. now the fields will be dtDouble, and are compatible with tgrid

how to copy a ftBlob with firedac arrayDML

I use Delphi Berlin.
I am trying to make a function/procedure to copy data from a FireDac query (connected to a database) to another FireDac query (connected to another database) using ArrayDML. first database is firebird and the other is MSSQL in the first care, but in another case both databases are Firebird.
So far so good and almost all datatype are working correct except ftBlob.
Here is the body of the function:
while not querySource.Eof do begin
paramPosition := -1;
Inc(mIndex);
for i := 0 to querySource.FieldCount - 1 do begin
Inc(paramPosition);
// daca exista o valoare
if querySource.FieldByName(querySource.Fields[i].FieldName).AsVariant <> Null then begin
case querySource.Fields[i].DataType of
ftDateTime, ftDate, ftTime, ftTimeStamp : queryInsert.Params[paramPosition].AsDateTimes[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsDateTime;
ftFloat, ftCurrency, ftBCD, ftFMTBcd : queryInsert.Params[paramPosition].AsFloats[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsFloat;
ftSmallint, ftInteger, ftLargeint : queryInsert.Params[paramPosition].AsIntegers[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsInteger;
ftString : queryInsert.Params[paramPosition].AsStrings[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsString;
ftBlob, ftMemo, ftGraphic : queryInsert.Params[paramPosition].AsBlobs[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsVariant;
end;
end;
end;
the blob value is not copy the correct value from the source.
how to use the arrayDML in this case? any workaround?
I won't answer your question but suggest you to use the TFDBatchMove component because you are reinventing wheel here. TFDBatchMove is just for what you want to do, for moving data from one database to another (and not only that).
You simply setup the TFDBatchMoveSQLReader as Reader and Writer, write SQL queries for both and the component will automatically map the fields by matching names. If the queries won't have matching field names, you can fine tune this by the Mappings property. Then you just call Execute.

How to get value of TDBLookupComboBox?

I have tables on my database Tb_barang and Tb_jenis. Tb_jenis the following columns kd_jenis (primary key) and jenis. I use TDBLookupComboBox to show the items from the Tb_jenis table. There are 6 items (Mie, Susu, etc). I want to save item selected as kd_jenis not jenis.
How to I save jenis in table to kd_jenis?
Sample data: Tb_jenis
jenis kd_jenis
Mie J1
Susu J2
Here it the code I've tried.
if (kd.Text='') or (jenis.Text='Pilih') then
ShowMessage('Data Tidak Lengkap, Silakkan Dilengkapi !')
else
begin
with dm.Qbarang do
begin
sql.Clear;
SQL.Add('select * from Tb_barang where kd_barang='+quotedstr(kd.text)+'');
open;
end;
if DM.Qbarang.Recordset.RecordCount > 0 then
ShowMessage('Data Sudah Ada, Silakkan Isi Yang Lain!')
else
begin
try
DM.koneksi.BeginTrans;
with DM.QU do
begin
close;
SQL.Clear;
SQL.Add('insert into Tb_barang values('+QuotedStr(kd.Text)+','
+QuotedStr(jenis.Text)+')');
ExecSQL;
end;
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
end;
end;
As i understand you want to get Key Value of table from DBLookupComboBox.
so in it is 2 important property in DBLookupComboBox ListField and KeyField
if you set them correctly For Example KeyField set as kd_jenis and List Field Set as jenis then you will see Jenis on List and you can access to jenis in DBLookupCombobox.text and also you can access to KeyField (in this case kd_jenis) by DBLookupCombobox.KeyValue
you should fallow this steps.
Put a TADOConnection to your form for connecting to a database.
build connection string for connecting to Database.
add a ADO table to your form.
Set Connection property to AdoConnection1(Default Name Of ADOCOnnection).
On ADOTable open the Table name Property and select One of your database table and then set active property as true.
Add a DataSource to your form and set Dataset Property as ADOTable1(Default Name of ADOTable)
Add DBLookupCombobox to your form and Set ListSource as DataSource1(the default name of datasource)
Open ListField Property and Select witch field do you want to show in Combobox.
Open the keyField Property and Select witch field is Key field.
All of above Steps should done at Design time. for testing your application add to edit box on your form as edit1 and edit2 then write a small code for OnCloseUp Evwnt of DBLookupCombobox like this
Edit1.Text:=DBLookUpCombobox1.KeyValue;
Edit2.Text:=DBLookupCombobox1.Text;
If you have problems with DBLookupComboBox you may use normal combobox and fill it like the example here.
After someone selects something in your DBLookupComboBox...The cursor to the table is moved.
You can access the value directly:
jenis.ListSource.DataSet.FieldByName('kd_jenis').AsString
This assumes that jenis is a TDBLookupComboBox.
That ListSource.DataSet points to Tb_jenis.
As Craig stated...you have a serious bug in your code...you need a RollbackTrans on exception...other wise you will never release the lock...
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.koneksi.RollbackTrans;
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
I do something like this...if I need to guarantee saving info and rolling back if fails.
procedure TForm8.SaveData;
begin
Assert(not ADOQuery1.Connection.InTransaction, 'Code problem-We should not be in a Transaction');
ADOQuery1.Connection.BeginTrans;
try
ADOQuery1.ExecSQL;
ADOQuery1.Connection.CommitTrans;
finally
if ADOQuery1.Connection.InTransaction then
begin
{If you are here...your in an ErrorState...and didn't Commit your Transaction}
ADOQuery1.Connection.RollbackTrans;
HandleError(ADOQuery1);
end;
end;
end;

how to show Only relevant information in dbgrid delphi

information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47

Resources