Skip to Main Content
  • Questions
  • Delete Cascade if foreign keys exists with different delete rule

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Prince.

Asked: March 20, 2002 - 4:03 pm UTC

Last updated: May 24, 2010 - 7:36 am UTC

Version: 8.1.7.2

Viewed 10K+ times! This question is

You Asked

Tom,

Q1:
----
Suppose I have tables t1, t2, t3 and having the relation t1 -> t2 -> t3, where t1 is the root level parent and t3 is the child.

Suppose, if there exists a relation between t1 and t2 with delete cascade,t2 and t3 with delete cascade and t1 and t3 with delete restrict.

t1 --> t2 --> t3 (say delete cascade)
t1 --> t3 (say delete restrict)

Now, how does oracle determines, whether to delete the records from "t3" or not?

If it chooses the path of t1 --> t2, t2 --> t3 then, it deletes the record and t1 --> t3 may find no record to delete.
On the other hand, if it chooses t1 -- t3, it fails with "child record found". Now my question is what strategy oracle uses to choose the delete path.

Q2:
----
is it possible to UNION a LONG and VARCHAR2 column? If so how?

Thanks,


and Tom said...

q1) it would appear we cascade the delete and then check:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t1 ( x int primary key );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t2 ( y int primary key, x references t1 on delete cascade );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t3 ( y references t2 on delete cascade, x references t1 );

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t1 values ( 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t2 values ( 1 , 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t3 values ( 1 , 1 );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> delete from t1;

1 row deleted.

ops$tkyte@ORA817DEV.US.ORACLE.COM>

(weird model if you ask me)...

q2) no, especially with a UNION since that implies a sort/distinct at the end and longs cannot be sorted nor distincted...


Rating

  (20 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

I agree with u it being a wierd model. I was just trying that.

Prince, March 21, 2002 - 2:20 am UTC

Thanks Tom, for the prompt answer.

I know its a weird model. I just had a thought and was experimenting it. But, I couldn't confirm whether it always uses a certain path are it chooses different path based on the optimizer's path selection.

And, regarding the Q2,
If that is the case shouldn't "UNION ALL" work. It seems the union is not allowed as the data types are incompatible. Can a long be converted to varchar2 easily?


Tom Kyte
March 21, 2002 - 3:49 pm UTC

No, a long cannot be converted to a varchar2 "easily". See

see
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:55212348054 <code>
for a method you can use to return the first 4000 characters of a long as long as the overall long is <= 32k in length using a PLSQL function.

Oracle 9.2 HR demo schema

pingu, May 05, 2003 - 9:58 am UTC

Hi

I am working with Oracle 9.2 Human Resurces demo schema, I was trying to delete some records in Employees and it complained about

ORA-02292: integrity constraint (HR.DEPT_MGR_FK) violated - child record found

So, I went to departments table and tried to delete the child records, but obviously departments is also employees parent table!!!! So when I issue a delete statement I got

ORA-02292: integrity constraint (HR.EMP_DEPT_FK) violated - child record found

How can we delete in such situations where two tables are child and parent of each other....?



Tom Kyte
May 05, 2003 - 10:16 am UTC

update one of the fkeys to null -- now you can delete the other parent.



just wondering

PINGU, May 05, 2003 - 10:25 am UTC

Hi

I am just wondering because I am not very efficient in RDBMS theory. Is this kind of model quite common? Tables are parent and child of each other

Thx

Tom Kyte
May 05, 2003 - 10:36 am UTC

common? no, not very common but definitely they happen and they exist.

consider EMP:

create table emp ( empno number primary key,
...
mgr references emp
);

there the single table is both parent and child.

A reader, May 06, 2003 - 1:08 am UTC

I would consider DEFERRED constraints to be a good choice in such a case. are there any reasons NOT to use them HERE?

Tom Kyte
May 06, 2003 - 7:46 am UTC

i don't think they are necessary unless you want to do an update cascade operation (which means you have an ill designed model in the first place as you are updating a primary key -- meaning it is not really a primary key at all).


no reason 'not to', but then again no reason 'to'

on delete cascade

Junnior, October 10, 2003 - 2:30 pm UTC

Tom:

Why people not use "on delete cascade" so often?
What is the disadvetage to use it?
What is the syntax to modify the exist tables to be on delete cascade?

Thanks.

Tom Kyte
October 10, 2003 - 4:23 pm UTC

I don't like it personally -- I'd rather have an error when I delete a parent that has child records. Doesn't "seem" right to have that stuff just disappear.


It is useful in limited cases.

You can only have on delete cascade when creating the constraint (so you drop and recreate the constraint)

It's all about business logic

Michael, October 10, 2003 - 6:48 pm UTC

> Why people not use "on delete cascade" so often?
> What is the disadvetage to use it?

We use "on delete cascade", where the desired behaviour is to eliminate the connected data and don't use it, where we want to avoid the cascading deletes of the data.

Lets say you have a customer table and a referencing address table. If you theoretically would delete the customer, the address isn't needed anymore, so we can "on delete cascade" the address. But if this customers has made a purchase and we have a bill for him, we can't delete him anymore, so we can't use "on delete cascade" for the referencing purchases.

A reader, May 13, 2005 - 3:01 pm UTC

Tom,

We have a scenario, where in we have to delete records from several child tables when a record is deleted from parent table.

We want to implement 'CASCADE DELETE'. Is it a good idea?
Will there be any issues?

Thanks in advance!

Tom Kyte
May 13, 2005 - 4:12 pm UTC

if and only if EVERY time you delete from parent you want to delete from child, on delete cascade is OK.

but, if there are sometimes that is not the expected behavior -- don't do that, use a stored procedure.

make sure to index those foreign keys, else either approach would be slow slow slow.

deletio of Foxpro as compare to Oracle

Muhammad Waseem Haroon, May 14, 2005 - 8:49 am UTC

There is a feature in foxpro for deletion that when we delete rows from DELETE command then the rows will be marked as deleted by foxpro and whenever i will open the *.dbf file it will always show the marked records untill the PACK command is not issued. After pack command it will be permanently deleted.
if you don't want to retrieved marked rows you have to issue
SET DELETE ON command. There is no need to use "where marked = something", foxpro will automatically use this.
if u want to unmark those rows which are marked you can use
RECALL command.

is there any built-in feature in oracle like foxpro?

Tom Kyte
May 14, 2005 - 9:42 am UTC

cascade delete without cascade delete rule on Foreign Key

Shyjin, June 04, 2005 - 1:27 am UTC

We have a table which is referenced by many other tables,
the foreign keys were created without the on delete cascade rule. For loading test data we want to purge the referenced table but when trying to do so we get integrity constraint voilation - child record found error.
Is there any way to delete parent records along with the child records without the cascade delete rule set.

We can delete all the child tables manually, but there are many of them so we want to do it automatically with some script.

Thanks


Tom Kyte
June 04, 2005 - 8:26 am UTC

manually, if you want to automate it in some script -- great, but it'll be "table by table" on your part (in the script)

You could even write a query against the data dictionary to generate the list of tables, loop over them in PLSQL and issue the necessary deletes.

Very Useful... but what happens if you want to keep the history?

NooNoo, October 29, 2005 - 7:43 am UTC

Suppose you have a supplierA who supplies productA. Suppose you also have a change of supplier to supplierB. SupplierB also supplies productA.

How would you go about keeping the history of productA (who it was sold to, when etc), while deleting the reference supplierA?

Or in other words... if you delete supplierA by turning off/deferring the referential constraint in the supplier (parent) table.... what happens to the orphaned foreign key in the products (child) table?

Tom Kyte
October 29, 2005 - 11:19 am UTC

have you investigated workspace management?

It's useful, but didn't cover the scenario below

Jian Wu, July 23, 2006 - 8:46 pm UTC

Tom, I recently encountered a puzzling scenario and hope you can help me to understand it.
I need to delete 18388 rows of recordS from a parent table (let's say it's table A), and I also want any records in child table (table B) to be deleted. So it's delete cascade situation.
I issued the delete statement and received no error. But the statement was never finishED after over 12 hours of waiting (I had to cancel it). Meanwhile, I noticed that the redo logs were being written.
-------
Eventually I ended up doing the following and all steps completed within an hour.
1. Disable the foreign key constraints.
2. Delete records from table A (parent table).
3. Delete records from table B (child table). SQL is to delete records not exists in table A.
4. Enable the foreign key constraints.
-------
I then examined the DDL of table B (child table). It contains this clause:
"REFERENCES CHARGEBACKS_TARGET (TRANSACTION_ID) ON DELETE CASCADE DISABLE)".
-------
My questions are:
1. Why cascade deletion didn't work or takes forever?
2. Is "delete cascade disable" the same as "delete cascade"?
Thanks.

Tom Kyte
July 24, 2006 - 10:00 am UTC

1) unindexed foreign key perhaps. maybe you were causing us to do 18,388 full table scans of the unindexed child table.

2) that means the create was created disabled - AND if the foriegn key has been enforced, someone came along later and enabled it. the DISABLE is not a modifier to the "delete cascade" clause, but rather to the entire constraint itself:

tkyte%ORCL> create table p( x int primary key );

Table created.

tkyte%ORCL> create table c( x int );

Table created.

tkyte%ORCL>
tkyte%ORCL> alter table c
2 add constraint c_fk_p
3 foreign key(x) references p(x)
4 on delete cascade
5 DISABLE;

Table altered.

tkyte%ORCL>
tkyte%ORCL> insert into c values ( 1 );

1 row created.

tkyte%ORCL> commit;

Commit complete.

constraint exists, but is NOT ENABLED, we are not enforcing it..

tkyte%ORCL>
tkyte%ORCL> alter table c modify constraint c_fk_p enable;
alter table c modify constraint c_fk_p enable
*
ERROR at line 1:
ORA-02298: cannot validate (TKYTE.C_FK_P) - parent keys not found


tkyte%ORCL> delete from c;

1 row deleted.

tkyte%ORCL> commit;

Commit complete.

tkyte%ORCL> alter table c modify constraint c_fk_p enable;

Table altered.

but now we are.

tkyte%ORCL> insert into c values ( 1 );
insert into c values ( 1 )
*
ERROR at line 1:
ORA-02291: integrity constraint (TKYTE.C_FK_P) violated - parent key not found

Follow up my previous review/question

Jian Wu, July 24, 2006 - 12:53 am UTC

Tom: I figured out that the "disable" key work after the "on delete cascade" is due to the fact that the constraint was disabled when the DDL for the child table was generated.
Now my only question for you is:
Why delete cascade didn't work when the constraint was enabled? This is a reproducible problem. If I want to do further analysis, where/what should I look for?
Thanks.

Tom Kyte
July 24, 2006 - 10:33 am UTC

it did work, I'm guessing "unindexed foreign key"

it was working, it was just slow with over 18,000 full table scans.

Alexander the ok, July 24, 2006 - 10:06 am UTC

Tom,

Oracle will still scan the child table on unindexed fks even if the constraints are disabled? If so why?

Tom Kyte
July 24, 2006 - 10:59 am UTC

i'm guessing....

the constraint was NOT DISABLED any longer.

Else, they would not have had to of disabled it themselves (see their step 1 in getting the data purged - "disable foreign key constraints").

question on PK and FK

A reader, October 25, 2009 - 3:24 am UTC

Hi Tom,

I searched first this site in order to find an answer to my question but didn't find; so I am sorry to ask it here

In one client data base they a table T1 with say id as a PK. This id is a FK in another table say T2. Very often all the child records in T2 that correspond to a given Id in T1 are all deleted letting the PK id without child.

This situation represents a serious bug for them. Is it possible to ensure that it will never happen to have a given PK id without at least one child record.

Thanks and sorry
Tom Kyte
October 26, 2009 - 2:08 pm UTC

I hate triggers, but this might be one case whereby they could be used.


ops$tkyte%ORA11GR2> create table p
  2  ( pk int primary key,
  3    /* ... other columns */
  4    child_cnt number default 0 not null constraint check_cnt check( child_cnt > 0 ) deferrable initially deferred
  5  )
  6  /

Table created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create table c
  2  ( fk references p
  3  )
  4  /

Table created.

ops$tkyte%ORA11GR2> create trigger c after delete or insert or update of fk on c for each row
  2  begin
  3          if (inserting or updating)
  4          then
  5                  update p set child_cnt = child_cnt+1 where pk = :new.fk;
  6          end if;
  7          if (updating or deleting)
  8          then
  9                  update p set child_cnt = child_cnt-1 where pk = :old.fk;
 10          end if;
 11  null;
 12  end;
 13  /

Trigger created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> insert into p (pk) values ( 1 );

1 row created.

ops$tkyte%ORA11GR2> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.CHECK_CNT) violated


ops$tkyte%ORA11GR2> insert into p (pk) values ( 1 );

1 row created.

ops$tkyte%ORA11GR2> insert into c (fk) values ( 1 );

1 row created.

ops$tkyte%ORA11GR2> commit;

Commit complete.

ops$tkyte%ORA11GR2> delete from c;

1 row deleted.

ops$tkyte%ORA11GR2> commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02290: check constraint (OPS$TKYTE.CHECK_CNT) violated




just be aware that updating child_cnt by any other program would break this (you could hid that column from view by using VIEWS for application access)

and be aware that direct path loads do NOT fire triggers

and be aware that triggers can be disabled



Or...

Narendra, October 27, 2009 - 5:41 am UTC

Tom,

Or we can continue to hate triggers and still achieve this.
:)
SQL> select * from v$version ;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
PL/SQL Release 10.2.0.4.0 - Production
CORE 10.2.0.4.0 Production
TNS for Solaris: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production

SQL> create table p (pk int primary key, other_col varchar2(10));

Table created.

SQL> create table c(fk references p, misc_col varchar2(10)) ;

Table created.

SQL> create materialized view log on c with ROWID, SEQUENCE(fk) including new values ;

Materialized view log created.

SQL> create materialized view log on p with ROWID, PRIMARY KEY including new values ;

Materialized view log created.

SQL> create materialized view atleast_one_child
  2  refresh fast on commit as
  3  select pk, count(*) child_cnt
  4  from p, c
  5  where p.pk = c.fk
  6  group by pk ;

Materialized view created.

SQL> alter table atleast_one_child add constraint con_cnt check(child_cnt > 0) ;

Table altered.

SQL> insert into p values (1, 'x') ;

1 row created.

SQL> insert into c values (1, 'y') ;

1 row created.

SQL> commit ;

Commit complete.

SQL> delete from c ;

1 row deleted.

SQL> commit ;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (HR.CON_CNT) violated


SQL> spool off


Learnt from you only
http://tkyte.blogspot.com/2008/05/another-of-day.html
Tom Kyte
October 27, 2009 - 11:44 am UTC

too bad it doesn't work.

You didn't think about creating a parent - but never creating a child.


think about that....


(this solution *does not work*)

ops$tkyte%ORA10GR2> alter table atleast_one_child add constraint con_cnt check(child_cnt > 0) ;

Table altered.

ops$tkyte%ORA10GR2> insert into p values (1, 'x') ;

1 row created.

ops$tkyte%ORA10GR2> commit ;

Commit complete.



and if you think about it - your example *does not have to work*. And - it might not work. When you delete from c - you basically wipe out the rows in your materialized view. You "got lucky" for this one.

The problem is you would need an outer join to C, but you cannot.


Maybe you can do this using materialized views after all...

Brian Camire, October 27, 2009 - 3:16 pm UTC

What about this?

SQL> select * from v$version ;

BANNER
----------------------------------------------------------------
Personal Oracle Database 10g Release 10.2.0.3.0 - Production
PL/SQL Release 10.2.0.3.0 - Production
CORE    10.2.0.3.0      Production
TNS for 32-bit Windows: Version 10.2.0.3.0 - Production
NLSRTL Version 10.2.0.3.0 - Production

SQL> create table p (pk int primary key, other_col varchar2(10));

Table created.

SQL> create table c(fk references p, misc_col varchar2(10)) ;

Table created.

SQL> create materialized view log on c with ROWID, SEQUENCE(fk) including new values ;

Materialized view log created.

SQL> create materialized view log on p with ROWID, PRIMARY KEY including new values ;

Materialized view log created.

SQL> create materialized view atleast_one_child
  2  refresh fast on commit as
  3  select p.rowid as prowid, c.rowid as crowid, pk
  4  from p, c
  5  where c.fk (+) = p.pk
  6  and
  7  c.fk is null;

Materialized view created.

SQL> alter table atleast_one_child add constraint must_be_empty check (pk is null);

Table altered.

SQL> insert into p values (1, 'x') ;

1 row created.

SQL> insert into c values (1, 'y') ;

1 row created.

SQL> commit;

Commit complete.

SQL> delete from c;

1 row deleted.

SQL> commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (TEMP.MUST_BE_EMPTY) violated


SQL> insert into p values (2, 'z');

1 row created.

SQL> commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (TEMP.MUST_BE_EMPTY) violated


SQL> insert into p values (3, 'zz');

1 row created.

SQL> insert into c values (3, 'zzz');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from c;

        FK MISC_COL
---------- ----------
         1 y
         3 zzz

SQL> update c set fk = 3 where fk = 1;

1 row updated.

SQL> commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-02290: check constraint (TEMP.MUST_BE_EMPTY) violated

Tom Kyte
October 27, 2009 - 5:15 pm UTC

yup, that outer join does it - and the "is null" makes it consume no space...

My bad

Narendra, October 28, 2009 - 3:49 am UTC

Tom/Brian,

Thanks.
I think I need to curb my enthusiasm and improve on my testing skills. :)
My point was to simply avoid triggers (which is possible in this case as Brian showed.)

direct path load and trigger

A reader, April 20, 2010 - 7:15 am UTC

Hi Tom,

In your comment to
"question on PK and FK October 25, 2009 - 3am Central time zone " you wrote this

'and be aware that direct path loads do NOT fire triggers'

Do you mean that the hint /*+ append */ is silently ignored when trigger are present in the inserted table?

Or you really mean that triggers are ignored during direct path loads?

Please clarify

Mohamed Houri


Tom Kyte
April 20, 2010 - 8:44 am UTC

... Do you mean that the hint /*+ append */ is silently ignored when trigger are
present in the inserted table?
...


correct


IF you use a direct path load (eg: sqlldr, OCI) then triggers are not fired.


ops$tkyte%ORA11GR2> create table t ( created timestamp, modified timestamp, msg varchar2(20) );

Table created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> insert /*+ append */ into t select systimestamp, systimestamp, null from dual;

1 row created.

ops$tkyte%ORA11GR2> select count(*) from t;
select count(*) from t
                     *
ERROR at line 1:
ORA-12838: cannot read/modify an object after modifying it in parallel

<b>that shows the append hint was used - we did a direct path load..</b>

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create or replace trigger t
  2  before insert on t for each row
  3  begin
  4          :NEW.MSG := 'I FIRED';
  5  end;
  6  /

Trigger created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> insert /*+ append */ into t select systimestamp, systimestamp, null from dual;

1 row created.

ops$tkyte%ORA11GR2> select count(*) from t;

  COUNT(*)
----------
         2

<b>that shows the append hint was ignored - we did NOT direct path, else we would NOT be able to read the table</b>

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> delete from t;

2 rows deleted.

ops$tkyte%ORA11GR2> commit;

Commit complete.

ops$tkyte%ORA11GR2> !cat t.ctl
load data
INFILE *
INTO TABLE t
replace
FIELDS TERMINATED BY '|' trailing nullcols
(created timestamp "YYYY-MM-DD HH24:MI:SSXFF",
modified timestamp "YYYY-MM-DD HH24:MI:SSXFF")
begindata
2009-09-09 05:06:04.066162|2009-09-09 05:06:04.066162

ops$tkyte%ORA11GR2> !sqlldr / t.ctl direct=no

SQL*Loader: Release 11.2.0.1.0 - Production on Tue Apr 20 09:43:05 2010

Copyright (c) 1982, 2009, Oracle and/or its affiliates.  All rights reserved.

Commit point reached - logical record count 1

ops$tkyte%ORA11GR2> select * from t;

CREATED
---------------------------------------------------------------------------
MODIFIED
---------------------------------------------------------------------------
MSG
--------------------
09-SEP-09 05.06.04.066162 AM
09-SEP-09 05.06.04.066162 AM
I FIRED

<b>sqlldr ran, loaded table, and fired trigger...</b>

ops$tkyte%ORA11GR2> !sqlldr / t.ctl direct=yes

SQL*Loader: Release 11.2.0.1.0 - Production on Tue Apr 20 09:43:05 2010

Copyright (c) 1982, 2009, Oracle and/or its affiliates.  All rights reserved.


Load completed - logical record count 1.

ops$tkyte%ORA11GR2> select * from t;

CREATED
---------------------------------------------------------------------------
MODIFIED
---------------------------------------------------------------------------
MSG
--------------------
09-SEP-09 05.06.04.066162 AM
09-SEP-09 05.06.04.066162 AM


<b>sqlldr ran, direct path was used, trigger DID NOT fire</b>


A reader, April 20, 2010 - 8:50 am UTC

Thanks very much

sandhya, May 17, 2010 - 4:15 am UTC

I have two tables having relationship between them i,e common uid,i want a condition that if i delete a record from a primary table it should also deleted from the other table.i have also given on delete cascade.

delete from usr1 INNER JOIN proj ON usr1.uid=pro.uid where usr1.uid=A2

Tom Kyte
May 24, 2010 - 7:36 am UTC

you just delete from the parent table, if you have an on delete cascade foreign key from child to parent, it'll happen.

do not involve the child in the delete, that is what the on delete cascade DOES.