delphi Ado (mdb) update records - delphi

I´m trying to copy data from one master table and 2 more child tables. When I select one record in the master table I copy all the fields from that table for the other. (Table1 copy from ADOQuery the selected record)
procedure TForm1.copyButton7Click(Sender: TObject);
SQL.Clear;
SQL.Add('SELECT * from ADoquery');
SQL.Add('Where numeracao LIKE ''%'+NInterv.text);// locate record selected in Table1 NInterv.text)
Open;
// iniciate copy of record´s
begin
while not tableADoquery.Eof do
begin
Table1.Last;
Table1.Append;// how to append if necessary!!!!!!!!!!
Table1.Edit;
Table1.FieldByName('C').Value := ADoquery.FieldByName('C').Value;
Table1.FieldByName('client').Value := ADoquery.FieldByName('client').Value;
Table1.FieldByName('Cnpj_cpf').Value := ADoquery.FieldByName('Cnpj_cpf').Value;
table1.Post;
table2.next;///
end;
end;
//How can i update the TableChield,TableChield1 from TableChield_1 and TableChield_2 fields at the same time?
do the same for the child tables
TableChield <= TableChield_1
TableChield1 <= TableChield_2
thanks

The fields will all be updated at the same time. The actual update is performed when you call post (or not even then, it depends if the Batch Updates are on or off).
But please reconsider your logic. It would be far more efficient to use SQL statements (INSERT) in order to insert the data to the other table
SQL.Clear;
SQL.Add('INSERT INOT TABLE_1(C, client, Cnpj_cpf)');
SQL.Add('VALUES(:C, :client, :Cnpj_cpf)');
Then just fill the values in a loop.
SQL.Parameters.ParamByName('C').Value := ADoquery.FieldByName('C').Value;
SQL.Parameters.ParamByName('client').Value := ADoquery.FieldByName('client').Value;
SQL.Parameters.ParamByName('Cnpj_cpf').Value := ADoquery.FieldByName('Cnpj_cpf').Value;
SQL.ExecSQL;
You can also do the Updade - Insert pattern if the data can alredy be in the target table.
Like This:
if SQL.ExecSQL = 0 then
begin
// no records were update, do an insert
end;
And also the indication that you are copying data from table 1 to table 2 could be a sign of design flaw. But I can't say that for sure without knowing more. Anyway data duplication is never good.

I believe the asker was thinking about the data integrity, that means, ensure that only all the tables will updated or none...
The way I know to achieve this with security is executing all this updates (or inserts, a.s.o.) using SQL commands inside a transition.

Related

Duplicate key error when updating key field in a set of records

I have a master detail relation between two tables. Troq is the master table and Troq_Alma is the detail table. Troq_Alma primary key is formed by Troq primary key (Troq.cod) and "Num_Orden". This "Num_Orden" field is not only part of the PK of the table but is the priority of that specific Alma out of the several Almas for one Troq. (Troq_Alma is the table that relates the different Troq's with their related Alma's)
And at a certain point, the user needs to modify this priority therefore modify the primary key of a set of records (The set of almas related to one specific troq). In the form there are two buttons (Spinbuttons) related with one TDBGrid. If I push the "Up" button, selected Troq_Alma should decrease "Num_Orden" by 1 and the Troq_alma with that value in "Num_Orden" would increase its value by 1. So in the "image" we have of the table in the dbgrid, there is no duplicate key.
Naturally when I do Apply this updates: TFDQ_Troq_Alma.ApplyUpdates(-1);
I get duplicate key error which I find very logical as in any way that firedac would try to make this update, the first modification is going to throw duplicate key error until the amendment is made for that other record that previously had that primary key, being the fact that it still has that primary key.
I really don't know if there is any "fair" solution to this problem, the only thing that I imagined was to first add some amount to all "num_orden" for one specific Troq do the update and then update again to the originally modified values, which is a really odd job, but, on my short knowledge of delphi and firedac, I really don´t appear to find any other way to solve it.
Working on Delphi XE6 with cached Updates against Postgres 11.8 Database with firedac.
In case it could be of any interest, here is the code for both spin buttons (Up and Down):
procedure TFRM_Mant_TROQ.SpinBut_Troq_AlmaDownClick(Sender: TObject);
var iValActNumOrd, iValNumOrd2 : Integer;
RegActual: TBookMark;
iValMax: Integer;
begin
RegActual := DM_Mant_Troq.FDQ_Troq_Alma.GetBookmark;
iValMax := DM_DatosComun.MaxVal_FDQ(DM_Mant_Troq.FDQ_Troq_Alma, 'num_orden');
DM_Mant_Troq.FDQ_Troq_Alma.GotoBookmark(RegActual);
iValActNumOrd := DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value;
if iValActNumOrd < iValMax then begin
DM_Mant_Troq.FDQ_Troq_Alma.Next;
iValNumOrd2 := DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value;
DM_Mant_Troq.FDQ_Troq_Alma.Edit;
DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value := iValActNumOrd;
// DM_Mant_Troq.FDQ_Troq_Alma.Post;
DM_Mant_Troq.FDQ_Troq_Alma.GotoBookmark(RegActual);
DM_Mant_Troq.FDQ_Troq_Alma.Edit;
DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value := iValNumOrd2;
DM_Mant_Troq.FDQ_Troq_Alma.Post;
end;
end;
procedure TFRM_Mant_TROQ.SpinBut_Troq_AlmaUpClick(Sender: TObject);
var iValActNumOrd, iValNumOrd2 : Integer;
RegActual: TBookMark;
iValMin: Integer;
begin
RegActual := DM_Mant_Troq.FDQ_Troq_Alma.GetBookmark;
iValMin := DM_DatosComun.MinVal_FDQ(DM_Mant_Troq.FDQ_Troq_Alma, 'num_orden');
DM_Mant_Troq.FDQ_Troq_Alma.GotoBookmark(RegActual);
iValActNumOrd := DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value;
if iValActNumOrd > iValMin then begin
DM_Mant_Troq.FDQ_Troq_Alma.Prior;
iValNumOrd2 := DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value;
DM_Mant_Troq.FDQ_Troq_Alma.Edit;
DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value := iValActNumOrd;
DM_Mant_Troq.FDQ_Troq_Alma.GotoBookmark(RegActual);
DM_Mant_Troq.FDQ_Troq_Alma.Edit;
DM_Mant_Troq.FDQ_Troq_Alma.FieldByName('NUM_ORDEN').Value := iValNumOrd2;
DM_Mant_Troq.FDQ_Troq_Alma.Post;
end;
end;
This code can probably be improved but as I mention, I believe this code is working alright, the matter on my point of view is on how to indicate firedac to "eventually avoid" this unique key validation.
Actually Troq.Cod, Num_Orden is not the primary key of the table Troq_Alma, but there is a unique index on this two fields and I really like this unique index for database integrity purposes.
If we accept that you have a good reason to change a unique key then you need to ensure that you avoid a key violation. It looks like you are working with data cached locally in a TFDQuery and then applying the updates.
The problem with that approach is that the TFDQuery remembers the loaded value of the field and the current value, so your intermediate changes are lost, resulting in a key violation.
A quick way to avoid that problem is to ApplyUpdates after every change. This could be a problem for you if you are remote from the main store.
If you need to assign a temporary value to an indexed field, you need to ensure that that value is unique. If the data schema allows it you could just negate the value of the Integer field (if it's not defined as unsigned). Although there could be another record with that value, if your convention is to only allocate these values temporarily you should avoid a key conflict.

Datasnap - insert record with parameter

On DatasnapServer I have :
TSQLConnection connected to my database.
SQLDataset1 (CommandType=ctQuery) that fetches data from my table (lets call it "RESORTS".
DataSetProvider1 is connected to my SQLDataset1.
DataSetProvider1 is set to AllowCommandText.
This works OK. Server starts without a problem. Data is obtained.
On the Client side I have :
SQLConnection1 which is connected OK.Driver is Datasnap.
DSProviderConnection1 is linked to my SQLConnection1.
Both connected without problem.
Then I have DataSource1 which is connected to a ClientDataSet1.
ClientDataSet1 is connected to my DataSetProvider1.
Setting it active retrieves the data from the server.
All displayed right in the grid.
On the Client form I have a Edit1 and a Button1.
I try and run a query using the ClientDataset1
procedure TForm2.Button1Click(Sender: TObject);
begin
ClientDataSet1.Close;
ClientDataSet1.CommandText := ' INSERT INTO RESORTS (RES_NAME) VALUES (:RN)';
ClientDataSet1.FieldByName('RN').AsString := Edit1.Text;
ClientDataSet1.Execute;
ClientDataSet1.Open;
end;
I get : ClientDataSet1: Field 'RN' not found.
So, I am wondering what is going on? Why cant I insert data with parameter?
If I substitute the parameter with :
ClientDataSet1.CommandText := ' INSERT INTO RESORTS (RES_NAME) VALUES ("TRY")';
I get :Remote error: SQLDataSet1: Cursor not returned from Query.
However, the data does get inserted.
What am I doing wrong here ?
(Rewritten based on new information provided by the poster in comments.)
Your entire approach is wrong. :-) You don't use parameters, SQL or CommandText. The TClientDataSet.CommandText documentation clearly says:
CommandText specifies what data the client dataset wants to receive from its (internal or external) provider. It is either:
An SQL statement (query) for the database server to execute.
The name of a table or stored procedure
An SQL statement (query) means only a SELECT is acceptable SQL. An INSERT is not a query, so it cannot be used in a CommandText to insert data.
To insert data in a TClientDataSet, you simply Insert or Append, and then use FieldByName to set the value, and then call the Post method:
ClientDataSet1.Insert;
ClientDataSet1.FieldByName('RES_NAME').AsString := Edit1.Text;
ClientDataSet1.Post;
To edit, you simply use Edit instead of Insert or Append; the rest stays exactly the same.
ClientDataSet1.Edit;
ClientDataSet1.FieldByName('RES_NAME').AsString := Edit1.Text;
ClientDataSet1.Post;
When you're ready to actually update the server data from the changes made in the TClientDataSet, call it's ApplyUpdates:
ClientDataSet1.ApplyUpdates(0);
You can use ClientDataSet1.ParamByName('RN').AsString := Edit1.Text;

Refresh Nested DataSet with poFetchDetailsOnDemand

Is there a way to refresh only the Detail DataSet without reloading all master dataset?
this is what I've tried so far:
DM.ClientDataSet2.Refresh;
DM.ClientDataSet2.RefreshRecord;
I have also tried:
DM.ClientDataSet1.Refresh;
But the method above refreshes the entire Master dataset, not just the current record.
Now, the following code seems to do anything:
DM.ClientDataSet1.RefreshRecord;
Is there a workaround or a proper way to do what I want? (maybe an interposer...)
Additional Info:
ClientDataSet1 = Master Dataset
ClientDataSet2 = Detail DataSet , is the following: *
object ClientDataSet2: TClientDataSet
Aggregates = <>
DataSetField = ClientDataSet1ADOQuery2
FetchOnDemand = False
.....
end
Provider properties:
object DataSetProvider1: TDataSetProvider
DataSet = ADOQuery1
Options = [poFetchDetailsOnDemand]
UpdateMode = upWhereKeyOnly
Left = 24
Top = 104
end
Googling finds numerous articles that say that it isn't possible at all with nested ClientDataSets without closing and re-opening the master CDS, which the OP doesn't want to do in this case. However ...
The short answer to the q is yes, in the reasonably simple case I've tested, and it's quite straightforward, if a bit long-winded; getting the necessary steps right took a while to figure out.
The code is below and includes comments explaining how it works and a few potential problems and how it avoids or works around them. I have only tested it with TAdoQueries feeding the CDSs' Provider.
When I started looking into all this, it soon became apparent that with the usual master
+ detail set-up, although Providers + CDSs are happy to refresh the master data from the server, they simply will not refresh the detail records once they've been read from the server for the first time since the cdsMaster was opened. This may be by design of course.
I don't think I need to post a DFM to go with the code. I simply have AdoQueries set up in the usual master-detail way (with the detail query having the master's PK as a parameter), a DataSetProvider pointed at the master AdoQuery, a master CDS pointed at the provider, and a detail cDS pointed at the DataSetField of the cdsMaster. To experiment and see what's going on, there are DBGrids and DBNavigators for each of these datasets.
In brief, the way the code below works is to temporarily filter the AdoQuery master and the CDS masterdown to the current row and then force a refresh of their data and the dtail data for the current master row. Doing it this way, unlike any other I tried, results in the detail rows nested in the cdsMaster's DataSet field getting refreshed.
Btw, the other blind alleys I tried included with and without poFetchDetailsOnDemand set to true, ditto cdsMaster.FetchDetailsOnDemand. Evidently "FetchDetailsOnDemand" doesn't mean ReFetchDetailsOnDemand!
I ran into a problem or two getting my "solution" working, the stickiest one being described in this SO question:
Refreshing a ClientDataSet nested in a DataSetField
I've verified that this works correctly with a Sql Server 2000(!) back-end, including picking up row data changes fired at the server from ISqlW. I've also verified, using Sql Server's Profiler, that the network traffic in a refresh only involves the single master row and its details.
Delphi 7 + Win7 64-bit, btw.
procedure TForm1.cdsMasterRowRefresh(MasterPK : Integer);
begin
// The following operations will cause the cursor on the cdsMaster to scroll
// so we need to check and set a flag to avoid re-entrancy
if DoingRefresh then Exit;
DoingRefresh := True;
try
// Filter the cdsMaster down to the single row which is to be refreshed.
cdsMaster.Filter := MasterPKName + ' = ' + IntToStr(MasterPK);
cdsMaster.Filtered := True;
cdsMaster.Refresh;
Inc(cdsMasterRefreshes); // just a counter to assist debugging
// release the filter
cdsMaster.Filtered := False;
// clearing the filter may cause the cdsMaster cursor to move, so ...
cdsMaster.Locate(MasterPKName, MasterPK, []);
finally
DoingRefresh := False;
end;
end;
procedure TForm1.qMasterRowRefresh(MasterPK : Integer);
begin
try
// First, filter the AdoQuery master down to the cdsMaster current row
qMaster.Filter := MasterPKName + ' = ' + IntToStr(MasterPK);
qMaster.Filtered := True;
// At this point Ado is happy to refresh only the current master row from the server
qMaster.Refresh;
// NOTE:
// The reason for the following operations on the qDetail AdoQuery is that I noticed
// during testing situations where this dataset would not be up-to-date at this point
// in the refreshing operations, so we update it manually. The reason I do it manually
// is that simply calling qDetail's Refresh provoked the Ado "Insufficient key column
// information for updating or refreshing" despite its query not involving a join
// and the underlying table having a PK
qDetail.Parameters.ParamByName(MasterPKName).Value := MasterPK;
qDetail.Close;
qDetail.Open;
// With the master and detail rows now re-read from the server, we can update
// the cdsMaster
cdsMasterRowRefresh(MasterPK);
finally
// Now, we can clear the filter
qMaster.Filtered := False;
qMaster.Locate(MasterPKName, MasterPK, []);
// Obviously, if qMaster were filtered in the first place, we'd need to reinstate that later on
end;
end;
procedure TForm1.RefreshcdsMasterAndDetails;
var
MasterPK : Integer;
begin
if cdsMaster.ChangeCount > 0 then
raise Exception.Create(Format('cdsMaster has %d change(s) pending.', [cdsMaster.ChangeCount]));
MasterPK := cdsMaster.FieldByName(MasterPKName).AsInteger;
cdsDetail.DisableControls;
cdsMaster.DisableControls;
qDetail.DisableControls;
qMaster.DisableControls;
try
try
qMasterRowRefresh(MasterPK);
except
// Add exception handling here according to taste
// I haven't encountered any during debugging/testing so:
raise;
end;
finally
qMaster.EnableControls;
qDetail.EnableControls;
cdsMaster.EnableControls;
cdsDetail.EnableControls;
end;
end;
procedure TForm1.cdsMasterAfterScroll(DataSet: TDataSet);
begin
RefreshcdsMasterAndDetails;
end;
procedure TForm1.cdsMasterAfterPost(DataSet: TDataSet);
// NOTE: The reason that this, in addition to cdsMasterAfterScroll, calls RefreshcdsMasterAndDetails is
// because RefreshcdsMasterAndDetails only refreshes the master + detail AdoQueries for the current
// cdsMaster row. Therefore in the case where the current cdsMaster row or its detail(s)
// have been updated, this row needs the refresh treatment before we leave it.
begin
cdsMaster.ApplyUpdates(-1);
RefreshcdsMasterAndDetails;
end;
procedure TForm1.btnRefreshClick(Sender: TObject);
begin
RefreshcdsMasterAndDetails;
end;
procedure TForm1.cdsDetailAfterPost(DataSet: TDataSet);
begin
cdsMaster.ApplyUpdates(-1);
end;

Saving deleted records to another table

I am deleting records from one table (based on a condition) like :
procedure TForm3.AdvGlowButton1Click(Sender: TObject);
begin
if MessageDlg('Are you sure???' , mtConfirmation, [mbYes, mbNo], 0) = mrNo then
Abort else
Case cxRadioGroup1.ItemIndex of
0: begin
with Form1.ABSQuery1 do begin
Form1.ABSQuery1.Close;
Form1.ABSQuery1.SQL.Clear;
Form1.ABSQuery1.SQL.Text :='delete from LOG where status="YES" ';
Form1.ABSQuery1.ExecSQL;
Form1.ABSTable1.Refresh;
end;
end;
End;
end;
However,I want to save these deleted records in another table that I have created for the purpose (LOG_ARCHIVE) which is identical to the LOG table. So how do I save these deleted records over there ?
If you were using a database that supported it, you could use a BEFORE DELETE trigger. However, according to a search on the Absolute Database documentation, there's no support for CREATE TRIGGER and a search on triggers at the same site returns nothing about them either.
The lack of trigger support probably just leaves you with performing an INSERT into the other table first, before doing the DELETE from your LOG table. According to the documentation again, a query is able to be used as the source of data for an INSERT (see the second example on the linked page). This means you can do something like this:
ABSQuery1.SQL.Text := 'insert into LOG_ARCHIVE'#13 +
'(select * from LOG where status = ''Yes'')';
ABSQuery1.SQL.ExecSQL;
ABSQuery1.Close;
{
No need to use SQL.Clear here. Setting the SQL.Text replaces
what was there before with new text.
}
ABSQuery1.SQL.Text :='delete from LOG where status=''YES''';
ABSQuery1.ExecSQL;
You really should wrap this entire operation in a transaction (Delphi example here), so that in case something fails both the INSERT and DELETE can be undone. (For instance, if the INSERT works putting the rows in the LOG_ARCHIVE file, but the DELETE then fails for some reason, you have no way to remove the rows you inserted into the archive file.) A transaction can be started before you do the INSERT, rolled back if it (or the DELETE fails or committed if both of them succeed.

ADO, Adonis, Update Criteria

On a form, I have a Quantum Grid and some db-aware editcomponents. When appending a new record in the grid, typing some editvalues both in the grid and the separate editcompoennts, I get an error:
EOleException: Row cannot be located for updating. Some values may have been chenged since it was last read
After some googling, I think changing the 'Update Criteria'-property from adCriteriaAllCols to adCriteriaKey may be the right solution. But how, and when, do I do that on a Adonis query?
if your dataset contains an autoincrement field or one or more fields have default values then this may be the problem. After calling Post fields change their value in the db but that may not be detected by your dataset
Although I don't use Adonis, I believe there is a possibility it use the same mechanism TClientDataset (CDS) uses to identify the fields that compose the primary key: the property TField.ProviderFlags.
I believe it could be a good place to start looking.
Actually ADO provides dynamic property to control Query Based Update (QBU) behavior
Most of code is covered in ADOInt.pas, the appropriate event is OnAfterOpen for recordset properties (any TADODataSet), and OnCreate for connection properties (TADOConnection).
I guess ‘Update Criteria’ is not the solution in this case, since it deals with WHERE clause to specify fields to be used for updates.
You may change 'Update Resync' as follows:
//After open a TCustomADODataSet
TCustomADODataSet(DataSet).Properties['Update Resync'].Value :=
adResyncAutoIncrement + adResyncUpdates + adResyncInserts;
if you have join table in your query :
TADOQuery cannot be edit and post data in sql database.
you can create other TADOQuery and select by primary key without any join table and edit and then post data in sql database.
I had a similar problem - Updating a row where all values were the same would result in the error you're getting. I added the flag "Option=2" to the end of my ado connection and this fixed the issue.
This is old. but my situation may happens for someone else. so I post this answer.
This Error happened for me too.
I was using Access database and using some TADOTable on the form.
the relation was master-detail and I connected all of the tables with the IDE Designer together.
my tables were tbl_Floor,tbl_FloorParts,tbl_Seat which tbl_Floor was master of tbl_FloorParts and tbl_FloorParts was master of tbl_Seat.
So for solving this error I did this trick.
procedure Tfrm_Main.UpdateTblFloor(...);
var
FID:Integer;
q:TADOQuery
begin
FID:=tbl_Floor.FieldByName('FID').AsInteger;
tbl_Floor.Close;
q:=TADOQuery.Create(nil);
try
q.Connection:=tbl_Floor.Connection;
q.SQL.Add('Update [Floor]');
q.SQL.Add(...);//Set Fields that needed to be updated
q.SQL.Add('where [FID]='+IntToStr(FID));
q.ExecSQL;
finally
q.free;
end;
tbl_Floor.Open;
tbl_Floor.Locate('FID',FId,[loPartialKey]);
end;
and I added these events for tbl_Floor,tbl_FloorParts
procedure Tfrm_Main.tbl_FloorAfterOpen(DataSet: TDataSet);
begin
tbl_FloorParts.Open;
end;
procedure Tfrm_Main.tbl_FloorBeforeClose(DataSet: TDataSet);
begin
tbl_FloorParts.Close;
end;
procedure Tfrm_Main.tbl_FloorPartsAfterOpen(DataSet: TDataSet);
begin
tbl_Seat.Open;
end;
procedure Tfrm_Main.tbl_FloorPartsBeforeClose(DataSet: TDataSet);
begin
tbl_Seat.Close;
end;

Resources