I was wondering whether there was any way possible to reference tableoid's as foreign keys in an inheritance relationship. For example:
CREATE TABLE employee
(
name TEXT,
PRIMARY KEY(name, TABLEOID)
);
CREATE TABLE hourly_employee
(
hours_worked INT,
PRIMARY KEY(name)
) INHERITS(employee);
CREATE TABLE salaried_employee
(
anniversary_date DATE,
PRIMARY KEY(name)
) INHERITS(employee);
CREATE TABLE employee_training
(
training_id SERIAL,
due_date DATE,
employee_name TEXT,
emp_oid OID,
PRIMARY KEY(training_id),
FOREIGN KEY(employee_name, emp_oid) REFERENCES employee(name, TABLEOID)
);
INSERT INTO hourly_employee (name, hours_worked) VALUES ('Joe Smith', 40);
INSERT INTO salaried_employee(name, anniversary_date) VALUES ('Bob Brown', '2014-02-20');
INSERT INTO employee_training (due_date, employee_name, emp_oid) VALUES ('2016-08-16', 'Bob Brown', 'salaried_employee'::REGCLASS);
In this example, the foreign key is created without a problem, but the last insert will fail with the error Key (employee_name, emp_oid)=(Bob Brown, 16403) is not present in table "employee" even though I can confirm that 16403 is the correct tableoid for salaried_employee.
Is there any way to make this work?
Sadly inheritance has some serious limitations. Several elements (including unique indexes / foreign keys) only apply to one table and not the children. Personally I've found it much less useful than I'd have liked it to be.
I know its annoying to suggest you re-design but in my opinion you'd be better to have a single table employee with optional columns instead of the parent / child relations.
CREATE TABLE employee
(
name TEXT,
employee_type TEXT,
hours_worked INT,
anniversary_date DATE,
PRIMARY KEY(name, TABLEOID)
);
In the long run you often find the code becomes simpler and frankly much more portable between DBMS as well.
You can ensure the correct fields have been entered for the correct type using constraints to manage which fields are mandatory for each type.
Eg:
ALTER TABLE employee ADD CHECK (
(type = 'hourly' and hours worked is not null)
or (type = 'salaried' and anniversary_date is not null))
Related
This question already has answers here:
How can you represent inheritance in a database?
(7 answers)
Closed 3 years ago.
I have a table in Postgres which contains Things. Each of these Things can be of 1 of 3 types:
is a self contained Thing
is an instance of a SuperThingA
is an instance of a SuperThingB.
Essentially, in object terms, there's an AbstractThing, extended in
different ways by SuperThingA or SuperThingB and all the records on
the Thing table are either unextended or extended in 1 of the 2
ways.
In order to represent this on the Thing table, I have a number of fields which are common to Things of all types, and I have 2 optional FK reference columns into the SuperThingA and SuperThingB tables.
Ideally, I would like a "FK IF NOT NULL" constraint on each the two, but this doesn't appear to be possible; as far as I can see, all I can do is make both fields nullable and rely on the using code to maintain the FK relationships, which is pretty sucky. This seems to be doable in other databases, for example SQL Server, as per SQL Server 2005: Nullable Foreign Key Constraint, but not any way that I've found so far in PG
How else can I handle this - an insert/update trigger which checks the values when either of those fields is not null and checks the value is present on whichever parent table? That's maybe doable on a small parent table with limited inserts on the Thing table (which, in fairness, is largely the case here - no more than a couple of hundred records in each of the parent tables, and small numbers of inserts on the Thing table), but in a more general case, would be a performance black hole on the inserts if one or both parent table were large
This is not currently enforced with a FK relationship. I've reviewed the PG docs, and it seem pretty definitive that I can't have an optional FK relatioship (which is understandable). It leaves me with a table definition something like this:
CREATE TABLE IF NOT EXISTS Thing(
Thing_id int4 NOT NULL,
Thing_description varchar(40),
Thing_SuperA_FK int4,
Thing_SuperB_FK char(10),
CONSTRAINT ThingPK PRIMARY KEY (Thing_id)
)
;
Every foreign key on a nullable column is only enforced when the value is non-null. This is default behavior.
create table a (
id int4 not null primary key
);
create table b (
id int4 not null primary key,
a_id int4 references a(id)
);
In the example table b has an optional reference to table a.
insert into a values(1); -- entry in table a
insert into b values (1, 1); -- entry in b with reference to a
insert into b values (2, null); -- entry in b with no reference to a
Depending on your use case it also might make sense to reverse your table structure. Instead of having the common table pointing to two more specialized tables you can have it the other way around. This way you avoid the non-null columns entirely.
create table common(
id int4 primary key
-- all the common fields
);
create table special1(
common_id int4 not null references common(id)
-- all the special fields of type special1
);
create table special2(
common_id int4 not null references common(id)
-- all the special fields of type special2
);
You need your SuperN_FK fields defined as nullable foreign keys, then you'll need check constraint(s) on the table to enforce the optional NULLability requirements.
CREATE TABLE Things
( ID int primary key
, col1 varchar(1)
, col2 varchar(1)
, SuperA_FK int constraint fk_SuperA references Things(ID)
, cola1 varchar(1)
, constraint is_SuperA check ((SuperA_FK is null and cola1 is null) or
(SuperA_FK is not null and cola1 is not null))
, SuperB_FK int constraint fk_SuperB references Things(ID)
, colb1 varchar(1)
, constraint is_SuberB check ((SuperB_FK is null and colb1 is null) or
(SuperB_FK is not null))
, constraint Super_Constraint check (
case when SuperA_FK is not null then 1 else 0 end +
case when SuperB_FK is not null then 1 else 0 end
<= 1 )
);
In the above example I've split the check constraints up for ease maintenance. The two is_SuperN constraints enforce the NULL requirements on the FK and it's related detail columns, either all NULLs or the FK is not null and some or all of the detail columns are not null. The final Super_Constraint ensures that at most one SuperN_FK is not null.
I'm trying to implement an Audit table design in PostgreSQL, where I have different types of user id's that can be audited.
Let's say I have a table named admins (which belong to an organization), and table superadmins (which don't).
CREATE TABLE example.organizations (
id SERIAL UNIQUE,
company_name varchar(50) NOT NULL UNIQUE,
phone varchar(20) NOT NULL check (phone ~ '^[0-9]+$')
);
and an example of a potential admin design
CREATE TABLE example.admins (
id serial primary_key,
admin_type varchar not null,
#... shared data
check constraint admin_type in ("super_admins", "regular_admins")
);
CREATE TABLE example.regular_admins (
id integer primary key,
admin_type varchar not null default "regular_admins"
organization_id integer references example.organizations(id),
#... other regular admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "regular_admins"
);
CREATE TABLE example.super_admins (
id integer primary key,
admin_type varchar not null default "super_admins"
#... other super admin fields
foreign key (id, admin_type) references example.admins (id, admin_type),
check constraint admin_type = "super_admins"
);
Now an audit table
CREATE TABLE audit.organizations (
audit_timestamp timestamp not null default now(),
operation text,
admin_id integer primary key,
before jsonb,
after jsonb,
);
This calls for inheritance or polymorphism at some level, but I'm curious about how to design it. I've heard that using PostgreSQL's inheritance functionality is not always a great way to go, although I'm finding it to fit this use case.
I'll need to be able to reference a single admin id in the trigger that updates the audit table, and it would be nice to be able to get the admin information when selecting from the audit table without using multiple queries.
Would it be better to use PostgreSQL inheritance or are there other ideas I haven't considered?
I wouldn't say that it calls for inheritance or polymorphism. Admins and superadmins are both types of user, whose only difference is that the former belong to an organization. You can represent this with a single table and a nullable foreign key. No need to overcomplicate matters. Especially if you're using a serial as your primary key type: bad things happen if you confuse admin #2 for superadmin #2.
I would like to prevent a mismatch between course_code and course_namewhen inserting values to the table below.
CREATE TABLE course (
course_id INT4 NOT NULL PRIMARY KEY,
course_code CHAR(4) NOT NULL,
course_name VARCHAR(30) NOT NULL
);
For both I created an enumeration (see below), now I want to link 'C101' to 'Computer Science' etc.
CREATE TYPE e_course_code AS ENUM (
'C101',
'B102',
'E103',
'V104',
'A105',
'E104'
);
CREATE TYPE e_course_name AS ENUM (
'Computer Science',
'Business Information Management',
'Electronics',
'Visual Programming',
'Audio Technology',
'Engineering'
);
Is it possible to link specified (enumerated) values for two (or even more) columns? Something that returns an error message when inserting a course_code and course_name that do not match?
The fast and reliable tool to implement what you ask in the title would be a foreign key constraint with MATCH FULL:
CREATE TABLE course (
course_code char(4) PRIMARY KEY
, course_name text NOT NULL
);
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, course_name text
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code, course_name)
REFERENCES course(course_code, course_name) MATCH FULL
);
Related:
MATCH FULL vs MATCH SIMPLE in foreign key constraints
However, some_other_table.course_name would be completely redundant, and the clean implementation would be the normalized form instead:
CREATE TABLE some_other_table (
some_other_id serial PRIMARY KEY
, course_code char(4)
, -- more columns
, CONSTRAINT course_fk FOREIGN KEY (course_code) REFERENCES course(course_code)
);
Or you add course_id as PK to the course table and use that as FK column.
You can always add a VIEW to display course_name additionally.
The simplest way to solve this (as i see it) would make two separate tables- one with id and code, another with code and name. See this question - Difference between 3NF and BCNF in simple terms (must be able to explain to an 8-year old) - the example in the answer is similar to your problem.
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
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.