PostgreSQL delete fails with ON DELETE rule on inherited table - sql

In my PostgreSQL 9.1 database I've defined RULEs that delete rows from child tables whenever a parent table row is deleted. This all worked OK, until I introduced inheritance. If the parent (referencing) table INHERITS from another table and I delete from the base table then the DELETE succeeds, but the RULE doesn't appear to fire at all - the referenced row is not deleted. If I try to delete from the derived table I get an error:
update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
There is no other row in the parent table that would violate the foreign key: it's being referenced by the row that's being deleted! How do I fix this?
The following script reproduces the problem:
-- Schema
CREATE TABLE base
(
id serial NOT NULL,
name character varying(100),
CONSTRAINT pk_base PRIMARY KEY (id)
);
CREATE TABLE referenced
(
id serial NOT NULL,
value character varying(100),
CONSTRAINT pk_referenced PRIMARY KEY (id)
);
CREATE TABLE derived
(
referenced_id integer,
CONSTRAINT pk_derived PRIMARY KEY (id),
CONSTRAINT fk_derived_referenced FOREIGN KEY (referenced_id) REFERENCES referenced (id)
)
INHERITS (base);
-- The rule
CREATE OR REPLACE RULE rl_derived_delete_referenced
AS ON DELETE TO derived DO ALSO
DELETE FROM referenced r WHERE r.id = old.referenced_id;
-- Some test data
INSERT INTO referenced (id, value)
VALUES (1, 'referenced 1');
INSERT INTO derived (id, name, referenced_id)
VALUES (2, 'derived 2', 1);
-- Delete from base - deletes the "base" and "derived" rows, but not "referenced"
--DELETE FROM base
--WHERE id = 2;
-- Delete from derived - fails with:
-- update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
DELETE FROM derived
WHERE id = 2

As I said in my comment, this seems an unusual way to do things. But you can make it work with a deferred constraint.
CREATE TABLE derived
(
referenced_id integer,
CONSTRAINT pk_derived PRIMARY KEY (id),
CONSTRAINT fk_derived_referenced FOREIGN KEY (referenced_id)
REFERENCES referenced (id) DEFERRABLE INITIALLY DEFERRED
)
INHERITS (base);
The PostgreSQL docs, Rules vs. Triggers, say
Many things that can be done using triggers can also be implemented
using the PostgreSQL rule system. One of the things that cannot be
implemented by rules are some kinds of constraints, especially foreign
keys.
But it's not clear to me that this specific limitation is what you're running into.

Also, you need to check if other records are still referencing the to-be-deleted rows. I added a test derived record#3, which points to the same #1 reference record.
-- The rule
CREATE OR REPLACE RULE rl_derived_delete_referenced
AS ON DELETE TO tmp.derived DO ALSO (
DELETE FROM tmp.referenced re_del
WHERE re_del.id = OLD.referenced_id
AND NOT EXISTS ( SELECT * FROM tmp.derived other
WHERE other.referenced_id = re_del.id
AND other.id <> OLD.id )
;
);
-- Some test data
INSERT INTO tmp.referenced (id, value)
VALUES (1, 'referenced 1');
-- EXPLAIN ANALYZE
INSERT INTO tmp.derived (id, name, referenced_id)
VALUES (2, 'derived 2', 1);
INSERT INTO tmp.derived (id, name, referenced_id)
VALUES (3, 'derived 3', 1);
-- Delete from base - deletes the "base" and "derived" rows, but not "referenced"
--DELETE FROM base
--WHERE id = 2;
-- Delete from derived - fails with:
-- update or delete on table "referenced" violates foreign key constraint "fk_derived_referenced" on table "derived"
EXPLAIN ANALYZE
DELETE FROM tmp.derived
WHERE id = 2
;
SELECT * FROM tmp.base;
SELECT * FROM tmp.derived;
SELECT * FROM tmp.referenced;

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.

Create constraint for control insert in table

There are two tables - orders and a list of services. In the first there is a bool field that the order is approved, if it is true then you can’t insert / delete values in the second table. With the UPDATE of the first table and the DELETE of the second, it is clear.
INSERT make as
INSERT INTO b (a_id, b_value)
SELECT *
FROM (VALUES (1, 'AA1-BB1'),(1, 'AA1-BB2'),(1, 'AA1-BB3')) va
WHERE (SELECT NOT confirm FROM a WHERE a_id = 2);
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=7b0086967c1c38b0c80ca5624ebe92e9
How to forbid to insert without triggers and stored procedures? Is it possible to compose somehow complex constraint or a foreign key for checking conditions at the DBMS level?
The most recent version of Postgres supports generated columns. So, you can do:
alter table b add confirm boolean generated always as (false) stored;
Then create a unique key in a:
alter table a add constraint unq_a_confirm_id unique (confirm, id);
And finally the foreign key relationship:
alter table b add constraint fk_b_a_id_confirm
foreign key (confirm, a_id) references a(confirm, id);
Now, only confirmed = false ids can be used. Note that this will prevent updates to a that would invalidate the foreign key constraint.

How do I switch two ID [PK] on postgres database?

I want to change the ID on two rows on Postgres, to switch them. They are already defined as foreign key so I cannot use a third number to do the switch.
How can I do this in one SQL query or transaction?
Example:
UPDATE mytable SET id=2 WHERE ID=1;
UPDATE mytable SET id=1 WHERE ID=2
You mention foreign keys, but it remains unclear whether id is the referenced or the referencing column of a foreign key constraint.
If id is the referenced column you just define the fk constraint ON UPDATE CASCADE. Then you can change your id as much as you want. Changes are cascaded to the depending columns.
If id is the referencing column (and no other foreign key constraints point to it), then there is another, faster way since PostgreSQL 9.0. You can use a deferrable primary or unique key. Consider the following demo:
Note that you can't use this if you want to reference id with a foreign key constraint from another table. I quote the manual here:
The referenced columns must be the columns of a non-deferrable unique
or primary key constraint in the referenced table.
Testbed:
CREATE TEMP TABLE t
( id integer
,txt text
,CONSTRAINT t_pkey PRIMARY KEY (id) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO t VALUES
(1, 'one')
,(2, 'two');
Update:
UPDATE t
SET id = t_old.id
FROM t t_old
WHERE (t.id, t_old.id) IN ((1,2), (2,1));
Result:
SELECT * FROM t;
id | txt
---+-----
2 | one
1 | two
You can also declare the constraint DEFERRABLE INITIALLY IMMEDIATE and use SET CONSTRAINTS ... DEFERRED in the same transaction.
Be sure to read about the details in the manual:
CREATE TABLE
SET CONSTRAINTS
Even seems to work with DEFERRABLE INITIALLY IMMEDIATE and no SET CONSTRAINTS. I posted a question about that.
begin;
alter table mytable drop constraint_name;
UPDATE mytable SET id=-1 WHERE ID=1;
UPDATE mytable SET id=1 WHERE ID=2;
UPDATE mytable SET id=2 WHERE ID=-1;
alter table mytable add table_constraint;
commit;
Have you tried something like:
BEGIN;
CREATE TEMP TABLE updates ON COMMIT DROP AS
SELECT column1::int oldid, column2::int newid
FROM ( VALUES (1, 2), (2, 1) ) foo;
UPDATE mytable
FROM updates
SET id = newid
WHERE id = oldid;
--COMMIT;
ROLLBACK;
Of course rollback gets commented out and commit in when you are ready to go.

Using references in MYSQL

I got an answer to another question here:
DB Schema For Chats?
It's a great answer, but I am not understanding the bit about references. I can do SQL statements but I have never used references.
What are they used for?
How are they used?
Give an example please
The REFERENCES keyword is part of a foreign key constraint and it causes MySQL to require that the value(s) in the specified column(s) of the referencing table are also present in the specified column(s) of the referenced table.
This prevents foreign keys from referencing ids that do not exist or were deleted, and it can optionally prevent you from deleting rows whilst they are still referenced.
A specific example is if every employee must belong to a department then you can add a foreign key constraint from employee.departmentid referencing department.id.
Run the following code to create two test tables tablea and tableb where the column a_id in tableb references the primary key of tablea. tablea is populated with a few rows.
CREATE TABLE tablea (
id INT PRIMARY KEY,
foo VARCHAR(100) NOT NULL
) Engine = InnoDB;
INSERT INTO tablea (id, foo) VALUES
(1, 'foo1'),
(2, 'foo2'),
(3, 'foo3');
CREATE TABLE tableb (
id INT PRIMARY KEY,
a_id INT NOT NULL,
bar VARCHAR(100) NOT NULL,
FOREIGN KEY fk_b_a_id (a_id) REFERENCES tablea (id)
) Engine = InnoDB;
Now try these commands:
INSERT INTO tableb (id, a_id, bar) VALUES (1, 2, 'bar1');
-- succeeds because there is a row in tablea with id 2
INSERT INTO tableb (id, a_id, bar) VALUES (2, 4, 'bar2');
-- fails because there is not a row in tablea with id 4
DELETE FROM tablea WHERE id = 1;
-- succeeds because there is no row in tableb which references this row
DELETE FROM tablea WHERE id = 2;
-- fails because there is a row in tableb which references this row
Important note: Both tables must be InnoDB tables or the constraint is ignored.
The REFERENCES keyword shows a foreign key constraint, which means that:
FOREIGN KEY (`chat_id` ) REFERENCES `chats`.`chat` (`id` )
...the chat_id column in the current table can only contain values that already exist in the chat table, id column.
For example, if the CHAT.id column contains:
id
----
a
b
c
..you can not add any values other than a/b/c into the chat_id column.

Stop invalid data in a attribute with foreign key constraint using triggers?

How to specify a trigger which checks if the data inserted into a tables foreign key attribute, actually exists in the references table. If it exist no action should be performed , else the trigger should delete the inserted tuple.
Eg: Consider have 2 tables
R(A int Primary Key) and
S(B int Primary Key , A int Foreign Key References R(A) ) .
I have written a trigger like this :
Create Trigger DelS
BEFORE INSERT ON S
FOR EACH ROW
BEGIN
Delete FROM S where New.A <> ( Select * from R;) );
End;
I am sure I am making a mistake while specifying the inner sub query within the Begin and end Blocks of the trigger. My question is how do I make such a trigger ?
Wouldn't a foreign key constraint better achieve what you want?
ALTER TABLE [dbo].[TABLE2] WITH CHECK
ADD CONSTRAINT [FK_TABLE2_TABLE1] FOREIGN KEY([FK_COLUMN])
REFERENCES [dbo].[TABLE1] ([PK_COLUMN])
GO
This is what foreign key constraints are meant to do - specifically, not allow a record to be inserted that violate the foreign key relationship.
Note that to make this example more readable, I used different column and table names - S, A, R and B looked like a mess.