Cascade deletion of an object that can be related to itself - tsql

In my database, I have a number of objects that can be related to each other.
This is fine, until I decide I want to delete these objects. Because of the relation record, I need to implement cascade delete to prevent an exception from being thrown.
When an object that is on either side of the relation is deleted, I want the relation record to be deleted too. I would like to create a database structure that looks like this:
CREATE TABLE [MyObject]
(
[ID] [int] IDENTITY PRIMARY KEY,
...
);
CREATE TABLE [MyObjectRelation]
(
[ID] [int] IDENTITY PRIMARY KEY,
[MyObjectID] [int] FOREIGN KEY REFERENCES [MyObject] ([ID]) ON DELETE CASCADE,
[RelatedMyObjectID] [int] FOREIGN KEY REFERENCES [MyObject] ([ID]) ON DELETE CASCADE
)
However, whenever I attempt to run this on my database, I receive this error message:
Introducing FOREIGN KEY constraint '...' on table 'MyObjectRelation' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I have read the documentation about this, but I do not see how this layout could cause a cycle. It is entirely possible of course, that I have misinterpreted the documentation on MSDN for cascade delete, and the database layout above will not achieve what I want here.
I would be very interested in hearing what I can do to implement the behaviour that I want.

You receive this error message because in SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree.
You can use triggers to achieve the same behavior.

Related

postgres not setting foreign key to null when truncating

I'm trying to truncate a set of tables, but it keeps complaining about a foreign key.
but that foreign key is set to on delete Set null
to reproduce:
create table test_players (id SERIAL PRIMARY KEY, name VARCHAR(255));
create table test_items (id SERIAL PRIMARY KEY, name VARCHAR(255), player_id INTEGER FOREIGN KEY (player_id) REFERENCES test_players(id) ON DELETE SET NULL);
now if you truncate the test_players it will complain:
ERROR: cannot truncate a table referenced in a foreign key constraint
DETAIL: Table "test_items" references "test_players".
HINT: Truncate table "test_items" at the same time, or use TRUNCATE ... CASCADE.
SQL state: 0A000
what must I do to make me be able to delete test_players without deleting the test_items?
You cannot do what you are attempting. You will have to do this in 3 steps.
Update test_items and for each player_id. Well technically you don't need this, but if you don't give yourself data integrity issues.
Drop the test_items to test_players FK.
Then truncate test_players
The reason is that truncate basically just zaps the table, it does NOT process individual rows. Therefore it would not process the FK set null, it throws the error you got instead. In fact even if the child table is empty, or for that matter even if the parent is empty. See fiddle here. The fiddle also contains a function to do it, and a test for it.
The of course you could just Delete from test_players and let the triggers take care of updating test_items. Takes longer, esp if larger table, but you keep your FK. Of course there's
Recreate your FK.

sql developer -data modeller generated DDL includes changes already committed into database

using the data modeller embedded in sql developer 19.4 against an oracle 12c database.
I added a couple of foreign keys within the data modeller erd diagram then I clicked the "syncrhonize data modeller with model" ; then I noticed that in the generated DDL other than my legitimate changes the DDL contain also other SQL relative to changes already existing in the database; anyway I amended the DDL by deleting the unwanted changes and applied and commit my changes. then I run the same compare again and the "Compare Models" window correctly show no changes but... the DDL has the same shanges already applied and the ones that already exists in the database.
I also reverted the comparison by comparing the model with the erd and hit "merge" thinking that this is a problem with some kind of cahced memory etc.. but same issue here: "Compare Models" view does correctly show no chaages but DDL contain changes ??
and below the DDL with the script of changes that already exists in the database.
ALTER TABLE gasgendev.audit_errors
ADD CONSTRAINT audit_errors_look_audit_types_fk FOREIGN KEY ( audit_type )
REFERENCES gasgendev.look_audit_types ( audit_type_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
ALTER TABLE gasgendev.audit_logs
ADD CONSTRAINT audit_logs_look_audit_types_fk FOREIGN KEY ( audit_type )
REFERENCES gasgendev.look_audit_types ( audit_type_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
ALTER TABLE gasgendev.halo_inputs
ADD CONSTRAINT halo_inputs_look_assets_fk FOREIGN KEY ( look_assets_l_asset_id )
REFERENCES gasgendev.look_assets ( l_asset_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
ALTER TABLE gasgendev.halo_inputs
ADD CONSTRAINT halo_inputs_look_datasets_fk FOREIGN KEY ( dataset_id )
REFERENCES gasgendev.look_datasets ( l_dataset_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
ALTER TABLE gasgendev.manual_inputs
ADD CONSTRAINT manual_inputs_look_manual_inputs_fk FOREIGN KEY ( look_manual_inputs_look_manual_input_id )
REFERENCES gasgendev.look_manual_inputs ( look_manual_input_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
ALTER TABLE gasgendev.manual_inputs
ADD CONSTRAINT manual_inputs_look_datasets_fk FOREIGN KEY ( dataset_id )
REFERENCES gasgendev.look_datasets ( l_dataset_id )
ON DELETE CASCADE
NOT DEFERRABLE ENABLE VALIDATE;
Of course if I run that script in SQL developer I get errors stating that those constraints already exists.
Can anyone tell me what i am doing wrong here?
This error means that either your Data Modeller or your SQL Dev are out of date, causing them to not cooperate. The constraints are there, but your DM isn't recognizing them. Update your system, and if that doesn't work, reinstall each manually. I've seen this happen once before, and it wasn't pretty. Hope this fixes your problem!

VALIDATE CONSTRAINT sometimes validates, sometimes doesn't

I have a table named base_types that contains this constraint:
ALTER TABLE public.base_types
ADD CONSTRAINT base_type_gas_type_fk FOREIGN KEY (gas_type)
REFERENCES public.gas_types (gas_type) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
And I have a table named alarm_history that contains five constraints, including this one:
ALTER TABLE public.alarm_history
ADD CONSTRAINT alarm_history_device_fk FOREIGN KEY (device)
REFERENCES public.bases (alarm_device) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
I am trying to convert a database from one that didn't bother with anything weird and useless like constraints into one that uses them. I am beginning with this script:
delete from gas_types;
select conversion.convert_base_types();
alter table base_types validate constraint base_type_gas_type_fk;
select conversion.convert_alarm_history();
alter table alarm_history validate constraint alarm_history_base_fk;
alter table alarm_history validate constraint alarm_history_charge_fk;
alter table alarm_history validate constraint alarm_history_cooler_fk;
alter table alarm_history validate constraint alarm_history_device_fk;
alter table alarm_history validate constraint alarm_history_furnace_fk;
I duly get an error message telling me that the gas_type field in my new base_types record doesn't match anything in the gas_types table, since the gas_types table is empty. But if I comment out the base_types commands, I get 18,000 nice, shiny new records in the alarm_history table, despite the fact that every single one of them violates at least one of that table's five foreign key constraints, since all of the tables those keys are referring to are empty. I need to ensure that my converted data is consistent, and therefore I need to validate my constraints, but that's obviously not happening. Why not?
Since the constraints above are created as DEFERRABLE INITIALLY DEFERRED, they are not checked until the DML statements (your delete statement) are committed or in your case you until you explicitly validate the constraint.
This is the normal and expected operation of an initially deferred deferrable constraint.
To change this functionality within your current transaction you can issue a SET CONSTRAINTS command to alter this:
SET CONSTRAINTS alarm_history_device_fk IMMEDIATE;
delete from gas_types;
Which should raise a foreign key violation alerting you earlier that you have data dependent on the records you are tying to delete.

Delete "on delete cascade" constraint

In a previous command, I foolishly wrote:
alter table UserInfo
add column gcal_id integer references GoogleCal on delete cascade
I've since realized that I don't want on delete cascade. How do I alter gcal-id in UserInfo to no longer have that constraint without losing the information saved in current entries?
Happily, it's fairly simple.
First \d+ UserInfo to see the constraint name, which will appear below the table's column definitions.
In your case it will probably be something like
Foreign-key constraints:
"userinfo_gcal_id_fkey" FOREIGN KEY (gcal_id) REFERENCES googlecal(id) ON DELETE CASCADE
Then, just drop and re-add the constraint in one command:
ALTER TABLE UserInfo
DROP CONSTRAINT userinfo_gcal_id_fkey,
ADD CONSTRAINT userinfo_gcal_id_fkey FOREIGN KEY (gcal_id) REFERENCES googlecal(id);
omitting the ON DELETE CASCADE part.

What happens when two equal foreign keys with conflicting on-deletes are defined on the same table in PostgreSQL?

In order to delete some rows referenced by a foreign key constraint without cascading on delete, I created a temporary foreign key constraint, deleted the row, and then deleted the temporary constraint:
ALTER TABLE rel_user_right
ADD CONSTRAINT temp_fk_rel_user_right_user_right_02
FOREIGN KEY (right_id) REFERENCES user_right (id)
ON DELETE CASCADE;
DELETE FROM user_right WHERE "name" LIKE '%.statusLight.%';
ALTER TABLE rel_user_right
DROP CONSTRAINT temp_fk_rel_user_right_user_right_02;
where this table already had the following constraint defined on it:
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right_02
FOREIGN KEY (right_id) REFERENCES user_right (id);
This worked fine for me, but seems to have failed on my colleague's computer. As you can see, the two FK constraints define conflicting ON DELETE behaviour. Is precedence defined in this situation, or is it non-deterministic?
Postgres allows to create two references differing only in ON DELETE clause.
I could find no information on the impact of such a case.
In my tests I was unable to cover the existing constraint with new one (i.e. DELETE was always restricted despite of the existence of the second cascading constraint).
However this behaviour is undocumented and one should not rely on it.
The normal way to proceed should be replacing the old constraint with new one:
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right_temp
FOREIGN KEY (right_id) REFERENCES user_right (id)
ON DELETE CASCADE,
DROP CONSTRAINT fk_rel_user_right_user_right;
DELETE FROM user_right WHERE "name" LIKE '%.statusLight.%';
ALTER TABLE rel_user_right
ADD CONSTRAINT fk_rel_user_right_user_right
FOREIGN KEY (right_id) REFERENCES user_right (id),
DROP CONSTRAINT fk_rel_user_right_user_right_temp;
DISABLE CONSTRAINT would be useful here, but there is no such feature in Postgres (there have been attempts to implement it, but they did not end in success). You can use DISABLE TRIGGER for it, but the above solution is simpler and more natural.