Skip to Main Content
  • Questions
  • Can Cascade Delete be identified in PL/SQL?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Eileen.

Asked: January 03, 2006 - 2:10 pm UTC

Last updated: February 27, 2007 - 3:12 pm UTC

Version: 10.1.0

Viewed 1000+ times

You Asked

I have two tables tbl_host and tbl_alias. I need to send an email when a record is deleted from either table. I was planning to use triggers to send the email. When the delete cascades from tbl_host to tbl_alias, I only want to send one email, from tbl_host. Only if a record is deleted from tbl_alias directly do I want the alias email to be sent. In a trigger, is there a way to know if the delete cascaded?

When a record is deleted from tbl_alias, the email must say Alias XXX was deleted for hostname YYY. The production version of tbl_host contains 10 additional fields which I have omitted for simplicity. When a record is deleted from tbl_host, the email must state each field in the deleted record as well as each associated alias.

The triggers will not send email directly. They will insert the email message into an email table. A scheduler job will periodically check the email table for new messages and send them.

CREATE TABLE tbl_host (
hostid number(18) constraint host_pk primary key,
ipaddress varchar2(12) constraint host_ipaddress_nn not null,
hostname varchar2(30) constraint host_hostname_nn not null
);

insert into tbl_host values (1,'100100100100','mypc');

CREATE TABLE tbl_alias (
aliasid number(18) constraint alias_pk primary key,
hostid number(18) constraint alias_hostid_nn not null,
alias varchar2(30) constraint alias_alias_nn not null
);

alter table tbl_alias add constraint alias_host_fk foreign key(hostid) references tbl_host(hostid) on delete cascade;

insert into tbl_alias values (1,1,'pc1');
insert into tbl_alias values (2,1,'pc2');

and Tom said...

This would definitely be something I would want to do in a stored procedure - NOT in an automagic "stuff happens by accident" in the background trigger. The stored procedures (two of them, one to delete from tbl_host, the other from tbl_alias) would be able to accomplish this in a very very very straight forward, managable, maintainable, understandable fashion.

Doing this in triggers - ugly, complex, hard to understand, automagic (side effects, stuff just "happens"), hard to maintain.

but it would work like this (strongly discourage this, really)...

A state package in which to "build the email":

ops$tkyte@ORA10GR2> create or replace package state_pkg
2 as
3 g_text long;
4 newline constant varchar2(2) := chr(10);
5 end;
6 /

Package created.


with the on delete cascade, the before and after DELETE triggers will fire on tbl_alias, so we'll use the before delete to reset the package state. Even if the act of deleting from tbl_host deletes many rows - the before delete trigger will fire once:


ops$tkyte@ORA10GR2> create or replace trigger tbl_alias_SETUP
2 before delete on tbl_alias
3 begin
4 state_pkg.g_text := null;
5 end;
6 /

Trigger created.

then we use delete, for each row triggers to capture the rows being deleted:

ops$tkyte@ORA10GR2> create or replace trigger tbl_host_before_delete_fer
2 before delete on tbl_host for each row
3 begin
4 state_pkg.g_text := state_pkg.g_text || state_pkg.newline ||
5 'tbl_host( hostid, ip, name ) = ' ||
6 :old.hostid || ', ' ||
7 :old.ipaddress || ', ' ||
8 :old.hostname;
9 end;
10 /

Trigger created.

ops$tkyte@ORA10GR2> create or replace trigger tbl_alias_after_delete_fer
2 after delete on tbl_alias for each row
3 begin
4 state_pkg.g_text := state_pkg.g_text || state_pkg.newline ||
5 'tbl_alias( aliasid, hostid, alias ) = ' ||
6 :old.aliasid || ', ' ||
7 :old.hostid || ', ' ||
8 :old.alias;
9 end;
10 /

Trigger created.

and it would be in the single invocation of the after delete trigger on alias that we would queue the email to be sent...

ops$tkyte@ORA10GR2> create or replace trigger tbl_alias_CLEANUP
2 after delete on tbl_alias
3 begin
4 dbms_output.put_line( 'we would email ' || state_pkg.newline || state_pkg.g_text );
5 end;
6 /

Trigger created.

I broadened your example out

ops$tkyte@ORA10GR2> select * from tbl_host;

HOSTID IPADDRESS HOSTNAME
---------- ------------ ------------------------------
0 100100100100 mypc
1 100100100100 mypc
2 202020202020 myotherpc

ops$tkyte@ORA10GR2> select * from tbl_alias;

ALIASID HOSTID ALIAS
---------- ---------- ------------------------------
1 1 pc1
2 1 pc2
3 2 Apc1
4 2 Bpc2

ops$tkyte@ORA10GR2> delete from tbl_host where hostid = 0;
we would email

tbl_host( hostid, ip, name ) = 0, 100100100100, mypc

1 row deleted.

delete from host, no rows to cascade - we capture what we needed...



ops$tkyte@ORA10GR2> delete from tbl_host where hostid = 1;
we would email

tbl_host( hostid, ip, name ) = 1, 100100100100,
mypc
tbl_alias( aliasid, hostid, alias ) = 1, 1, pc1
tbl_alias( aliasid,
hostid, alias ) = 2, 1, pc2

1 row deleted.

when it cascades - we get it...

ops$tkyte@ORA10GR2> delete from tbl_alias where aliasid = 3;
we would email

tbl_alias( aliasid, hostid, alias ) = 3, 2, Apc1

1 row deleted.

and when no cascade - just alias, we get it. and even if we delete multiple host records:


ops$tkyte@ORA10GR2> rollback;

Rollback complete.

ops$tkyte@ORA10GR2> delete from tbl_host;
we would email

tbl_host( hostid, ip, name ) = 0, 100100100100, mypc
tbl_host(
hostid, ip, name ) = 1, 100100100100, mypc
tbl_alias( aliasid, hostid, alias )
= 1, 1, pc1
tbl_alias( aliasid, hostid, alias ) = 2, 1, pc2
tbl_host( hostid,
ip, name ) = 2, 202020202020, myotherpc
tbl_alias( aliasid, hostid, alias ) =
3, 2, Apc1
tbl_alias( aliasid, hostid, alias ) = 4, 2, Bpc2

3 rows deleted.


we get one email with all of the details.

(notes: you can store the data in the state_pkg however you want and for real you might use a clob if the email is "large")

Rating

  (24 ratings)

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

Comments

great! but...

Menon, January 04, 2006 - 12:00 pm UTC

"This would definitely be something I would want to do in a stored procedure "

Hi Tom
What do you mean by that? I can understand your solution using triggers but what do you mean by the above statement? Can you elaborate a bit more?

OK let me guess what you mean...

You would have separate procedures delete_tbl_host and delete_tbl_alias that would do the delete (instead of directly using the delete statement. The app user would have execute privileges on these procedure but no direct "delete" from tables privileges...Is this what you meant?

If so, I dont understand in this case how you would detect the "delete cascade" situation? I can think of detecting that only via triggers...

Thanx!

thanx!

Tom Kyte
January 04, 2006 - 12:24 pm UTC

No delete on tbl_host, no delete on tbl_alias

Rather a stored procedure "remote_host" and another "remote_alias"


You would have a TRANSACTIONAL API which does the "right thing"

DBA deletions

Eileen, January 04, 2006 - 12:38 pm UTC

Our users do not have direct access to any tables, all data access is through packages. However, since I can't force DBA users to use these packages, I want to ensure that the email gets sent however the data is accessed.

Are you saying that I can't actually detect a cascade delete? I must design it so that it can handle either case?

Tom Kyte
January 04, 2006 - 1:09 pm UTC

If you want to do it in a trigger - it is going to look like this regardless of whether you could detect the cascade update or not. This is what it would look like "anyway".

You need a trigger to reset the pacakge state.
You need a trigger to send the email at the end (queue it to be sent)
You need row triggers to capture the details of what was deleted

It matters not if it were a cascade update or whatever.

But no, a delete cascade is not any different to us than anything else - there is nothing "special" or flagged about it.



If your DBA's are going around your application, you have a serious SERIOUS serious issue.

and if your DBA's want to, they will get around the trigger as well.



well..

Menon, January 04, 2006 - 12:42 pm UTC

"No delete on tbl_host, no delete on tbl_alias

Rather a stored procedure "remote_host" and another "remote_alias"


You would have a TRANSACTIONAL API which does the "right thing"

I know what you mean here but sometimes, you may have to create APIs for reuse. What if two "transactional" APIs need to delete from the same tables for two different transactions? Your main point of not having "table" API (or TAPI) is well taken but you may still need to have these APIs (in most cases, I woudl imagine). For example., anytime two apis need to "insert" they would need an API if you dont wanna go the "trigger" route.

Anyways, my question still stands - how would you "detect" delete cascade without "triggers"?

Tom Kyte
January 04, 2006 - 1:12 pm UTC

These API's are infinitely reusable.

Heck, I wouldn't even use an on delete cascade personally - remove_host would delete from child and then delete from parent - do whatever else needs to happen and return (no commit, no rollback, no transactional control - that is up to the client).

So, you have a logical unit of work "remove host" that does SOMETHING (maybe it deletes a row, maybe 500 rows, you neither know nor care - it is a unit of work, self contained)

remove_host and add_host could be combined into a single unit of work by a client

begin
remove_host;
add_host;
commmit;
end;


at any time - infinite reuse.

ah...I see what you mean...

Menon, January 04, 2006 - 1:48 pm UTC

I guess the "remote_host" and "remote_pc" (instead of "remove_host" and "remove_pc" in the earlier "review" threw me off a bit:)

Anyways, I think my intents were "same" though things are a bit more clear. What you are saying is that you usually will never have a case where two distinct "transactions" need to reuse "delete from a table". Instead, you envision cases where they both need the "action" of removing a host etc (which internally implement it by a delete or whatever). And I also get how you would "detect" "delete cascade" in this case.

It makes sense from an application point of view but if you encode these "rules" in API, you would still need to contend with the fact that DBAs would have to use the funcitonality in the same fashion (via the API). Nothing wrong there except it may not work out in many orgs (for non technical reasons).

Tom Kyte
January 04, 2006 - 2:33 pm UTC

yes, sorry about that - t/v same thing right :)



DBA's should not be allowed to directly modify application tables, they have no clue what the ramifications could be.

No more than they would update the data dictionary!

hmm...

Menon, January 04, 2006 - 9:09 pm UTC

"t/v"? For a moment I thought you had violated one of your pet rules of not using sms speak;)

Tom Kyte
January 05, 2006 - 10:17 am UTC

not at all! I reached up for the T when I meant to reach down for the V apparently in "remove"/"remote" above...

are cascade deletes good/bad in general?

A reader, January 05, 2006 - 9:40 am UTC

Hi Tom,
In your opinion, are cascade deletes a good idea? I personally don't feel safe using them knowing that anybody with access to the affected tables could alter, add, or remove the foreign constraints within the chain. I think that a delete statement should affect one table and one table ALONE.

Thanks.


Tom Kyte
January 05, 2006 - 11:00 am UTC

it is a matter of opinion I think.

The older I get, the less I like what I refer to as "magic" - that is "side effects that just happen".

My re-design of asktom for example - triggers, they will be avoided like the plague. Too many crept in over time with other fingers in the pie. The unintended side effects of triggers doing things "by sheer and utter magic" has caused so many problems over time (not just with this application - but pretty much every application I've seen).

Delete cascade to me is the same thing, I prefer to be explicit anymore. Sure, I type an extra line of code - but you know what? I can read it, maintain it, understand it, enhance it. And since I do everything via transactional API's - it doesn't really matter that I typed that one extra line of code once.



huh

Niall Litchfield, January 05, 2006 - 9:41 am UTC

I'm probably being daft but I don't get "t/v".

I'm with you on the dbas should not update directly argument, with the exception of cases where the api/app is faulty and or does not offer the ability to correct incorrect data (more commonly known as a bug in the app/api).



Niall

Menon, January 05, 2006 - 9:54 am UTC

t/v came from "remote_host" vs "remove_host". He put "t" instead of the "v".

t/v

A reader, January 05, 2006 - 9:56 am UTC

TO those who are confused about what t/v meant, Tom was just making a joke over the the difference between remote and remove. t/v are the same, correct?

Tom Kyte
January 05, 2006 - 11:03 am UTC

Yes - I accidently typed in remote_host and remote_alias instead of remoVe_host and remoVe_alias above - t/v problem..

Why Triggers Bad?

A reader, January 05, 2006 - 2:48 pm UTC

If application logic dictates that a DML action on a table result in further DML on other table(s), database triggers come in handy.

Are you saying that avoid detailed code in database triggers but call your procedures/packages from triggers instead?

Or are you saying that not have database triggers at all?

If latter, wouldn't you have to re-invoke your "triggered" logic from every place that original DML happens?

Tom Kyte
January 05, 2006 - 3:25 pm UTC

I avoid triggers at all costs. I will use them only when no other option is available.

Would I have to re-invoke your triggered logic? Not at all - transactional api's, reusable code, centralized control of data logic.

Triggers are things that just happen "by accident", "in the background", "unbeknownest to the person reading the code".


With few exceptions - they are massively overused and abused.


I'm saying I put my code into packaged procedures - do away with the triggers - and have people invoke the packaged procedures.

How Can You Avoid Manually Re-Invoking Triggered Logic When NOT Using Database Triggers?

A reader, January 05, 2006 - 5:00 pm UTC

<You said>
Would I have to re-invoke your triggered logic? Not at all - transactional
api's, reusable code, centralized control of data logic.
<\You said>


Let's say here is the situation: The application demands that an update of COLUMN_A in TABLE_1 should insert a new record into TABLE_2.

Now, this update could happen from several (any) application. When using database triggers, all you need to do is write the insert into the trigger on TABLE_1, and you are done.

Are you saying you would prefer to identify all the placed where TABLE_1 is being updated from, and then invoke the insert logic from each of those places?

Tom Kyte
January 05, 2006 - 6:49 pm UTC

The application should be calling stored procedures (centralized logic). Then application demands squat - application asks nicely "I would like to perform this transaction - this transaction that many others before me and many others after me will be calling, would you be so kind as to run it for me"

Applications should never demand a thing, applications come and applications go. The business dictates what happens to data and it should be consistently applied. To me - that means "use procedures to encapsulate the business logic".

I just don't like triggers - been too frequently burned by automagical side effects that are too easily forgotten about.

I love it when I get a question like:

question: hey, Oracle is broke, look at this:

sql> update t set x = 5 where y = 10;
sql> select x from t where y = 10;

X
-------
42

answer: what triggers do you have on the table

response: oh


I'm saying applications - smapplication. transactional APIs are what I prefer.

if its still not clear...

SRK, January 05, 2006 - 10:33 pm UTC

A Reader -

What Tom says is this: Instead of putting your business logic (of inserting one record in table2) in a after insert trigger on table1, use a transactional api which will implement the same business rule for you. That is, you will revoke the insert privilege on table1 and table2 from everybody and instead give them execute privileg on a plsql package. And the package will insert one row in table1 followed by another in table2 or whatever according to your business rule.

In short, do transactions only using the transactional apis, not plain dml+triggers. HTH.

Alexander the ok, January 06, 2006 - 9:07 am UTC

Tom,

Don't forget about the parsing triggers cause. Doesn't the code in the trigger need to be soft parsed for each row due to how Oracle caches statements for the time of the transaction? If that's the case, I don't think you need to go into the difference between a soft parse and no parse again, pretty significant.

Tom Kyte
January 06, 2006 - 2:04 pm UTC

triggers do not cache their parsed sql between calls - this is true, so if you have a trigger that does 3 sql statements and you issue 3 DML statements that each invoke the trigger - you'll have 9 parses.

but the solution to that is trivial, move the code into a package and have the trigger invoke the package, now you'll have but 3 parses.

It is a valid reason for putting the logic into a package, but not a reason for not having a trigger necessarily. My reasons for not having triggers center around

understandability (it goes out the window)
maintainability (gone in my opinion as well)
ability to show code works in all conditions (much harder)

I just don't like them - been burned too many times by them. I use them when I have to - they are not my first choice.

APIs are fine, but...

A reader, January 06, 2006 - 10:12 am UTC

Tom and SRK, APIs are not the silver bullet for a tool like Oracle Forms. Base-table blocks in Forms do direct DML. How can you API'ze them?

One could use blocks based exclusively on procedures, but for existing Forms (and even for new forms), it's a lot easier to go the easy way and use base tables, for that's the express selling proposition of that tool.

Tom Kyte
January 06, 2006 - 2:12 pm UTC

I don't like triggers.
I will avoid triggers at all costs.

The tools I've used recently (since about 1995) haven't gotten in my way. It is true that sometimes a tool can make certain approaches unwieldy.

there is no such thing as a "silver bullet"

SRK, January 06, 2006 - 10:58 am UTC

I dont believe in silver bullets, I don't think Tom does either.

If you want to use "Base-table blocks", design your tables accordingly so that the transaction does not span multiple tables. Otherwise use a transactional api, which is the proper way to do it.

Using triggers to mimic multi table transactional behavior is the slow and ugly method. But, its your system - do whatever makes sense to you.

Oh Yes

A reader, January 06, 2006 - 11:56 am UTC

I will, thank you very much!

About delete cascade

Rajeswari, January 09, 2006 - 2:45 am UTC

In one of your followup, you said
<Quote>
Delete cascade to me is the same thing, I prefer to be explicit anymore. Sure,
I type an extra line of code - but you know what? I can read it, maintain it,
understand it, enhance it. And since I do everything via transactional API's -
it doesn't really matter that I typed that one extra line of code once.
<Quote>

In our schema we used to have lot of foreign key constraints with delete cascade. All insert, update, delete comes from Java statements. In this case I feel on looking into table definition I will get some idea on business rules.

Personally I didn't burn my hand lot of time except table lock in child table if no index on foreign key.

Whether you mean to say if all the logic is in procedures (Transactional API) it is not necessary to have delete cascade constraint?

What about check constraint except primary key?

Sorry if I am missing something which is obvious.




Tom Kyte
January 09, 2006 - 8:02 am UTC

on delete cascade is not data integrity - it is an automagical side effect that happens.

the foreign key - that is data integrity.
the check constraint - that is data integrity.

that all child records should be removed when the parent is removed - that is transactional logic that I prefer to keep in the transaction - which I prefer to write in stored procedures so that any "programming paradigm" that comes along next year can make REUSE of them.

Totally agree...

Scot, January 09, 2006 - 9:58 am UTC

Thanks Tom, great thread.

Great Tom

A reader, January 10, 2006 - 3:54 am UTC

I got the point what you said. Thank you very much.

Back to the On Delete Cascade

Chandini, January 11, 2006 - 5:33 am UTC

Tom,
Going back to the issue of On delete Cascade and Oracle Forms.
I've got a Forms application where the user can go in and delete rows and Forms handles all the underlying transaction code.
In this scenario, I do want to allow the user to delete from a parent table, but want to make sure that the child records are deleted automatically. I could go about and code an ON-delete trigger (for example) on the Forms block and do a 'delete child_table.....' but wouldn't this then be 'reinventing the wheel'. Especially if Oracle provides you with the same functionality with the ON DELETE CASCADE? I'm not sure about your take that the cascade functionality is not part of the constraint definition. Look forward to your comments

Tom Kyte
January 12, 2006 - 10:15 am UTC

look - I'll just say:

It is my opinion, my preference to do things with a transactional API - hence, I would not use on delete cascade myself.

However, your choice of tool might lead you personally down a separate path. I prefer to do things a certain way.




Insert, delete, update in Stored Procedures

Branka, February 16, 2007 - 11:12 am UTC

I need to insert, update, delete record in parent table, and in several child tables. In each child table, I can have more than one record. How can I have transaction logic in stored procedure, when I don't know how many record will I insert into child tables?
Should I have one stored procedure for each table, let Java to call each of them for insert, update, delete?

Tom Kyte
February 17, 2007 - 11:03 am UTC

you have collections, you can pass 'arrays'

Insert, delete, update in Stored Procedures

Branka, February 27, 2007 - 3:03 pm UTC

What to do when one of child tables has child table?
Tom Kyte
February 27, 2007 - 3:12 pm UTC

what not to do?

not sure what you are asking here at all.

Insert, delete, update in Stored Procedures

Branka, February 28, 2007 - 10:23 am UTC

I have parent table T1, than I have child table T2, and I have table T3, that is child of T2.
I would like to pass data for all 3 tables to the stored procedure.
I Don't know how to pass parameters for T3 table.

Thanks

create or replace PACKAGE BDE AS
TYPE type_T3 IS RECORD ( cd T3.cd%TYPE);
TYPE type_T3_TABLE IS TABLE OF type_T3 INDEX BY BINARY_INTEGER;

TYPE TYPE_T2 IS RECORD
( LEGAL_NM T2.LEGAL_NM%TYPE,
FAMILY_NM T2.FAMILY_NM%TYPE,
p_T3 type_T3_TABLE
);
TYPE TYPE_T2_TABLE IS TABLE OF TYPE_T2 INDEX BY BINARY_INTEGER;

PROCEDURE SAVE_DATA (p_APPL_NO T1.APPL_NO%TYPE,
p_applicant IN TYPE_T2_TABLE );
END BDE;


create or replace PACKAGE BODY BDE AS
PROCEDURE SAVE_DATA (p_APPL_NO T1.APPL_NO%TYPE,
p_applicant IN TYPE_T2_TABLE)

AS
p_appl_id T1.appl_id%TYPE;
p_applcnt_id T1.appl_id%TYPE;

BEGIN
SELECT THSEQ.nextval INTO p_appl_id FROM DUAL;

INSERT INTO T1(ppl_id, APPL_NO)
VALUES ( p_appl_id, p_APPL_NO);

FOR i IN 1..p_applicant.COUNT
LOOP
SELECT THSEQ.nextval INTO p_applcnt_id FROM DUAL;
INSERT INTO T2( FK_appl_id, APPLICANT_ID, LEGAL_NM,FAMILY_NM)
VALUES ( p_appl_id, p_applcnt_id, p_applicant(i).LEGAL_NM,p_applicant(i).FAMILY_NM);

FOR j IN 1..?????
LOOP

INSERT INTO T3(da_id,applicant_id,des_id,cd)
VALUES (p_appl_id, p_applcnt_id,THSEQ.nextval, ?????);
END LOOP;

END LOOP;
COMMIT;
END SAVE_DATA;



END BDE;

Insert, delete, update in Stored Procedures

Branka, February 28, 2007 - 3:31 pm UTC

I made it to work
FOR j IN 1..p_applicant(i).p_T3.COUNT
LOOP

INSERT INTO T3(da_id,applicant_id,des_id,cd)
VALUES (p_appl_id, p_applcnt_id,THSEQ.nextval, p_applicant(i).p_T3(j).cd);
END LOOP;

Thanks

More to Explore

CLOB/BLOB/etc

Complete documentation on Securefiles and Large Objects here