absolute database batch move - delphi

I would like to use the batch component to archive some old records within a table. I looked at the example on the Ace components site but I am unsure how to use it. The command is :
DestinationTable.BatchMove(SourceTable,TABSBatchMoveType(bmtAppend));
For the task I intended to use two datetimepickers. So a query would go something like with parameters:
SELECT * from MYTABLE where DATE BETWEEN :a1 and :a2
ABSTQuery1.Parameters.ParamByName ('a1').AsDate := DateTimePicker1.Date;
ABSTQuery1.Parameters.ParamByName ('a2').AsDate := DateTimePicker2.Date;
ABSQuery.ExecSql;
How do I incorporate the query with the batchmove command? I want all the retrieved records to move from my source table to destination table.

Absolute Database's BatchMove appears to be modeled after the old BDE TBatchMove, which required two TTable components; IIRC, it didn't work with TQuery, but I could be remembering wrong. (The BDE has been deprecated for more than a decade, and I haven't used it since Delphi 1.)
You don't need BatchMove, though. You can do it all with your single query (exception handling omitted for brevity):
// Copy rows into destination
ABSTQuery1.SQL.Text := 'INSERT INTO DestTable'#32 +
'(SELECT * from MYTABLE where DATE BETWEEN :a1 and :a2)';
ABSTQuery1.Parameters.ParamByName ('a1').AsDate := DateTimePicker1.Date;
ABSTQuery1.Parameters.ParamByName ('a2').AsDate := DateTimePicker2.Date;
ABSTQuery1.ExecSql;
ABSTQuery1.Close;
// Remove them from source (you said "move", after all)
ABSTQuery1.SQL.Text := 'DELETE FROM MyTable'#32 +
`WHERE Date BETWEEN :a1 and :a2';
ABSTQuery1.Parameters.ParamByName ('a1').AsDate := DateTimePicker1.Date;
ABSTQuery1.Parameters.ParamByName ('a2').AsDate := DateTimePicker2.Date;
ABSTQuery1.ExecSql;
ABSTQuery1.Close;
Replace DestTable with the name of the destination table in the first SQL statement.
More info in the Absolute Database on-line manual
I haven't used Absolute Database, but if their SQL support includes scripting (I'll leave that research up to you - docs link above) and multiple statements, you can do it in one pass:
// Note addition of `;` at end of each SQL statement
// and change in param names for second statement.
// Some DBs will allow you to just use one pair, and
// set the value for each once. Some require setting
// each twice, and some require unique param names.
// Check the documentation for Absolute DB.
//
ABSTQuery1.SQL.Text := 'INSERT INTO DestTable'#32 +
'(SELECT * from MYTABLE where DATE BETWEEN :a1 and :a2);'
'DELETE FROM MyTable WHERE Date BETWEEN :d1 and :d2;';
ABSTQuery1.Parameters.ParamByName ('a1').AsDate := DateTimePicker1.Date;
ABSTQuery1.Parameters.ParamByName ('a2').AsDate := DateTimePicker2.Date;
// New param names for second pass
ABSTQuery1.Parameters.ParamByName ('d1').AsDate := DateTimePicker1.Date;
ABSTQuery1.Parameters.ParamByName ('d2').AsDate := DateTimePicker2.Date;
ABSTQuery1.ExecSQL;
ABSTQuery1.Close;

Related

How read records from multi table with tadodataset delphi and save in a table when post

I have a grid that connected to AdoDataset. i want read records from two table with join SQL, but save record in a table.
Read data:
adodataset.commandtext := 'select * from Table1 left join Table2 on Table1.ID = Table2.ID';
adodataset.Open;
I want save Table1 Fields only when post
You can use ReadOnly property of DBGrid.Columns. For example if you have a query like this:
ADODataSet1.CommandText := 'SELECT * FROM Table1 JOIN Table2 ON Table1.ID = Table2.ID';
Then your DBGrid will be like:
You can then make 3rd and 4th columns read only at design time or at run time by these codes:
DBGrid1.Columns[2].ReadOnly := True;
DBGrid1.Columns[3].ReadOnly := True;
Also note that if you want to delete records only from Table1 then you should run this code when ADODataSet1 is active:
ADODataSet1.Properties['Unique Table'].Value := 'Table1';
Update:
As suggested in comments it's a good idea to set desired fields ReadOnly at DataSet level:
ADODataSet1.FieldByName('ID_1').ReadOnly := True;
ADODataSet1.FieldByName('Table2_Value').ReadOnly := True;
First of all the query example in the commandtext is wrong / ambiguous. I don't know what you want to perform but I'm guessing you want to change/save data in a table. In this case, why don't you perform the update in sql? Let's say you have the primary key field called "id", and you want to save a field called "name" with another value.
var
id, NewName: string;
//...
begin
//...
id := adodataset.fieldbyname('id').AsString;
adodataset.connection.execute(
'UPDATE table1 SET name = ' + quotedstr(NewName) + ' WHERE id = ' + quotedstr(id)
);
// refresh the records by closing and reopening the adodataset
adodataset.close;
adodataset.open;
// move to the wanted record
adodataset.Locate('id', id, []);
Why not use TAdoQuery ?
Qry.Close;
Qry.SQL.Clear;
Qry.SQL.Add("select * from Table1 left join Table2 on Table1.ID = Table2.ID");
Qry.Open;
while not Qry.EOF do
begin
[do stuff]
Qry.Next;
end;

provide column data type in DBSMS_SQL.DEFINE_COLUMN oracle 12C

I have to copy data from one table to another with below two conditions
table names will be known at run time
records need to be copied one at a time so that modifications can be done in column values when required
I have created a procedure to to do this through dynamic query. Since the column list is not known already I am not able to declare a rowtype variable. I saw an example of DBMS_SQL where you can define the columns for select clause. Below is the format
DBMS_SQL.DEFINE_COLUMN(cursor_var,position,column_var);
Problem here is that in all the examples I found the column_var were already declared. However in my case I will get to know the no of columns that will be in cursor sql and their data type at run time. so I need to find a way to pass the data type of "column_var" as part of DBMS_SQL.DEFINE_COLUMN. Is there a way to do that? Is there a better way?
Below is just a sample code
CREATE OR REPLACE PROCEDURE pr_test (P_TABLE_NAME IN VARCHAR2)
IS
V_SQL VARCHAR2(500);
SRC_CUR INT;
DEST_CUR INT;
TYPE COL_DTL_TYPE IS RECORD
(
COLUMN_ID INT,
COLUMN_NAME VARCHAR2(250),
DATA_TYPE VARCHAR2(250),
DATA_LENGTH INT
);
COL_DTL_REC COL_DTL_TYPE;
TYPE TBL_COL_LIST_TYPE IS TABLE OF COL_DTL_TYPE;
TBL_COL_LIST TBL_COL_LIST_TYPE;
V_CNT INT := 0;
BEGIN
V_SQL := 'SELECT * FROM ' || P_TABLE_NAME;
SRC_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SRC_CUR,V_SQL,DBMS_SQL.NATIVE);
TBL_COL_LIST := TBL_COL_LIST_TYPE();
FOR COL_DTL_REC IN (
SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH
FROM ALL_TAB_COLUMNS WHERE TABLE_NAME =P_TABLE_NAME
)
LOOP
V_CNT := V_CNT + 1;
TBL_COL_LIST.EXTEND;
TBL_COL_LIST(V_CNT) := COL_DTL_REC;
-- Here is where I am stuck and not able to give column data type
DBMS_SQL.DEFINE_COLUMN(SRC_CUR,V_CNT,COL_DTL_REC.COLUMN_NAME COL_DTL_REC.DATA_TYPE , COL_DTL_REC.DATA_LENGTH)
END LOOP;
END;
copying to destination table will come later.

How to limit number of records in TFDMemTable?

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. *)

Get name of table that TField is from

I'm using a TDataSet where the CommandText property is set to an SQL query. I have also made the following function which creates part of an SQL query based on the fields of TDataSet. It is however incomplete. As you can see I still need to get the name of the table that a TField is from. How do I achieve this?
function GetDataSetFieldsMSSQL(Dataset: TDataSet): String;
var
I, L: Integer;
TableName: String;
begin
Result := '';
L := Dataset.Fields.Count;
if (L > 0) then
begin
TableName := ... // Name of the table for the Dataset.Fields[0] field.
Result := '[' + TableName + '].[' + Dataset.Fields[0].FieldName + ']';
I := 1;
while (I < L) do
begin
TableName := ... // Name of the table for the Dataset.Fields[I] field.
Result := Result + ',[' + TableName + '].[' + Dataset.Fields[I].FieldName + ']';
Inc(I);
end;
end;
end;
You can use the Delphi Function GetTableNameFromQuery(SQL : String):String; from the DBCommon unit. Just Add The DBCommon on the uses. =)
Maybe there is no solution at all for a simple TDataSet?
I believe not. Because an TDataset can source its' data not only from RDBMS' tables.
It can be:
an RSS feed
An XML file. Example: TCliendataset is an TDataset descendant that can read XML from its'
own format or using an XMLTransformProvider.
It can be an SQL for reading an Excel spreadsheet or a text file if you have an ODBC driver for
that and configured the datasource.
Sky (and the imagination of Delphi's programmers around the world) is the limit for what a field can represent in an TDataset.
You have some alternatives, since you are using an ADODataset:
Parsing the commandText of ADOCommand
Using the BASETABLENAME property of ADORecordSet (as in kobik's comment)
Guessing by convention ( Abelisto's answer )
As I know there is no any way to get the name of the table from the SQL query component.
However you can give aliases for fields, for example: "select foo_field as foo_dot_foo_field from foo" and then replace them to the correct syntax: "Result := '[' + StringReplace(DataSet.Fields[0].FieldName, 'dot', '].[', [rfReplaceAll]) + ']'"
What you are trying to do is impossible if you have no knowledge or control over the SQL used in the query.
The query could contain calculated/computed fields or could be returning fields from a view etc. Furthermore the database might have several tables that contain the same field names.
If possible you can query the SQL server view INFORMATION_SCHEMA.COLUMNS and that way try to figure out what table a fieldname is from. However if the field names are not unique this might also prove impossible.

Delphi TAdoQuery - Multiple Inserts?

Is it possible with a Delphi TAdoQuery to execute multiple inserts in one go, or do you have to execute each statement separately? What I want to do is this:
AdoQuery.SQL.Clear;
AdoQuery.SQL.Add('INSERT INTO user VALUES (1, "User 1");');
AdoQuery.SQL.Add('INSERT INTO user VALUES (2, "User 2");');
AdoQuery.SQL.Add('INSERT INTO user VALUES (3, "User 3");');
AdoQuery.ExecSQL;
AdoQuery.Close;
Is this possible? I'm getting an error from MySQL when executing this. I also tried adding BEGIN; and END; around the queries, but that didn't work either.
Edit: I want to do this because when I execute the inserts in a for loop it seems like it takes a really long time for > 10 queries. I'm assuming adding them all like above would speed things up. Does anyone know if the AdoQuery.Close call is necessary between inserts?
Try something like this (with an AdoCommand):
sSql := 'INSERT INTO User (FieldName1, FieldName2) values (:Nr, :Strng)';
AdoCmd.Parameters.Clear();
AdoCmd.CommandText := sSql;
AdoCmd.CommandType := cmdText;
AdoCmd.Parameters.ParseSQL( sSql, True );
AdoCmd.Parameters.ParamByName('Nr').DataType := ftInteger
AdoCmd.Parameters.ParamByName('Strng').DataType := ftString;
for i := 1 to 10 do
begin
AdoCmd.Parameters.ParamByName('Nr').Value := i;
AdoCmd.Parameters.ParamByName('Strng').Value := sUserName(i);
AdoCmd.Execute;
end;
You could speed up thing by using .Params(0) and .Params(1) because ParamByName takes up some time.
But the trick here is the ParseSql statement. It keeps your code clear but still only parses the sql string only once.
And you can use transactions if necessary ... by using AdoCmd.Connection.BeginTrans and AdoCmd.Connection.CommitTrans / RollbackTrans.
With MySQL you can use the syntax:
INSERT INTO user VALUES (1, "User 1"), (2, "User 2"), (3, "User 3")
Then you may use parameters:
AdoQuery.SQL.Text := 'INSERT INTO user VALUES (:p11, :p12), (:p21, :p22), (:p31, :p32)';
AdoQuery.Parameters[0].Value := 1;
AdoQuery.Parameters[1].Value := 'User 1';
AdoQuery.Parameters[2].Value := 2;
AdoQuery.Parameters[3].Value := 'User 2';
AdoQuery.Parameters[4].Value := 3;
AdoQuery.Parameters[5].Value := 'User 3';
AdoQuery.ExecSQL;
Proper use of transactions will also speed up your inserts. If each statement needs to be committed, it will take longer to be executed. If you can execute everything within a single transaction and just commit at the end it will be faster.
Don't know MySQL, but some databases also support "array DML", where a single SQL statement is sent to the DB together array of parameters and thereby execute multiple times but with a single communication roundtrip.

Resources