Trigger that selects from table that is being deleted - oracle

This trigger ends up raising an error regardless of the if statement values. I'm basically selecting from the same table the delete is occurring on and it's not liking it.
CREATE OR REPLACE TRIGGER delete_schedules
AFTER DELETE
ON SCHEDULES
FOR EACH ROW
DECLARE
lParentCond schedules.cond_code%type;
lParentActive schedules.active_flag%type;
lError exception;
BEGIN
if :OLD.thread is not null then
select cond_code, active_flag
into lParentCond, lParentActive
from schedules where schedule_seq = :old.thread;
if lParentCond = 'OK' and lParentActive in ('*', 'F') then
raise lError;
end if;
end if;
EXCEPTION
when lError then
raise;
WHEN OTHERS THEN RAISE;
END delete_schedules;
Any ideas of a workaround?

You're probably getting the dreaded 'MUTATING TABLE' error. Oracle doesn't allow us to fetch data from the table on which the trigger is defined in an AFTER trigger - but in this case you don't need to because the 'old' values are already available. Rewrite your trigger as:
CREATE OR REPLACE TRIGGER delete_schedules
AFTER DELETE
ON SCHEDULES
FOR EACH ROW
BEGIN
if :OLD.thread is not null AND
:OLD.COND_CODE = 'OK' and
:OLD.ACTIVE_FLAG in ('*', 'F')
then
RAISE_APPLICATION_ERROR(-20100, 'Invalid combination of COND_CODE and ACTIVE_FLAG');
end if;
END delete_schedules;
This assumes (based on the use of a singleton SELECT in the question) that there's only one row in SCHEDULES for the given THREAD value. If that's not the case there are other work-arounds, including using a COMPOUND TRIGGER.
Best of luck.

Related

Making a trigger with RAISERROR [duplicate]

Hello fellow programmers and happy new year to you all!
I have few university tasks for winter break and one of them is to create trigger on table:
PERSON(ID, Name, Surname, Age);
Trigger is supposed to inform user when they have inserted row with invalid ID. Vadility criteria is that ID is 11 digits long.
I tried to write solution like this:
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
DECLARE
idNew VARCHAR(50);
lengthException EXCEPTION;
BEGIN
SELECT id INTO idNew FROM INSERTED;
IF LENGTH(idNew) <> 11 THEN
RAISE lengthException;
END IF;
EXCEPTION
WHEN lengthException THEN
dbms_output.put_line('ID for new person is INVALID. It must be 11 digits long!');
END;
Then I realized that INSERTED exists only in sqlserver and not in oracle.
What would you suggest I could do to fix that?
Thanks in advance!
Do you want to raise an exception (which would prevent the insert from succeeding)? Or do you want to allow the insert to succeed and write a string to the dbms_output buffer that may or may not exist and may or may not be shown to a human running the insert?
In either case, you'll want this to be a row-level trigger, not a statement-level trigger, so you'll need to add the for each row clause.
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
FOR EACH ROW
If you want to raise an exception
BEGIN
IF( length( :new.id ) <> 11 )
THEN
RAISE_APPLICATION_ERROR( -20001,
'The new ID value must have a length of 11' );
END IF;
END;
If you want to potentially print output but allow the insert to succeed
BEGIN
IF( length( :new.id ) <> 11 )
THEN
dbms_output.put_line( 'The new ID value must have a length of 11' );
END IF;
END;
Of course, in reality, you would never use a trigger for this sort of thing. In the real world, you would use a constraint.

Error: can't create trigger properly (ORA-24344: success with compilation error ORA-06512). Oracle SQL

Oracle APEX. I want to create trigger: if user deletes a row where ENDDATE is null the row won't be deleted overwise it will. This is my script:
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF(OLD.ENDDATE IS NULL)
INSERT INTO CAREER VALUES (OLD.JOBNO, OLD.EMPNO, OLD.STARTDATE, OLD.ENDDATE);
END IF;
END CHECK_NOT_NULL_;
But I have ORA-24344 error. Can you explain why and what should I do to fix it?
Your trigger attempts to re-insert the row if the END_DATE is null. This won't work (you'll get the notorious mutating table error). But anyway, if you want to prevent deletion of the row it's simpler and clearer to simply do that:
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF :OLD.ENDDATE IS NULL THEN
raise_application_error(-20000, 'Cannot delete a row when ENDDATE is null');
END IF;
END CHECK_NOT_NULL_;
This fails the action and tells the user why their action was refused. Silently undoing a user's action is bad practice, because it's mystifying, and mystified users are unhappy and often angry users.
Precede all olds with a colon :, i.e.
CREATE OR REPLACE TRIGGER CHECK_NOT_NULL_
BEFORE DELETE ON CAREER
FOR EACH ROW
BEGIN
IF(:OLD.ENDDATE IS NULL)
INSERT INTO CAREER VALUES (:OLD.JOBNO, :OLD.EMPNO, :OLD.STARTDATE, :OLD.ENDDATE);
END IF;
END CHECK_NOT_NULL_;
Also, I'd suggest you to name all columns you're inserting into, e.g.
insert into career (jobno, empno, startdate, enddate)
values (:old.jobno, :old.empno, :old.startdate, :old.enddate);

Checking time in trigger before updating,inserting,or deleting

I would like to create a trigger that store Who(Names) as the Loged User, Action as what he/she has done, And the Time he/she made it. But Also it should be capable of revolking the changes when it's not Working Time for example working time can be (from 8H:00 AM to 05:00 PM). So Tried to create it.
// This is the Table to Store the Log named "System_events"
create table system_events (who varchar2(10),action varchar2(10), when date);
Actually the trigger will check and save any change on a table named students
//Codes to create that trigger
create or replace trigger all_actions
before insert or update or delete on students
declare
user_action system_events.action%type;
begin
if INSERTING then
user_action :='Insert';
elsif UPDATING then
user_action :='Update';
elsif DELETING then
user_action :='Delete';
else
raise_application_error (-20001, 'Yous should never get this error.');
end if;
insert into system_events(who,action,when) values(user,user_action,sysdate);
end;
So this trigger I created is working. However it doesn't check the Time. So I would like to add that feature of checking the Time and revolke the change if it's not in the right time
Thank you!!
It is not clear what do you mean by "revolke the change". I assume you want to stop the users from doing any DML operation except during work hours. If so, you could add another IF condition and raise an exception for times beyond 8am - 5pm.
CREATE OR replace TRIGGER all_actions
BEFORE INSERT OR UPDATE OR DELETE ON students
DECLARE
user_action system_events.action%TYPE;
BEGIN
IF inserting THEN
user_action := 'Insert';
ELSIF updating THEN
user_action := 'Update';
ELSIF deleting THEN
user_action := 'Delete';
ELSE
raise_application_error (-20001, 'You should never get this error.');
END IF;
IF to_number(to_char(SYSDATE, 'HH24')) NOT BETWEEN 8 AND 16 THEN
raise_application_error (-20001, 'You should not do '
||user_action
||' operation during this time.');
END IF;
INSERT INTO system_events
(who,
action,
when)
VALUES (USER,
user_action,
SYSDATE);
END;
/
Note: You may put this IF condition before checking the operation type as well if you want, without user_action

Oracle Trigger only activates the first IF statement

I have a trigger that has multiple IF statements (not elseif). I did bring the code back down to having twice the same IF statement but only the first IF statement is run.
Is this standard Oracle trigger behavior? one of the if statements is pretty much hard-coded while the other uses a function so there is only a select few cases where both IFs are to be run.
Is there a different way to approach an issue like this? or would the issue be in the trigger code and should i post it here?
the code below has this behavior on our server but is dummied down from the original.
create or replace TRIGGER V_INV_TRANS_BIZTALK
AFTER INSERT ON INVENTORY_TRANSACTION
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
l_type pre_advice_header.user_def_type_4%type;
l_status order_header.status%TYPE;
l_from_loc_zone location.zone_1%TYPE;
l_to_loc_zone location.zone_1%TYPE;
l_patype pre_advice_header.pre_advice_type%type;
l_retour pre_advice_header.user_def_type_6%TYPE;
l_client pre_advice_header.client_id%TYPE;
BEGIN
l_client := :new.client_id;
--Client_id = SD
IF l_client = 'SD'
THEN
CASE
--InBound + Return : Pre_Advice_header
WHEN (:new.code = 'PreAdv Status' and :new.notes in ('In Progress --> Complete')) THEN
select PRE_ADVICE_TYPE
into l_patype
from pre_advice_header
where pre_advice_id = :new.reference_id
and client_id = :new.client_id;
END CASE;
END IF;
--TRANSPARIX
IF l_client = 'SD'
--(beldba.is_transparix_client(p_client_id => :new.client_id) = '1' )
THEN
CASE
--Order is shipped
WHEN (:new.notes like ('%--> Shipped')) THEN
--TRANSEXT
INSERT INTO beldba.biztalk_trigger
(
event_id,
status,
system_id,
client_id,
reference_id,
receiver_id,
user_def_type_1
)
VALUES
(
'TRANSExt',
'Pending',
'DCS',
:new.client_id,
:new.reference_id,
'TRANSPARIX',
''
);
END CASE;
END IF;
COMMIT;
EXCEPTION when others then
NULL;
END;
I think there are two problems here:
If there are no matching cases in the CASE statement, and there is no ELSE clause, the CASE statement will raise an ORA-06592 error 'CASE not found when executing CASE statement'. If you don't want to do anything when there is no matching case, add the following section to your CASE statement:
ELSE
NULL;
You end your trigger with
EXCEPTION when others then
NULL;
This swallows all exceptions, including the one that Oracle was raising to tell you that it couldn't find a CASE to execute. Of course, as this is at the end of your trigger, once your trigger raises an exception no further trigger code gets executed.
EXCEPTION WHEN OTHERS THEN NULL is, quite frankly, a cardinal sin in Oracle. I cannot recommend strongly enough deleting this section of your trigger.
Removing the case statements and replacing with IF statements resolved the issue.

Trigger to RAISE_APPLICATION_ERROR but execute inner commands

I'm working on the following PL/SQL trigger:
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.type = 'PRESIDENT' THEN
INSERT INTO trigger_log VALUES (sysdate, 'Nope.', 'No change.');
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
Here, I want to cancel the UPDATE command on the worker table, when a PRESIDENT's payment is about to get changed. At the same time, I wish to log this command into table called trigger_log. The problem is, when I RAISE_APPLICATION_ERROR the UPDATE got cancelled, but the logging (INSERT INTO trigger_log) aswell. How can I RAISE_APPLICATION_ERROR or throw an EXCEPTION, but still have all commands to be run inside the TRIGGER?
You have to commit your INSERT-Statement before raising the error.
Maybe think about AUTONOMOUS_TRANSACTION
EDIT
As others have stated already, you should not and cannot commit inside a trigger (exception autonomous transaction). So think about using the solution of Tom Thomas or calling a logging-procedure/-package.
You can call a stored procedure from your trigger. That stored procedure should be declared as PRAGMA AUTONOMOUS_TRANSACTION. Try like this,
CREATE OR REPLACE
PROCEDURE log_error_p
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
COMMIT;
END;
--
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.TYPE = 'PRESIDENT' THEN
log_error_p();
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
/
First of all, never use a commit or a rollback inside the trigger. It's a coding standard. As for your question i think this is a better way than raise_application_error
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
REFERENCING OLD as o AND NEW as n
FOR EACH ROW
DECLARE
InsertException EXCEPTION;
BEGIN
IF o.TYPE = 'PRESIDENT' THEN
RAISE InsertException;
END IF;
EXCEPTION
WHEN InsertException THEN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

Resources