Home » SQL & PL/SQL » SQL & PL/SQL » Unique problem with TRIGGER (Oracle 10g)
Unique problem with TRIGGER [message #513181] |
Fri, 24 June 2011 04:42  |
 |
saswatic
Messages: 5 Registered: June 2011 Location: India
|
Junior Member |
|
|
Hello Everybody,
I am facing a challenge regarding a trigger,details of which is given below:-
I have a table with below structure:-
CREATE TABLE SUBSCRIBER_SUBSCRIPTION (
SUBSCRIBER_ID NUMBER(5) NOT NULL,
STATUS VARCHAR2(20),---which can be ACTIVE/INACTIVE
EXPIRY_DATE DATE);
and a corresponding history table with same structure as above.
Now I have a trigger which is a row level trigger ON table SUBSCRIBER_SUBSCRIPTION and it triggers before update OF column STATUS.The function of the trigger is to populate the
history table whenever there is any status change to INACTIVE on table SUBSCRIBER_SUBSCRIPTION.
Below is the trigger body:-
CREATE OR REPLACE TRIGGER SUBSCRIBER_SUBSCRIPTION_BRU
BEFORE UPDATE
OF STATUS
ON SUBSCRIBER_SUBSCRIPTION
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
WHEN (
NEW.STATUS = 'INACTIVE'
)
BEGIN
:NEW.STATUS := to_char('IN_' || to_char(sysdate, 'YYYYMMDDHH24MI'));
INSERT INTO SUBSCRIBER_SUBSCRIPTION_HIST (
SUBSCRIBER_ID, STATUS,
EXPIRY_DATE)
VALUES (:OLD.SUBSCRIBER_ID, :NEW.STATUS,
:OLD.EXPIRY_DATE);
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END;
/
The above trigger is working fine but now the problem is that we require as part of this trigger to delete the record from SUBSCRIBER_SUBSCRIPTION which are INACTIVE because those
records are already present in table SUBSCRIBER_SUBSCRIPTION_HIST.
But I am unable to do so because if I simply put delete from table SUBSCRIBER_SUBSCRIPTION,it will give mutating problem but if I use AUTONOMOUS Transaction then also am getting
the below error:-
ORA-00060: deadlock detected while waiting for resource
Please find below the updated trigger code that I have used and testdata script:-
CREATE OR REPLACE TRIGGER SUBSCRIBER_SUBSCRIPTION_BRU
BEFORE UPDATE
OF STATUS
ON SUBSCRIBER_SUBSCRIPTION
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
WHEN (
NEW.STATUS = 'INACTIVE'
)
----NEW CODE-----------------------------------
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
-----------------------------------------------
BEGIN
:NEW.STATUS := to_char('IN_' || to_char(sysdate, 'YYYYMMDDHH24MI'));
INSERT INTO SUBSCRIBER_SUBSCRIPTION_HIST (
SUBSCRIBER_ID, STATUS,
EXPIRY_DATE)
VALUES (:OLD.SUBSCRIBER_ID, :NEW.STATUS,
:OLD.EXPIRY_DATE);
COMMIT;
BEGIN
FOR i IN(SELECT * FROM SUBSCRIBER_SUBSCRIPTION_HIST)
LOOP
IF (i.STATUS LIKE '%IN%') THEN
DELETE FROM SUBSCRIBER_SUBSCRIPTION
WHERE SUBSCRIBER_ID=i.SUBSCRIBER_ID;
COMMIT;
END IF;
END LOOP;
END;
---COMMIT;
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END;
/
Before Trigger Creation Test Data
------------------------------------
INSERT INTO SUBSCRIBER_SUBSCRIPTION VALUES(111,'ACTIVE',SYSDATE+100);
INSERT INTO SUBSCRIBER_SUBSCRIPTION VALUES(222,'ACTIVE',SYSDATE+200);
INSERT INTO SUBSCRIBER_SUBSCRIPTION VALUES(333,'ACTIVE',SYSDATE+300);
COMMIT;
After Trigger Creation Test Data
------------------------------------
UPDATE SUBSCRIBER_SUBSCRIPTION
SET STATUS='INACTIVE'
WHERE SUBSCRIBER_ID=333;
COMMIT;
Can anyone please help me how I can do this within the trigger code??
Advanced Thanks,
Sasw
CM: added [code] tags, please do so yourself next time.
[Updated on: Fri, 24 June 2011 04:46] by Moderator Report message to a moderator
|
|
|
Re: Unique problem with TRIGGER [message #513183 is a reply to message #513181] |
Fri, 24 June 2011 04:49   |
cookiemonster
Messages: 13967 Registered: September 2008 Location: Rainy Manchester
|
Senior Member |
|
|
Do not try and do this in a trigger. Do it in the application code that does the update.
Mutating table means you have design flaw.
You can never get round it using autonomous_transaction.
|
|
|
|
|
Re: Unique problem with TRIGGER [message #513338 is a reply to message #513185] |
Sun, 26 June 2011 22:24   |
 |
Barbara Boehmer
Messages: 9106 Registered: November 2002 Location: California, USA
|
Senior Member |
|
|
Although implementing in a procedure would probably be better, I have provided a trigger method below for your consideration.
SCOTT@orcl_11gR2> CREATE TABLE subscriber_subscription
2 (subscriber_ID NUMBER(5) NOT NULL,
3 status VARCHAR2(20),
4 expiry_date DATE)
5 /
Table created.
SCOTT@orcl_11gR2> CREATE TABLE subscriber_subscription_hist
2 (subscriber_ID NUMBER(5) NOT NULL,
3 status VARCHAR2(20),
4 expiry_date DATE)
5 /
Table created.
SCOTT@orcl_11gR2> CREATE OR REPLACE TRIGGER subscriber_subscription_bru
2 BEFORE UPDATE OF status ON subscriber_subscription
3 FOR EACH ROW
4 WHEN (NEW.status = 'INACTIVE')
5 BEGIN
6 :NEW.status := 'IN_' || TO_CHAR (SYSDATE, 'YYYYMMDDHH24MI');
7 INSERT INTO subscriber_subscription_hist
8 (subscriber_ID, status, expiry_date)
9 VALUES (:OLD.subscriber_ID, :NEW.status, :OLD.expiry_date);
10 END subscriber_subscription_bru;
11 /
Trigger created.
SCOTT@orcl_11gR2> SHOW ERRORS
No errors.
SCOTT@orcl_11gR2> CREATE OR REPLACE TRIGGER subscriber_hist_ari
2 AFTER INSERT ON subscriber_subscription_hist
3 FOR EACH ROW
4 WHEN (NEW.status LIKE 'IN%')
5 DECLARE
6 v_job NUMBER;
7 BEGIN
8 DBMS_JOB.SUBMIT (v_job,
9 'DELETE FROM subscriber_subscription
10 WHERE subscriber_ID = ''' || :NEW.subscriber_ID || ''';');
11 END subscriber_hist_ari;
12 /
Trigger created.
SCOTT@orcl_11gR2> SHOW ERRORS
No errors.
SCOTT@orcl_11gR2> INSERT ALL
2 INTO SUBSCRIBER_SUBSCRIPTION VALUES(111,'ACTIVE',SYSDATE+100)
3 INTO SUBSCRIBER_SUBSCRIPTION VALUES(222,'ACTIVE',SYSDATE+200)
4 INTO SUBSCRIBER_SUBSCRIPTION VALUES(333,'ACTIVE',SYSDATE+300)
5 SELECT * FROM DUAL
6 /
3 rows created.
SCOTT@orcl_11gR2> COMMIT
2 /
Commit complete.
SCOTT@orcl_11gR2> UPDATE SUBSCRIBER_SUBSCRIPTION
2 SET STATUS = 'INACTIVE'
3 WHERE SUBSCRIBER_ID = 333
4 /
1 row updated.
SCOTT@orcl_11gR2> COMMIT
2 /
Commit complete.
SCOTT@orcl_11gR2> SELECT * FROM subscriber_subscription_hist
2 /
SUBSCRIBER_ID STATUS EXPIRY_DA
------------- -------------------- ---------
333 IN_201106262023 21-APR-12
1 row selected.
SCOTT@orcl_11gR2> -- wait for job to finish running before checking results:
SCOTT@orcl_11gR2> exec dbms_lock.sleep (5)
PL/SQL procedure successfully completed.
SCOTT@orcl_11gR2> SELECT * FROM subscriber_subscription
2 /
SUBSCRIBER_ID STATUS EXPIRY_DA
------------- -------------------- ---------
111 ACTIVE 04-OCT-11
222 ACTIVE 12-JAN-12
2 rows selected.
SCOTT@orcl_11gR2>
|
|
|
|
Re: Unique problem with TRIGGER [message #513353 is a reply to message #513181] |
Mon, 27 June 2011 00:04  |
 |
Kevin Meade
Messages: 2103 Registered: December 1999 Location: Connecticut USA
|
Senior Member |
|
|
All good advice. I would like add the following for you to think about:
Triggers are best used to add logic to a data system that is not part of application logic, or to modify the result of application logic without the application being aware of it. This is at least my opinion anyway.
To this end, you should put logic in triggers without needing to change your application. If you cannot do that, then it is likely that you are not using triggers correctly for whatever it is you are attempting and thus are heading for trouble.
In your case, I can think of two reasons why you might want to use a trigger here:
1) your history row capture could be considered a form of auditing. As such it is fine to use a trigger for this. Indeed, you almost have to because if you don't then there are potential holes in your auditing scheme and it won't pass them muster of a good auditer.
2) you may have an application that is not under your control and you cannot therefore change the code in this application. In your case, your app may be doing an update from ACTIVE to INACTIVE, but someone has decided that this should actually delete the row instead but they do not or cannot change the code doing the update. In this situation you should consider using instead-of-triggers. They will allow you do to the delete without getting MUTATING/CONSTRAINING table errors.
But the trick is to understand the design of your system. Has an update of status really been changed to a delete? If so then triggers are a valid answer to avoiding code changes and ensuring consistent behavior of your system, especially if you have apps that can do delete in addition to update of status=inactive.
As always, you should read up about any feature, study it, and actively research it before using in a production system.
Again, I caution that you need to ask some probing questions about why status=inactive means delete the original row. It should be clear that SOMEONE has an idea of how the application is intended to work. For example, you should be able to find someone who can tell you that: "they want the application to continue to say UPDATE... STATUS=INACTIVE... rather than INSERT INTO HISTORY/DELETE ORIGINAL ROW". To answer this you might consider what a person from SQLPLUS who understand the basic application would do. Would you expect them to INSERT/DELETE, or would you expect them to do STATUS=INACTIVE? If the later, then a trigger is good, if the former then the trigger is a mistake. If you are confused about what I am saying here, that is good, because it means you are starting to grapple with the philosophy of what triggers are about. You should appreciate the WHY of triggers before you jump into them.
Triggers and INSTEAD-OF-TRIGGERS are not meant to be crutches for doing quick fixes to application shortcommings.
Triggers and INSTEAD-OF-TRIGGERS are intended to be the rubber meets the road in implementing advanced database designs and solving in practical ways otherwise costly design dilemas.
Good luck. Kevin
Here is an example of an instead-of-trigger that does the kind of thing you are asking for. I have not compiled or tested any of this. I leave that to you to do if you are interested in researching instead-of-triggers. Notice in particular the extent to which this process goes to ensure that existing code that uses the original table is not required to change. Also realize that advanced features of the oracle database come with limitations. Instead-of-triggers are no different. You will have to get used to the idea of no parallel insert/update/delete on your underlying table because of the instead-of-trigger, and no use of the merge statement.
create table emp (emp_id integer not null, ename varchar2(10) not null, status varchar2(10));
alter table emp
add constraint emp_pk primary key (emp_id)
add constraint emp_Uk1 unique (ename)
/
create table emp_h (emp_id integer not null, ename varchar2(10) not null, status varchar2(10));
alter table emp_h
add constraint emp_pk primary key (emp_id)
add constraint emp_Uk1 unique (ename)
/
rename emp to emp_tab;
create or replace view emp
as
select *
from emp_tab
/
create or replace trigger io_emp
instead of insert or update or delete on emp
for each row
begin
--
-- do the original operation on the real table
--
if inserting then null;
insert into emp_tab value (:new.emp_id,:new.ename,:new.status);
if updating then null;
update emp_tab set
emp_id = :new.emp_id
, ename = :new.ename
, status = :new.status
where emp_id = :old.emp_id
;
else null;
delete
from emp_tab
where emp_id = :old.emp_id
;
end if;
--
-- do the update logic
--
if updating then;
if updating('status') then
if :new.status = 'INACTIVE' then
insert into emp_h
select emp_id,ename,status
from emp_tab
where emp_id = :new.emp_id
;
delete from emp_tab where emp_id = :old.emp_id;
end if;
end if;
end if;
end;
/
show errors
[Updated on: Mon, 27 June 2011 00:09] Report message to a moderator
|
|
|
Goto Forum:
Current Time: Sat Aug 09 16:07:12 CDT 2025
|