Сalling PL/SQL function from select statement does not work [duplicate] - oracle

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.

Related

Different default error handling in Oracle and PostgreSQL

I compare the default behavior of Oracle and PostgreSQL after encountering an error in a PL/SQL (PL/pgSQL) code. For this purpose, I wrote an analogous Oracle and PostgreSQL code shown below.
Oracle code (db<>fiddle):
CREATE TABLE table1 (col1 int);
CREATE PROCEDURE raise_error AS
BEGIN
INSERT INTO table1 VALUES (1/0);
END;
/
INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;
PostgreSQL code (db<>fiddle):
CREATE TABLE table1 (col1 int);
CREATE PROCEDURE raise_error() AS $$
BEGIN
INSERT INTO table1 VALUES (1/0);
END;
$$ LANGUAGE plpgsql;
BEGIN TRANSACTION; -- disable auto-commit
INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;
Note: In PostgreSQL I additionally run the BEGIN TRANSACTION statement to disable auto-commit, because Oracle doesn't have auto-commit, and I want both codes to be analogous.
The result of the SELECT * FROM table1 query is one row in Oracle, and no rows in PostgreSQL.
As you can see, the analogous code in Oracle and PostgreSQL gives different results. What is the reason of this difference in the default error handling?
Oracle and PostgreSQL indeed behave differently here.
Oracle has something that I would call “statement-level rollback”: if a statement running inside a transaction causes an error, only the effects of that statement are rolled back, and the transaction continues.
In PostgreSQL, any error inside a transaction aborts the whole transaction, so you can only roll back the transaction, and it has no effects at all. This is more in the spirit of “all or nothing”, but as far as I can see, the SQL standard is not specific about this, so both can behaviors can be argued.
You can, however, use standard conforming savepoints in PostgreSQL to “recover” from an error in a transaction:
START TRANSACTION;
INSERT INTO table1 VALUES (1);
/* set a savepoint we can revert to */
SAVEPOINT x;
CALL raise_error();
ROLLBACK TO SAVEPOINT x;
/* now the INSERT can be committed */
COMMIT;
But be warned that you don't use too many savepoints (not more than 64) per transaction, else performance may suffer.
In Oracle you are using two separate transactions, the first is successful but the second fails. In PostgreSQL, you are explicitly telling it to only use one transaction and handle the statements together.
In Oracle, if you use a PL/SQL anonymous block to group the statements into a single transaction:
BEGIN
INSERT INTO table1 VALUES (1);
raise_error();
END;
/
And, equivalently in PostgreSQL:
DO
$$
BEGIN
INSERT INTO table1 VALUES (1);
CALL raise_error();
END;
$$ LANGUAGE plpgsql;
Then there will be no rows in the table as the exception from the procedure will rollback the entire transaction.
Or, in Oracle, you could do:
INSERT INTO table1 VALUES (1);
DECLARE
divide_by_zero EXCEPTION;
PRAGMA EXCEPTION_INIT( divide_by_zero, -1476 );
BEGIN
raise_error();
EXCEPTION
WHEN DIVIDE_BY_ZERO THEN
ROLLBACK;
END;
/
Which would have the same effect of rolling back both transactions to the last commit.
db<>fiddle Oracle PostgreSQL

Oracle: Call a function from "select from dual" do nothing

I have a function that inserts to and updates tables. Calling it from:
select AGENTS_REGISTRATION() from dual;
or:
declare
x NUMBER;
begin
select AGENTS_REGISTRATION() into x from dual;
end;
/
do nothing. Another form makes necessary changes:
declare
x NUMBER;
begin
x := AGENTS_REGISTRATION();
end;
/
What is wrong with first two expression?
Oracle does not allow DML operations inside a query. Calling function that inserts/updates/deletes data in a query will raise:
ORA-14551: cannot perform a DML operation inside a query
Query will not raise error if exception was trapped inside the function - it is very dangerous to use WHEN OTHERS exception handler (or catching ORA-14551 directly using pragma exception_init) without re-raising the error, as you have seen in this example.
Although I do not recommend this, it is possible to use DML operations in a query if function is defined as autonomous transaction (by using pragma autonomous_transaction) - but this WILL lead to more problems.
The solution is not to call function inside a query - it certainly does not make sense to do this.
Check http://www.dba-oracle.com/t_ora_14551_cannot_perform_a_dml_operation_inside_a_query.htm
create or replace function myFunction return varchar2 as
begin
update emp set empno = empno +1 where empno = 0;
return 'Yeah';
end;
/
SQL> var myVar VARCHAR2
SQL> call myFunction() INTO :myVar;
SQL> print myVar

How to commit individual transaction in Oracle PLSQL

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

Sync between two tables and show the result in an oracle function

I am making an oracle function that sync values between two tables and My intention is to show a string that show how many rows affected and displays it when user execute the function.
my function creation is like this
CREATE OR REPLACE FUNCTION WELTESADMIN.DUM_MST_SYNC(PROJNAME IN VARCHAR2)
RETURN NUMBER IS NUM_ROWS NUMBER;
BEGIN
MERGE INTO MASTER_DRAWING DST
USING (SELECT WEIGHT_DUM, HEAD_MARK_DUM FROM DUMMY_MASTER_DRAWING) SRC
ON (DST.HEAD_MARK = SRC.HEAD_MARK_DUM)
WHEN MATCHED THEN UPDATE SET DST.WEIGHT = SRC.WEIGHT_DUM
WHERE DST.PROJECT_NAME = SRC.PROJECT_NAME_DUM
AND DST.PROJECT_NAME = PROJNAME;
dbms_output.put_line( sql%rowcount || ' rows merged' );
END;
if i execute the begin part in the TOAD or SQL Developer i can see how many rows affected. My target is to collect this function into a procedure and when user wants to sync tables they just need to run the procedure with PROJNAME value supplied for specific project.
Please help me on how to improve this code,
Best regards
You can use SQL%ROWCOUNT to get the number of rows affected by the MERGE. Add the following statement in your code immediately after the MERGE :
dbms_output.put_line( sql%rowcount || ' rows merged' );
To return this value, declare a NUMBER variable and assign the sql%rowcount value to it. And then return that value. Something like :
Function
.......
Return NUMBER
.......
num_rows NUMBER;
......
Begin
Merge....
num_rows := SQL%ROWCOUNT;
RETURN num_rows;
END;
And, you don't need a procedure to execute the function. You can execute in SQL :
select function(project_name)
from dual
/
Update Since OP is trying to use DML inside a function, need to make it autonomous transaction to be able to perform DML without raising the ORA-14551.
You could use the directive pragma autonomous_transaction. This will run the function into an independent transaction that will be able to perform DML without raising the ORA-14551. However, remember, the results of the DML will be committed outside of the scope of the parent transaction. If you have only single transaction, you could use the workaround that I suggested. Add, PRAGMA AUTONOMOUS_TRANSACTION; immediately after the RETURN statement before BEGIN.
CREATE OR REPLACE
FUNCTION WELTESADMIN.DUM_MST_SYNC(
PROJNAME IN VARCHAR2)
RETURN NUMBER
IS
num_rows NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
MERGE INTO MASTER_DRAWING DST USING
(SELECT WEIGHT_DUM, HEAD_MARK_DUM FROM DUMMY_MASTER_DRAWING
) SRC ON (DST.HEAD_MARK = SRC.HEAD_MARK_DUM)
WHEN MATCHED THEN
UPDATE
SET DST.WEIGHT = SRC.WEIGHT_DUM
WHERE DST.PROJECT_NAME = SRC.PROJECT_NAME_DUM
AND DST.PROJECT_NAME = PROJNAME;
num_rows := SQL%ROWCOUNT;
COMMIT;
RETURN num_rows;
END;
/

INSERT INTO + COMMIT in packages

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.

Resources