Change column name of a dbf file - delphi

I have this items in my dbase file (.dbf)
INDICE NOME COR ESTILO ESCALA
100 SAOJOAO 18 0,00
I need to change column name of INDICE to ID, so I use this code:
while not ADOQuery1.Eof do
begin
Adoquery1.Edit;
ADOQuery1.FieldByName('NOME').TEXT:= 'ID';
Adoquery1.Post;
ADOQuery1.Next;
end;
When I run the above I get these results:
INDICE NOME COR ESTILO ESCALA
ID SAOJOAO 18 0,00
Connection string used:
Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=C:_workspace\projects\DBFEditor\te‌​mp
I have a system that need import dbf file and only recognize a file which have id column name.

The demo project below shows a way to do what you seem to want. I don't claim that
it's the most efficient way or the best way, but it's probably as simple as you're likely to get.
If you were wanting just to change the displayed name of a field in a Delphi application, for example in the column header of a DBGrid, you could do that by changing the DisplayLabel property of the field in question (AdoQuery1.FieldByName('INDICE').DisplayLabel := 'ID'), as I said in a comment earlier. But in your latest edit, it seems that what you actually want to do is to change the name of the INDICE column as it seen by a program reading the datafile to ID. To do that, you have to make an alteration to the on-disk structure of your .DBF file. This is what my code below does.
It uses a User DSN set up for the MS ODBC driver for dBase files as the target of the AdoConnection's connection string.
Ideally, I would have liked the find a flavour of the ALTER TABLE Sql statement
which would simply rename the INDICE column, but the MS dBase driver doesn't seem
to support that, because it generated an exception when I tried. So instead, my code works by making a copy of the table and its contents, with the INDICE column renamed to ID.
In short, the program
Creates a table MATest with a first column named INDICE and a couple of other columns and inserts a single row into it. This is just to set up a table to work from.
Creates a second table MATest2 with the same structure as the MATest one, except that the first
column is named ID rather than INDICE.
Populates the MATest2 table by copying all the rows from MATest, using an INSERT INTO Sql statement.
The important steps for what you want to do are carried out in the btnCreateTableCopyClick
procedure. Note that you will have to comment out the first two lines, which drop the
table MATest2 the first time you run the app, otherwise it will complain, cryptically,
that MATest2 can't be dropped because it doesn't exist.
I leave it to you to adapt the code as necessary to your data.
Code:
type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
btnCreateSrcTable: TButton;
ADOQuery1: TADOQuery;
btnOpenSrcTable: TButton;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnDropTable: TButton;
btnCreateTableCopy: TButton;
procedure btnCreateSrcTableClick(Sender: TObject);
procedure btnDropTableClick(Sender: TObject);
procedure btnOpenSrcTableClick(Sender: TObject);
procedure btnCreateTableCopyClick(Sender: TObject);
private
protected
public
procedure CreateSourceTable;
end;
[...]
procedure TForm1.btnCreateTableCopyClick(Sender: TObject);
var
Sql : String;
begin
Sql := 'drop table MATest2';
AdoConnection1.Execute(Sql);
Sql := 'create table MATest2(ID int, AName char(20), AValue char(20))';
AdoConnection1.Execute(Sql);
Sql := 'insert into MATest2 select INDICE, AName, AValue from MATest';
AdoConnection1.Execute(Sql);
end;
procedure TForm1.btnCreateSrcTableClick(Sender: TObject);
begin
CreateSourceTable;
end;
procedure TForm1.btnDropTableClick(Sender: TObject);
var
Sql : String;
begin
// Sql := 'drop table MATest';
// AdoConnection1.Execute(Sql);
end;
procedure TForm1.btnOpenSrcTableClick(Sender: TObject);
begin
AdoQuery1.Open;
end;
procedure TForm1.btnCreateTableCopyClick(Sender: TObject);
var
Sql : String;
begin
Sql := 'drop table MATest2';
AdoConnection1.Execute(Sql);
Sql := 'create table MATest2(ID int, AName char(20), AValue char(20))';
AdoConnection1.Execute(Sql);
Sql := 'insert into MATest2 select INDICE, AName, AValue from MATest';
AdoConnection1.Execute(Sql);
end;
procedure TForm1.CreateSourceTable;
var
Sql : String;
begin
Sql := 'create table MATest(INDICE int, AName char(20), AValue char(20))';
AdoConnection1.Execute(Sql);
Sql := 'insert into MATest(INDICE, AName, AValue) values(1, ''aaa'', ''vvv'')';
AdoConnection1.Execute(Sql);
end;
Obviously it would be better to generate your data with the ID fieldname in the first place and avoid all this, but presumably there is a good reason why you can't.

Related

Merge two Firebird databases from Delphi

I'm using the Delphi Sydney version and the database is Firebird 2.5.
I have two databases with identical structures, but it's not 100% the same data.
My strategy is to create two FDConnections and two FDQueries, but I don't know how to get data from one table and insert it into another without creating a TYPE and complete structure for every table and sending every field. Datasource/FDTable?
procedure TForm1.Button2Click(Sender: TObject);
begin
Memo1.Lines.Clear;
DM_Data.FDQryDatabase.Close;
DM_Data.FDQryDatabase.SQL.Clear;
DM_Data.FDQryDatabase.SQL.Add('SELECT * FROM DOCUMENTI_FAMIGLIA');
DM_Data.FDQryDatabase.Open;
while not(DM_Data.FDQryDatabase.Eof) do
begin
Memo1.Lines.Add(DM_Data.FDQryDatabase.FieldValues['FDOC_IDFAMIGLIA']);
DM_Data.FDQryDatabase.Next;
end;
Memo1.Lines.Add('--------------------');
DM_Data.FDQuerySource.Close;
DM_Data.FDQuerySource.SQL.Clear;
DM_Data.FDQuerySource.SQL.Add('SELECT * FROM DOCUMENTI_FAMIGLIA');
DM_Data.FDQuerySource.Open;
while not(DM_Data.FDQuerySource.Eof) do
begin
Memo1.Lines.Add(DM_Data.FDQuerySource.FieldValues['FDOC_IDFAMIGLIA']);
// Insert
DM_Data.FDQuerySource.Next;
end;
end;
I would like to know how to migrate from one database to another using Delphi objects making a shorter solution. FDQryDatabase will be the primary database, FDQuerySource will be added to FDQryDatabase. I don't have a problem doing table-by-table.
Some tables have 17 thousand of data; I will need to include them without duplicate data and this process runs every day until the system migrates to another (I don't know when).
You can add a flag to know what you already move :
procedure TForm1.Button1Click(Sender: TObject);
var
LList: TList<string>;
i: integer;
begin
LList := TList<string>.Create;
try
FDQryDatabase.Close;
FDQryDatabase.SQL.Clear;
FDQryDatabase.SQL.Add('SELECT * FROM DOCUMENTI_FAMIGLIA');
FDQryDatabase.Open;
while not FDQryDatabase.Eof do
begin
LList.Add(FDQryDatabase.FieldValues['FDOC_IDFAMIGLIA']);
FDQryDatabase.Next;
end;
FDQuerySource.SQL.Clear;
for i := 0 to LList.Count - 1 do
begin
if i mod 500 = 0 then
begin
FDQuerySource.ExecSQL;
FDQuerySource.SQL.Clear;
end;
FDQuerySource.SQL.Add('INSERT INTO DOCUMENTI_FAMIGLIA (FDOC_IDFAMIGLIA)');
FDQuerySource.SQL.Add(' SELECT ' + LList.Items[i]);
FDQuerySource.SQL.Add(' FROM DOCUMENTI_FAMIGLIA');
FDQuerySource.SQL.Add(' WHERE NOT EXISTS (SELECT 1 FROM DOCUMENTI_FAMIGLIA df WHERE df.FDOC_IDFAMIGLIA = ' + LList.Items[i] + ');');
end;
if FDQuerySource.SQL.Count > 0 then
FDQuerySource.ExecSQL;
finally
FDQryDatabase.Close;
FreeAndNil(LList);
end;
end;

BDE to FireDAC: pack table, regenerate index

An old program of ours uses dBase tables and an .MDX index - other systems use these tables too, so we're stuck with them. We wish to replace BDE with FireDAC in our software. It seems that BDE methods DbiRegenIndex and DbiPackTable (regenerate index and pack table, respectively) are not provided by FireDAC - is there a way to perform these functions using FireDAC?
The code below shows how to index a dBase table using the MS dBase driver. I've
used the Ado components, rather than FireDAC because it is easier to set up all
their properties in code, so you can see what I'm doing. Note that as well as CREATE INDEX
the driver also supports DROP INDEX. See e.g. https://learn.microsoft.com/en-us/sql/odbc/microsoft/create-index-for-paradox
(which is for Paradox, but works for dBase as well)
To set yourself up for this project, you need to set up an ODBC system DSN called DBFTest using
the MS dBase driver.
It should be straightforward to translate this Ado example into FireDAC.
type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
btnCreateTable: TButton;
ADOQuery1: TADOQuery;
btnOpenTable: TButton;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnDropTable: TButton;
btnAddIndex: TButton;
procedure FormCreate(Sender: TObject);
procedure btnAddIndexClick(Sender: TObject);
procedure btnCreateTableClick(Sender: TObject);
procedure btnDropTableClick(Sender: TObject);
procedure btnOpenTableClick(Sender: TObject);
public
procedure CreatedBaseTable;
end;
[...]
procedure TForm1.FormCreate(Sender: TObject);
begin
AdoConnection1.ConnectionString := 'Provider=MSDASQL.1;Persist Security Info=False;Data Source=DBFTest';
end;
procedure TForm1.btnAddIndexClick(Sender: TObject);
var
Sql : String;
begin
if AdoQuery1.Active then
AdoQuery1.Close;
Sql := 'create index byID on dBaseTest (ID)';
AdoConnection1.Execute(Sql);
AdoQuery1.Open;
end;
procedure TForm1.btnCreateTableClick(Sender: TObject);
begin
CreatedBaseTable;
end;
procedure TForm1.btnDropTableClick(Sender: TObject);
var
Sql : String;
begin
Sql := 'drop table dBaseTest';
AdoConnection1.Execute(Sql);
end;
procedure TForm1.btnOpenTableClick(Sender: TObject);
begin
AdoQuery1.SQL.Text := 'select * from dBaseTest';
AdoQuery1.Open;
end;
procedure TForm1.CreatedBaseTable;
var
Sql : String;
i : Integer;
begin
Screen.Cursor := crSqlWait;
Update;
try
Sql := 'create table dBaseTest(ID int, AName char(20))';
AdoConnection1.Execute(Sql);
for i := 1 to 100 do begin
Sql := Format('insert into dBaseTest(ID, AName) values(%d, ''%s'')', [i, 'Name' + IntToStr(i)]);
AdoConnection1.Execute(Sql);
end;
finally
Screen.Cursor := crDefault
end;
end;
Obviously, to "regenerate" the indexes this way, you would just drop them if they exist, handling any exceptions if they don't, and then create them again.
I don't know whether the dBase driver supports a "pack table" command, but you could probably do this yourself using an INSERT INTO ... SELECT * FROM ..." to copy the active rows into temporary table, then delete all rows from your working table, then copy them back from the temporary one.

How to clone a dataset structure (TFDQuery) in Delphi 10?

Can anyone help me to clone a TFDQuery in run-time? I'm coding in Delphi Tokyo , I have a Datamodule with a TFDQuery in which I defined all fields properties using Fields Editor at design time, in this way my DBGrid1 that points to this a Datamodule of this dataset, has all columns properly formated (dislay names, width, format, order). During run-time I need to create new instances of TFDQuery, TDatamodule and link these new objects with the Dbgrid1. I need this new TFDQuery be identiical to the existing one defined at design-time in order to keep DBgrid1 with same display names, display width and display formats as the design-time!
I tried the following approaches to copy dataset field definitions :
**1st Approach : Method Assign for TFDQuery (didn't work) **
type
TFormDados = class(TForm)
Edit1: TEdit;
Button1: TButton;
DBGrid1: TDBGrid;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
vconnection : TFDConnection;
vdataset : TFDQuery;
vdatasource : Tdatasource;
public
{ Public declarations }
end;
var
FormDados: TFormDados;
implementation
{$R *.dfm}
Uses
unitdata;
procedure TFormDados.Button1Click(Sender: TObject);
var
i : integer;
begin
vconnection := TFDConnection.Create(nil);
vconnection.Assign(Dtmodule.FDConGrafico);
vdataset := TFDQuery.Create(nil);
vdataset.Connection := vconnection;
vdataset.Assign(Dtmodule.FDQueryDados); // Runtime Error : Cannot assign a TFDQuery to a TFDQuery
2nd Approach : Assign FieldDefs from the existing Dataset to the new one - didn't work !
...
vdataset.FieldDefs.Assign(Dtmodule.FDQueryDados.FieldDefs);
vdataset.sql := Dtmodule.FDQueryDados.sql;
vdataset.params := Dtmodule.FDQueryDados.Params;
vdataset.FieldDefs.Update;
vdataset.CreateDataSet;
vdatasource := Tdatasource.create(nil);
vdatasource.DataSet := vdataset;
dbgrid1.DataSource := vdatasource;
vdataset.close;
vdataset.Params[0].Asinteger := strtoint(edit1.Text);
vdataset.Params[1].Asinteger := strtoint(edit2.Text);
vdataset.Open;
Althought Assign method had run, vdataset didn't receive the fields definitions of the existing FDQquery . After open the vdataset , DBGrid1 did not show the columns sequence, labels and formats fro the source dataset , WHY ?
3rd Approach - Copy fields definition, one by one - didn't work
for i:=0 to Dtmodule.FDQueryDados.Fields.Count -1 do
begin
with vdataset.FieldDefs.AddFieldDef do
begin
Name := Dtmodule.FDQueryDados.FieldDefs[i].Name;
Datatype := Dtmodule.FDQueryDados.FieldDefs[i].DataType;
Displayname := Dtmodule.FDQueryDados.FieldDefs[i].Displayname;
Fieldno := Dtmodule.FDQueryDados.FieldDefs[i].FieldNo;
end;
end;
vdataset.FieldDefs.Update;
vdataset.CreateDataSet;
vdatasource := Tdatasource.create(nil);
vdatasource.DataSet := vdataset;
dbgrid1.DataSource := vdatasource;
...
This code lead to the same result as the approach 2nd, i.e., it run but after opened vdataset , DBGrid1 did not show the columns sequence, labels and formats fro the source dataset.
I appreciate your help to fix the above code OR to implement the right method to copy dataset fields definitions from one existing dataset to a new one.
Thank you all in advance !
When you use the Fields editor for queries you are creating Fields not FieldDefs. From what I can tell the FieldDefs are kept in sync with the FieldsCollection when the component is created (or maybe opened not 100% sure). The Display* properties are not available on the FieldDef object - they only exist on the Field object. When you go to copy the structure you need to iterate the fields. The the method we use is below.
Note that the loop and the items created are "Fields", but we use a temporary FieldDef object to make the code simpler. The TFieldDef.CreatField serves as a class factory method to get the correct type of field i.e. TIntegerField vs TStringField. Also if you are using calculated fields you will need to hookup the OnCalcField event. This method does not do that.
procedure CopyFieldStructure(Source: TDataSet; Target: TDataset);
{^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^}
var
Field: TField;
NewField: TField;
FieldDef: TFieldDef;
begin
Target.Fields.Clear;
Target.FieldDefs.Clear;
// Cannot perform the next operation on an opened dataset
if Target.State <> dsInactive then
Target.Close;
for Field in Source.Fields do
begin
// We are going to setup the first part in a FieldDef
// that will set us use the CreateField Call in order to
// get the correct subclass of TField created.
FieldDef := Target.FieldDefs.AddFieldDef;
FieldDef.DataType := Field.DataType;
FieldDef.Size := Field.Size;
FieldDef.Name := Field.FieldName;
NewField := FieldDef.CreateField(Target);
NewField.Visible := Field.Visible;
NewField.DisplayLabel := Field.DisplayLabel;
NewField.DisplayWidth := Field.DisplayWidth;
NewField.EditMask := Field.EditMask;
NewField.Calculated := Field.Calculated;
end;
end;
Here is a similar StackOverflow question. I think this is where I originally took my code from: Is there some better way to copy all DataSet Fields and their properties to another DataSet?
And here is one other blog post that uses a similar approach: How to: Clone TField and TDataset fields structure
Also don't get fooled by the TDataSet.CopyField method. The help makes it seem like it could copy the field structure. When really it copies the current field "values" for any matching field names.

Clear a field when another field data doesn't exist

I have a Field named "refFile" which may or may not have a Path description in it.
I need to go through the entire database and check whether Paths that are defined in "refFile" still actually exist.
Using Delphi (Pascal) this takes many, many minutes
bigDB.First;
while not(bigDB.EOF) do
begin
if Trim(bigDB.FieldByName('refFile').AsString) > '' then
begin
if not(FileExists(bigDB.FieldByName('refFile').AsString)) then
begin
bigDB.Edit;
bigDB.FieldByName('refFile').AsString:='';
bigDB.Post;
end;
end;
bigDB.Next;
end;
How do I do that in SQL?
Thank you.
You cannot check the validity of a path in SQLLite but you can filter records with something in the path and reduce the list of lines to check.
You can order the records on this field (if you have an index on it) and check only the paths you didn't checked before.
You also can use threads to do this long operation in background. Simply use TThread.Createanonymousthread(procedure begin end).Start;
You can't check the existence of a file in a plain SQLLite query. You could do that by using an UDF (User defined function) but it would be a little more complex and would requires some skills in other programming languages (Note that in that case your files should be accessible from the server, otherwise it wouldn't work).
If you are looking for a simpler solution, I think you can speed up your program by reducing the number of records resulted by the query and by improving your Delphi code in order to make it a little more efficient.
Select SQL:
Use length and trim functions due to reduce the number of records to be verified by your Delphi code.
select refFile
from myTable
where (refFile is not null) and (length(trim(refFile)) > 0)
Delphi:
Call TDataSet.FieldByName only once.
Try using TDataSet.DisableControls and TDataSet.EnableControls (In this way, some dataset's components are faster, even if the dataset component is not linked to any control).
var
Fld : TField;
begin
BigDB.DisableControls();
try
Fld := BigDB.FieldByName('refFile');
BigDB.First;
while not(BigDB.Eof) do
begin
if not(FileExists(Fld.AsString)) then
begin
BigDB.Edit;
Fld.AsString := '';
BigDB.Post;
end;
BigDB.Next;
end;
finally
BigDB.EnableControls();
end;
Furthermore, you could consider these other optimizations:
If the refFile field contains the same value multiple times, you could sort the query by the refFile field and change the Delphi code in order to verify each filename only once. (You can do that by storing the last value and the result of the FileExists function).
You can run your code asyncronusly by using the TThread class. In this way your application won't freeze and it could be faster.
For example with FireDAC it's extremely easy to create user defined functions. If you're using it, try something like this. It could save some time because the engine doesn't need to fetch the resultset to the client application:
uses
FireDAC.Phys.SQLiteWrapper;
type
TForm1 = class(TForm)
Button1: TButton;
FDQuery1: TFDQuery;
FDConnection1: TFDConnection;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FValidator: TSQLiteFunction;
procedure ValidateFile(AFunc: TSQLiteFunctionData; AInputs: TSQLiteInputs;
AOutput: TSQLiteOutput; var AUserData: TObject);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDConnection1.Open;
FValidator := TSQLiteFunction.Create((TObject(FDConnection1.CliObj) as TSQLiteDatabase).Lib);
FValidator.Args := 1;
FValidator.Name := 'FileExists';
FValidator.OnCalculate := ValidateFile;
FValidator.InstallAll;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
FDQuery1.SQL.Text :=
'UPDATE MyTable SET FileName = NULL WHERE ' +
'FileName IS NOT NULL AND NOT FileExists(FileName)';
FDQuery1.ExecSQL;
end;
procedure TForm1.ValidateFile(AFunc: TSQLiteFunctionData; AInputs: TSQLiteInputs;
AOutput: TSQLiteOutput; var AUserData: TObject);
begin
AOutput.AsBoolean := FileExists(AInputs[0].AsString);
end;
Or simply drop the TFDSQLiteFunction component, fill out the FunctionName property with name of the function, write OnCalculate event handler similar to the above and enable the component by setting the Active property.

does ADO has Record/Key deleted Error?

i am working on Delphi 7+ SQL server.
i am converting my application from BDE to ADO.
and in some places they are handling Record/Key deleted error and the error code they are checking for is 8708.
do we have Record/Key deleted Error in ADO? and can any one please explain me in what scenario it will raise that error?
The following may help you exploring how to reconcile update conflicts in Sql Server tables
using TAdo* components. By way of preparation, create a table on your server with a definition like this
CREATE TABLE [dbo].[ATable](
[ID] [int] NOT NULL,
[name] [varchar](40) NOT NULL,
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
and then populate it with a few rows.
Then, create a minimal Ado Delphi project with a TBGrid and TDNNavigator and the code below.
Update If you have read the original version of my answer to your q,
it talked in terms of doing batch updates on the dataset. However, since posting it, I've discovered what seems to be an anomaly in how
TAdoQuery.UpdateBatch works, so I've simplified the example code
to avoid using batched updates.
Now, compile and run the project, and open a second instance of it in a CMD window.
Change the [name] field in a row in the 2nd instance and save it, then try and make
a different change to the same row in the IDE instance. You should get an error
with words to the effect of
Record cannot be located for updating. Some values may have changed since was
last read.
How you deal with this condition is entirely up to you. You could, for example, save a local copy of the current user's version of the row's
fields, cancel the edit to the row and then fetch the new row version from the server and ask the user
whether his changes should be applied to it.
YOu should see that when the error occurs, the NativeError code is 32. Unfortunately, that is also the NativeError returned when the second
instance of the app deletes the record rather than changes it (which
makes sense in a way, because in either case, the original version
of the record no longer exists in the server table. If you want to be
able to distinguish between rows that have been changed and ones that
have been deleted, you could e.g. run a query against the table to
see if the row with the current row ID exists.
Code
type
TForm1 = class(TForm)
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
ADOConnection1: TADOConnection;
ADOQuery1: TADOQuery;
Button1: TButton;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
procedure OnException(Sender: TObject; E: Exception);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := OnException;
AdoQuery1.LockType := ltOptimistic;
AdoQuery1.CursorType := ctKeySet;
AdoQuery1.SQL.Text := 'select * from atable';
AdoQuery1.Open;
DBGrid1.Options := DBGrid1.Options + [dgEditing];
DBGrid1.Columns[0].ReadOnly := True;
end;
procedure TForm1.OnException(Sender: TObject; E: Exception);
var
AErrors : Errors;
AError : Error;
i : Integer;
S : String;
begin
Caption := 'Exception';
if E is EDatabaseError then begin
AErrors := AdoQuery1.Connection.Errors;
for i := 0 to AErrors.Count - 1 do begin
AError := AErrors.Item[i];
S := Format('Number: %d, NativeError: %d, source: %s, description: %s',
[AError.Number, AError.NativeError, AError.Source, AError.Description]);
Memo1.Lines.Add(S);
end;
end;
end;
end.

Resources