Very slow SQL DELETE query on table with foreign key constraint - sql

I have got some trouble with a SQL DELETE query.
I work on a database (postgres 9.3) with 2 tables (Parent and Child).
The child has a relation to the parent with a foreign key.
Parent Table
CREATE TABLE parent
(
id bigint NOT NULL,
...
CONSTRAINT parent_pkey PRIMARY KEY (id)
)
Child Table
CREATE TABLE child
(
id bigint NOT NULL,
parent_id bigint,
...
CONSTRAINT child_pkey PRIMARY KEY (id),
CONSTRAINT fk_adc9xan172ilseglcmi1hi0co FOREIGN KEY (parent_id)
REFERENCES parent (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I inserted in both tables 200'000 entries without any relation ( Child.parent_id = NULL).
But a DELETE query like below has a duration of more than 20 minutes.
And that even without a WHERE conditions.
DELETE FROM Parent;
If I don't add the relation constraints the execution time will be done in 400 ms.
What did I miss?
A workable solution is the example below. But I don't know if this is a good idea. Maybe anyone could tell me a better way to do that.
BEGIN WORK;
ALTER TABLE Parent DISABLE TRIGGER ALL;
DELETE FROM Parent;
ALTER TABLE Parent ENABLE TRIGGER ALL;
COMMIT WORK;

When you delete from Parent, the Child table needs to be queried by parent_id to ensure that no child row refers to the parent row you are about to delete.
To ensure that the child lookup runs quickly, you need to have an index on your parent_id column in the Child table.

Related

Performance of ON DELETE CASCADE in PostgresSQL

I have an issue related to performance of ON DELETE CASCADE. I'm trying to understand why it takes so long. For this topic purposes I simplified real case to schema presented below:
CREATE TABLE IF NOT EXISTS public.items
(
id uuid NOT NULL,
name text COLLATE pg_catalog."default",
CONSTRAINT items_pk PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public.links
(
parent uuid,
child uuid,
CONSTRAINT links_parent_fk FOREIGN KEY (parent)
REFERENCES public.items (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT links_child_fk FOREIGN KEY (child)
REFERENCES public.items (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS parent_idx
ON public.links USING btree
(parent ASC NULLS LAST);
CREATE INDEX IF NOT EXISTS child_idx
ON public.links USING btree
(child ASC NULLS LAST);
CREATE EXTENSION "uuid-ossp";
and data can be generated with:
INSERT INTO public.items
SELECT uuid_generate_v4 (), 'item_' || i
FROM generate_series(1, 134001) AS i;
INSERT INTO links
SELECT (SELECT id FROM public.items WHERE name='item_1'), id FROM public.items;
Briefly, data base contains two tables. Table items contains a list of items (identifier and name column) and table links which defines relations between items (parent <-> child). In presented case all items (children) belongs to item named 'item_1' (parent).
I call a query in order to delete all children assigned to parent:
BEGIN;
EXPLAIN ANALYZE DELETE FROM public.items where id in (SELECT child FROM public.links WHERE parent = (SELECT id FROM public.items WHERE name='item_1'));
ROLLBACK;
From execution plan we can read among others:
"Trigger for constraint links_parent_fk: time=10451.471 calls=134001"
"Trigger for constraint links_child_fk: time=2962.035 calls=134001"
The question is why trigger for constraint links_parent_fk consumes a lot time?
I performed some attempts with exchanging data between columns in links table. After that trigger for links_child_fk consumed ~10 s and trigger for links_parent_fk took ~3 s. I'm curious why there is such difference between execution of this delete cascades?
PostgreSQL version: 12.4 and 13.9.

Cascade Delete Children not working as expected

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.
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
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).
create a column in corporations and add a foreign key constraint on it that references a created corporation_relationships pk.
Remove a child_id column from corporation_relationships, it's incorrect and useless at this point.
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.
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();

SQL - How do I delete two entities referencing each other

So I have two entities referencing each other, parent, child.
child must be deleted if parent is deleted, but cannot be deleted while there's still a parent referencing it.
These are the two constraints I've been given:
ALTER TABLE public.parent
ADD CONSTRAINT parent__child_id__fk
FOREIGN KEY (child_id) REFERENCES child(id)
ON DELETE CASCADE
;
ALTER TABLE public.child
ADD CONSTRAINT child__parent_code__id__fk
FOREIGN KEY (parent_code, id) REFERENCES parent(code, child_id)
ON UPDATE CASCADE
ON DELETE RESTRICT
DEFERRABLE INITIALLY DEFERRED
;
I now want to delete a parent (and the corresponding child) ...
SQL Error [23503]:
ERROR: update or delete on table "parent" violates foreign key constraint
"child__parent_code__id__fk" on table "child"
Detail: Key (code, child_id)=(A0B7EBF6-3_DELETE_ME, 10)
is still referenced from table "child".
Whoop-dee-doo ...
Yes, it's referenced by the bloody entry I'm trying to delete...
(which I know because there's a unique constraint on parent.code)
Looks like I CAN delete the entry if I set the child's fk to ON DELETE CASCADE, but that doesn't seem to be what the guy breathing down my neck wants, which is "if you delete a parent delete its child, too, if you delete a child that has a parent, DON'T".
How do I achieve this?
Delete from both tables in one statement using a CTE:
WITH x AS (
DELETE FROM parent
WHERE ...
RETURNING id
)
DELETE FROM child
WHERE ...;

How to delete records from parent table which is referenced by multiple child tables?

I have a table which is referenced by multiple tables (around 52) and further,few of the child tables have multiple foreign keys also that is referencing other tables too.
I want to delete a record from parent table, I am unable to do so, as I am getting error "The DELETE statement conflicted with the REFERENCE constraint "FK_xxx". The conflict occurred in database "MyDB", table "dbo.A", column 'x'."
I want a generalized T-SQL solution which is irrespective of tables and number of references.
You have to look at the "on delete" keyword which is a part of the foreign key constraint definition.
Basically you have 4 options:
NO ACTION (does nothing)
CASCADE (deletes the child aswell)
SET NULL (sets the reference field to null)
SET DEFAULT (sets the reference field to the default value)
An example would be:
CREATE TABLE parent (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE -- replace CASCADE with your choice
) ENGINE=INNODB;
(for this example and more details look here: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html )
If you now want to modify your constraint, you first have to drop it, and create a new one like for example:
ALTER TABLE child
ADD CONSTRAINT fk_name
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE; -- replace CASCADE with your choice
I hope this helped. Also to mention it, you should think about maybe not really deleting your parent, and instead creating another boolean column "deleted", which you fill with "yes" if someone clicks the delete. In the "Select"-query you filter then by that "deleted" column.
The advantage is, that you do not lose the history of this entry.
Your problem is this: A FK constraint is designed to prevent you from creating an orphaned child record in any of the 52 tables. I can provide you with the script you seek, but you must realise first that when you try to re-enable the FK constraints the constraints will fail to re-enable because of the orphaned data (which the FK constraints are designed to prevent). For your next step, will have to delete the orphaned data in each of the 52 tables first anyway. It is actually much easier just to redo the constraints with ON DELETE CASCADE, or drop the constraints and forget about referential integrity altogether. You can't have it both ways.

I created a foreign key constraint with update cascade but it does not update child table

Parent table
SELECT * FROM dbo.TBEMPLOYEE
2|Sohail Ashraf|1980-01-12 00:00:00.000|2013-12-05 15:40:48.303|Sen.Software Engineer|
Child table
SELECT * FROM dbo.TBADDRESS
2|Sohail Ashraf|sohail.azfal#netsoltech.com|923334447777gulberg|Lahore|Pakistan
Where 2 is the emp_id (PK) in parent table and FK in child table
When I update parent table like this
UPDATE TBEMPLOYEE
set emp_nme = 'Mohammad Sohail Ashraf'
WHERE emp_id = 2
Here is the result
2|Mohammad Sohail Ashraf|1980-01-12 00:00:00.000|2013-12-05 15:40:48.303|Sen.Software Engineer
but is does not update child table record.
Child table same as it above after update of parent table. Child table result like this
2|Sohail Ashraf|sohail.azfal#netsoltech.com|923334447777|gulberg|Lahore|Pakistan
Can anyone help me ?
You misunderstand what that cascade means. It only applies to the foreign key column(s), not the whole record. If you change the value in the PK field in the parent record then the new value will cascade to the FK field in the child record(s). No other fields are affected.
That begs the question, why do you have data duplicated in the first place? If you have the name stored in the parent record then why do you have it in the child record as well? It should only be in one or the other. If you need data from both tables then you perform a join.
A cascade update updates the foreign key in the child table when the primary key in the parent table is updated.