We are using Oracle database in our projects. And we define as much as constraints that can be applied to database, (including primary, unique, check and foreign key constraints).
It seems that defining constraints DEFERRABLE allows us to DEFER them when it is required, so why shall any constraint be defined as NOT DEFERRABLE?
Why databases such as Oracle have NOT DEFERRABLE as default case?
Are there any pros for defining a constraint NOT DEFERRABLE?
The major use case for deferrable constraints is that you don't need to worry about the order in which you do DML statements for multiple tables that have a foreign key relationship.
Consider the following example:
create table parent
(
id integer not null primary key
);
create table child
(
id integer not null primary key,
parent_id integer not null references parent
);
create table grand_child
(
id integer not null primary key,
child_id integer not null references child
);
If the constraints are immediate you have to insert (or delete) rows (that reference each other) in the proper sequence, which can e.g. be a problem when bulk loading data. If the constraints are deferred you can insert/delete the rows in any sequence as long as everything is fine when you commit your transaction.
So with a deferrable constraint (which the above example does not create!) you could the following:
insert into grand_child values (1,1);
insert into child values (1,1);
insert into parent values (1);
commit;
That would not be possible if the constraints were immediate.
A special case of the above example are cyclic references:
create table one
(
id integer not null primary key,
id_two integer not null
);
create table two
(
id integer not null primary key
id_one integer not null
);
alter table one add constraint fk_one_two (id_two) references two(id);
alter table two add constraint fk_two_one (id_one) references one(id);
Without declaring the constraints as deferrable you will not be able to insert data into those tables at all.
The workaround for DBMS that do not support deferrable constraints would be to make the fk columns nullable. And then insert null values first:
insert into one values (1, null);
insert into two values (1, 1);
update one
set id_two = 1
where id = 1;
With a deferrable constraint you don't need the additional update statement.
(The design using a cyclic reference is however very often questionable!)
I don't use deferrable constraints where often, but I wouldn't want to live without them.
One drawback of deferrable constraints is error checking though. You don't know until you commit if your data is correct. That makes finding out what went wrong a bit more complicated. If you get the error when doing the insert (or delete or update) you immediately know which values caused the error.
Related
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.
create table parent (
child_type not null
child_id not null
);
create table child1(id not null);
create table child2(id not null);
create table child3(id not null);
And there's some rows in table parent like this:
child_type,child_id
"child1",1
"child1",2
"child2",1
"child3",1
I want to delete child row when I delete parent row.
Is there any way to make this trigger on delete cascade?
I presume that (child_type,child_id) is the primary key of parent (and this advice will only work if it is thus: if you want deleting of a parent row to trigger a delete in a child via a FK cascade, the parent must have a primary key)
You create associations like this:
create table child1(
child_type VARCHAR(20) DEFAULT 'child1',
id INT not null
FOREIGN KEY (child_type,id) REFERENCES parent(child_type, child_id) ON DELETE CASCADE
);
create table child2(
child_type VARCHAR(20) DEFAULT 'child2',
id INT not null
FOREIGN KEY (child_type,id) REFERENCES parent(child_type, child_id) ON DELETE CASCADE
);
create table child3(
child_type VARCHAR(20) DEFAULT 'child3',
id INT not null
FOREIGN KEY (child_type,id) REFERENCES parent(child_type, child_id) ON DELETE CASCADE
);
You can't have just id in the child references part of the composite PK in the parent; child has to have the same N columns with the same values as the parent PK has
FWIW that table structure is really wonky, and it will probably come around to bite you time and again.
Prefer something more normal, like:
create table parent (
id PRIMARY KEY
);
create table child1(id PRIMARY KEY, parent_id REFERENCES parent(id));
create table child2(id PRIMARY KEY, parent_id REFERENCES parent(id));
create table child3(id PRIMARY KEY, parent_id REFERENCES parent(id));
I hope this is a contrived situation for you actual problem, as it really is a terrible design. Assuming you actually "want to delete child row when I delete parent row". Unless you alter your data model and define FK constraints you require a delete trigger on table parent. You CANNOT cascade deletes without FK as that is where you define to Postgres to do so. BTW, your table definitions are invalid. Not Null is a constraint not a data type, you have not established a data type. After correcting that you can build a trigger which deletes the corresponding rows from the appropriate child table if your child_type column is understood to actually name the table in which the child resides. A very poor design leading to a extremely risky assumption, but:
-- setup
create table parent (
child_type text not null
,child_id integer not null
);
create table child1(id integer not null);
create table child2(id integer not null);
create table child3(id integer not null)
insert into parent(child_type, child_id)
values ('child1',1),('child1',2),('child2',1),('child3',1);
insert into child1(id) values (1),(2);
insert into child2(id) values (1);
insert into child3(id) values (1);
Now create the trigger function then 'attach' to parent table'
The trigger function now builds and dynamically executes the appropriate delete statement. Note I always generate a raise notice to display the actual statement before executing it, and do so here. You may consider it not necessary.
-- build trigger function.
create or replace function parent_adr()
returns trigger
language plpgsql
as $$
declare
base_del_lk constant text = 'delete from %s where id = %s';
sql_delete_stmt_l text;
begin
sql_delete_stmt_l = format(base_del_lk,old.child_type, old.child_id);
raise notice 'Running statement==%', sql_delete_stmt_l;
EXECUTE sql_delete_stmt_l;
return old;
end;
$$;
-- and define the trigger on the parent table.
create trigger parent_adr_trig
after delete
on parent
for each row
execute procedure parent_adr();
--- test.
delete from parent where child_type = 'child1';
Consider a situation where I define an object, a group of objects, then a table that links them together:
CREATE TABLE obj (
id INTEGER PRIMARY KEY,
name text
) ;
CREATE TABLE group (
id INTEGER PRIMARY KEY ;
grpname TEXT
) ;
CREATE TABLE relation (
objid INTEGER,
grpid INTEGER,
PRIMARY KEY (objid, grpid)
) ;
I am looking for cascade delete when applicable so I add the foreign key
ALTER TABLE relation
ADD FOREIGN KEY (objid)
REFERENCES obj(id)
ON DELETE CASCADE ;
ALTER TABLE relation
ADD FOREIGN KEY (grpid)
REFERENCES group(id)
ON DELETE CASCADE ;
So far is all OK. Now suppose I want to add support for group of groups. I am thinking to change the relation table like this:
CREATE TABLE relation_ver1 (
parent INTEGER,
child INTEGER,
PRIMARY KEY (parent, child)
) ;
ALTER TABLE relation_ver1
ADD FOREIGN KEY (parent)
REFERENCES group(id)
ON DELETE CASCADE ;
Here I get to the question: I would like to apply cascade delete to child too, but I do not know here if child refers to a group or object.
Can I add a foreign key to table obj or group?
The only solution I have found do fare is add child_obj and child_grp fields, add the relative foreign keys and then, when inserting e.g an object use a 'special' (sort of null) group, and do the reverse when inserting subgroup.
Consider the relation:
relation_ver1(parent, child_obj, child_group)
I claim that this relation has the following disadvantages:
You have to deal with the NULL special case.
Approx. 1/3 of values are NULL. NULL values are bad.
Fortunately, there is an easy way to fix this. Since there is a multi-value dependency in your data, you can decompose your table into 2 smaller tables that are 4NF compliant. For example:
relation_ver_obj(parent, child_obj) and
relation_ver_grp(parent, child_group).
The primary reason why we have foreign keys is not so as to be able to do things like cascaded deletes. The primary reason for the existence of foreign keys is referential integrity.
This means that grpid is declared as REFERENCES group(id) in order to ensure that grpid will never be allowed to take any value which is not found in group(id). So, it is an issue of validity. A cascaded DELETE also boils down to validity: if a key is deleted, then any and all foreign keys referring to that key would be left invalid, so clearly, something must be done about them. Cascaded deletion is one possible solution. Setting the foreign key to NULL, thus voiding the relationship, is another possible solution.
Your notion of having a child id refer to either a group or an object violates any notion of referential integrity. Relational Database theory has no use and no provision for polymorphism. A key must refer to one and only one kind of entity. If not, then you start running into problems like the one you have just discovered, but even worse, you cannot have any referential integrity guarantees in your database. That's not a nice situation to be in.
The way to handle the need of relationships to different kinds of entities is with the use of a set of foreign keys, one for each possible related entity, out of which only one may be non-NULL. So, here is how it would look like:
CREATE TABLE tree_relation (
parent_id INTEGER,
child_object_id INTEGER,
child_group_id INTEGER,
PRIMARY KEY (parent_id, child_object_id, child_group_id) );
ALTER TABLE tree_relation
ADD FOREIGN KEY (parent_id) REFERENCES group(id) ON DELETE CASCADE;
ALTER TABLE tree_relation
ADD FOREIGN KEY (child_object_id) REFERENCES object(id) ON DELETE CASCADE;
ALTER TABLE tree_relation
ADD FOREIGN KEY (child_group_id) REFERENCES group(id) ON DELETE CASCADE;
All you need to do is ensure that only one of child_object_id, child_group_id is non-NULL.
I am trying to make inserts on a table with a Primary key composed of 2 attributes. However when I try to make an insert and one of the attributes in the primary key is identical to one already inserted I get the following error:
Unique index or primary key violation: "CONSTRAINT_INDEX_CCC ON PUBLIC.ABWESENHEIT(DATUM) VALUES
Here is my table:
Create TABLE Abwesenheit (
s_id INTEGER NOT NULL REFERENCES Schueler(id) ON DELETE CASCADE,
entschuldigt BOOLEAN DEFAULT FALSE,
datum TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
kommentar VARCHAR(40),
kalendereintrag_id VARCHAR(40) REFERENCES Schoolsubject(name) ON DELETE CASCADE,
deleted BOOLEAN DEFAULT FALSE,
PRIMARY KEY (s_id,datum)
);
And here are 2 inserts which reproduce the problem:
insert into Abwesenheit (s_id, entschuldigt, kommentar,datum,kalendereintrag_id) values (1,'false','','2015-12-21 11:59:00.0','Geschichte');
insert into Abwesenheit (s_id, entschuldigt, kommentar,datum,kalendereintrag_id) values (2,'false','','2015-12-21 11:59:00.0','Geschichte');
Even though the "datum" attribute defaults to CURRENT_TIMESTAMP, I need to be able to insert custom timestamps (i.e. for retroactive entries)
.
Given the fact that the "s_id" is different in the two inserts they should work. However they do not. Any ideas on what might be causing the problem?
Thanks in advance!
The problem was caused by another table which was referencing my table, under the assumption that the dates in the table Abwesenheit were unique.
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;