Tables are updating in loop, but if error come in one of table than transaction failed and all the tables data updated is gone so provide me the solution in which each time any table is update that its progress can save.
d0
$$
declare g record;
declare tablename varchar(50);
BEGIN
--fetching tablename from catalog.table
for g in execute formate ('select table_name from catalog.table');
loop
tablename= lower(g.tablename);
--passing tablename to function for some execution
execute'select function('''||tablename||''')';
end loop;
end;
$$
The transaction won't fail if you trap the error.
BEGIN
execute your query
EXCEPTION WHEN unique_violation OR foreign_key_violation OR ... THEN
END;
When a function or codeblock is executed there is always already a transaction either created explicitly with a BEGIN or automatically. The BEGIN of the exception block acts as a SAVEPOINT in the transaction. When the error is trapped by the EXCEPTION part only the work after the BEGIN is lost because it rollsback to the savepoint.
When you let an error escape from the function a rollback of the whole transaction is done.
For details see the manual.
BTW. postgresql 9.1 is not being maintained you should consider upgrading.
Related
I have this procedure which basically insert data.
Begin Transaction
Insert into [dbo].Values
(
EQ
)
values
(
#EQ
)
End
--Set #STATUSRet= 'Created'
--Set #ErrorRet= ''
Commit Transaction
End Try
Begin Catch
Set #STATUSRet= 'Failed'
Set #ErrorRet= (Select ERROR_MESSAGE())
Rollback Transaction
End Catch
Now I want to add a piece of code that calls another database server and insert data into the table in that server i.e. remotely. That's ok I will do that but if that fails then that should not effect my current process of inserting the data as I have described above i.e. if the remote data insertion fails it should not effect the prior insert in any way and should return successfully to the calling application behaving like nothing happened.
The default method of controlling transactions is auto-commit:
Any single statement that changes data and executes by itself is
automatically an atomic transaction. Whether the change affects one
row or thousands of rows, it must complete successfully for each row
to be committed. You cannot manually rollback an auto-commit
transaction.
So, if the two inserts are not wrapped in explicit transaction this will be the behavior. If you have more code blocks, then you can use two separate explicit transactions blocks like this:
DECLARE #ExecuteSecondTransaction BIT = 0;
-- local database
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
SET #ExecuteSecondTransaction = 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
-- remote database
IF #ExecuteSecondTransaction = 1
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
END;
I have strange situation
It's bit hard to explain but I'll do my best
There are 3 different database included
From DB1 I call function on DB2 (over dblink)
That procedure calls another procedure that inserts data into table on DB3
Function on DB2 has EXCEPTION handle that should rollback everything that it did in case of exception
I did example run, and everything went well (there was no error) but insert from procedure 3 was not rollbacked and I have to rollback from DB1 to truly rollback
If i commit from db1, row is inserted
Am I doing something wrong and is there a way to rollback directly from function on db2
Here is some example code:
--DB1
PROCEDURE 1
BEGIN
x := function2#dblink_to_db2();
END;
--DB2
FUNCTION 2
BEGIN
procedure3();
RAISE SOME EXCEPTION;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
do_something_else();
RETURN 0;
END;
PROCEDURE 3
BEGIN
INSERT INTO tableA#dblink_to_db3 VALUES ... ;
END;
So no error is raised but insert into table on db3 is not rollbacked
You must be having a commit somewhere in your code either before the raise exception or in Procedure3. I just tested the below code and it rolledback everything before the exception. Please ignore the naming conventions, had to go due to time constraint.
Database 3
CREATE TABLE temp
(col1 NUMBER);
create or replace procedure testp(i number)
as
BEGIN
INSERT INTO temp VALUES (i);
END;
/
Database2
CREATE OR REPLACE FUNCTION DLR_TRANS.testf(i number)
return number
as
e exception;
BEGIN
testp#TO_DB3(i);
RAISE e;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RETURN 0;
END;
/
database1
declare
x number;
BEGIN
x := testf#TO_DB2(15);
DBMS_OUTPUT.PUT_LINE ( 'x = ' || x );
commit;
END;
x returns 0 due to exception in DB2's function.
And below is the data when I ran the below statement on DB3
select * from temp;
Hope this helps
The problem is that you have "handled" the exception in [function 2]. You should not put the exception block in [function 2] at all. And let the exception propagate up to [procedure 1]. Here you will implicitly or explicitly rollback.
If you must have an exception block in [function 2] then you should have a [raise] at the end. Handling the exception like this is saying [I have handled it and for all practical purposes the caller of this should not think anythibg bad has happened]
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'm trying to check if the room that is going to be inserted in the system is already rented at that date or not. I've though about counting the rows that match both the room number and the date, and then rolling back the transaction. But I'm getting the following error, even though I have changed the code to raise user-defined exceptions:
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
CONTEXT: PL/pgSQL function "checkRoom"() line 17 at SQL statement
CREATE OR REPLACE FUNCTION "checkRoom"() RETURNS TRIGGER AS
$BODY$
DECLARE
counter integer;
BEGIN
SELECT COUNT("num_sesion")
FROM "Sesion"
INTO counter
WHERE "Room_Name"=NEW."Room_Name" AND "Date"=NEW."Date";
IF (counter> 0) THEN -- Probably counter>1 as it's triggered after the transaction..
raise notice 'THERE'S A ROOM ALREADY!!';
raise exception 'The room is rented at that date';
END IF;
RETURN new;
EXCEPTION
WHEN raise_exception THEN
ROLLBACK TRANSACTION;
RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Then I create the trigger:
CREATE TRIGGER "roomOcupied" AFTER INSERT OR UPDATE OF "Room_Name", "Date"
ON "Sesion" FOR EACH ROW
EXECUTE PROCEDURE "checkRoom"();
It's been 2 years from my last approach to SQL and the changes between plsql and plpgsql are getting me crazy.
A couple of issues with your trigger function:
Use IF EXISTS (...) THEN instead of counting all occurrences. Faster, simpler. See:
PL/pgSQL checking if a row exists
A trigger function AFTER INSERT OR UPDATE can just return NULL. RETURN NEW is only relevant for triggers called BEFORE. The manual:
The return value is ignored for row-level triggers fired after an operation, and so they can return NULL.
Unbalanced single quote.
As #Pavel explained, you cannot control transactions from within a plpgsql function. Any unhandled exception forces your entire transaction to be rolled back automatically. So, just remove the EXCEPTION block.
Your hypothetical trigger rewritten:
CREATE OR REPLACE FUNCTION check_room()
RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS (
SELECT FROM "Sesion" -- are you sure it's not "Session"?
WHERE "Room_Name" = NEW."Room_Name"
AND "Date" = NEW."Date") THEN
RAISE EXCEPTION 'The room is rented at that date';
END IF;
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
A BEFORE trigger makes more sense.
But a UNIQUE INDEX ON ("Room_Name", "Date") would do the same, more efficiently. Then, any row in violation raises a duplicate key exception and rolls back the transaction (unless caught and handled). In modern Postgres you can alternatively skip or divert such INSERT attempts with INSERT ... ON CONFLICT .... See:
How to UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL?
Advanced usage:
How to use RETURNING with ON CONFLICT in PostgreSQL?
PostgreSQL processes errors significantly differently from other databases. Any unhandled errors are raised to the user. Inside PL/pgSQL you can trap any exception or you can raise any exception, but you cannot explicitly control transactions. Any PostgreSQL statement is executed inside of a transaction (functions too). And the most outer transaction is automatically broken when any unhandled exception goes to the top.
What you can:
raise exception (often in triggers)
BEGIN
IF CURRENT_USER <> 'Admin' THEN
RAISE EXCEPTION 'missing admin rights';
END IF;
RETURN NEW;
END;
trapping exception
BEGIN
BEGIN -- start of protected section
-- do some, what can be stopped by exception
EXCEPTION WHEN divide_by_zero THEN
-- exception handler;
RAISE WARNING 'I was here';
-- should ignore
EXCEPTION WHEN others THEN
-- any unexpected exception
RAISE WARNING 'some unexpected issue';
RAISE; -- forward exception'
END;
There is no other possibility - so writing application in PL/pgSQL is very simple, but different than PL/SQL or TSQL.
I am acquiring lock on row using "select * from table_name where att1 = 'some_value' for update" query using sqldeveloper. And from SP trying to release the lock using rollback on same row in case of exception but rollback is not working from SP. But if i do rollback from sqldeveloper its working fine and releasing the lock.
please guide me if i am doing anything wrong. Here is my stored procedure.
DECLARE
resource_busy EXCEPTION;
resource_busy2 EXCEPTION;
PRAGMA EXCEPTION_INIT (resource_busy, -30006);
PRAGMA EXCEPTION_INIT (resource_busy2, -00054);
BEGIN
counter := 0;
SELECT COUNT (*)
INTO counter
FROM TBLACCOUNT
WHERE TBLACCOUNT.ACCOUNT_ID = RPAD (ACCT_NUM, 20, ' ');
IF (counter > 0) THEN
BEGIN
SELECT TBLACCOUNT.AVAILABLE_BALANCE, TBLACCOUNT.ACTUAL_BALANCE
INTO Avail_Bal, Curr_Bal
FROM TBLACCOUNT
WHERE TBLACCOUNT.ACCOUNT_ID = RPAD (ACCT_NUM, 20, ' ')
FOR UPDATE WAIT 1;
EXCEPTION
WHEN resource_busy OR resource_busy2
THEN
ROLLBACK; --This rollback is not working.
RETURN -2;
END;
END IF;
END;
This SP return -2 whenever i acquire lock using select for update but not doing rollback.
If you are executing the stored procedure in a different session than the SQL Developer session where you acquired the lock, issuing a rollback will not release the lock. The SQL Developer session (Session A) holds the lock so nothing the session where the stored procedure is being executed (Session B) does can affect that. Only Session A can issue the rollback and release the lock.
If you are executing the stored procedure in the same session as the SQL Developer session where you acquired the lock, the SELECT ... FOR UPDATE statement in the stored procedure will not generate an exception because the current session already holds the lock. That would mean that the stored procedure never enters the EXCEPTION block and never issues the ROLLBACK.