I need to write a PL/SQL procedure, within this procedure I need to call another procedure within its own transaction bounds, and commit it regardless of failure or commit of main transaction. In other words I need something like REQUIRES NEW transaction propagation.
Something like:
procedure mainProcedure(arugements) is
begin
// some statements
nestedProcedure(someArguments);
// some other statements
end;
procedure nestedProcedure(arguments) is
begin
// start a new transaction
// some statements, lock some objects!
// commit the new transaction and release locked objects
end;
How can I achieve this?
Have a look at Autonomous transation. Also see demo
CREATE TABLE t (
test_value VARCHAR2(25));
CREATE OR REPLACE PROCEDURE child_block IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO t
(test_value)
VALUES
('Child block insert');
COMMIT;
END child_block;
/
CREATE OR REPLACE PROCEDURE parent_block IS
BEGIN
INSERT INTO t
(test_value)
VALUES
('Parent block insert');
child_block;
ROLLBACK;
END parent_block;
/
Execution:
-- empty the test table
TRUNCATE TABLE t;
-- run the parent procedure
exec parent_block;
-- check the results
SELECT * FROM t;
You may use pragma autonomous_transaction. It does the same what you need. But don't forget that in the sub transaction you will not see any updates from above transactions.
procedure mainProcedure(arugements) is
begin
// some statements
nestedProcedure(someArguments);
// some other statements
end;
procedure nestedProcedure(arguments) is
pragma autonomous_transaction;
begin
// start a new transaction
// some statements, lock some objects!
// commit the new transaction and release locked objects
commit;
end;
Create a (Commit single transaction Procedure), you can create the procedure and use it as illustrated in the comments.
CREATE OR REPLACE PROCEDURE SWD_AUTON_DML (p_dmlstat VARCHAR2)
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN -- Main transaction suspends here.
EXECUTE IMMEDIATE p_dmlstat; -- Autonomous transaction begins here.
COMMIT; -- Autonomous transaction ends here.
END; -- Main transaction resumes here.
--------------------- How to USE -------------------------------------
----------------------------------------------------------------------
---EXECUTE AUTON_DML(q'[UPDATE staging_pm_schedule_table SET NOTE = 'TEST_Variable']');
--OR
---EXECUTE AUTON_DML('UPDATE staging_pm_schedule_table SET NOTE = '''TEST_Variable'''');
for more information:
https://brainsdazed.blogspot.com/2018/09/oracle-procedure-to-commit-per-dml.html
Related
Is it possible/make sense to have COMMIT statement in SQL functions?
Technically, the answer ist yes. You can do the following:
create or replace function committest return number as
begin
update my_table set col = 'x';
commit;
return 1;
end;
/
declare
number n;
begin
n := committest();
end;
/
However, you can't do the following:
select committest() from dual;
this would be a commit during a query and thus result in a
ORA-14552: Cannot Perform a DDL Commit or Rollback Inside a Query or DML
Yes, you can do that if you make the function an autonomous transaction. That way it will not be part of the current transaction anymore.
create or replace function doIt
as
pragma autonomous_transaction;
begin
... code ...
commit;
end;
/
More documentation
No, it's not possible, see the documentation:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5009.htm
Restrictions on User-Defined Functions
.... In addition, when a function is called from within a query or DML
statement, the function cannot: ....
Commit or roll back the current transaction, create a savepoint or roll back to a savepoint, or alter the session or the system. DDL
statements implicitly commit the current transaction, so a
user-defined function cannot execute any DDL statements.
I have a procedure that inserts data to a table and has a commit statement at the end. It commits each call to the procedure and inserts data.
create or replace procedure abc
begin
insert stmt;
commit;
end;
Now, I wonder if there is a way I can commit multiple inserts to the table through the procedure at the end of the transaction(group transaction). Which means, i don't want to commit each time when the procedure gets called(from the front end), but after several calls to the procedure i need this to be committed.
Remove the commit from the procedure all together. Move it to the calling process. For example:
Create or replace procedure procedure abc(p1 varchar2, p2 ...)
is
begin
insert ....
end abc;
The calling process then becomes something like:
begin
for vars in (select c1, c2, ... from tab)
loop
....
abc(vars.v1, vars.c2, ...);
end loop;
COMMIT; -- only when transaction is complete;
end ;
NOTE; the calling process does NOT have to be plsql, it can be anything that can make the database connection.
Try to create the procedure with parameter. The parameter can take the values: Y or N (or how you need) and based on that value, you will commit, or not.
Example:
create or replace procedure abc (commit_y_n varchar2) as
begin
insert stmt;
if abc.commit_y_n == 'Y' then {
commit;
}
end;
Exception
WHEN OTHERS THEN
--dbms_output.put_line('pl_update_sidm_user r: ERROR CODE:' || sqlcode || '~' ||
--sqlerrm || ' EMPL_NBR:' || r.EMPL_NBR);
insert into ERROR_MSG (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL, 'pl_update_sidm_user_duty_role r2');
END;
I would like to put the error result to a table.
However, how can I do that?
Can I put the result of dbms_output to a table as a string?
If not, can I get the sqlcode,sqlerrm without using dbms_output?
Thank you !!
From the documentation,
A SQL statement cannot invoke SQLCODE or SQLERRM. To use their values
in a SQL statement, assign them to local variables first
Also,
Oracle recommends using DBMS_UTILITY.FORMAT_ERROR_STACK except when using the FORALL statement with its SAVE EXCEPTIONS clause
So, for SQLCODE or SQLERRM, you should assign them into variables and use them.
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION
WHEN OTHERS THEN
v_errcode := SQLCODE;
v_errmsg := SQLERRM;
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) --change your table name
values (ERROR_MSG_ID_SEQ.NEXTVAL,
v_errcode||':'||v_errmsg);
END;
/
Preferably use insert like this instead, as per Oracle's recommendation.
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL,
DBMS_UTILITY.FORMAT_ERROR_STACK);
Demo
Technically what others are suggesting is correct: the "insert" operation executed in the "exception when others" block will actually insert a new row in the log table.
the problem is that such insert statement will be part of the same transaction of the main procedure and, since you had an error while executing it, you are very likely to rollback that transaction, and this will rollback also the insert in your log table
I suppose the problem you are facing is not that you aren't successfully logging the error message: it is that you are rolling it back immediately afterwards, along with all the other writes you did in the same transaction.
Oracle gives you a way of executing code in a SEPARATE transaction, by using "autonomous transaction" procedures.
you need to create such a procedure:
create or replace procedure Write_Error_log(
arg_error_code number,
arg_error_msg varchar2,
arg_error_backtrace varchar2) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
INSERT INTO error_msg (
error_msg_id,
error_code,
error_msg,
error_stack)
VALUES (
error_msg_id_seq.NEXTVAL,
arg_error_code,
arg_error_msg,
arg_error_backtrace);
commit; -- you have to commit or rollback always, before exiting a
-- pragma autonomous_transaction procedure
end;
What this procedure does is to write a new record in the log table using a totally separate and independent transaction: the data will stay in the log table even if you execute a roll back in your calling procedure. You can also use such a procedure to create a generic log (not only errors).
All you have to do now is to call the procedure above whenever you need to log something, so your code becomes:
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION WHEN OTHERS THEN
Write_Error_log(SQLCODE, SQLERRM, dbms_utility.format_error_backtrace);
END;
/
P.S: there might be some typos in my code: I can't test it right now since I can't reach an oracle server in this moment.
I am trying to declare a function in pl/sql and return a variable content from it.
CREATE OR REPLACE FUNCTION executeTransaction
RETURN VARCHAR2 as
pragma autonomous_transaction;
myLog VARCHAR(1200) :='myLog';
BEGIN
savepoint my_savepoint;
begin
insert into datalogger(MPRN_FK,K0213,K0214) values(465,'2142342','423423');
myLog := 'transaction completed with rows insert ' || SQL%ROWCOUNT;
rollback to my_savepoint;
end;
insert into myTbl(col) values(myLog);
RETURN myLog;
END executeTransaction;
The function compiles but when I execute it like
select executeTransaction from dual;
I am getting
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "ECO_AFMS_GAS_CUST.GET_ALLITEMS", line 14
06519. 00000 - "active autonomous transaction detected and rolled back"
*Cause: Before returning from an autonomous PL/SQL block, all autonomous
transactions started within the block must be completed (either
committed or rolled back). If not, the active autonomous
transaction is implicitly rolled back and this error is raised.
*Action: Ensure that before returning from an autonomous PL/SQL block,
any active autonomous transactions are explicitly committed
or rolled back.
and the variable content is not returned? I know that I must either commit or rollback but in this case I rollback so it should work?
Since you are using pragma autonomous_transaction, you need to add COMMIT at the end of your function. As the error states
If not, the active autonomous transaction is implicitly rolled back and this error is raised.
CREATE OR REPLACE FUNCTION executeTransaction
RETURN VARCHAR2 as
pragma autonomous_transaction;
myLog VARCHAR(1200) :='myLog';
BEGIN
savepoint my_savepoint;
begin
insert into datalogger(MPRN_FK,K0213,K0214) values(465,'2142342','423423');
myLog := 'transaction completed with rows insert ' || SQL%ROWCOUNT;
rollback to my_savepoint;
end;
insert into myTbl(col) values(myLog);
commit; --add commit
RETURN myLog;
END executeTransaction;
what is the best solution to write a oracle package for record persistence?
I've always written something like this:
create or replace
PACKAGE BODY "USP_PRICELIST" AS
PROCEDURE usp_TABLE1Save
(
pErrorCode OUT NUMBER,
pMessage OUT VARCHAR2,
pPARAM1 IN CHAR,
pPARAM2 IN CHAR
)
IS
BEGIN
pErrorCode := 0;
INSERT INTO TABLE1
(PARAM1, PARAM2)
VALUES
(pPARAM1, pPARAM2);
EXCEPTION
WHEN OTHERS THEN pErrorCode := SQLCODE; pMessage := SQLERRM;
END usp_TABLE1Save;
END USP_PRICELIST;
and I was wondering if I have to COMMIT after the INSERT INTO.
Alberto
I would not put a commit in the procedure, and leave that to the code that calls the procedure. This allows the procedure to be used as part of a larger transaction. The insert is not implicitly committed.
It really depends on whether you want your operation to take part in a transaction or to be atomic.
Be careful, if you place the commit in the package it will commit the entire transaction
create table testcommit (colA varchar2(50)) ;
DECLARE
PROCEDURE SELFCOMMIT(VAL IN TESTCOMMIT.COLA%TYPE) AS
BEGIN
INSERT INTO TESTCOMMIT(COLA) VALUES(VAL);
COMMIT ;
END SELFCOMMIT ;
PROCEDURE NOCOMMIT(VAL IN TESTCOMMIT.COLA%TYPE) AS
BEGIN
INSERT INTO TESTCOMMIT(COLA) VALUES(VAL);
END NOCOMMIT ;
BEGIN
INSERT INTO TESTCOMMIT(COLA) VALUES('INITIAL');
SELFCOMMIT('FIRST SELF COMMIT');
ROLLBACK ; --KILL TRANSACTION
INSERT INTO TESTCOMMIT(COLA) VALUES('SECOND MAIN INSERT');
NOCOMMIT('NO AUTO COMMIT');
ROLLBACK;
END ;
/
SELECT * FROM TESTCOMMIT;
-->
COLA
--------------------------------------------------
INITIAL
FIRST SELF COMMIT
-->NOTE THE SELFCOMMIT AFFECTS THE ENTIRE TRANSACTION, THUS RENDERING THE ROLLBACK MOOT
--drop table testcommit;
You should also look at the concept of autonomous transactions
By default Oracle has no auto-commit, so you have to.