Foreign key for inherited table in PostgreSQL - sql

I have tables animals and water inherited from targets table. They look like this:
targets
create table targets
(
id integer not null
primary key,
name text
);
animals (water table is the same)
create table animals
(
type_id integer,
flock_id integer,
primary key (id)
)
inherits (targets);
Also I have a actions table which has a foreign key target_id.
actions
create table actions
(
id integer not null
primary key,
target_id integer
references targets(id),
);
I have some rows in animals and water tables. And I am trying to insert new row into actions table with target_id equals to id of some animal.
INSERT INTO actions (id, target_id)
VALUES (1, 4);
But I receive an error:
[23503] ERROR: insert or update on table "actions" violates foreign key constraint "actions_target_id_fkey"
Key (target_id)=(4) is not present in table "targets".
Maybe I don't understand the table's inheritance, but how can I do my task correctly (target_id should be a foreign key for water and animals tables at the same time)?

I hope this might wrork , you have two tables, animals and water, that inherit from a parent table, targets. You want to create a foreign key in a different table that can reference both the animals and water tables using the id columnn of the targets table.
for that, you can create a foreign key that references the id column of the targets table and use a CHECK constraint to ensure that the referenced row is in either the animals or water table
CREATE TABLE some_table (
id serial primary key,
target_id integer references targets(id),
-- other columns
CONSTRAINT target_id_animals_water_check CHECK (
target_id IN (SELECT id FROM animals)
OR target_id IN (SELECT id FROM water)
)
);
some_table is the name of the table that you want to create the foreign key in, and target_id is the name of the column that will store the foreign key. The references clause specifies that the target_id column should reference the id column of the targets table.
The CHECK constraint ensures that the referenced row is in either the animals or water table. The IN operator is used to check if the target_id is in the set of id values in either the animals or water table.
With this setup, you can insert rows into the animals and water tables as well as the some_table table, and the foreign key constraint will ensure that only valid references are allowed and you can insert a row into the some_table table like this
INSERT INTO some_table (target_id) VALUES (1);

Related

Foreign key reference to junction table create issue

I have two tables that have a junction table in between due to fact that there is many to many relationship between. How do I make a foreign key reference from one of the tables to the junction table to obtain a specific ID(the primary key ID of the opposite table), the ID is part of the primary key of the junction table that is made up of two foreign keys of the two tables. I made a foreign key reference straight to the junction table but its throwing an error "The number of columns in the referencing column list for foreign key does not match those of the primary key in the referenced table" when launching the CREATE query for the tables. What should I do to successfully make a foreign key reference to junction table?
The many side of both relationships will be at the junction table eg (written in SQL)
create table t1 (
id int primary key
) ;
create table t2 (
id int primary key
) ;
create table junction (
t1id int references t1( id ) not null
, t2id int references t2( id ) not null
) ;
-- PK: 2 columns
alter table junction
add constraint pkey_j primary key( t1id, t2id ) ;
DBfiddle here.

PostgreSQL: table referencing only part of a composite primary key

I am Creating a movie and tv show streaming service database(for school project) using Postgres 9.6.2. I am running into the following error:
there is no unique constraint matching given keys for referenced table "watchedepisodes"
The TVratings table below will take a tv show, as long as a user has watched at least one episode (that shows up in the WatchedEpisodes table) and allow the user to rate it. Since WatchedEpisodes has a composite primary key of the user id, tv show id, season, and episode, it won't let me just reference from that table a composite key of just uid and tid.
CREATE TABLE WatchedEpisodes (
uid int unique references Users(uid),
tid int,
season int,
episode int,
FOREIGN KEY(tid, season, episode) REFERENCES Episodes(tid, season, episode),
PRIMARY KEY (uid, tid, season, episode)
);
CREATE TABLE TVRatings (
uid int,
tid int,
rating int check (rating > 0 and rating < 6),
FOREIGN KEY(uid, tid) REFERENCES WatchedEpisodes(uid,tid),
PRIMARY KEY(uid, tid)
);
Wondering if there is a way to only reference part of that composite key. These are only two of my tables so if further information is needed, I can add more.
This is actually in the spec, but it's not implemented yet. It's called MATCH PARTIAL
CREATE TABLE foo (
a int,
b int,
c int,
PRIMARY KEY (a,b,c)
);
CREATE TABLE bar (
a int,
b int,
FOREIGN KEY (a,b) REFERENCES foo (a,b) MATCH PARTIAL
);
You can read about it in the docs,
A value inserted into the referencing column(s) is matched against the values of the referenced table and referenced columns using the given match type. There are three match types: MATCH FULL, MATCH PARTIAL, and MATCH SIMPLE (which is the default). MATCH FULL will not allow one column of a multicolumn foreign key to be null unless all foreign key columns are null; if they are all null, the row is not required to have a match in the referenced table. MATCH SIMPLE allows any of the foreign key columns to be null; if any of them are null, the row is not required to have a match in the referenced table. MATCH PARTIAL is not yet implemented. (Of course, NOT NULL constraints can be applied to the referencing column(s) to prevent these cases from arising.)
I think currently it's viewed as an anti-feature, so it's not likely to land anytime soon.
As a workaround, you can create another table that just has (a,b)
CREATE TABLE baz (
a int,
b int,
PRIMARY KEY (a,b)
);
From here you can link both tables to it...
CREATE TABLE foo (
a int,
b int,
c int,
PRIMARY KEY (a,b,c),
FOREIGN KEY (a,b) REFERENCES baz
);
CREATE TABLE bar (
a int,
b int,
FOREIGN KEY (a,b) REFERENCES baz
);

PostgreSQL Foreign Key and Child Tables

I have the following tables:
--Competition tables
CREATE TABLE IF NOT EXISTS Tr.Competitions(
competition_id SERIAL PRIMARY KEY,
competition_name text NOT NULL
);
CREATE TABLE IF NOT EXISTS Tr.CompetitionsQuestions(
competition_id int NOT NULL,
question_id int NOT NULL,
FOREIGN KEY (competition_id) REFERENCES Tr.Competitions(competition_id),
FOREIGN KEY (question_id) REFERENCES Tr.Questions(question_id)
);
--Questions tables
CREATE TABLE IF NOT EXISTS Tr.Questions(
question_id SERIAL PRIMARY KEY,
question_text text NOT NULL
);
CREATE TABLE IF NOT EXISTS Tr.MultiQuestions(
possible_answers text ARRAY NOT NULL,
correct_answer int NOT NULL
) INHERITS(Tr.Questions);
I try to insert some dummy data into Tr.CompetitionQuestions like so:
--Test Fixtures
INSERT INTO Tr.MultiQuestions (question_text, possible_answers, correct_answer)
VALUES ('Which of the following is awesome?', '{"Indian Food","Soccer","All the above"}', 2);
INSERT INTO Tr.Competitions(competition_name)
VALUES ('Awesome Competition');
INSERT INTO Tr.CompetitionsQuestions(competition_id, question_id)
VALUES ((SELECT competition_id FROM Tr.Competitions WHERE competition_id=1),
(SELECT question_id FROM Tr.Questions WHERE question_id=1));
Having these stored in an .sql file and running \i some.sql is gerenating the following error. How do I add a question foreign key to the CompetitionsQuestions table?
ERROR: insert or update on table "competitionsquestions" violates foreign key constraint "competitionsquestions_question_id_fkey"
DETAIL: Key (question_id)=(1) is not present in table "questions".
Seems like a weird error since SELECT * FROM tr.questions WHERE question_id=1 actually gives me the stored multiquestion row.
EDIT:
Simplifying to:
INSERT INTO Tr.CompetitionsQuestions(competition_id, question_id)
VALUES (1, 1);
gives me the same error;
(Assuming, from comments, that you're using PostgreSQL's table inheritance features, since your question doesn't really contain full info about the schema and how you populate its contents):
Foreign keys don't apply across all members of an inheritance tree. They can only be to the specific table.
The same is true of a UNIQUE constraint or PRIMARY KEY.
You can see what a foreign key constraint will see in a table if you:
SELECT * FROM ONLY thetable;
The ONLY keyword tells PostgreSQL not to include child tables. That's what's used in foreign key constraint checks.

Check if data exists in another table on insert?

Table A
(
Table_A_ID int
)
Table B
(
Table_B_ID int
Value int
)
Say I want to insert data into Table B, where 'Value' would be the same as a Table_A_ID.
How would I make a constraint or check that the data actually exists in the table on insertion?
You probably need to enforce data integrity not only on INSERT into Table B, but also on UPDATE and DELETE in both tables.
Anyway options are:
FOREIGN KEY CONSTRAINT on Table B
TRIGGERs on both tables
As a last resort if for some reason 1 and 2 is not an option STORED PROCEDUREs for all insert, delete update operations for both tables
The preferred way to go in most cases is FOREIGN KEY CONSTRAINT.
Yap, I agree with #peterm.
Cause, if your both Table_A_ID and Table_B_Id are primary keys for both tables, then you don't even need two tables to store the value. Since, your two tables are seems to be on 'one-to-one' relationship. It's one of the database integrity issues.
I think you didn't do proper normalisation for this database.
Just suggesting a good idea!
I found this example which demonstrates how to setup a foreign key constraint.
Create employee table
CREATE TABLE employee (
id smallint(5) unsigned NOT NULL,
firstname varchar(30),
lastname varchar(30),
birthdate date,
PRIMARY KEY (id),
KEY idx_lastname (lastname)
) ENGINE=InnoDB;
Create borrowed table
CREATE TABLE borrowed (
ref int(10) unsigned NOT NULL auto_increment,
employeeid smallint(5) unsigned NOT NULL,
book varchar(50),
PRIMARY KEY (ref)
) ENGINE=InnoDB;
Add a constraint to borrowed table
ALTER TABLE borrowed
ADD CONSTRAINT FK_borrowed
FOREIGN KEY (employeeid) REFERENCES employee(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
NOTE: This tells MySQL that we want to alter the borrowed table by adding a constraint called ‘FK_borrowed’. The employeeid column will reference the id column in the employee table – in other words, an employee must exist before they can borrow a book.
The final two lines are perhaps the most interesting. They state that if an employee ID is updated or an employee is deleted, the changes should be applied to the borrowed table.
NOTE: See the above URL for more details, this is just an excerpt from that article!
Create a foreign key constraint on the column 'Value' on table B that references the 'Table_A_ID' column.
Doing this will only allow values that exist in table A to be added into the 'Value' field of table B.
To accomplish this you first need to make Table_A_ID column the primary key for table A, or it at least has to have some sort of unique constraint applied to it to be a foreign key candidate.
BEGIN TRANSACTION -- REMOVE TRANSACTION AND ROLLBACK AFTER DONE TESTING
--PUT A PRIMARY KEY ON TABLE A
CREATE TABLE A
( Table_A_ID int CONSTRAINT PK_A_Table_A_ID PRIMARY KEY)
--ON VALUE ADD A FOREIGN KEY CONSTRAINT THAT REFERENCEs TABLE A
CREATE TABLE B
( Table_B_ID int,
[Value] int CONSTRAINT FK_B_Value_A REFERENCES A(Table_A_ID)
)
-- TEST VALID INSERT
INSERT A (Table_A_ID) VALUES (1)
INSERT B (Table_B_ID, [Value]) VALUES (1,1)
--NOT ALLOW TO INSERT A VALUE THAT DOES NOT EXIST IN A
--THIS WILL THROW A FOREIGN KEY CONSTRAINT ERROR
INSERT B (Table_B_ID, [Value]) VALUES (1,2) -- 2 DNE in table A
ROLLBACK
Note: there is no magic to 'FK_B_Value_A' or 'PK_A_Table_A_ID' it simply a naming convention and be called anything. The syntax on the foreign key and primary key lines work like this:
column-definition CONSTRAINT give-the-constraint-a-name REFERENCES table-name ( table-column )
column-definition CONSTRAINT give-the-constraint-a-name PRIMARY KEY

How to declare a foreign key with an OR condition using Oracle?

I have a table (A) whose primary key is either a foreign key to table (B) or table (C).
create table A (
akey number,
txt varchar2(10)
);
create table B (
bkey number,
txt varchar2(10)
);
create table C (
ckey number,
txt varchar2(10)
);
What I want is something like:
alter table A add constraint BorCkey foreign key (akey) references B(bkey)` or C(ckey);
Is this possible?
No, that sort of thing is not possible in Oracle.
Your options generally are
Create two different columns (bkey and ckey) in A where bkey references B.bkey and ckey references C.ckey and create a constraint that ensures that only one is non-NULL at any point in time.
Create some sort of "combined B & C" entity that B & C have foreign keys to and make the foreign key in A reference the key of this combination entity.
If you want a constraint that ensures that exactly one of two columns is NULL and one is NOT NULL for any row
create table one_key(
col1 number,
col2 number,
check( nvl2(col1,1,0) + nvl2(col2,1,0) = 1 )
)
A foreign key constraint is to one foreign table.
That means you'd need to use two ALTER TABLE statements in this situation to setup foreign keys to reference the two tables. There's no opportunity in there to specify an OR in the relationship -- the value in A.akey would have to exist in both B.bkey and C.ckey at the same time. For example, if B.bkey has a value of NULL, but C.ckey does not -- then A.akey can never have a value of NULL. Foreign keys are deferrable in Oracle, but the behavior described is what you will encounter if both foreign keys are enabled at the same time -- you won't be able to enable a constraint if all the values don't satisfy the relationship.
You need to review your needs for how to simplify the relationship so it doesn't need two tables to make this work.
Sounds like you have some form of subtype/supertype relationship going on.
A typical example is 'PERSON' which may be either a 'CUSTOMER' or a 'SUPPLIER'.
You might have, in the PERSON table the unique key of PERSON_ID plus an attribute of PERSON_TYPE ('CUST' or 'SUPP'). If you create the primary key on PERSON_ID,PERSON_TYPE you can reference that in the subtype tables (SUPPLIER/CUSTOMER).
Then you add a unique constraint on the person_id to ensure that any value of person_id must be either a customer or supplier but not both, and check constraints on the subtype tables so that only one type is represented in the table.
create table person
(person_id number,
person_type varchar2(4),
name varchar2(10),
constraint person_pk primary key (person_id, person_type),
constraint person_id_uk unique (person_id));
create table supplier
(supplier_id number,
supplier_type varchar2(4),
blah varchar2(10),
constraint supplier_pk primary key (supplier_id, supplier_type),
constraint supp_pers_fk foreign key (supplier_id, supplier_type)
REFERENCES person (person_id, person_type)
)
/
alter table supplier add constraint supp_type_ck check (supplier_type = 'SUPP');
Its not pretty but types/subtypes are more of an object concept than a relational one.
My solution inspired by Justin:
CREATE OR REPLACE TRIGGER abc
BEFORE INSERT OR UPDATE ON a
FOR EACH ROW
DECLARE
v_testB NUMBER:= 0;
v_testC NUMBER:= 0;
BEGIN
SELECT
COUNT(bkey)
INTO
v_testB
FROM
b
WHERE
bkey = :new.aKey;
SELECT
COUNT(ckey)
INTO
v_testC
FROM
c
WHERE
ckey = :new.aKey;
IF ((v_testB + v_testC) <> 1) THEN
RAISE_APPLICATION_ERROR(-20002,'Foreign key to B or C missing.');
END IF;
END;
/
SHOW ERRORS TRIGGER abc
Create a materialized view that unions tables B & C, and point your FK constraint to the view