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;
Related
I have ported an application from ADO to FireDAC applying several RegExp replaces on the source code to convert the ADOQuery, ADOTables, ADOCommands, ADOStoredProcs, etc. ... to the corresponding FireDAC components.
It has worked fine, but now when running that application plenty of forms raise errors because of the type of the persistent fields being different than the type expected (the one defined from ADO when the persistent field was created).
I'm trying to make a list of those errors, creating an instance of all my forms and opening their datasets with persistent fields, and logging the errors. I can get the list of forms from the project source code, but when I try to use FindClass to create each form I get an error telling that the class has not been found.
Is there any other way to create a Form/DataModule from its class name ?.
This is my current code:
class procedure TfrmCheckFormularis.CheckDatasets(ProjecteFile: string);
var frmCheckFormularis: TfrmCheckFormularis;
Projecte: string;
rm: TMatch;
cc: TComponentClass;
c: TComponent;
i: integer;
Dataset: TFDQuery;
begin
Projecte := TFile.ReadAllText(ProjecteFile);
frmCheckFormularis := TfrmCheckFormularis.Create(Application);
try
with frmCheckFormularis do begin
Show;
qryForms.CreateDataSet;
qryErrors.CreateDataSet;
// I get a list of all the forms and datamodules on my project
for rm in TRegEx.Matches(Projecte, '^(?:.* in '')(?<File>.*)(?:'' {)(?<Class>.*)(?:},)', [roMultiline]) do begin
qryForms.AppendRecord([rm.Groups['File'].Value, rm.Groups['Class'].Value]);
end;
// Check every form and datamodule
qryForms.First;
while not qryForms.Eof do begin
cc := TComponentClass(FindClass(qryFormsClass.Value));
c := cc.Create(frmCheckFormularis);
try
for i := 0 to c.ComponentCount - 1 do begin
if c.Components[i] is TFDQuery then begin
Dataset := c.Components[i] as TFDQuery;
// When the Dataset has persistent fields, I open it to check if the persistent fields are correct
if Dataset.FieldDefs.Count > 1 then begin
try
Dataset.Open;
except
on E: Exception do qryErrors.AppendRecord([c.Name, Dataset.Name, E.Message]);
end;
end;
end;
end;
finally
c.Free;
end;
qryForms.Next;
end;
end;
finally
frmCheckFormularis.Free;
end;
end;
Thank you.
Using the "new" RTTI in Delphi is quite easy. The following code will (hopefully*) create one instance of each form in your application:
procedure TForm1.Button1Click(Sender: TObject);
var
Context: TRttiContext;
&Type: TRttiType;
InstanceType: TRttiInstanceType;
begin
Context := TRttiContext.Create;
for &Type in Context.GetTypes do
begin
if (&Type.TypeKind = tkClass) and &Type.IsInstance then
begin
InstanceType := TRttiInstanceType(&Type);
if InstanceType.MetaclassType.InheritsFrom(TForm) and (InstanceType.MetaclassType <> TForm) then
TFormClass(InstanceType.MetaclassType).Create(Application){.Show}; // optionally show it
end;
end;
end;
* Technically, it will create one instance of each proper descendant class of TForm.
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.
Im working in a Tdbadvgrid from TMS. I am adding colums dynamically based on a sql query length. Everything works great, with the exception of the column width saving.
Adding the columns:
//Captions
for i := 0 to oRow.Count - 1 do
begin
grdFieldData.Columns.Insert(1);
grdFieldData.Cells[i + 1, 0] := TabelList.Captions[i].Caption;
end;
//Data
for r := 0 to TabelList.Count - 1 do //rows
begin
for c := 0 to oRow.Count - 1 do //cols
begin
grdFieldData.Cells[c+1, r+1] := TabelList.Rows[r].Fields[c].Value;
end;
if r <> TabelList.Count - 1 then
grdFieldData.RowCount := grdFieldData.RowCount + 1;
end;
Now the save function is built into the TMS TDBAdvGrid, and looks like this:
I have tried messing with all the options with no luck.
The table has 1 fixed row (for captions) and one empty row. The row is only there because the number of fixed rows must be smaller then the number of rows.
When saving the data to either .ini file or to registry, it saves and loads the first column, but the dynamicly added ones get written in as default value (64) but never saved/loaded when i drag them to adjust size. The .ini file table looks like this:
[Recept]
Col0=20
Col1=97
Col2=64
Col3=64
Col4=64
Col5=64
Col6=64
Col7=64
Col8=64
Col9=64
Col10=64
Col11=64
Col12=64
Col13=64
Col14=64
When loaded in it looks like this:
Does anyone know what I can do to make the columns save properly so the widths will be saved?
So, after a few hours of sitting with it, I decided that there was no reason to struggle so much with something built in when I could just build something simple myself.
In this instance, it simply saves to a chosen ini file at a chosen directory. The filename and directory are currently hardcoded to each inhreited class. A feature to change them could easily be implemented. Perhaps even a directory selecter to a button or something like it.
The code for loading (called on FormShow):
procedure TfrmReceptEditor.LoadColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
grdFieldData.Columns.Items[i].Width := Ini.ReadInteger('Recept','col'+IntToStr(i),75);
end;
grdFieldData.FixedColWidth := 20;
finally
Ini.Free;
end
end;
And the code for saving the data (on FormClose):
procedure TfrmReceptEditor.SaveColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
Ini.WriteInteger('Recept', 'col'+IntToStr(i), grdFieldData.Columns.Items[i].Width);
end;
finally
Ini.Free;
end;
end;
Some might want to build in features to check if the Ini files exists (aka if the entered path is correct). But it will work smoothly even if the file doesnt exist, it will simply create it.
I have got a TTable component that uses the BDE to access a DBase table. There is no index on the table, so the sort order is the physical order of the records in the table. If I read the RecNo property, it contains the expected number for the current record.
I was under the impression that with this constellation (BDE + DBase) it is also possible to set the RecNo property to move to the corresponding record. But apparently this does not work in my program.
So: Do I remember this incorrectly? Or is there anything special I need to do for this to work?
(Please do not advise about dropping the BDE. I am aware of its issues and we are already migrating away from it.)
TBDEDataSet implements RecNo setter only for Paradox (not DBase).
unit DBTables;
...
procedure TBDEDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
if (FRecNoStatus = rnParadox) and (Value <> RecNo) then
begin
DoBeforeScroll;
if DbiSetToSeqNo(Handle, Value) = DBIERR_NONE then
begin
Resync([rmCenter]);
DoAfterScroll;
end;
end;
end;
You might want to try something generic like this:
procedure SetRecNo(DataSet: TDataSet; const RecNo: Integer);
var
ActiveRecNo, Distance: Integer;
begin
if (RecNo > 0) then
begin
ActiveRecNo := DataSet.RecNo;
if (RecNo <> ActiveRecNo) then
begin
DataSet.DisableControls;
try
Distance := RecNo - ActiveRecNo;
DataSet.MoveBy(Distance);
finally
DataSet.EnableControls;
end;
end;
end;
end;
In my app I have different forms that use the same datasource (so the queries are the same too), defined in a common datamodule. Question is, is there a way to know how many times did I open a specific query? By being able to do this, I could avoid close that query without closing it "every where else".
Edit: It's important to mention that I'm using Delphi3 and it is not a single query but several.
The idea is to use the DataLinks property of the TDataSource.
But, as it is protected, you have to gain access to it. One common trick is to create a fake descendant just for the purpose of casting:
type
TDataSourceHack = class(TDataSource);
Then you use it like:
IsUsed := TDataSourceHack(DataSource1).DataLinks.Count > 0;
You can get creative using a addref/release like approach. Just create a few functions and an integer variable in your shared datamodule to do the magic, and be sure to call them..partial code follows:
TDMShared = class(tDataModule)
private
fQueryCount : integer; // set to 0 in constructor
public
function GetQuery : tDataset;
procedure CloseQuery;
end;
function TDMShared.GetQuery : tDataset;
begin
inc(fQueryCount);
if fQueryCount = 1 then
SharedDatsetQry.open;
Result := shareddatasetqry; // your shared dataset here
end;
procedure TDMShared.CloseQuery;
begin
dec(fQueryCount);
if fQueryCount <= 0 then
shareddatasetqry.close; // close only when no refs left.
end;
EDIT: To do this with multiple queries, you need a container to hold the query references, and a way to manipulate them. a tList works well for this. You will need to make appropriate changes for your TDataset descendant, as well as create a FreeAndNil function if you are using an older version of Delphi. The concept I used for this was to maintain a list of all queries you request and manipulate them by the handle which is in effect the index of the query in the list. The method FreeUnusedQueries is there to free any objects which no longer have a reference...this can also be done as part of the close query method, but I separated it to handle the cases where a specific query would need to be reopened by another module.
Procedure TDMShared.DataModuleCreate(Sender:tObject);
begin
dsList := tList.create;
end;
Function TDMShared.CreateQuery(aSql:String):integer;
var
ds : tAdoDataset;
begin
// create your dataset here, for this example using TADODataset
ds := tAdoDataset.create(nil); // self managed
ds.connection := database;
ds.commandtext := aSql;
ds.tag := 0;
Result := dsList.add(ds);
end;
function TDMShared.GetQuery( handle : integer ) : tDataset;
begin
result := nil;
if handle > dsList.count-1 then exit;
if dsList.Items[ handle ] = nil then exit; // handle already closed
result := tAdoDataset( dsList.items[ handle ]);
Inc(Result.tag);
if Result.Tag = 1 then
Result.Open;
end;
procedure TDMShared.CloseQuery( handle : integer );
var
ds : tAdoDataset;
begin
if handle > dsLIst.count-1 then exit;
ds := tAdoDataset( dsList.items[ handle ]);
dec(ds.Tag);
if ds.Tag <= 0 then
ds.close;
end;
procedure TDMShared.FreeUnusedQueries;
var
ds : tAdoDataset;
ix : integer;
begin
for ix := 0 to dsList.Count - 1 do
begin
ds := tAdoDataset(dsLIst.Items[ ix ]);
if ds.tag <= 0 then
FreeAndNil(dsList.Items[ix]);
end;
end;
procedure TDMShared.DataModuleDestroy(Sender: TObject);
var
ix : integer;
begin
for ix := 0 to dsList.count-1 do
begin
if dsLIst.Items[ix] <> nil then
FreeAndNil(dsLIst.Items[ix]);
end;
dsList.free;
end;
Ok, a completely different solution...one that should work for Delphi 3.
Create a new "Descendant Object" from your existing dataset into a new unit, and add some behavior in the new object. Unfortunately I do not have Delphi 3 available for testing, but it should work if you can find the proper access points. For example:
TMySharedDataset = class(tOriginalDataset)
private
fOpenCount : integer;
protected
procedure Internal_Open; override;
procedure Internal_Close; override;
end;
TMySharedDataset.Internal_Open;
begin
inherited Internal_Open;
inc(fOpenCount);
end;
TMySharedDataset.Internal_Close;
begin
dec(fOpenCount);
if fOpenCount <= 0 then
Inherited Internal_Close;
end;
Then just include the unit in your data module, and change the reference to your shared dataset (you will also have to register this one and add it to the palette if your using components). Once this is done, you won't have to make changes to the other units as the dataset is still a descendant of your original one. What makes this all work is the creation of YOUR overridden object.
You could have a generic TDataSet on the shared datamodule and set it on the OnDataChange, using the DataSet property of the Field parameter
dstDataSet := Field.DataSet;
This way, when you want to close the dataset, close the dataset on the datamodule, which is a pointer to the correct DataSet on some form you don't even have to know