I have a small doubt regarding BEFORE INSERT TRIGGER in oracle,
my trigger looks like this:
CREATE OR REPLACE TRIGGER some_trigger BEFORE INSERT
ON some_table REFERENCING NEW AS newRow
FOR EACH ROW
DECLARE
some_var number(25, 4);
BEGIN
-- do some stuff
:newRow.some_column :=some_var;
exception
when no_data_found then
NULL;
when others then
NULL;
END;
Here the update which I am doing on newRow.some_column is an optional thing, so my requirement is that even the trigger fails, the newRow should be inserted into the table and this is why I am eating up exceptions.
Is my assumption correct that if I eat up exception, the newRow will be inserted into the table in all scenarios ?
Thanks heaps.
Your exception "handling" will make sure that the insert succeeds, even if you have an exception in your trigger.
Some thoughts:
Your current code cannot cause a NO_DATA_FOUND-exception.
Do you really want your code to fail silently?
Why do you catch both NO_DATA_FOUND and OTHERS and ignore both? OTHERS will catch NO_DATA_FOUND too.
EDIT
I'd just catch the NO_DATA_FOUND and add a good comment about why you can silently ignore it in your case.
Make sure that your SELECT only returns a single row, otherwise TOO_MANY_ROWS needs to be handled too.
Ignoring OTHERS is generally considered bad practice. Your code could fail and you'd never notice. There is a new Compiler Warning for this, actually.
Related
I need to delete one or more row from list of tables stored in a table, and commit only if all deletion succeed.
So I wrote something like this (as part of a bigger procedure):
BEGIN
SAVEPOINT sp;
FOR cur_table IN (SELECT * FROM TABLE_OF_TABLES)
LOOP
EXECUTE IMMEDIATE 'DELETE FROM ' || cur_table.TABNAME || ' WHERE ID = :id_bind'
USING id;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO SAVEPOINT sp;
END;
I know this couldn't work, because of the "execute immediate".
So, what is the correct way to do that?
Dynamic SQL (Execute Immediate) doesn't commit the transaction. You have to commit explicitly. Your code is fine but it doesn't record/log the errors in case if they occur.
Log the errors in the exception handler section.
Honestly, this sounds like a bad idea. Since you have the tables stored in the database, why not just list them out in your procedure? Surely, you're not adding tables that often that this procedure would need to be updated often?
As pointed out in the comments, this will work fine, since EXECUTE IMMEDIATE does not automatically commit.
Don't forget to add a RAISE at the end of your exception block, or you'll never know that an error happened.
Suppose I have the following trigger :
create or replace trigger trigInsertSaloane before insert on saloane
for each row
declare
myExcp exception;
pragma exception_init (myExcp,-20005);
begin
for i in (select * from saloane) loop
if(:new.numar_salon=i.numar_salon) and (trim(upper(:new.nume_sectie))=trim(upper(i.nume_sectie))) then
raise myExcp;
end if;
end loop;
exception when myExcp then dbms_output.put_line('Record exists');
end;
/
All I want is to not insert the row if exception is raised, so something like rollback. In my case if exception is raised and caught, the line is also inserted. I don`t want that. Also I want to make that in a pretty way, by showing up a message and not getting any errors.How to make it?
ok.. a few points.
1) you need to raise an exception from a trigger for the insert will fail and not be inserted. so either do not catch your exception or re RAISE it again.
2) using dbms_output.put_line() will only display a message if the user/client has it turned on.
3) you do not need to loop over your cursor. adding a where clause is more efficient
4) your trigger will not work.. it will throw
ORA-04091: table SALOANE is mutating, trigger/function may not see it if you insert more then 1 row at a time. (try insert into saloane select * from saloane )
5) it may just be your example.. it looks like you could more simply use a unique constraint on the given columns to enforce this requirement.
Adding few more points as mentined in other answer.
Since you are handling the EXCEPTION (PRETTY WELL :p ) in the
Trigger so ideally Trigger event execution is successful so
INSERT/UPDATE/DELETE will happen.
What you need here is to RAISE some kind of exception which will
force the Trigger to fails with the Exception so
in this you need to have RAISE_APPLICATION_ERROR condition to
handle this.
As mentioned above UNIQUE Key constraint will be your best friend to
work in this case.
I have a simple insert script that I want to expand upon.
DECLARE
i varchar2(3000) := dbms_random.string('A',8);
BEGIN
INSERT INTO BUYERS
(USER_ID,BUYER_CD,BUYER_ENG_NM,REG_DT)
VALUES
(i,'tes','test','test');
EXCEPTION WHEN OTHER
THEN
(this is where I need help)
end;
We have dynamic replication going on between two DB's. However, for some odd reason we have to run a script twice for the changes to commit to both DB's for that reason I am creating a script that will attempt to do a insert amongst all tables. As of now I'm only working on one table. Within the exception handler how do I make the script run again when the initial insert fails? Any help is appreciated.
If a problem happens with the insert then the best approach is to find out what the error is and raise the error. This is best accomplished by an autonomous logging procedure that will record the what, where, when and then RAISE the error again so processing stops. You do not want to take a chance of inserting records once, twice or not at all which could happen if the errors are not raised again.
The LOG_ERROR procedure below can be created from the answers to your previous questions about error handling.
DECLARE
i varchar2(3000) := dbms_random.string('A',8);
BEGIN
INSERT INTO BUYERS
(USER_ID,BUYER_CD,BUYER_ENG_NM,REG_DT)
VALUES
(i,'tes','test','test');
EXCEPTION WHEN OTHER
THEN
--by the time you got here there is no point in trying to insert again
LOG_ERROR(SQLERRM, LOCATION);
RAISE;
end;
I have a table which I am trying to do an insert/update on depending on the values I am given. But the insert is not working for this particular table, yet it works for the previous tables which the script has run on.
To test this problem, I put in a few anonymous blocks into oracle's sqldeveloper which inserts or updates depending on whether a key is present. Updates seem to work fine, but when it comes to inserting a new row, nothing is inserted.
If I had this table:
COFFEE_ID TEA_ID NAME
11 100 combo 1
12 101 combo 2
13 102 combo 3
Doing this will not insert anything and will instead move on to the next anonymous block:
begin
insert into COFFEE_TEA(COFFEE_ID, TEA_ID, NAME) values (14, 103, 'combo 4');
exception when dup_val_on_index then
update ....
end;
....
I suspect it has something to do with the trigger on this table. It is a BEFORE EACH ROW trigger type, and it would do an insert data into some other table. There is no exception handling in the trigger, so I'm guessing it must fail but not report it (doesn't show up in sqldeveloper when I run the script).
My two questions would be,
When the trigger runs, what happens if the ID it's trying to insert to the other table already exists? Looks like it silently fails?
How best should I fix this? I am unsure if I can change the trigger code itself, but would it be possible to catch the error inside my anonymous block (assuming that it's actually the trigger that's causing the problem). If so, how would I know what exception to catch if it fails silently?
I removed the exception in sqldeveloper and it tells me that a unique constraint was violated. Namely that the data being inserted into the other table through the trigger is the cause.
Your additional information tells us that your trigger is hurling ORA-00001, a unique key violation. This is the error which the DUP_VAL_ON_INDEX exception handles. So it seems like your exception handler which is supposed to be dealing with key violations on COFFEE_TEA is also swallowing the exceptions from your trigger. Messy.
There are two possible solutions. One is to put decent error handling in the trigger code. The other is to use MERGE for your data loading routine.
I always prefer MERGE as a mechanism for performing upserts, because I don't like using exceptions to handle legitimate expected states. Find out more.
Ideally you should do both. Triggers are supposed to be self-contained code: imposing unhandled exceptions on routines which interact with their tables breaks the enscapsulation.
A trigger will not modify the DML process on a table. Remove the exception block, the insert will either succeed or fail with an error if COFFEE_TEA is a table.
In other words, the following script will never output 0 if COFFEE_TEA is a table:
BEGIN
INSERT INTO coffee_tea(COFFEE_ID, TEA_ID, NAME) values (14, 103, 'combo 4');
dbms_output.put_line(sql%rowcount);
END;
The following link on the PostgreSQL documentation manual http://www.postgresql.org/docs/8.3/interactive/populate.html says that to disable autocommit in postgreSQL you can simply place all insert statements within BEGIN; and COMMIT;
However I have difficulty in capturing any exceptions that may happen between the BEGIN; COMMIT; and if an error occurs (like trying to insert a duplicate PK) I have no way to explicitly call the ROLLBACK or COMMIT commands. Although all insert statements are automatically rolled back, PostgreSQL still expects an explicit call to either the COMMIT or ROLLBACK commands before it can consider the transaction to be terminated. Otherwise, the script has to wait for the transaction to time out and any statements executed thereafter will raise an error.
In a stored procedure you can use the EXCEPTION clause to do this but the same does not apply in my circumstance of performing bulk inserts. I have tried it and the exception block did not work for me because the next statement/s executed after the error takes place fails to execute with the error:
ERROR: current transaction is aborted, commands ignored until end of transaction block
The transaction remains open as it has not been explicitly finalised with a call to COMMIT or ROLLBACK;
Here is a sample of the code I used to test this:
BEGIN;
SET search_path TO testing;
INSERT INTO friends (id, name) VALUES (1, 'asd');
INSERT INTO friends (id, name) VALUES (2, 'abcd');
INSERT INTO friends (id, nsame) VALUES (2, 'abcd'); /*note the deliberate mistake in attribute name and also the deliberately repeated pk value number 2*/
EXCEPTION /* this part does not work for me */
WHEN OTHERS THEN
ROLLBACK;
COMMIT;
When using such technique do I really have to guarantee that all statements will succeed? Why is this so? Isn't there a way to trap errors and explicitly call a rollback?
Thank you
if you do it between begin and commit then everything is automatically rolled back in case of an exception.
Excerpt from the url you posted:
"An additional benefit of doing all insertions in one transaction is that if the insertion of one row were to fail then the insertion of all rows inserted up to that point would be rolled back, so you won't be stuck with partially loaded data."
When I initialize databases, i.e. create a series of tables/views/functions/triggers/etc. and/or loading in the initial data, I always use psql and it's Variables to control the flow. I always add:
\set ON_ERROR_STOP
to the top of my scripts, so whenever I hit any exception, psql will abort. It looks like this might help in your case too.
And in cases when I need to do some exception handling, I use anonymous code blocks like this:
DO $$DECLARE _rec record;
BEGIN
FOR _rec IN SELECT * FROM schema WHERE schema_name != 'master' LOOP
EXECUTE 'DROP SCHEMA '||_rec.schema_name||' CASCADE';
END LOOP;
EXCEPTION WHEN others THEN
NULL;
END;$$;
DROP SCHEMA master CASCADE;