Foreign Key referencing inherited table - postgresql

I have the following tables:
CREATE TABLE mail (
id serial,
parent_mail_id integer,
...
PRIMARY KEY (id),
FOREIGN KEY (parent_mail_id) REFERENCES mail(id),
...
);
CREATE TABLE incoming (
from_contact_id integer NOT NULL REFERENCES contact(id),
...
PRIMARY KEY (id),
---> FOREIGN KEY (parent_mail_id) REFERENCES mail(id), <---
...
) INHERITS(mail);
CREATE TABLE outgoing (
from_user_id integer NOT NULL REFERENCES "user"(id),
...
PRIMARY KEY (id),
--> FOREIGN KEY (parent_mail_id) REFERENCES mail(id), <--
...
) INHERITS(mail);
incoming and outgoing inherit from mail and define their foreign keys (and primary keys) again, as they are not automatically inherited.
The problem is:
If I'd insert an incoming mail, it is not possible to reference it from the outgoing table as the foreign key only works with the super table (mails).
Is there a workaround for that?

PostgreSQL 9.3 docs:
A serious limitation of the inheritance feature is that indexes
(including unique constraints) and foreign key constraints only apply
to single tables, not to their inheritance children. This is true on
both the referencing and referenced sides of a foreign key constraint.
Thus, in the terms of the above example:
If we declared cities.name to be UNIQUE or a PRIMARY KEY, this would not stop the capitals table from having rows with names
duplicating rows in cities. And those duplicate rows would by default
show up in queries from cities. In fact, by default capitals would
have no unique constraint at all, and so could contain multiple rows
with the same name. You could add a unique constraint to capitals, but
this would not prevent duplication compared to cities.
Similarly, if we were to specify that cities.name REFERENCES some other table, this constraint would not automatically propagate to
capitals. In this case you could work around it by manually adding the
same REFERENCES constraint to capitals.
Specifying that another table's column REFERENCES cities(name) would allow the other table to contain city names, but not capital
names. There is no good workaround for this case.
These deficiencies will probably be fixed in some future release, but
in the meantime considerable care is needed in deciding whether
inheritance is useful for your application.
And not really a workaround, so maybe make mails a non-inherited table, and then separate incoming_columns and outgoing_columns for their respective extra columns, with the mail id as both their primary and foreign key. You can then create a view outgoing as mail INNER JOIN outgoing_columns, for example.

You may use a constraint trigger
CREATE OR REPLACE FUNCTION mail_ref_trigger()
RETURNS trigger AS
$BODY$
DECLARE
BEGIN
IF NOT EXISTS (
SELECT 1 FROM mail WHERE id = NEW.parent_mail_id
) THEN
RAISE foreign_key_violation USING MESSAGE = FORMAT('Referenced mail id not found, mail_id:%s', NEW.parent_mail_id);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE CONSTRAINT TRIGGER mail_fkey_trigger
AFTER UPDATE OR INSERT ON incoming
DEFERRABLE
FOR EACH ROW EXECUTE PROCEDURE mail_ref_trigger();

Related

How to edit a record that results in a uniqueness violation and echo the change to child tables?

PostgreSQL 11.1
How can "Editing" of a record be transmitted to dependent records?
Summary of my issue:
The master table, disease, needs a unique constraint on the description column. This unique constraint is needed for foreign key ON UPDATE CASCADE to its children tables.
To allow for a temporary violation of the unique constraint, it must be made deferrable. BUT A DEFERABLE CONSTRAINT CAN NOT BE USED IN A FOREIGN KEY.
Here is the situation.
The database has 100+ tables (and keeps on growing).
Most all information has been normalized in that repeating groups of information have been delegated to their own table.
Following normalization, most tables are lists without duplication of records. Duplication of records within a table is not allowed.
All tables have a unique ID assigned to each record (in addition to a unique constraint placed on the record information).
Most tables are dependent on another table. The foreign keys reference the primary key of the table they are dependent on.
Most unique constraints involve a foreign key (which in turn references the primary key of the parent table).
So, assume the following schema:
CREATE TABLE phoenix.disease
(
recid integer NOT NULL DEFAULT nextval('disease_recid_seq'::regclass),
code text COLLATE pg_catalog."default",
description text COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT disease_pkey PRIMARY KEY (recid),
CONSTRAINT disease_code_unique UNIQUE (code)
DEFERRABLE,
CONSTRAINT disease_description_unique UNIQUE (description)
,
CONSTRAINT disease_description_check CHECK (description <> ''::text)
)
CREATE TABLE phoenix.dx
(
recid integer NOT NULL DEFAULT nextval('dx_recid_seq'::regclass),
disease_recid integer NOT NULL,
patient_recid integer NOT NULL,
CONSTRAINT pk_dx_recid PRIMARY KEY (recid),
CONSTRAINT dx_unique UNIQUE (tposted, patient_recid, disease_recid)
,
CONSTRAINT dx_disease_recid_fkey FOREIGN KEY (disease_recid)
REFERENCES phoenix.disease (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT dx_patients FOREIGN KEY (patient_recid)
REFERENCES phoenix.patients (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT
)
(Columns not involved in this question have been removed. :) )
There are many other children tables of disease with the same basic dependency on the disease table. Note that the primary key of the disease table is a foreign key to the dx table and that the dx table uses this foreign key in a unique constraint. Also note that the dx table is just one table of a long chain of table references. (That is the dx table also has its primary key referenced by other tables).
The problem: I wish to "edit" the contents of the parent disease record. By "edit", I mean:
change the data in the description column.
if the result of the change causes a duplication in the disease table, then one of the "duplicated" records will need to be deleted.
Herein lies my problem. There are many different tables that use the primary key of the disease table in their own unique constraint. If those tables ALSO have a foreign key reference to the duplicated record (in disease), then cascading the delete to those tables would be appropriate -- i.e., no duplication of records will occur.
However, if the child table does NOT have a reference to the "correct" record in the parent disease table, then simply deleting the record (by cascade) will result in loss of information.
Example:
Disease Table:
record 1: ID = 1 description = "ABC"
record 2: ID = 2 description = "DEF"
Dx Table:
record 5: ID = 5 refers to ID=1 of Disease Table.
Editing of record 1 in Disease table results in description becoming "DEF"
Disease Table:
record 1: ID = 1 "ABC" --> "DEF"
I have tried deferring the primary key of the disease table so as to allow the "correct" ID to be "cascaded" to the child tables. This causes the following errors:
A foreign key can not be dependent on a deferred column. "cannot use a deferrable unique constraint for referenced table "disease"
additionally, the parent table (disease) has no way of knowing ahead of time if its children already have a reference to the "correct" record so allowing deletion, or if the child needs to change its own column data to reflect the new "correct" id.
So, how can I allow a change in the parent table (disease) and notify the child tables to change their column values -- and delete within them selves should a duplicate record arise?
Lastly, I do not know today what future tables I will need. So I cannot "precode" into the parent table who its children are or will be.
Thank you for any help with this.

Is this a good idea to store relations to many different tables in one field? [duplicate]

I have a database which has three tables
Messages - PK = MessageId
Drafts - PK = DraftId
History - FK = RelatedItemId
The History table has a single foreign Key [RelatedItemId] which maps to one of the two Primary keys in Messages and Drafts.
Is there a name for this relationship?
Is it just bad design?
Is there a better way to design this relationship?
Here are the CREATE TABLE statements for this question:
CREATE TABLE [dbo].[History](
[HistoryId] [uniqueidentifier] NOT NULL,
[RelatedItemId] [uniqueidentifier] NULL,
CONSTRAINT [PK_History] PRIMARY KEY CLUSTERED ( [HistoryId] ASC )
)
CREATE TABLE [dbo].[Messages](
[MessageId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Messages] PRIMARY KEY CLUSTERED ( [MessageId] ASC )
)
CREATE TABLE [dbo].[Drafts](
[DraftId] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Drafts] PRIMARY KEY CLUSTERED ( [DraftId] ASC )
)
In a short description the solution you have used is called:
Polymorphic Association
Objective: Reference Multiple Parents
Resulting anti-pattern: Use dual-purpose foreign key, violating first normal form (atomic issue), loosing referential integrity
Solution: Simplify the Relationship
More information about the problem.
BTW createing a common super-table will help you:
Is there a name for this relationship?
There is no standard name that I'm aware of, but I've heard people using the term "generic FKs" or even "inner-platform effect".
Is it just bad design?
Yes.
The reason: it prevents you from declaring a FOREIGN KEY, and therefore prevents the DBMS from enforcing referential integrity directly. Therefore you must enforce it trough imperative code, which is surprisingly difficult.
Is there a better way to design this relationship?
Yes.
Create separate FOREIGN KEY for each referenced table. Make them NULL-able, but make sure exactly one of them is non-NULL, through a CHECK constraint.
Alternatively, take a look at inheritance.
Best practice I have found is to create a Function that returns whether the passed in value exists in either of your Messages and Drafts PK columns. You can then add a constraint on the column on the History that calls this function and will only insert if it passes (i.e. it exists).
Adding non-parsed example Code:
CREATE FUNCTION is_related_there (
IN #value uniqueidentifier )
RETURNS TINYINT
BEGIN
IF (select count(DraftId) from Drafts where DraftId = #value + select count(MessageId) from Messages where MessageId = #value) > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
ALTER TABLE History ADD CONSTRAINT
CK_HistoryExists CHECK (is_related_there (RelatedItemId) = 1)
Hope that runs and helps lol

Why does this foreign key using inheritance not work? [duplicate]

This question already has answers here:
PostgreSQL foreign key not existing, issue of inheritance?
(2 answers)
Closed 8 years ago.
create table abstract_addresses (
address_id int primary key
);
create table phone_numbers (
phone_number text not null unique
) inherits (abstract_addresses) ;
create table contacts (
name text primary key,
address_id int not null references abstract_addresses(address_id)
);
insert into phone_numbers values (1, '18005551212'); --works
select * from abstract_addresses;
address_id
1
select * from phone_numbers;
address_id phone_number
1 18005551212
insert into contacts values ('Neil', 1); --error
I get this error message:
ERROR: insert or update on table "contacts" violates foreign key constraint "contacts_address_id_fkey"
SQL state: 23503
Detail: Key (address_id)=(1) is not present in table "abstract_addresses".
Just a bad use-case for postgresql table inheritance?
Per the caveats in the docs:
A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children. This is true on both the referencing and referenced sides of a foreign key constraint.
http://www.postgresql.org/docs/current/static/ddl-inherit.html
To do what you want:
Create a table with only an id — like you did.
Don't use inheritance. Really don't. It's useful to partition log tables; not for what you're doing.
Make phone number ids default to nextval('abstract_addresses_address_id_seq'), or whatever the sequence name is.
Add a foreign key in phone_numbers referencing abstract_addresses (address_id). Make it deferrable, initially deferred.
Add an after insert trigger on phone_numbers that inserts a new row in abstract_addresses when needed.
If appropriate, add an after delete trigger on phone_numbers that cascade deletes abstract_addresses — make sure it occurs after the delete, else affected rows will report incorrect values when you delete from phone_numbers.
That way, you'll have an abstract_address for use in occasional tables that need such a thing, while still being able to have a hard reference to phone_numbers where the latter is what you actually want.
One caveat to be aware of: it doesn't play well with ORMs.

parent and child table foreign key

I currently have a parent table:
CREATE TABLE members (
member_id SERIAL NOT NULL, UNIQUE, PRIMARY KEY
first_name varchar(20)
last_name varchar(20)
address address (composite type)
contact_numbers varchar(11)[3]
date_joined date
type varchar(5)
);
and two related tables:
CREATE TABLE basic_member (
activities varchar[3]) // can only have 3 max activites
INHERITS (members)
);
CREATE TABLE full_member (
activities varchar[]) // can 0 to many activities
INHERITS (members)
);
I also have another table:
CREATE TABLE planner (
day varchar(9) FOREIGN KEY REFERENCES days(day)
time varchar(5) FOREIGN KEY REFERENCES times(time)
activity varchar(20) FOREIGN KEY REFERENCES activities(activity)
member bigint FOREIGN KEY REFERENCES members(member_id)
);
ALTER TABLE planner ADD CONSTRAINT pk_planner PRIMARKY KEY (day,time,activity,member);
I am currently trying to add with
INSERT INTO planner VALUES ('monday','09:00','Weights',2);
I have added a set into full_members with
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', (12,'Forest Road','Mansfield','Nottinghamshire','NG219DX'),'{01623485764,07789485763,01645586754}',20120418,'Full');
My insert into Planner is currently not working — can you explain why?
i managed ot answer my own question it was becuase at the moment posgreSQL doesn't work very well with inheritence and foreign keys, so i have ot create a rule
CREATE RULE member_ref
AS ON INSERT TO planner
WHERE new.member NOT IN (SELECT member_id FROM members)
DO INSTEAD NOTHING;
this is basically the same as a foreign key
Not sure if this will be better solution but here it goes...
The principle is quite simple:
create new table lets call it table_with_pkeys which will replicate primary key column(s) of inherited tables child1, child2, child3...
create triggers on inherited tables, after insert, insert new PK into table_with_pkeys newly created PK, after update if it changes update it and after delete delete the same PK from table_with_pkeys.
Then in every table which should reference child1, child2 or whichever through parent table's PK using FK, point that FK not to parent's PK, but to table_with_pkeys which has copies of all childs PK's, and so you will have easy manageable way to have foreign keys that can cascade updates, restrict updates and so on.
Hope it helps.
You are missing an open quote before the 12 in the address:
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', (12 Forest Road', 'Mansfield', 'Nottinghamshire', 'NG219DX'),
'{01623485764,07789485763,01645586754}',20120418,'Full');
should be:
INSERT INTO full_members
VALUES (Default, 'Hayley', 'Sargent', ('12 Forest Road', 'Mansfield', 'Nottinghamshire', 'NG219DX'),
'{01623485764,07789485763,01645586754}',20120418,'Full');
If the materialized view approach doesn't work for you above, create constraint triggers to check the referential integrity. Unfortunately declarative referential integrity doesn't work well with inheritance at present.

Reflecting PostgreSQL database with inheritance in SQLAlchemy results in missing foreign key relationship

I have created a database in PostgreSQL (8.4 - I need to use this version because I want to use MapFish which does not (yet) support 9.0) which has some inherited tables:
CREATE TABLE stakeholder
(
pk_stakeholder integer DEFAULT nextval('stakeholder_seq') NOT NULL,
fk_stakeholder_type integer NOT NULL,
name character varying(255) NOT NULL,
CONSTRAINT stakeholder_primarykey PRIMARY KEY (pk_stakeholder),
CONSTRAINT stakeholder_fk_stakeholder_type FOREIGN KEY (fk_stakeholder_type)
REFERENCES stakeholder_type (pk_stakeholder_type) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
);
CREATE TABLE individual
(
firstname character varying(50),
fk_title integer,
email1 character varying (100),
email2 character varying (100),
phone1 character varying (50),
phone2 character varying (50),
CONSTRAINT individual_primarykey PRIMARY KEY (pk_stakeholder),
CONSTRAINT individual_fk_title FOREIGN KEY (fk_title)
REFERENCES individual_title (pk_individual_title) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION
) INHERITS (stakeholder)
(as learned from an earlier question, I'm using a seperate table (stakeholder_pk) to keep track of my primary keys using triggers)
Now I'd like to reflect my database in SQLAlchemy (0.7.1):
meta.metadata.reflect(bind=engine)
table_stakeholder = meta.metadata.tables["stakeholder"]
table_individual = meta.metadata.tables["individual"]
stakeholder_mapper = orm.mapper(Stakeholder, table_stakeholder,
polymorphic_on=table_stakeholder.c.fk_stakeholder_type,
polymorphic_identity='stakeholder')
orm.mapper(Individual, table_individual, inherits=stakeholder_mapper,
polymorphic_identity='individual')
This however results in an sqlalchemy.exc.ArgumentError: Can't find any foreign key relationships between 'stakeholder' and 'individual'.
Now I've seen some examples where they use the primary key of the child tables (in my case: individual) as a foreign key to point at the primary key of the parent table (stakeholder). However, PostgreSQL will not let me do this, saying that this would violate a foreign key constraint since the primary key in the parent table (stakeholder) is not there (?).
So now I'm pretty much stuck and after hours of searching for a solution I'm starting to lose track of it. Is this a problem in PostgreSQL (similar to the primary key & inheritance issue) or is it because of SQLAlchemy? Or is it just me doing something fundamentally wrong?
It is in PostgreSQL:
All check constraints and not-null constraints on a parent table are automatically inherited by its children. Other types of constraints (unique, primary key, and foreign key constraints) are not inherited.
These deficiencies will probably be fixed in some future release, but in the meantime considerable care is needed in deciding whether inheritance is useful for your application.
http://www.postgresql.org/docs/9.0/interactive/ddl-inherit.html
Is it possible to drop triggers and to have in individual:
pk_stakeholder integer DEFAULT nextval('stakeholder_seq') NOT NULL,
...
CONSTRAINT stakeholder_primarykey PRIMARY KEY (pk_stakeholder),
This will not stop individual to have pk_stakeholder that exists in stakeholder if you update pk_stakeholder later. So here triggers are required to stop update (easier) or to check.