Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Don.

Asked: May 01, 2020 - 3:49 pm UTC

Last updated: May 04, 2020 - 1:57 pm UTC

Version: 12c

Viewed 1000+ times

You Asked

I'm working with a system that, in the most generous terms that I can come up with, has a horrendous data model. There is a set of tables that are "peers," in that they have a 1-1 relationship and the PK of table A is the PK in tables X1 to Xn. Table A is the "master" and tables X... are "auxiliary" (ORM inheritance). All these table may have related child tables. Table A must have a related row in 1 and only 1 of the auxiliary tables, and when a row is deleted from an auxiliary table the corresponding row must be deleted from table A, and vice-versa. I cannot enforce that the delete always begin with table A; it may be initiated at any of the peer tables. I've tried combinations of cascading deletes and initially deferred foreign keys which work when there is exactly one auxiliary table, but you can't put multiple FKs on a single column, so it doesn't work for the 1..n tables. I've tried putting a trigger on the auxiliary table that deletes the parent which in turn is related to the other tables with a cascading delete, but this fails with a data mutating error.

Since remodeling the tables (e.g. composition instead of inheritance) is, sadly, off the table, the "best" remaining alternative appears to be that all deletes are done by stored procedure. This has the problem that the "n" part of Xn is not static over time. As new peer tables are added someone has to remember to update the stored procedure, and my organization has demonstrated a deficiency in that area.

Statements 3 and 5 of the liveSql script have error trapping for the mutating data comment out. Without the error trapping the process fails on the errors. With the error trapping, the master row does not get deleted.

Any ideas?

with LiveSQL Test Case:

and Chris said...

Hmmm, sounds like you're in a bit of a mess!

I can think of a couple of ways out of this:

- Create a view outer joining all the tables. Put an instead DML trigger on it and run all deletes against the view
- Remove all the FKs...

The first is my preferred method ;)

create or replace view my_central_view as 
  select m.*, description_1, description_2
  from   my_central_table m
  left join my_auxiliary_table_1 t1
  on     pk = t1.auxiliary_key 
  left join my_auxiliary_table_2 t2
  on     pk = t2.auxiliary_key ;
  
create or replace trigger view_iiud 
instead of insert or update or delete on my_central_view
begin
  if deleting then
    delete my_central_table
    where  pk = :old.pk;
    
    delete my_auxiliary_table_1
    where  auxiliary_key = :old.pk;
    
    delete my_auxiliary_table_2
    where  auxiliary_key = :old.pk;
  end if;
end;
/

delete my_central_view;


Though this suffers from the same problem as a stored procedure: people have to remember to update it! I prefer using a stored procedure over the view with intead of triggers. But the view may be more acceptable to your dev team.

Removing the FKs obvious avoids the problem entirely. But brings it's own data quality issues!

Given that:

As new peer tables are added someone has to remember to update the stored procedure, and my organization has demonstrated a deficiency in that area.

I would be very hesitant to remove the FKs.

Ultimately you need to get the maintenance of stored procedures/views/etc. addressed. Sloppy processes will bite you eventually!

But this often requires organizational change, which is tough. Good luck!

Rating

  (1 rating)

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

Comments

A reader, May 04, 2020 - 3:27 pm UTC


More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.