I have a master and a detail TAdoQuery called FMaster and FDetail. They are connected via the DataSource FMasterSource. The detail query has a parameter, which is automatically filled and updated depending on the master query.
Now the problem is: After doing a FDetail.Locate (or in fact FDetail.Recordset.Clone), FMaster.Next throws an exception while trying to requery FDetail with the new parameter:
try
FMaster.Open;
FDetail.Open;
FDetail.Locate('Id', 2, []);
FMaster.Next; {<== throws Exception with ADO Errorcode 0x80040e05}
finally
FDetail.Close;
FMaster.Close;
end;
According to this List, Errorcode 0x80040e05 means "Object already open".
The problem seems to be, that FDetail.Locate creates a clone of the underlying Ado Recordset for later use, and saves it. We can therefore replace the Locate by
recordsetClone := FDetail.Recordset.Clone(adLockReadOnly); (which uses Winapi.ADOInt) and get the same error.
Inside FMaster.Next, the exception gets thrown by trying to requery FDetail after the new parameter for MasterId is set. This happens in TCustomADODataSet.RefreshParams.
When you disconnect Detail from Master, everything works just fine. Setting the parameter of FDetail by hand works just fine.
What did I miss?
Is it an error in the VCL?
Or is it yet another weird ADO bug?
Source Code
I am using Oracle 11g XE and Delphi 10.4 Sydney.
Oracle tables:
drop table Detail;
drop table Master;
create table Master (
Id NUMBER(2) not null,
constraint MasterPK primary key (Id)
);
create table Detail (
Id NUMBER(2) not null,
MasterId NUMBER(2),
constraint DetailPK primary key (Id),
constraint DetailFK foreign key (MasterId) references Master(Id)
);
insert into Master values (1);
insert into Master values (2);
insert into Detail values (1,1);
insert into Detail values (2,1);
insert into Detail values (3,2);
Delphi code:
procedure TForm1.Button1Click(Sender: TObject);
begin
//Initializing...
FConnection.Provider := 'OraOLEDB.Oracle.1';
FConnection.ConnectionString := 'Provider=OraOLEDB.Oracle.1;Password=xxxx;Persist Security Info=True;User ID=TEST;Data Source=XE';
FConnection.LoginPrompt := False;
FConnection.KeepConnection := False;
FMaster.Connection := FConnection;
FMaster.SQL.Text := 'select Id from Master';
FMaster.CursorLocation := clUseServer;
FMaster.CursorType := ctStatic;
FMasterSource.DataSet := FMaster;
FDetail.Connection := FConnection;
FDetail.DataSource := FMasterSource;
FDetail.SQL.Add('select Id, MasterId from Detail where MasterId = :Id');
FDetail.Parameters[0].DataType := ftInteger;
FDetail.CursorLocation := clUseServer;
//Do stuff
try
FMaster.Open;
FDetail.Open;
//FDetail.Locate('Id', 2, []);
recordsetClone := FDetail.Recordset.Clone(adLockReadOnly);
FMaster.Next; {<== throws Exception with ADO Errorcode 0x80040e05}
finally
FDetail.Close;
FMaster.Close;
end;
end;
Related
I am trying do same with FireDac as following SQL:
CREATE TABLE [Franchises]([Franchise] TEXT(100) PRIMARY KEY ASC NOT NULL UNIQUE);
CREATE UNIQUE INDEX [IFran] ON [Franchises]([Franchise] ASC);
But i don't see index in SQLite Expert, property UNIQUE, PK. And why FireDac create table and column name in UPPERCASE?
I used code:
procedure TDataModule1.DataModuleCreate(Sender: TObject);
var
allTables:TStrings;
Table: TFDTable;
DBExists:Boolean;
begin
FDConnection1.Params.Database:= pathINI+baseTableDB;
DBExists:=FileExists(FDConnection1.Params.Database);
FDConnection1.Params.Values['CreateDatabase']:=BoolToStr(not DBExists,True);
FDConnection1.Params.DriverID:='SQLite';
FDConnection1.Connected:= True;
allTables := TStringList.Create;
FDConnection1.GetTableNames('', '', '', allTables, [osMy, osOther, osSystem],[tkView, tkTable, tkTempTable, tkLocalTable]);
if allTables.IndexOf('Franchises')=-1 then
begin
Table := TFDTable.Create(nil);
try
Table.Connection := FDConnection1;
Table.TableName := 'Franchises';
Table.FieldDefs.Add('Franchise', ftString, 100, True);
{ define primary key index }
// Table.IndexDefs.Add('IFran', 'Franchise',[ixPrimary]);
Table.AddIndex('IFran', 'Franchise', '', [soPrimary,soUnique,soNoCase]);
Table.IndexesActive:=True;
Table.IndexName:='IFran';
Table.CreateTable(False,[tpTable, tpPrimaryKey, tpIndexes]);
finally
Table.Free;
end;
end;
allTables.Free;
FDTable1.Connection:=FDConnection1;
FDTable1.TableName:='Franchises';
FDTable1.Open;
end;
My Oracle DB has a table DOC_WF_COMM and its primary key is DWFC_ID. Primary key value is based on a sequence called SQ_DOC_WF_COMM.
I have created a row level AFTER INSERT trigger on that table and inside the trigger I need to join the inserted record with some other tables like this:
create or replace TRIGGER TRG_DOC_WF_COMM_AFT_INS AFTER INSERT ON DOC_WF_COMM REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
L_SUBJECT VARCHAR2(300);
L_BODY CLOB;
L_PNT_CODE VARCHAR(100) := NULL;
L_DR_PRJ_ID NUMBER(12);
L_STR_EMAIL VARCHAR2(120);
L_DWFC_TO_USR_ID VARCHAR2(12);
L_PNT_ID NUMBER(12);
L_PNT_EMAIL_YN VARCHAR(1);
L_PNT_ACTIVE_YN VARCHAR(1);
L_PNT_NOTIFY_YN VARCHAR(1);
BEGIN
IF INSERTING THEN
L_PNT_CODE := 'WFNT_MESSAGE';
SELECT DR_PRJ_ID, STR_EMAIL, DWFC_TO_USR_ID INTO L_DR_PRJ_ID, L_STR_EMAIL, L_DWFC_TO_USR_ID
FROM DOC_WF_COMM
JOIN DOC_WF_USERS ON DWFU_ID = DWFC_DWFU_ID
JOIN DOC_WORKFLOW ON DWF_ID = DWFU_DWF_ID
JOIN DOCUMENT_REF ON DR_ID = DWF_DR_ID
JOIN ST_REGISTER ON STR_ID = DWFU_STR_ID
WHERE DWFC_ID = :NEW.DWFC_ID AND DWFC_RESPONSE IS NULL;
-- SOME QUERIES HERE
END IF;
END;
The trigger is compiled successfully and when I insert record into DOC_WF_COMM table I get this error:
ORA-01403: no data found ORA-06512
The error is :NEW.DWFC_ID in WHERE clause and I have change it to these values:
:OLD.DWFC_ID
SQ_DOC_WF_COMM.NEXTVAL
SQ_DOC_WF_COMM.CURRVAL
But no any luck. Any idea why this error is and how can I resolve it?
The problem is this line in your trigger:
PRAGMA AUTONOMOUS_TRANSACTION;
That means the trigger executes as an isolated transaction in a separate session, which means it cannot see the uncommitted state of any other session. Crucially this includes the session which fires the trigger, so the autonomous transaction cannot see the record you just inserted. Hence, NO_DATA_FOUND.
You haven't posted the whole trigger or explained what you're trying to do, so only you know why you have included the PRAGMA. However, the chances are you don't need it. Remove the PRAGMA (and the COMMIT) and your trigger should work just fine.
If I understood you correctly, create a local variable and put the next sequence value in there. Then it can be referenced throughout the code, always having the same value. Something like this:
declare
l_seq number := my_seq.nextval;
begin
insert into table_a (id, ...) values (l_seq, ...);
update table_b set id = l_seq where ...
select ... into ... from ... where id = l_seq;
end;
I changed the query inside the trigger to this, it is working fine
SELECT STR_PRJ_ID, STR_EMAIL, :NEW.DWFC_TO_USR_ID INTO L_DR_PRJ_ID, L_STR_EMAIL, L_DWFC_TO_USR_ID
FROM DOC_WF_USERS, ST_REGISTER
WHERE :NEW.DWFC_TO_USR_ID = DWFU_US_ID AND DWFU_STR_ID = STR_ID AND DWFU_ID = :NEW.DWFC_DWFU_ID;
Not sure why is that. If anyone can figure out the mistake in the query given in the question, please let me know. Thanks
I am looking for a generic procedure that will generate audit trails for Oracle databases. We are currently using a similar procedure on SQL Server and wondering if an Oracle equivalent exists. We are hoping the audit table will be a separate table than the original table and include user/date time info.
Here is the SQL Server equivalent we are using: https://www.codeproject.com/Articles/21068/Audit-Trail-Generator-for-Microsoft-SQL
Any advice is greatly appreciated.
If you don't want to use Oracle native mechanism, you could have your very own framework that generates and reads your own auditing table (I know you can, we had similar thing where I once worked).
Here are the main components:
a_sqnc is the sequence you will use in TrackTable to keep track of the order of actions in column NO_ORD (even though there is also a D_UPD column with the modification time).
create sequence a_sqnc
minvalue 1
maxvalue 99999999
start with 1
increment by 1
nocache;
TrackTable will have a TABLE_NAME column in order to track changes from different tables. It also have a PK_VALUE and ROW_VALUE where we store the data that changed. Here is the table creation with useful indexes:
create table TrackTable (
table_name VARCHAR2(50) not null,
action VARCHAR2(240) not null,
no_ord NUMBER(12) not null,
nature VARCHAR2(3) not null,
pk_value VARCHAR2(4000),
row_value VARCHAR2(4000),
ori VARCHAR2(250),
c_user VARCHAR2(20),
d_upd DATE
);
create index AP_D_UPD on TrackTable (D_UPD);
create index AP_NO_ORD on TrackTable (NO_ORD);
create index AP_TABLE_NAME on TrackTable (TABLE_NAME);
Say you have a simple table BANK with two columns PK_val (the primary key) and val:
create table BANK (
pk_val VARCHAR2(50) not null,
val VARCHAR2(240) not null
);
alter table BANK
add constraint BK_PK primary key (pk_val)
using index ;
Use DBMS_APPLICATION_INFO.READ_MODULE(w_sess_mod, w_sess_act) to know what module and what action operates: I concatenate both in column ORI in TrackTable;
user Oracle session variable will allow you tracking who did the change in column c_user;
Here is how to create trigger TRCK_BNK to track changes in table BANK; it will categorize in 3 actions: DELETE, UPDATE, INSERT (you can remove the INSERT case if needed).
CREATE OR REPLACE TRIGGER "TRCK_BNK"
AFTER DELETE OR INSERT OR UPDATE
ON BANK
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
w_a VARCHAR2(10);
W_ERRM VARCHAR2(1000);
W_CODE VARCHAR2(1000);
w_n VARCHAR2(200) := 'BANK';
w_id NUMBER := a_sqnc.nextval;
w_act v$session.action%type;
w_mod v$session.module%type;
w_ori TrackTable.ORI%TYPE;
BEGIN
DBMS_APPLICATION_INFO.READ_MODULE(w_mod, w_act);
w_ori := 'Module : '||w_mod ||' ; Action : '||w_act;
----------------------------------
-- test which action is for change
----------------------------------
IF UPDATING
THEN
w_a := 'UPDATE';
ELSIF DELETING
THEN
w_a := 'DELETE';
ELSIF INSERTING
THEN
w_a := 'INSERT';
END IF;
----------------------------------
-- Insert into TrackTable
----------------------------------
If w_a in ('UPDATE', 'DELETE') then
Insert into TrackTable
Select w_n, w_a, w_id, 'OLD', :OLD.pk_val, :OLD.val
, w_ori, user, sysdate
From Dual;
End if;
-- if you update, there is a new value and an old value
If w_a in ('UPDATE', 'INSERT') then
Insert into TrackTable
Select w_n, w_a, w_id, 'NEW', :NEW.pk_val, :NEW.val
, w_ori, user, sysdate
From Dual;
End if;
Exception
When others then
Begin
W_ERRM := SQLERRM;
W_CODE := SQLCODE;
-- try inserting in case of error anyway
Insert into TrackTable
Select w_n, w_a, -1, 'ERR', 'Grrr: '||W_CODE, W_ERRM
, w_ori, user, sysdate
From Dual;
End;
End;
/
Then add functions to your framework that generates the triggers given a table, retrieves changes, reverts table to a given date...
NB:
This way of tracking every change on the table impairs performances if table changes a lot. But it is great for parameter tables that scarcely change.
Have a look at Oracles Flashback Data Archive which bases upon the UNDO Data. It can be configured to track any change to your data. It is available in any edition of oracle since 11g2 (11.2.0.4).
In the Oracle documentation it says that optimazation is limited but basic functionality is available in any edition.
Hi I created two tables
CREATE TABLE "LCM_001"."LCM_BDT$ACT"
( "ID" NUMBER(*,0) NOT NULL ENABLE,
"SHORTDESCRIPTION" VARCHAR2(25 CHAR),
"LONGDESCRIPTION" VARCHAR2(500 BYTE),
"VALIDFROM" TIMESTAMP (6) WITH LOCAL TIME ZONE,
"VALIDTO" TIMESTAMP (6) WITH LOCAL TIME ZONE,
"VERSION" NUMBER(*,0) NOT NULL ENABLE
)
The second table is exactly the same as the first, so I created it with "
create table LCM_BDT$HIST as select * from LCM_BDT$ACT where 1=0;
Then I created a view
select * from LCM_BDT$ACT
Union
select * from LCM_BDT$HIST
Then I wrote a a instead of trigger
create or replace TRIGGER LCM_BDT_DML
instead of insert
on LCM_BDT
-- for each row
declare
vAct LCM_BDT$ACT%rowtype;
vHist LCM_BDT$HIST%rowtype;
begin
IF INSERTING THEN
select BDT_SEQ.nextval into vAct.id from dual;
vAct.SHORTDESCRIPTION := :new.SHORTDESCRIPTION;
vAct.LONGDESCRIPTION := :new.LONGDESCRIPTION;
vAct.VALIDFROM := sysdate;
vAct.VALIDTO := TO_TIMESTAMP('31.12.3999','DD.MM.YYYY');
vAct.Version := 1;
Insert into LCM_BDT$ACT values vAct;
END IF;
IF UPDATING THEN
-- vHist.ID := :old.ID;
-- vHist.SHORTDESCRIPTION := :old.SHORTDESCRIPTION;
-- vHist.LONGDESCRIPTION := :old.LONGDESCRIPTION;
-- vHist.VALIDFROM := :old.VALIDFROM;
-- vHist.VALIDTO := sysdate;
-- vHist.VERSION := :old.Version;
--
-- Insert into LCM_BDT$ACT values vAct;
-- -- new record
-- UPDATE LCM_BDT$HIST set
-- vAct.SHORTDESCRIPTION := :new.SHORTDESCRIPTION;
-- vAct.LONGDESCRIPTION := :new.LONGDESCRIPTION;
-- vAct.VALIDFROM := sysdate;
-- vAct.VALIDTO := TO_TIMESTAMP('31.12.3999','DD.MM.YYYY');
-- vAct.Version := :old.Version +1;
-- Insert into LCM_BDT$HIST values vHist;
delete from LCM_BDT where id = :old.ID;
END IF;
END LCM_BDT_DML;
The Problem is that I alway got an SQL error:
SQL-Fehler: ORA-01732: data manipulation operation not legal on this view
01732. 00000 - "data manipulation operation not legal on this view"
*Cause:
*Action:
Any suggestions ?
Thank you
Your if updating branch includes:
delete from LCM_BDT where id = :old.ID;
That is trying to delete from the same view this trigger is against, not either of the underlying tables. (Presumably, if the inserts into BDT$ACT and BDT$HIST weren't commented out, you would really want to delete the old BDT$ACT record? Or both - perhaps eventually via the view.)
As the trigger is only instead of insert, that can't actually be reached; but any attempt to update or delete the view will get an error, since the trigger doesn't cover either of those operations.
You can allow update and delete (which will need a when deleting section by changing the trigger to be:
instead of insert or update or delete
on LCM_BDT
I am trying to delete data from two related tables - primary key in the table users and foreign key in the table login - but I'm getting error PL/SQL: ORA-00933: SQL command not properly ended.
Create table users and primary key:
/* table user*/
create table users (id_user number(10) not null,
name_user varchar(30) not null);
/* primary key */
alter table users add constraint user_pk primary key (id_user);
Create table login and primary key and foreign key:
/* table login*/
create table login (id_login number(10) not null,
id_user_login number(10) not null,
email_login varchar(20) not null,
password_login varchar(20) not null);
/* primary key */
alter table login add constraint login_pk primary key (id_login);
/* foreign key reference to user*/
alter table login add constraint login_fk_user foreign key (id_user_login)
references users(id_user) on delete cascade;
Procedure to create session with table users/login, which works:
PROCEDURE create_user_session( p_name IN VARCHAR2,
p_email IN VARCHAR2,
p_pass IN VARCHAR2,
p_error OUT NUMBER,
p_msg_error OUT VARCHAR2)
IS
BEGIN
p_error := 0;
INSERT ALL
INTO users (id_user, name_user) VALUES(seq_user.NEXTVAL,p_name)
INTO login(id_login, id_user_login, email_login, pass_login)
VALUES(seq_login.NEXTVAL, seq_user.CURRVAL, p_email, p_pass)
SELECT * FROM DUAL COMMIT;
EXCEPTION
WHEN OTHERS THEN
p_error := 1;
p_msg_error := 'Error!'||SQLERRM;
END create_user_session;
Now I want to delete this session, but I have error PL/SQL: ORA-00933: SQL command not properly ended from this procedure:
PROCEDURE delete_user_session(
p_id_user IN NUMBER,
p_error OUT NUMBER,
p_msg_error OUT VARCHAR2)
IS
BEGIN
p_error := 0;
DELETE FROM users, login USING users
INNER JOIN login WHERE users.id_user = p_id_user
AND login.id_user_login = p_id_user;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
p_error := 1;
p_msg_error := 'Error!'||SQLERRM;
END delete_user_session;
I have this image from SQL developer to show the error (red squiggle underline on the s, in DELETE FROM users, login ... if you can't see the image):
What am I doing wrong?
You can't delete from two tables in one statement - there is no delete equivalent of insert all. (Unless you have constraints that cascade the delete, or a trigger that does that manually). The documentation shows that your syntax is not valid, as there is no path to specify more than one table.
You will need to have two delete statements, removing the records from the child table first:
DELETE FROM login
WHERE login.id_user_login = p_id_user;
DELETE FROM users
WHERE users.id_user = p_id_user;
You could change your foreign key constraint to delete cascade:
alter table login add constraint login_fk_user foreign key (id_user_login)
references users(id_user) on delete cascade;
... which would mean you would only have to explicitly delete from the users table; but that may not actually be what you want, as it removes one level of validation - you may want to prevent a parent key being accidentally removed if it has children. Issuing two deletes doesn't really hurt here.
Incidentally, your first procedure is not committing, which you might be expecting. In this line:
...
SELECT * FROM DUAL COMMIT;
... the COMMIT is interpreted as an alias for the DUAL table, not a separate command. You would need a semicolon after DUAL, and preferably a new line for the COMMIT;. But it's generally considered better not to commit in a procedure, and let the top-level caller decide whether to commit or roll back to preserve data integrity.