It does not solve the issue
SriDHAR, January 03, 2003 - 2:01 pm UTC
Tom,
Your solution is educating, yet it does not solve the issue posed. User wants the owner_id to set to id, not to NULL value.
Just an observation from reader's perspective.
January 03, 2003 - 2:42 pm UTC
It solves the problem.
It more then solves the problem. It is a different way to get there, but tell me -- how does this NOT solve the problem which is:
o owner should be settable to a specific value (dog owned by cat)
o if the owner is deleted, the owner should go back to that rows key (dog owns dog)
So, look at the data, look at the solution and tell me that it does not solve the problem.
Can we make this 100% transparent even? To make it look just like "sql server" (which is not my goal in life, I propose people do things in the best way possible given the software they are using -- do it the best way for SS, do it the best way for Oracle). Consider:
ops$tkyte@ORA817DEV> create table t ( id int primary key,
2 owner_id references t ON DELETE SET NULL,
3 name varchar2(10) );
Table created.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace view MyTable
2 as
3 select id, nvl(owner_id,id) owner_id, name
4 from t
5 /
View created.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> create or replace trigger myTable_trigger
2 instead of insert or update on myTable
3 begin
4 if (inserting)
5 then
6 insert into t (id,owner_id,name) values (:new.id, :new.owner_id,:new.name);
7 else
8 update t set owner_id = :new.owner_id, name = :new.name
9 where id = :new.id;
10 end if;
11 end;
12 /
Trigger created.
ops$tkyte@ORA817DEV> insert into myTable values ( 1, 1, 'Cat' );
1 row created.
ops$tkyte@ORA817DEV> insert into myTable values ( 2, 1, 'Dog' );
1 row created.
ops$tkyte@ORA817DEV> insert into myTable values ( 3, 3, 'Mouse' );
1 row created.
ops$tkyte@ORA817DEV>
ops$tkyte@ORA817DEV> select * from myTable;
ID OWNER_ID NAME
---------- ---------- ------------------------------
1 1 Cat
2 1 Dog
3 3 Mouse
3 rows selected.
ops$tkyte@ORA817DEV> delete from myTable where name = 'Cat';
1 row deleted.
ops$tkyte@ORA817DEV> select * from myTable;
ID OWNER_ID NAME
---------- ---------- ------------------------------
2 2 Dog
3 3 Mouse
2 rows selected.
<b>but the best practice implementation is as I described above. I do not spoon feed wrong ways here, I offer up the best way to accomplish your GOALS. More then once I've said:
I could tell you how to do what you ask however, what you are asking to do is really bad. I'll tell you instead how to do this....
That is exactly what I did here.
They lose no functionality here. They gain alot (less code - like none)
</b>
Solves it now
SriDHAR, January 03, 2003 - 2:57 pm UTC
I only meant to point out that you might have overlooked the user's functinality request. I was just giving my observation, often we do not know why in the world users want it. Nevertheless, I was trying the solution myself and tried couple of options,
1. First option was,
1 create or replace trigger t_tgr after delete
2 on t for each row
3 declare
4 pragma autonomous_transaction;
5 begin
6 update t set owner_id = id where owner_id is null;
7 commit;
8* end;
SQL> /
Trigger created.
real: 100
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------
1 1 Cat
2 1 Dog
3 3 Mouse
real: 60
SQL> delete from t where id = 1;
delete from t where id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at "APPS.T_TGR", line 4
ORA-04088: error during execution of trigger 'APPS.T_TGR'
2. Second was the statement level trigger that you have just shown. By the time I came back, you are already there with the solution.
SQL> create or replace trigger t_tgr after delete on t
2 begin
3 update t set owner_id = id where owner_id is null;
4 end;
5 /
Trigger created.
real: 121
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------
1 1 Cat
2 1 Dog
3 3 Mouse
real: 70
SQL> delete from t where id = 1;
1 row deleted.
real: 30
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------
2 2 Dog
3 3 Mouse
I DO AGREE THAT YOUR ORIGINAL SOLUTION TAKES IT IN THE RIGHT DIRECTION.
January 03, 2003 - 6:35 pm UTC
1) first option will lock the system up with deadlocks as quick as a blink of an eye.
2) would be a performance nightmare. Do you really want to full scan the table everytime you delete?
How about letting each row take care of it's self?
Chuck Jolley, February 24, 2003 - 10:32 am UTC
If setting an un-owned row to own it's self is realy a business rule why not do it this way?
chajol@tax.cupid> create table t ( id int primary key,
2 owner_id references t ON DELETE SET NULL,
3 name varchar2(10) );
Table created.
chajol@tax.cupid> create or replace trigger t_orphan
2 before update of owner_id
3 on t
4 referencing new as new old as old
5 for each row
6 begin
7 if :new.owner_id is null then
8 :new.owner_id := :new.id;
9 end if;
10* end;
chajol@tax.cupid> /
Trigger created.
chajol@tax.cupid> insert into t values(1, 1, 'cat');
1 row created.
chajol@tax.cupid> insert into t values(2, 1, 'dog');
1 row created.
chajol@tax.cupid> insert into t values(3, 3, 'mouse');
1 row created.
chajol@tax.cupid> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------
1 1 cat
2 1 dog
3 3 mouse
chajol@tax.cupid> delete from t where name = 'cat';
1 row deleted.
chajol@tax.cupid> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------
2 2 dog
3 3 mouse
chajol@tax.cupid>
Which does bring up the question:
Is the behind the curtain work done for a trigger like this more, less, or "who knows?" than the corresponding NVL + view code?
Thanks,
chuck
Why Deadlock?
Vaishnavi, November 30, 2004 - 2:20 pm UTC
Hi Tom,
In the last response to Sridhar's followup, you said
<
1) first option will lock the system up with deadlocks as quick as a blink of an
eye.
>
I am not able to understand why Deadlock will occur in this case. Could you please explain us why?
Thanks for your time.
sincerely
Vaishnavi
November 30, 2004 - 8:15 pm UTC
autonomous transactions in triggers are pure evil. I've been known to say "there are no 100% good and no 100% evil features" -- but I'm thinking of changing my stance on that.
look to the fkey.
Whats the mistake I am commiting here?
Vaishnavi, December 01, 2004 - 12:08 am UTC
Tom,
I am totally confused now. I ran the following script in Session A.
SQL> DROP TABLE t;
Table dropped.
Elapsed: 00:00:00.02
SQL>
SQL> CREATE TABLE t(ID INT PRIMARY KEY, owner_id REFERENCES t ON DELETE SET NULL, NAME VARCHAR2(100));
Table created.
Elapsed: 00:00:00.01
SQL>
SQL> INSERT INTO t VALUES (1, 1, 'Cat');
1 row created.
Elapsed: 00:00:00.00
SQL> INSERT INTO t VALUES (2, 1, 'Dog');
1 row created.
Elapsed: 00:00:00.00
SQL> INSERT INTO t VALUES (3, 2, 'Mouse');
1 row created.
Elapsed: 00:00:00.00
SQL>
SQL> COMMIT ;
Commit complete.
Elapsed: 00:00:00.00
SQL> SELECT * FROM t;
ID OWNER_ID NAME
---------- ---------- ----------------------------------------------------------------------------------
1 1 Cat
2 1 Dog
3 2 Mouse
Elapsed: 00:00:00.00
SQL>
SQL> CREATE OR REPLACE TRIGGER t_trigger
2 AFTER DELETE
3 ON t
4 FOR EACH ROW
5 DECLARE
6 PRAGMA AUTONOMOUS_TRANSACTION;
7 BEGIN
8 DBMS_OUTPUT.put_line ('Before');
9
10 UPDATE t
11 SET owner_id = ID
12 WHERE owner_id IS NULL;
13
14 DBMS_OUTPUT.put_line ('After');
15 COMMIT;
16 END;
17 /
Trigger created.
Elapsed: 00:00:00.01
SQL>
SQL> delete from t where id = 1;
Before
After
1 row deleted.
Elapsed: 00:00:00.00
SQL>
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------------------------------------------------------------------------------
2 Dog
3 2 Mouse
Elapsed: 00:00:00.00
SQL>
And I ran the same select statement in Session B and Output is
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------------------------------------------------------------------------------------------------
1 1 Cat
2 1 Dog
3 2 Mouse
Could you please help in finding answers to these questions?
1. As you said in yor previous posts, Its not creating deadlock. What I am doing wrong?
2. In the second session (Session B), I am expecting the output like this.....
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- ----------------------------------------------------------------------------------------------------
1 1 Cat
2 2 Dog
3 2 Mouse
Why? Because the trigger in Session A, commited only 2nd record.
3. In the first session, Session A, I should get output like...
SQL> select * from t;
ID OWNER_ID NAME
---------- ---------- --------
2 2 Dog
3 2 Mouse
Why? Because already trigger updated second record. (Even though it is automonous, Its in the same session).
Thanks for your time Tom.
Sincerely
Vaishnavi
December 01, 2004 - 8:15 am UTC
atrans are evil, stay away from them.
in 8i that was self deadlocking. in 9i -- the behavior of this particular (really bad) example changed.
Look -- you have a table T, owner_id is "on delete set null". When you delete from t where id = 1, the row for 'Dog' is set to null. the trigger is attempting to update this row but the trigger cannot see that row
in 8i, it deadlocks.
in 9i, it just doesn't work (to set owner_id = id, it fails in its goal)
"Atrans are evil"
Tony Andrews, December 01, 2004 - 11:57 am UTC
> atrans are evil, stay away from them.
That's perfectly true in this context of course. But you do realise that autonomous transactions will be outlawed altogether now in many IT departments because someone heard that Tom Kyte said they were evil!
December 01, 2004 - 12:38 pm UTC
I have yet to see them applied "safely".
For the greater good I'm calling them evil.
logging! the perfect application of atrans
tom, December 01, 2004 - 12:52 pm UTC
The one sensible application of an ATRANS in a trigger is using the atrans for logging purposes [we want to know that user x attempted to do Y even though the business rules prevented and rolled back the update].
However, you're right, everyone will simply say "I know how to get round mutating triggers - autonomous transactions" without understanding why the trigger mutates in the first place and therefore why an autonomous transaction doesn't solve the problem.
ATrans evil?
A reader, December 01, 2004 - 12:53 pm UTC
ATrans are useful when used as they were meant to be used: small subtransactions. They are great for logging events. If something happens and you want to know that it happened, regardless of whether the user commits or rolls back their work (especially if they rollback their work), the atrans is very handy.
They ARE evil when they break the atomicity of a transaction. Like using an ATrans to modify a mutating table without getting the mutation error. If the "transaction" can successfully be committed (without deadlock), things will probably be ok. If there's a rollback, you'll likely have data where some of it says one thing and the rest says something completely different.
"atrans are evil, stay away from them."
Menon, December 01, 2004 - 12:59 pm UTC
Do you wanna qualify that a bit as you did in the
previous statement in the same thread?
atrans are evil when used in triggers and
atrans are evil <any other condition>
I am expecting to read some nice juicy stuff about this
in your new book!:)
December 01, 2004 - 1:48 pm UTC
nope....
"atrans are evil"
Tony Andrews, December 01, 2004 - 1:07 pm UTC
> I have yet to see them applied "safely".
> For the greater good I'm calling them evil.
I thought that might be the case, and I get your point: the good, safe usages of atrans are far outweighed by the evil, unsafe usages that abound. From what I have seen on Oracle-related forums, the number of people who are mistakenly using atrans to avoid "table is mutating" (SO much easier than the proper method that actually works!) is much higher than the number of people who know how to use them properly.