Re-run PL/SQL script command - oracle

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;

Related

Mutating Trigger Error with Trigger in Oracle PL/SQL [duplicate]

I get an error (ORA-04091: table DBPROJEKT_AKTIENDEPOT.AKTIE is mutating, trigger/function may not see it) when executing my trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
AFTER
INSERT OR UPDATE OF TAGESKURS
OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
bfr number;
Begin
bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
UPDATE AKTIE
SET BILANZ = TAGESKURS - WERT_BEIM_EINKAUF;
IF bfr < -50
THEN
DBMS_OUTPUT.PUT_LINE('ACHTUNG: The value (Nr: '||:new.AKTIEN_NR||') is very low!');
END IF;
END;
I want to check the value "BILANZ" after calculating it, wether it is under -50.
Do you have any idea why this error is thrown?
Thanks for any help!
There are several issues here:
Oracle does not allow you to perform a SELECT/INSERT/UPDATE/DELETE against a table within a row trigger defined on that table or any code called from such a trigger, which is why an error occurred at run time. There are ways to work around this - for example, you can read my answers to this question and this question - but in general you will have to avoid accessing the table on which a row trigger is defined from within the trigger.
The calculation which is being performed in this trigger is what is referred to as business logic and should not be performed in a trigger. Putting logic such as this in a trigger, no matter how convenient it may seem to be, will end up being very confusing to anyone who has to maintain this code because the value of BILANZ is changed where someone who is reading the application code's INSERT or UPDATE statement can't see it. This calculation should be performed in the INSERT or UPDATE statement, not in a trigger. It considered good practice to define a procedure to perform INSERT/UPDATE/DELETE operations on a table so that all such calculations can be captured in one place, instead of being spread out throughout your code base.
Within a BEFORE ROW trigger you can modify the values of the fields in the :NEW row variable to change values before they're written to the database. There are times that this is acceptable, such as when setting columns which track when and by whom a row was last changed, but in general it's considered a bad idea.
Best of luck.
You are modifying the table with the trigger. Use a before update trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
BEFORE INSERT OR UPDATE OF TAGESKURS OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
v_bfr number;
BEGIN
v_bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
:new.BILANZ := v_bfr;
IF v_bfr < -50 THEN
Raise_Application_Error(-20456,'ACHTUNG: The value (Nr: '|| :new.AKTIEN_NR || ') is very low!');
END IF;
END;

ORACLE PL/SQL Programming problems with trigger

Hi I got a problem running a trigger created using ORACLE PL/SQL programming. Basically, whenever there is an insertion with a location that does not exist in the database, I have to insert a new tuple into FEATURES table. Here's the Trigger -
CREATE OR REPLACE TRIGGER sightTrigger
AFTER INSERT
ON SIGHTINGS
FOR EACH ROW
DECLARE
x INTEGER;
BEGIN
SELECT COUNT(*)INTO x
FROM FEATURES
WHERE FEATURES.location = :NEW.location;
IF(x=0) THEN
RAISE APPLICATION ERROR(-20001, 'Warning: Insert into the SIGHTINGS ...');
INSERT INTO FEATUERS(LOCATION,CLASS,LATITUDE<LONGITUDE,MAP,ELEV)
VALUES(:NEW.location, 'UNKNOWN', null, null, null, null);
END IF;
END sightTrigger;
It compiled fine but I ran a simple query to test it -
INSERT INTO SIGHTINGS VALUES ('Douglas dustymaiden', 'Person A', 'Piute', TO_DATE('17­Feb­07', 'DD­MON­YY'));
And it gave me an error called -
"ORA-20001: Warning: Insert into the SIGHTINGS..." (What I wanted it)
"ORA-06512: at line 7"
"ORA-04088: error during execution of trigger"
Then the insertion into the FEATURES table didn't occur when I tested it. Please help.
As per documentation on raise_application_error:
When called, raise_application_error ends the subprogram and returns a user-defined error number and message to the application. The error number and message can be trapped like any Oracle error.
So, first do the other insert, then raise the error. However, if you raise an unhandled error in a trigger, that will roll back the entire transaction and any data modifications with it and the trigger will error. You may want to consider returning a message to the user in a different way.

Using a pl/sql procedure to log errors and handle exceptions

so far stack overflow and the oracle forums and docs have been my best friend in learning PLSQL. I'm running into an issue here. Any advice is appreciated. I'm writing a procedure that would be used to log any errors a package may encounter and log them into the error log table I created. here is my code thus far.
CREATE OR REPLACE PROCEDURE APMS.test_procedure AS
procedure write_error_log (errcode number, errstr varchar2, errline varchar2) is
pragma autonomous_transaction;
-- this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_line_no)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
errline);
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
exception when others then
write_error_log(sqlcode,sqlerrm,dbms_utility.format_error_backtrace);
raise;
END test_procedure;
/
In the procedure I currently am using a mockdata table to induce an invalid number error and log that to the error_log table. At this point the error log table proves to be functional and inserts the data needed. The next step for me is to use this procedure to be used in the exception handlers in other programs so that the error is caught and logged to the table. Currently, my procedure is only unique to the mock_data table. My mentor/superior is telling me I need to pass this program some parameters to use it in other packages and exception handlers. I'm just having a bit of trouble. Any help would be appreciated thank you!
Steven Feuerstein has written several articles in Oracle Magazine on how to handle errors in PLSQL. He offers a small framework (errpkg) for doing this. The DRY principle (Don't Repeat Yourself) is his mantra!
https://resources.oreilly.com/examples/0636920024859/blob/master/errpkg.pkg
First, you should not make your caller pass in errline. That's very tedious! And what happens when the developer needs to insert a line or two of code? Do they need to update every call to write_error_log after that point to update the line numbers? Your write_error_log should use dbms_utility.format_call_stack (or the 12c more convenient variant of that.. don't have its name handy) to figure out what line of code issued the call to write_error_log.
Then, you should have a 2nd procedure called, say, write_exception. All that needs to do is something like this:
write_error_log (SQLCODE, SUBSTR (DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace, 1, 4000));

PL/SQL Oracle Error Handling

I've created a trigger that only allows a user to have 10 current placed orders. So now when the customer tries to placed order number 11 the oracle database throws back a error. Well 3 errors.
ORA-20000: You currently have 10 or more orders processing.
ORA-06512: at "C3283535.TRG_ORDER_LIMIT", line 12
ORA-04088: error during execution of trigger
'C3283535.TRG_ORDER_LIMIT'
The top error is one I've created using:
raise_application_error(-20000, 'You currently have 10 or more orders
processing.');
I just wondered after search and trying many ways how to change the error messages for the other two errors or even not show them all together to the user?
Here is the code I've used
create or replace trigger trg_order_limit
before insert on placed_order for each row
declare
v_count number;
begin
-- Get current order count
select count(order_id)
into v_count
from placed_order
where fk1_customer_id = :new.fk1_customer_id;
-- Raise exception if there are too many
if v_count >= 10 then
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20000, 'You currently have 10 or more orders processing.');
end if;
end;
Thanks a lot
Richard
The exception propagation goes from the internal-to-external block, as opposed to variable scope which goes from external-to-internal block. For more reference on this, read McLaughlin's "Programming with PL/SQL", Chapter 5.
What you are getting here is an exception stack - exceptions raised from the innermost blocks to the outermost blocks.
When you raise an exception from a trigger, your raise_application_error statement returns an error.
It is then propagated to the trigger block which says ORA-06512: at "C3283535.TRG_ORDER_LIMIT", line 12. This is because the trigger treats the raised exception as an error and stops to continue.
The error is then propagated to the session which raises the ORA-04088: error during execution of trigger 'C3283535.TRG_ORDER_LIMIT'. This error reports to us about where, as in which part of the program, the error was raised.
If you are using a front-end program like Java Server Pages or PHP, you will catch the raised error - 20000 first. So, you can display the same to your end user.
EDIT :
About the first error - ORA-20000, you can change it in the RAISE_APPLICATION_ERROR statement itself.
If you want to handle the ORA-06512, you can use Uday Shankar's answer which is helpful in taking care of this error and showing an appropriate error message.
But, you will still be getting the last ORA-04088. If I was at your place I wouldn't have worried, as after getting the ORA-20000 I would raise an application error at the front end itself while hiding all the other details from the user.
In fact, this is the nature of Oracle's exception stack. All the errors from the innermost to the outermost block are raised. This is helpful a lot of times for us to identify the exact source of error.
In the trigger you can add the exception handling part as shown below:
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20000, 'You currently have 10 or more orders processing.');
I see that this is quite an old post, but I think that readers should be aware that
This does not really enforce the business rule (max 10 orders). If
is is just "some" number to avoid too high amounts and you don't
care if sometimes people have 12 orders then this may be fine. But if not, think of a scenario where you have 9 orders already, and then orders for the same customer are inserted from 2 different sessions / transactions simultaneously. In that case you will end up with 11 orders, without detecting this overflow situation. So you can not rely on this trigger actually.
Besides that, you might need to have this trigger fire on update too, if the fk1_customer_id may be updated (I have seen implementations where at first a NULL is put into the FK column, and later being updated to the actual value). You may want to consider if this scenario is realistic.
There is a fundamental flaw in the trigger. You are inside a transaction and inside a statement that is currently being executed but not complete yet. So what if the insert is not a single row insert but something like
insert into placed_order (select ... from waiting_orders ...)
what do you expect the trigger to see?
This kind of business rule is not easy to enforce. But if you choose to do it in a trigger, you better do it in an after statement trigger (thus, not in a before row trigger). An after statement trigger still will not see results of other uncommitted transactions, but at least the current statement is in a defined state.
In fact the business rule CAN fundamentally only be enforced at commit time; but there is not a thing like an ON-COMMIT trigger in the Oracle database.
What you can do is denormalising the count of records into the customers table (add a column ORDER_COUNT), and place a deferred constraint (ORDER_COUNT <= 10) in that table.
But then you are still relying on correctly maintaining this field throughout your code.
A fully reliable alternative, but somewhat cumbersome, is to create a materialized view (something like SELECT fk_customer_id, count(*) order_count from placed_orders group by fk_customer_id, with FAST REFRESH ON COMMIT on the placed_order table and create a check constraint order_count <= 10 on the materialized view.
This is about the only way to reliably enforce this type of constraints, without having to think of all possible situations like concurrent sessions, updates etc.
Note however that the FAST REFRESH ON COMMIT will slow down your commit; so this solution is not useable for high volumes (sigh... Why just does Oracle not provide an ON COMMIT trigger...)

Populating a database in PostgreSQL

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;

Resources