0

I have two tables one of which is for the polymorphic relationship of different corporations and I've added foreign key references to ids to ensure that if I delete a parent all children will be deleted. With this table setup below if I delete a parent corporation the child corporation persists which is not what I expected. If I delete a corporation_relationship via the parent_id the parent and its children cascade delete and if I a delete the relationship via the child_id the parent and siblings are unaffected. My questions are what am I doing wrong and how can I ensure that by deleting a parent the children are also deleted without adding any new columns?

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TYPE "corporation_relationship_type" AS ENUM (
  'campus',
  'network'
);

CREATE TABLE "corporations" (
  "id" uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
  "name" varchar(255) NOT NULL
);

CREATE TABLE "corporation_relationships" (
  "parent_id" uuid NOT NULL,
  "child_id" uuid NOT NULL,
  "type" corporation_relationship_type NOT NULL,
  PRIMARY KEY ("parent_id", "child_id")
);

ALTER TABLE "corporation_relationships" ADD FOREIGN KEY ("parent_id") REFERENCES "corporations" ("id") ON DELETE CASCADE;

ALTER TABLE "corporation_relationships" ADD FOREIGN KEY ("child_id") REFERENCES "corporations" ("id") ON DELETE CASCADE;

Example queries:

If I add 2 corporations and then add a relationship to the two like so:

insert into corporations (id, name) values ('f9f8f7f6-f5f4f3f2-f1f0f0f0-f0f0f0f0', 'Father');


insert into corporations (id, name) values ('f9f8f7f6-f5f4f3f2-f1f0f0f0-f0f0f0f1', 'Son');

insert into corporation_relationships (parent_id, child_id) values ('f9f8f7f6-f5f4f3f2-f1f0f0f0-f0f0f0f0', 'f9f8f7f6-f5f4f3f2-f1f0f0f0-f0f0f0f1');

My output for select * from corporations; will be:

                  id                  |        name
--------------------------------------+--------------------
 f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f0 | Father
 f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f1 | Son
(2 rows)

My output for select * from corporation_relationships; is:

              parent_id               |               child_id               |  type
--------------------------------------+--------------------------------------+--------
 f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f0 | f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f1 | campus

Now if I delete the 'father' by executing delete FROM corporations WHERE id = 'f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f0'; I would expect my output of select * from corporations; to be nothing but instead it is the following:

                  id                  |        name
--------------------------------------+--------------------
 f9f8f7f6-f5f4-f3f2-f1f0-f0f0f0f0f0f1 | Son
(1 row)

Also, it is noteworthy that the corporation_relationships table is empty after this delete as well but I would want the cascade to keep going past that table and delete the child entity as well.

Josh Wren
  • 338
  • 2
  • 12
  • 1
    The only way this is going to work is by deleting the appropriate `corporations.id`. The `ON DELETE CASCADE` goes from `corporations` to `corporation_relationships`. Since a parent can have many children I would expect that deleting one child would not delete the parent. – Adrian Klaver May 18 '22 at 20:09
  • @AdrianKlaver, maybe I didn't explain myself well. What you're describing as the "only way this is going to work" is what I'm trying to do and what is not working as expected. When I delete a coproration that has children they are not being deleted as well. – Josh Wren May 18 '22 at 21:12
  • That is not what you said: "If I delete a corporation_relationship via the parent_id the parent and its children cascade delete". You will need to provide some example data as well as the actual queries you are using in order to get a complete answer. – Adrian Klaver May 18 '22 at 22:41
  • @AdrianKlaver, I've added some sample queries hopefully to better explain my issue. – Josh Wren May 18 '22 at 23:51
  • `ON DELETE CASCADE` only goes one way from parent table to child table. You want it to reverse direction and delete a parent record when the child record is deleted and that will not happen. I would be dangerous if it did, as deleting the parent would create a cascade on all those records that referred to it. I could also see this creating a recursion situation. Your best bet is to set up a parent table with the 'father' records and a child table with the `son` records. Then a delete of a father would delete all the sons. – Adrian Klaver May 19 '22 at 15:39

2 Answers2

0

Your second foreign key constraint in the corporation_relationships table, that references to the corporations table has nothing with with your expectations of cascade deletions of children rows in corporations. To clearify, this foreign key do cascade deletions when you delete a referenced row in the corporations table. But you need the opposite.

To make it work as you expect in your design, you should have a column in corporations that references a primary key in corporation_relationships.

So you need to

  1. create a primary key column, e.g. id, in corporation_relationships (not those you already have, it's not a pk, it's a unique constraint).
  2. create a column in corporations and add a foreign key constraint on it that references a created corporation_relationships pk.
  3. Remove a child_id column from corporation_relationships, it's incorrect and useless at this point.
  4. When you create a relation you should set it's id to the fk column of corresponding child row in corporations. Now, if you delete a parent corporation, it would delete all relationships, those will delete corresponding children of corporation and so on recursively.

Meanwhile, in my opinion, your design is not correct.

To define a tree-like relations you do not need the transit table, i.e corporation_relationships. You can define it in a single corporations table. For that you need just a one column parent_id, those would be a foreign key with cascade delete rule, that references a pk in this table. Top-parent corporations would have a null in parent_id, all children - parent's id value.

Also, type column in corporation_relationships is not an attribute of relation itself, it's an attribute of child.

Alexey
  • 2,439
  • 1
  • 11
  • 15
  • I like your idea of adding an id column to the `corporation_relationships` table as a primary key and adding a reference to it in the `organizations` table but I disagree that `child_id` is not needed or useless or incorrect. Also type is an attribute of the relationship itself not the child and I am not able to add a column to the corporations table so that kind of simplistic approach will not benefit me here. – Josh Wren May 18 '22 at 21:15
  • @Josh Wren, what is it's purpose in this case? – Alexey May 18 '22 at 21:17
  • Delete a relation , if child is deleted? But it should be done in opposite way. Relation should be deleted and it should delete a child. – Alexey May 18 '22 at 21:25
  • to find a parent given a child_id for one – Josh Wren May 18 '22 at 21:29
  • You have all you need to find a parent of a child without storing redundant data. Your child references a relation, those references a parent – Alexey May 18 '22 at 21:33
  • I feel like you're zeroing in on irrelevant information but I see what you're getting at. – Josh Wren May 18 '22 at 22:06
0

Postgres doesn't mantain referential integrity with optional polymorphic relationships so I created a trigger to do this for me:



CREATE FUNCTION cascade_delete_children() RETURNS trigger AS $$
    BEGIN
        -- Check if the corporation is a parent
        IF OLD.id IN (SELECT parent_id FROM corporation_relationships) THEN
            -- Delete all of the corporation's children
            DELETE FROM corporations WHERE id IN (SELECT child_id FROM corporation_relationships WHERE parent_id = OLD.id);
        END IF;
        RETURN OLD;
    END;
$$ LANGUAGE plpgsql;

CREATE trigger cascade_delete_children BEFORE DELETE ON corporations
    FOR EACH ROW EXECUTE PROCEDURE cascade_delete_children();
Josh Wren
  • 338
  • 2
  • 12
  • Meanwhile you can execute`delete` query without checking of existence of `id` value in `parent_id` column. You just do not need one more query to execute – Alexey May 19 '22 at 06:08